DEV Community

Cover image for GoodGame Empire Auto-Attack Bot
Possessed Player
Possessed Player

Posted on

GoodGame Empire Auto-Attack Bot

Hi there,

Check out the bot video to understand how this bot works link here

To follow this guide please clone the project from here.

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.

In line 615, 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)

       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
Enter fullscreen mode Exit fullscreen mode

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)

        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)
        }
Enter fullscreen mode Exit fullscreen mode

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)

        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
Enter fullscreen mode Exit fullscreen mode

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)

        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)
Enter fullscreen mode Exit fullscreen mode

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)

        def open_attackWin():
            if findClick_object("confirm.bmp") == False: return False

            sleep(2)

            return repeat(lambda: find_object("use_preset.bmp"), True)
Enter fullscreen mode Exit fullscreen mode

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)

        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
Enter fullscreen mode Exit fullscreen mode

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

TASK 4 - apply_preset() function

(Line 912)

        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)
Enter fullscreen mode Exit fullscreen mode

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)

        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)
Enter fullscreen mode Exit fullscreen mode

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)

        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)
Enter fullscreen mode Exit fullscreen mode

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)

        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()
Enter fullscreen mode Exit fullscreen mode

"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)

    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()
Enter fullscreen mode Exit fullscreen mode

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.

Wrapping up:

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.

I developed this bot a long time ago, here are some must-read reflections:

  • 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.

Top comments (0)