def get_ui_element(self, element, screenshot=None, exit_on_fail=True): """ Find a UI element on the screen """ # Move the mouse so it doesn't obstruct search pyautogui.moveTo(400, 400) # Get a screenshot if screenshot is None: screenshot = utils.take_screenshot(False) # Try to match the template in the screenshot result = cv2.matchTemplate(screenshot, self.templates[element], cv2.TM_CCORR_NORMED) _, max_val, _, max_loc = cv2.minMaxLoc(result) # Found the element, return the location if max_val > 0.9: return max_loc # Failed to find the element with high enough confidence if not exit_on_fail: return False # Not finding the element is severe enough to quit the game utils.log("SEVERE", F"Failed to find {element}, max confidence was {max_val}") utils.quit_game()
def step(self, new_position): """ Given a set of coordinates, calculate what direction to step and send that key to the game """ # Calculate direction of movement x_diff = self.game_map.player_position[0] - new_position[0] y_diff = self.game_map.player_position[1] - new_position[1] # Determine what key to press direction = '' if x_diff == 1: direction = 'a' elif x_diff == -1: direction = 'd' elif y_diff == 1: direction = 'w' elif y_diff == -1: direction = 's' else: utils.log( 'SEVERE', F"Invalid step difference. xDiff: {x_diff}, yDiff: {y_diff}") utils.quit_game() # Move along path pyautogui.press(direction) sleep(0.1) # Player moved, re-detect environment screenshot = utils.take_screenshot() self.game_map.update_player_position(screenshot) self.game_map.update_map()
def get_weight(self): """ Gets the player's current and max weight """ # Find the current and total weight screenshot = utils.take_screenshot(False) weight = self.get_ui_element('weight', screenshot) weight = screenshot[weight[1]:(weight[1] + 12), (weight[0] + 40):(weight[0] + 84)] # Resize and parse the image to a string weight = cv2.resize(weight, (0, 0), fx=3, fy=3) weight = cv2.cvtColor(weight, cv2.COLOR_BGR2GRAY) weight = cv2.bitwise_not(weight) weight = cv2.fastNlMeansDenoising(weight, None, 9, 13) _, weight = cv2.threshold(weight, 180, 255, cv2.THRESH_BINARY) weight = cv2.blur(weight, (4, 2)) weight_text = '' try: weight_text = pytesseract.image_to_string( weight, config=utils.TESSERACT_CONF) except UnicodeDecodeError: utils.log( "SEVERE", "Tesseract failed to parse player weight from screenshot") utils.quit_game() # Split the equation and calculate the difference current_weight, max_weight = weight_text.split("/")[0::1] return int(current_weight), int(max_weight)
def mine(self): """ Mine ore until weight is nearing full. If player destroys all pickaxes, another will be purchased and mining will continue. """ # Get player's weight if self.player.is_weight_below_threshold(50): utils.log("INFO", F"Weight is below threshold, switching task to smelting") return self.player.TASKS.SMELT # No pickaxes left, buy one if not self.player.backpack.get_item('pickaxe'): self.merchant.buy_item('pickaxe', self.merchant.MERCHANTS.BLACKSMITH) self.mining_coords = self.move.go_to_mine() # First time mining, move to the mining region if self.mining_coords is None: self.mining_coords = self.move.go_to_mine() # Check if there is still ore to mine here screenshot = utils.take_screenshot() self.resolve_nothing_to_mine(screenshot) self.resolve_cannot_mine(screenshot) # Use pickaxe self.player.backpack.use_item('pickaxe', (self.mining_coords[0], self.mining_coords[1]), (9, 4)) # Continue mining return self.player.TASKS.MINE
def get_health(self): """ Gets the player's current health """ # Reduce the screenshot to include only the player's health screenshot = utils.take_screenshot(False) health = self.get_ui_element('health', screenshot) health = screenshot[health[1]:(health[1] + 12), (health[0] + 36):(health[0] + 92)] # Resize and parse the image to a string health = cv2.resize(health, (0, 0), fx=3, fy=3) health = cv2.cvtColor(health, cv2.COLOR_BGR2GRAY) health = cv2.bitwise_not(health) health = cv2.fastNlMeansDenoising(health, None, 9, 13) _, health = cv2.threshold(health, 180, 255, cv2.THRESH_BINARY) health = cv2.blur(health, (4, 2)) health_text = '' try: health_text = pytesseract.image_to_string( health, config=utils.TESSERACT_CONF) except UnicodeDecodeError: utils.log( "SEVERE", "Tesseract failed to parse player health from screenshot") utils.quit_game() # Split the equation and calculate the difference current = health_text.split("/")[0] return int(current)
def fire_smelter(self): """ Check if the forge has gone cold. If so, fires it """ # Get the smelter screenshot = utils.take_screenshot() forge = screenshot[152:168, 168:184] # Check if the cold forge exists result = cv2.matchTemplate(forge, self.cold_forge_template, cv2.TM_CCORR_NORMED) max_val = cv2.minMaxLoc(result)[1] # Found cold forge, light it and wait if max_val > 0.9: pyautogui.moveTo(192, 159, 0.15) pyautogui.doubleClick() sleep(1.5)
def get_backpack(self): """ Find and return the player's backpack. Returns a tuple containing the backpack and the coordinates """ # Find backpack in screenshot screenshot = utils.take_screenshot(False) result = cv2.matchTemplate(screenshot, self.backpack_template, cv2.TM_CCORR_NORMED) _, max_val, _, backpack_loc = cv2.minMaxLoc(result) # Failed to find the backpack with high confidence if max_val < 0.9: utils.log("SEVERE", "Unable to find backpack in screenshot") utils.quit_game() # Restrict screenshot to just include the player's backpack #backpack_loc = (backpack_loc[0] + 11, backpack_loc[1] + 33) backpack = screenshot[(backpack_loc[1]):(backpack_loc[1] + 144), (backpack_loc[0]):(backpack_loc[0] + 128)] return (backpack, backpack_loc)
# Set defaults task = Player.TASKS.MINE if len(sys.argv) > 1: task = Player.TASKS[sys.argv[1].upper()] # Initialize classes game_map = GameMap() player = Player(game_map, task) user_interface = UserInterface() utils.log("INIT", "====================================================") utils.log("INIT", "Initializing...") utils.log("INIT", F"Default task set to {task}") # Find blocking window in screenshot screenshot = utils.take_screenshot(False) result = cv2.matchTemplate(screenshot, user_interface.templates['sponsored'], cv2.TM_CCORR_NORMED) _, max_val, _, max_loc = cv2.minMaxLoc(result) # Found the blocking window window with high confidence if max_val > 0.9: click_at = (max_loc[0] + 428, max_loc[1] + 144) utils.log("INIT", "Closed blocking window") pyautogui.moveTo(click_at[0], click_at[1], 0.15) pyautogui.click() sleep(5) # Bring game to foreground utils.bring_game_to_foreground() # Detect environment
def take_screenshot(): utilities.take_screenshot(screen)
def update_player_position(self, screenshot): """To update the player position the following steps are taken. 1. The sextant is location and used 2. The coordinates are parsed and normalized 3. Old player position is removed from map 4. New player position is added to the map If any of these operations fails, the game will quit. Returns tuple containing player position as well as the tile map """ sextant_x = None sextant_y = None errors = 0 while sextant_x is None: # Move mouse to a neutral position that won't obstruct template matching pyautogui.moveTo(400, 400) # Find and use the sextant self.backpack.use_item('sextant', None, (5, 2), True) # Take a new screenshot that includes the location screenshot = utils.take_screenshot() # Find the current position position = screenshot[450:465, 120:180] # Resize and parse the image to a string position = cv2.resize(position, (0, 0), fx=5, fy=5) position = cv2.bitwise_not(position) position = cv2.blur(position, (8, 8)) text = '' try: text = pytesseract.image_to_string(position, config='--psm 8') # Split the text into coordinates sextant_x, sextant_y = text.split(", ")[0::1] except UnicodeDecodeError: utils.log("SEVERE", F"Unable to parse sextant coordinates string") utils.quit_game() except ValueError as value_error: utils.log("WARN", F"{value_error}") # Move mouse to a neutral position that won't obstruct template matching pyautogui.moveTo(400, 400) errors += 1 if errors == 10: utils.log("SEVERE", F"Sextant failed 10 times") utils.quit_game() # Normalize the coordinates to fit the map indicies normalized_x = int(sextant_x) - utils.NORMALIZATION_CONSTANT normalized_y = int(sextant_y) - utils.NORMALIZATION_CONSTANT # Remove old player position from the map self.game_map[self.game_map == self.TILES.PLAYER.value] = self.TILES.ACCESSIBLE.value self.player_position = (normalized_x, normalized_y) self.game_map[self.player_position] = self.TILES.PLAYER.value
def update_map(self, screenshot=None): """ Takes a screenshot of the game, this method will iterate over the gameplay region in 16x16 chunks. Each chunk is compared with all potential inaccessible tiles until a match is found. When a match is found, the frequency of that tile is incremented so future chunks are compared with high frequency tiles first. """ # Get the visible tiles nearby = self.game_map[(self.player_position[0] - 10):(self.player_position[0] + 11), (self.player_position[1] - 10):(self.player_position[1] + 11)] # Clear NPCs in the nearby as they may have moved nearby[nearby == self.TILES.WEAPON_SHOPKEEPER.value] = self.TILES.UNKNOWN.value nearby[nearby == self.TILES.BLACKSMITH.value] = self.TILES.UNKNOWN.value # Take screenshot and isolate the gamplay region if screenshot is None: screenshot = utils.take_screenshot() play = screenshot[8:344, 8:344] # Loop through all unknown tiles in the nearby for i, j in zip(*np.where(nearby == self.TILES.UNKNOWN.value)): # Scale up the dimensions tile_x = i * self.TILE_DIM tile_y = j * self.TILE_DIM # The center cell is always the player if i == 10 and j == 10: tile_x = self.player_position[0] + int(tile_x / 16) - 10 tile_y = self.player_position[1] + int(tile_y / 16) - 10 self.game_map[(tile_x, tile_y)] = self.TILES.PLAYER.value continue # Slice the tile from the play region tile = play[tile_y:tile_y + self.TILE_DIM, tile_x:tile_x + self.TILE_DIM] tile_x = self.player_position[0] + int(tile_x / 16) - 10 tile_y = self.player_position[1] + int(tile_y / 16) - 10 # Go through all tile types looking for a high confidence match template = None for potential_template in self.templates: if np.allclose(potential_template[0], tile, 1, 1): template = potential_template break # No match, assume it is inaccessible if template is None: self.game_map[(tile_x, tile_y)] = self.TILES.INACCESSIBLE.value continue # By default, mark tile as inaccessible label = None # Mark as mineable if re.search(r'rock', template[1], re.M | re.I): label = self.TILES.MOUNTAIN.value elif re.search(r'door', template[1], re.M | re.I): label = self.TILES.DOOR.value elif re.search(r'gravel', template[1], re.M | re.I): label = self.TILES.GRAVEL.value elif re.search(r'shopkeeper', template[1], re.M | re.I): label = self.TILES.WEAPON_SHOPKEEPER.value elif re.search(r'blacksmith', template[1], re.M | re.I): label = self.TILES.BLACKSMITH.value elif re.search(r'guard', template[1], re.M | re.I): label = self.TILES.INACCESSIBLE.value elif re.search(r'inaccessible', template[1], re.M | re.I): label = self.TILES.INACCESSIBLE.value elif re.search(r'accessible', template[1], re.M | re.I): label = self.TILES.ACCESSIBLE.value # Calculate coordinates of tile in the map relative to the player self.game_map[(tile_x, tile_y)] = label # Go through all tiles in the gameplay region to find the mountains for i, j in zip(*np.where(nearby == self.TILES.MOUNTAIN.value)): # Get the tile to the left of the mountain tile_left = nearby[(i - 1, j)] # Only allow mountains to be minable if they are beside gravel if not tile_left == self.TILES.GRAVEL.value: nearby[(i, j)] = self.TILES.INACCESSIBLE.value # Save the game map to disk np.savetxt('map.txt', self.game_map, fmt='%d')