GoodGame Empire Auto-Attack Bot
Tags: python, computervision, opencv, script
Published: 2023-03-06 19:57:49

## Hi there, 

**Check out the bot video to understand how this bot works [link here](https://youtu.be/hzsmYM8ikW0)**

**To follow this guide please clone the project** [from here](https://github.com/Possessed-Player/GoodGameEmpireBot).

All code from line 113 to 613 is related to the GUI(read on your own risk as it will not be explained here in this guide).

**This guide will go through lines from 615 to 1015.**

**<u>In line 615</u>**, you can see the function "run" contains a couple of function definitions which act as "helper" functions to aid in the cumbersome bot work, for example:

**(Line 616)**
```python
       def find_object(file):
            img = pag.screenshot()
            img = img.convert('RGB')
            img = np.array(img)
            img_rgb = img[:, :, ::-1].copy()
            img_gray = cv.cvtColor(img_rgb, cv.COLOR_BGR2GRAY)
            template = cv.imread("objects/" + file,0)
            w, h = template.shape[::-1]
            
            res = cv.matchTemplate(img_gray,template,cv.TM_SQDIFF_NORMED)
            mn,_,mnLoc,_ = cv.minMaxLoc(res)
            
            print(file, "->", mn)

            if mn < 0.007:
                return mnLoc
            else:
                return False
```

**The 'find_object' function, takes a screenshot of the game and tries to find an image "file" inside that screenshot, the use of this function is to locate game objects inside the game screen.**

Functions from 616 to 700, are all "helper" functions, most of the them use the library pyautogui which is shorted to "pag" in the code, pyautogui provides functions that move the mouse and type using the keyboard(read pyautogui docs to understand how these functions work).

Function from 700 to 930, are all "task" functions, it uses a mix of pyautogui functions and "helper" functions to do an in-game task(will be explained below).

We then move on to pre-loop initialization:

**(Line 931)**
```python
        castle = 1
        max = (4 if i > 1 else 10)
        cur_task = 0
        tasks = {
            0 : lambda : selector(i),
            1 : lambda : enter_target_info(ALL[i]["C" + str(castle) + "_x"], ALL[i]["C" + str(castle) + "_y"]),
            2 : lambda : open_attackWin(),
            3 : lambda : select_preset(int(ALL[i]['Slot'])),
            4 : lambda : apply_preset(),
            5 : lambda : attack(),
            6 : lambda : select_speedup(i)
        }
```

Here, variable "castle" is the castle number to start with(from the 10 recorded castles in the GUI).

"max" is the maximum number of castles to use.

"cur_task" is an index variable to keep track of the current running task.

"tasks" is a dictionary of in-game tasks that should be done in sequence to get a successful castle attack.

## TASK 0 - selector(i) function

**(Line 895)**
```python
        def selector(i):
            if i > 1 and not self.firstRotation:
                return extinguish(ALL[i]["C" + str(castle) + "_x"], ALL[i]["C" + str(castle) + "_y"])
            elif i <= 1 and not self.firstRotation:
                return waitFor_reappear(ALL[i]["C" + str(castle) + "_x"], ALL[i]["C" + str(castle) + "_y"])
            else:
                sleep(5)
                return True
```

This task job is to extinguish attacked castle when possible, if not it will wait for the castle to reappear, this task is called first although it's not actually used until the bot is done attacking, it works because it doesn't execute one of the two functions in the first rotation.

## TASK 1 - enter_target_info(a, b) function

**(Line 659)**
```python
        def enter_target_info(x, y):
            press("tab")
            pag.write(x)
            press("tab")
            pag.write(y)
            press("enter")
            sleep(0.5)
            press("tab")
            pag.write(x)
            press("tab")
            pag.write(y)
            press("enter")
            sleep(1)
            
            if findClick_object("target.bmp") == False: return False
            
            return repeat(lambda: find_object("success_targselect.bmp"), True)
```

This one is responsible of entering castle coordinates, using pyautogui, pynput.

Then clicks on the 'target.bmp' object, to open the attack dialog.

It also checks whether the attack dialog is open or not, and returns that as a bool.

## TASK 2 - open_attackWin() function

**(Line 904)**
```python
        def open_attackWin():
            if findClick_object("confirm.bmp") == False: return False
            
            sleep(2)
            
            return repeat(lambda: find_object("use_preset.bmp"), True)
```

This task clicks on confirm button on the attack dialog, in order to open the attack setup window.

It then checks whether the attack setup window is open or not, and returns that as a bool.

## TASK 3 - select_preset(i) function

**(Line 685)**
```python
        def select_preset(i):
            cords = find_object("drop_preset.bmp")
            
            if cords == False: return False
            
            click(cords[0] - 50, cords[1] + 60)
            sleep(1)
            if self.checkBox.isChecked():
                pag.move(0, -(int(22 * 1.25) * i))
            else:
                pag.move(0, -(22 * i))
            sleep(0.25)
            pag.click()
            
            self.preset_ready = True
```

Selects the preset number 'i' in the presets drop menu.

## TASK 4 - apply_preset() function

**(Line 912)**
```python
        def apply_preset():
            if findClick_object("use_preset.bmp", x=10, y=5) == False: return False
            
            sleep(1)
            
            return repeat(lambda: find_object("failed_preset_use.bmp"), False)
```

This task only job is to click the blue button to apply the preset, then check if the preset was applies and returns that as a bool.

## TASK 5 - attack() function

**(Line 919)**
```python
        def attack():
            if findClick_object("start_attack.bmp", x=50, y=10) == False: return False
            
            sleep(1)
            
            return repeat(lambda: find_object("success_attack.bmp"), True)
```

Again a simple task, goal is to click attack button then check if the last confirmation window is visible and returns that as a bool.

## TASK 6 - select_speedup(i) function

**(Line 876)**
```python
        def select_speedup(i):
            cords = find_object("confirm3.bmp")
            
            if cords == False: return False
            
            if i > 1:
                click(cords[0] - 300, cords[1] - 200)
            else:
                click(cords[0] + 100, cords[1] - 200)
                
            sleep(0.5)
            
            click(cords[0], cords[1])
            
            sleep(1)
            
            return repeat(lambda: find_object("success_attack.bmp"), False)
```

This task job is to select the appropriate attack speed, then clicks on the 'confirm' button, then it checks if the attack was executed and returns that as a bool.

**These are all the main bot tasks, which are controlled by the main bot loop**

Moving forward to the main bot loop:

**(Line 963)**
```python
        while self.on:
            if next_task() != False:
                #fcounter = 0
                if cur_task == (len(tasks) - 1):
                    cur_task = 0
                    
                    if castle == max:
                        self.firstRotation = False
                        castle = 1
                    else:
                        castle += 1
                        
                else:
                    cur_task += 1
            else:
                boxCheck()
```

"if next_task() != False:" line runs the next and checks if it was successful or not by checking the return boolean, and only moves to the next task if the task was done successfully.

The "boxCheck" function, as the name suggests it checks if there is a game dialog blocking bot interactions with the game, and removes the dialog if found.

**(Line 994)**
```python
    def kb_listener(self):
        def on_release(key):
            if key == Key.esc:
                self.on = False
                self.statusbar.showMessage("Stopping...")
                return False


        with Listener(on_release=on_release) as listener:
            listener.join()
```

This part is responsible of stopping the bot using a keyboard shortcut.

This function 'kb_listener' defines a key stroke listener, while the bot is running, once a key is released the function 'on_release(key)' is called with the released key "key".

The first line in that function checks "if key == Key.esc:", which states that it will only execute the if bracket if the released key was "escape or esc".

**Check pynput library for more info about this listener.**

<u>**Wrapping up:**</u>

The bot was developed in a sequence/tasks logic because the game is played by doing multiple dialogs after each other, so each task depends on the task before it to be done before it can be started.

**<u>I developed this bot a long time ago, here are some must-read reflections:</u>**

- The GUI code shouldn't be in the same module as the bot script.
- Helper functions shouldn't all be included in the main bot function.
- The bot loop should be in a function of it's own.
- The bot needs a safe fail method, in-case it gets stuck in one of the tasks.