From 33b0b533e6a199486856ff6a5accecd7cd3134e4 Mon Sep 17 00:00:00 2001 From: mgleed Date: Tue, 30 Nov 2021 16:40:05 -0500 Subject: [PATCH] Support list input for template_finder.search() and improve code quality (#125) --- game.ini | 12 +--- src/belt_manager.py | 8 +-- src/bot.py | 12 ++-- src/char/i_char.py | 25 +++---- src/death_manager.py | 2 +- src/game_recovery.py | 6 +- src/health_manager.py | 2 +- src/npc_manager.py | 22 +++--- src/pather.py | 13 ++-- src/shop/ias_gloves.py | 16 ++--- src/template_finder.py | 128 +++++++++++++++++++++------------- src/ui_manager.py | 73 +++++++++---------- src/utils/graphic_debugger.py | 10 +-- src/utils/node_recorder.py | 6 +- 14 files changed, 177 insertions(+), 158 deletions(-) diff --git a/game.ini b/game.ini index b4a051a..f93d8c6 100644 --- a/game.ini +++ b/game.ini @@ -25,12 +25,6 @@ ingame_ref_x=625 ingame_ref_y=669 ; some distance skill_bar_height=67 -normal_x=640 -normal_y=304 -nightmare_x=640 -nightmare_y=349 -hell_x=640 -hell_y=390 save_and_exit_x=633 save_and_exit_y=317 issue_occured_ok_x=640 @@ -99,9 +93,9 @@ max_walk_dist=127 is_overburdened=11,510,313,60 save_and_exit=500,233,273,173 go_btn=533,627,213,67 -normal_btn=560,280,163,47 -nightmare_btn=560,327,163,47 -hell_btn=560,367,163,47 +normal_btn=373,187,109,31 +nightmare_btn=373,218,109,31 +hell_btn=373,245,109,31 gold_btn=927,491,267,100 health_globe=160,580,240,140 mana_globe=887,580,240,140 diff --git a/src/belt_manager.py b/src/belt_manager.py index 6e90427..2d9f61c 100644 --- a/src/belt_manager.py +++ b/src/belt_manager.py @@ -155,10 +155,10 @@ class BeltManager: pot_positions = [] for column, row in itertools.product(range(num_loot_columns), range(4)): center_pos, slot_img = UiManager.get_slot_pos_and_img(self._config, img, column, row) - found = self._template_finder.search("SUPER_HEALING_POTION", slot_img, threshold=0.9)[0] - found |= self._template_finder.search("SUPER_MANA_POTION", slot_img, threshold=0.9)[0] - found |= self._template_finder.search("FULL_REJUV_POTION", slot_img, threshold=0.9)[0] - found |= self._template_finder.search("REJUV_POTION", slot_img, threshold=0.9)[0] + found = self._template_finder.search("SUPER_HEALING_POTION", slot_img, threshold=0.9).valid + found |= self._template_finder.search("SUPER_MANA_POTION", slot_img, threshold=0.9).valid + found |= self._template_finder.search("FULL_REJUV_POTION", slot_img, threshold=0.9).valid + found |= self._template_finder.search("REJUV_POTION", slot_img, threshold=0.9).valid if found: pot_positions.append(center_pos) keyboard.press("shift") diff --git a/src/bot.py b/src/bot.py index 852ce7e..a759ac0 100644 --- a/src/bot.py +++ b/src/bot.py @@ -126,7 +126,9 @@ class Bot: def on_create_game(self): self._game_stats.log_start_game() - self._template_finder.search_and_wait("D2_LOGO_HS", roi=self._config.ui_roi["hero_selection_logo"]) + template_match = self._template_finder.search_and_wait("D2_LOGO_HS", roi=self._config.ui_roi["hero_selection_logo"]) + if template_match.valid: + Logger.debug(f"Found {template_match.name}") if not self._ui_manager.start_game(): return self._template_finder.search_and_wait(["A5_TOWN_1", "A5_TOWN_0"]) @@ -199,7 +201,7 @@ class Bot: wait(0.5) # Check if merc needs to be revived - merc_alive, _ = self._template_finder.search("MERC", self._screen.grab(), threshold=0.9, roi=[0, 0, 200, 200]) + merc_alive = self._template_finder.search("MERC", self._screen.grab(), threshold=0.9, roi=[0, 0, 200, 200]).valid if not merc_alive: Logger.info("Reviving merc.") if not self._pather.traverse_nodes(self._curr_location, Location.QUAL_KEHK, self._char): @@ -269,7 +271,7 @@ class Bot: wait(0.2, 0.4) self.success &= bot._char.select_by_template(["A5_RED_PORTAL", "A5_RED_PORTAL_TEXT"], expect_loading_screen=True) time.sleep(0.5) - self.success &= bot._template_finder.search_and_wait(["PINDLE_0", "PINDLE_1"], threshold=0.65, time_out=20)[0] + self.success &= bot._template_finder.search_and_wait(["PINDLE_0", "PINDLE_1"], threshold=0.65, time_out=20).valid if not self.success: return if not bot._pre_buffed: @@ -304,7 +306,7 @@ class Bot: wait(1.0) bot._ui_manager.use_wp(4, 1) time.sleep(0.5) - self.success = bot._template_finder.search_and_wait(["ELDRITCH_0", "ELDRITCH_START"], threshold=0.65, time_out=20)[0] + self.success = bot._template_finder.search_and_wait(["ELDRITCH_0", "ELDRITCH_START"], threshold=0.65, time_out=20).valid if not self.success: return if not bot._pre_buffed: @@ -360,7 +362,7 @@ class Bot: self._tps_left -= 1 success &= not self._death_manager.handle_death_screen() if success: - success, _= self._template_finder.search_and_wait(["A5_TOWN_1", "A5_TOWN_0"], time_out=10) + success = self._template_finder.search_and_wait(["A5_TOWN_1", "A5_TOWN_0"], time_out=10).valid if success: self._tp_is_up = True self._curr_location = Location.A5_TOWN_START diff --git a/src/char/i_char.py b/src/char/i_char.py index 8054a63..15d9ddd 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -41,14 +41,14 @@ class IChar: def select_by_template(self, template_type: Union[str, List[str]], expect_loading_screen: bool = False) -> bool: if template_type == "A5_STASH": # sometimes waypoint is opened and stash not found because of that, check for that - if self._template_finder.search("WAYPOINT_MENU", self._screen.grab())[0]: + if self._template_finder.search("WAYPOINT_MENU", self._screen.grab()).valid: keyboard.send("esc") Logger.debug(f"Select {template_type}") start = time.time() while (time.time() - start) < 8: - success, screen_loc = self._template_finder.search_and_wait(template_type, time_out=2) - if success: - x_m, y_m = self._screen.convert_screen_to_monitor(screen_loc) + template_match = self._template_finder.search_and_wait(template_type, time_out=2) + if template_match.valid: + x_m, y_m = self._screen.convert_screen_to_monitor(template_match.position) mouse.move(x_m, y_m) wait(0.3, 0.4) mouse.click(button="left") @@ -113,23 +113,16 @@ class IChar: start = time.time() while (time.time() - start) < 8: img = self._screen.grab() - success1, pos1 = self._template_finder.search( - "BLUE_PORTAL", + template_match = self._template_finder.search( + ["BLUE_PORTAL","BLUE_PORTAL_2"], img, threshold=0.66, roi=roi, normalize_monitor=True ) - success2, pos2 = self._template_finder.search( - "BLUE_PORTAL_2", - img, - threshold=0.7, - roi=roi, - normalize_monitor=True - ) - if success1 or success2: - pos = pos1 if success1 else pos2 - pos = (pos[0], pos[1] + 30 ) + if template_match.valid: + pos = template_match.position + pos = (pos[0], pos[1] + 30) # Note: Template is top of portal, thus move the y-position a bit to the bottom mouse.move(*pos, randomize=6, delay_factor=[0.9, 1.1]) wait(0.08, 0.15) diff --git a/src/death_manager.py b/src/death_manager.py index ca2c4d5..7703a85 100644 --- a/src/death_manager.py +++ b/src/death_manager.py @@ -52,7 +52,7 @@ class DeathManager: wait(0.1, 0.2) keyboard.release(self._config.char["show_items"]) time.sleep(6) - if self._template_finder.search("D2_LOGO_HS", self._screen.grab(), roi=self._config.ui_roi["hero_selection_logo"])[0]: + if self._template_finder.search("D2_LOGO_HS", self._screen.grab(), roi=self._config.ui_roi["hero_selection_logo"]).valid: # in this case chicken executed and left the game, but we were still dead. return True keyboard.send("esc") diff --git a/src/game_recovery.py b/src/game_recovery.py index eb87297..f90ab7d 100644 --- a/src/game_recovery.py +++ b/src/game_recovery.py @@ -24,10 +24,10 @@ class GameRecovery: # make sure we are not on loading screen is_loading = True while is_loading: - is_loading = self._template_finder.search("LOADING", self._screen.grab())[0] + is_loading = self._template_finder.search("LOADING", self._screen.grab()).valid time.sleep(0.5) # first lets just see if you might already be at hero selection - found, _ = self._template_finder.search_and_wait("D2_LOGO_HS", time_out=1, take_ss=False, roi=self._config.ui_roi["hero_selection_logo"]) + found = self._template_finder.search_and_wait("D2_LOGO_HS", time_out=1, take_ss=False, roi=self._config.ui_roi["hero_selection_logo"]).valid if found: return True # would have been too easy, maybe we have died? @@ -39,7 +39,7 @@ class GameRecovery: keyboard.send("esc") time.sleep(1) templates = ["SAVE_AND_EXIT_NO_HIGHLIGHT","SAVE_AND_EXIT_HIGHLIGHT"] - found, _ = self._template_finder.search_and_wait(templates, roi=self._config.ui_roi["save_and_exit"], time_out=1.5, take_ss=False) + found = self._template_finder.search_and_wait(templates, roi=self._config.ui_roi["save_and_exit"], time_out=1.5, take_ss=False).valid if found: keyboard.send("esc") time.sleep(1) diff --git a/src/health_manager.py b/src/health_manager.py index dc3ff4d..4d62d52 100644 --- a/src/health_manager.py +++ b/src/health_manager.py @@ -109,7 +109,7 @@ class HealthManager: self._belt_manager.drink_potion("mana", stats=[health_percentage, mana_percentage]) self._last_mana = time.time() # check merc - merc_alive, _ = self._template_finder.search("MERC", img, roi=self._config.ui_roi["merc_icon"]) + merc_alive = self._template_finder.search("MERC", img, roi=self._config.ui_roi["merc_icon"]).valid if merc_alive: merc_health_percentage = self.get_merc_health(img) last_drink = time.time() - self._last_merc_healh diff --git a/src/npc_manager.py b/src/npc_manager.py index ec626f3..89c7bd3 100644 --- a/src/npc_manager.py +++ b/src/npc_manager.py @@ -62,9 +62,9 @@ class NpcManager: img = self._screen.grab() results = [] for key in self._npcs[npc_key]["template_group"]: - res, pos = self._template_finder.search(key, img, threshold=0.35, roi=roi, normalize_monitor=True) - if res: - results.append({"pos": pos, "score": self._template_finder.last_score}) + res = self._template_finder.search(key, img, threshold=0.35, roi=roi, normalize_monitor=True) + if res.valid: + results.append({"pos": res.position, "score": res.score}) results = sorted(results, key=lambda r: r["score"], reverse=True) for result in results: @@ -73,13 +73,13 @@ class NpcManager: wait(0.2, 0.3) _, filtered_inp_w = color_filter(self._screen.grab(), self._config.colors["white"]) _, filtered_inp_g = color_filter(self._screen.grab(), self._config.colors["gold"]) - res_w, _ = self._template_finder.search(self._npcs[npc_key]["name_tag_white"], filtered_inp_w, 0.9, roi=roi) - res_g, _ = self._template_finder.search(self._npcs[npc_key]["name_tag_gold"], filtered_inp_g, 0.9, roi=roi) + res_w = self._template_finder.search(self._npcs[npc_key]["name_tag_white"], filtered_inp_w, 0.9, roi=roi).valid + res_g = self._template_finder.search(self._npcs[npc_key]["name_tag_gold"], filtered_inp_g, 0.9, roi=roi).valid if res_w: mouse.click(button="left") wait(1.4, 1.7) _, filtered_inp = color_filter(self._screen.grab(), self._config.colors["gold"]) - res, _ = self._template_finder.search(self._npcs[npc_key]["name_tag_gold"], filtered_inp, 0.9, roi=roi) + res = self._template_finder.search(self._npcs[npc_key]["name_tag_gold"], filtered_inp, 0.9, roi=roi).valid if res: return True elif res_g: @@ -88,13 +88,13 @@ class NpcManager: def press_npc_btn(self, npc_key: Npc, action_btn_key: str): _, filtered_inp = color_filter(self._screen.grab(), self._config.colors["white"]) - res, pos = self._template_finder.search( - self._npcs[npc_key]["action_btns"][action_btn_key], - filtered_inp, 0.85, roi=self._config.ui_roi["cut_skill_bar"], + res = self._template_finder.search( + self._npcs[npc_key]["action_btns"][action_btn_key], + filtered_inp, 0.85, roi=self._config.ui_roi["cut_skill_bar"], normalize_monitor=True ) - if res: - mouse.move(*pos, randomize=3, delay_factor=[1.0, 1.5]) + if res.valid: + mouse.move(*res.position, randomize=3, delay_factor=[1.0, 1.5]) wait(0.2, 0.4) mouse.click(button="left") wait(0.3, 0.4) diff --git a/src/pather.py b/src/pather.py index ce0a240..cbdbacb 100644 --- a/src/pather.py +++ b/src/pather.py @@ -137,7 +137,10 @@ class Pather: success = True ref_pos_screen = template_map[template_type] else: - success, ref_pos_screen = self._template_finder.search(template_type, img) + template_match = self._template_finder.search(template_type, img) + if template_match.valid: + ref_pos_screen = template_match.position + success = True if success: template_map[template_type] = ref_pos_screen # Get reference position of template in abs coordinates @@ -201,10 +204,10 @@ class Pather: def find_abs_node_pos(self, node_idx: int, img: np.ndarray) -> Tuple[float, float]: node = self._nodes[node_idx] for template_type in node: - success, ref_pos_screen = self._template_finder.search(template_type, img) - if success: + template_match = self._template_finder.search(template_type, img) + if template_match.valid: # Get reference position of template in abs coordinates - ref_pos_abs = self._screen.convert_screen_to_abs(ref_pos_screen) + ref_pos_abs = self._screen.convert_screen_to_abs(template_match.position) # Calc the abs node position with the relative coordinates (relative to ref) node_pos_rel = self._get_node(node_idx, template_type) node_pos_abs = self._convert_rel_to_abs(node_pos_rel, ref_pos_abs) @@ -231,7 +234,7 @@ class Pather: while not continue_to_next_node: img = self._screen.grab() if (time.time() - last_move) > time_out: - success, _ = self._template_finder.search("WAYPOINT_MENU", img) + success = self._template_finder.search("WAYPOINT_MENU", img).valid if success: # sometimes bot opens waypoint menu, close it to find templates again Logger.debug("Opened wp, closing it again") diff --git a/src/shop/ias_gloves.py b/src/shop/ias_gloves.py index 475fbb3..efe9bd7 100644 --- a/src/shop/ias_gloves.py +++ b/src/shop/ias_gloves.py @@ -78,19 +78,19 @@ class JavaShopper: img = self._screen.grab() # 20 IAS gloves have a unique color so we can skip all others - ias_glove_found, pos = self._template_finder.search( + ias_glove_found = self._template_finder.search( ref=load_template(asset_folder + "ias_gloves.png", 1.0), inp_img=img, threshold=0.96, roi=self.config.ui_roi["vendor_stash"], normalize_monitor=True, ) - if ias_glove_found: + if ias_glove_found.valid: self.ias_gloves_seen += 1 - mouse.move(*pos) + mouse.move(*ias_glove_found.position) time.sleep(0.1) img = self._screen.grab() - gg_gloves_found, pos = self._template_finder.search( + gg_gloves_found = self._template_finder.search( ref=load_template( asset_folder + "gg_gloves.png", 1.0 ), @@ -98,7 +98,7 @@ class JavaShopper: threshold=0.80, normalize_monitor=True, ) - if gg_gloves_found: + if gg_gloves_found.valid: mouse.click(button="right") Logger.info("GG gloves bought!") self.gloves_bought += 1 @@ -126,9 +126,9 @@ class JavaShopper: def select_by_template(self, template_type: str) -> bool: Logger.debug(f"Select {template_type}") - success, screen_loc = self._template_finder.search_and_wait(template_type, time_out=10) - if success: - x_m, y_m = self._screen.convert_screen_to_monitor(screen_loc) + template_match = self._template_finder.search_and_wait(template_type, time_out=10) + if template_match.valid: + x_m, y_m = self._screen.convert_screen_to_monitor(template_match.position) mouse.move(x_m, y_m) wait(0.1, 0.2) mouse.click(button="left") diff --git a/src/template_finder.py b/src/template_finder.py index d4934c1..eab946d 100644 --- a/src/template_finder.py +++ b/src/template_finder.py @@ -1,6 +1,7 @@ import cv2 from screen import Screen from typing import Tuple, Union, List +from dataclasses import dataclass import numpy as np from logger import Logger import time @@ -9,13 +10,19 @@ from config import Config from utils.misc import load_template +@dataclass +class TemplateMatch: + name: str = None + score: float = -1.0 + position: Tuple[float, float] = None + valid: bool = False + class TemplateFinder: def __init__(self, screen: Screen, scale_factor: float = None): """ :param screen: Screen object :param scale_factor: Scale factor that is used for templates. Note: UI and NPC templates will always have scale of 1.0 """ - self.last_score = -1.0 self._screen = screen self._config = Config() self._scale_factor = 1.0 @@ -138,18 +145,20 @@ class TemplateFinder: def search( self, - ref: Union[str, np.ndarray], + ref: Union[str, np.ndarray, List[str]], inp_img: np.ndarray, threshold: float = None, roi: List[float] = None, normalize_monitor: bool = False, - ) -> Tuple[bool, Tuple[float, float]]: + best_match: bool = False + ) -> TemplateMatch: """ Search for a template in an image - :param ref: Either key of a already loaded template or a image which is used as template + :param ref: Either key of a already loaded template, list of such keys, or a image which is used as template :param inp_img: Image in which the template will be searched :param threshold: Threshold which determines if a template is found or not :param roi: Region of Interest of the inp_img to restrict search area. Format [left, top, width, height] + :param best_match: If list input, will search for list of templates by best match. Default behavior is first match. :return: Returns found flag and the position as [bool, [x, y]]. If not found, position will be None. Position in image space. """ threshold = self._config.advanced_options["template_threshold"] if threshold is None else threshold @@ -160,31 +169,61 @@ class TemplateFinder: inp_img = inp_img[ry:ry + rh, rx:rx + rw] if type(ref) == str: - template = self._templates[ref][0] - scale = self._templates[ref][1] + templates = [self._templates[ref][0]] + scales = [self._templates[ref][1]] + names = [ref] + best_match = False + elif type(ref) == list: + templates = [self._templates[i][0] for i in ref] + scales = [self._templates[i][1] for i in ref] + names = ref else: - template = ref - scale = 1.0 + templates = [ref] + scales = [1.0] + best_match = False - img: np.ndarray = cv2.resize(inp_img, None, fx=scale, fy=scale, interpolation=cv2.INTER_NEAREST) - rx *= scale - ry *= scale - rw *= scale - rh *= scale + scores = [0] * len(ref) + ref_points = [(0, 0)] * len(ref) + for count, template in enumerate(templates): + template_match = TemplateMatch() + scale = scales[count] - if img.shape[0] > template.shape[0] and img.shape[1] > template.shape[1]: - res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED) - _, max_val, _, max_pos = cv2.minMaxLoc(res) - self.last_score = max_val - if max_val > threshold: - ref_point = (max_pos[0] + int(template.shape[1] * 0.5) + rx, max_pos[1] + int(template.shape[0] * 0.5) + ry) - ref_point = (int(ref_point[0] * (1.0 / scale)), int(ref_point[1] * (1.0 / scale))) + img: np.ndarray = cv2.resize(inp_img, None, fx=scale, fy=scale, interpolation=cv2.INTER_NEAREST) + rx *= scale + ry *= scale + rw *= scale + rh *= scale - if normalize_monitor: - ref_point = self._screen.convert_screen_to_monitor(ref_point) + if img.shape[0] > template.shape[0] and img.shape[1] > template.shape[1]: + res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED) + _, max_val, _, max_pos = cv2.minMaxLoc(res) + if max_val > threshold: + ref_point = (max_pos[0] + int(template.shape[1] * 0.5) + rx, max_pos[1] + int(template.shape[0] * 0.5) + ry) + ref_point = (int(ref_point[0] * (1.0 / scale)), int(ref_point[1] * (1.0 / scale))) - return True, ref_point - return False, None + if normalize_monitor: + ref_point = self._screen.convert_screen_to_monitor(ref_point) + + if best_match: + scores[count]=max_val + ref_points[count]=ref_point + else: + try: template_match.name = names[count] + except: pass + template_match.position = ref_point + template_match.score = max_val + template_match.valid = True + return template_match + + if max(scores) > 0: + idx=scores.index(max(scores)) + try: template_match.name = names[idx] + except: pass + template_match.position = ref_points[idx] + template_match.score = scores[idx] + template_match.valid = True + + return template_match def search_and_wait( self, @@ -192,36 +231,34 @@ class TemplateFinder: roi: List[float] = None, time_out: float = None, threshold: float = None, + best_match: bool = False, take_ss: bool = True - ) -> Tuple[bool, Tuple[float, float]]: + ) -> TemplateMatch: """ Helper function that will loop and keep searching for a template - :param ref: Key of template which has been loaded beforehand + :param ref: Key of template (or list of keys) which has been loaded beforehand :param time_out: After this amount of time the search will stop and it will return [False, None] :param threshold: Adapt threshold for being found + :param best_match: If list input, will search for list of templates by best match. Default behavior is first match. :param take_ss: Bool value to take screenshot on timeout or not (flag must still be set in params!) Rest of params same as TemplateFinder.search() """ + if type(ref) is str: + ref = [ref] threshold = self._config.advanced_options["template_threshold"] if threshold is None else threshold Logger.debug(f"Waiting for Template {ref}") start = time.time() while 1: img = self._screen.grab() + template_match = self.search(ref, img, roi=roi, threshold=threshold, best_match=best_match) is_loading_black_roi = np.average(img[:, 0:self._config.ui_roi["loading_left_black"][2]]) < 1.0 - - if type(ref) is str: - ref = [ref] - for x in ref: - success, pos = self.search(x, img, roi=roi, threshold=threshold) - if success: - break - if not is_loading_black_roi: - if success: - return True, pos - elif time_out is not None and (time.time() - start) > time_out: + if not is_loading_black_roi or "LOADING" in ref: + if template_match.valid: + return template_match + if time_out is not None and (time.time() - start) > time_out: if self._config.general["info_screenshots"] and take_ss: cv2.imwrite(f"./info_screenshots/info_wait_for_{ref}_time_out_" + time.strftime("%Y%m%d_%H%M%S") + ".png", img) - return False, None + return template_match # Testing: Have whatever you want to find on the screen @@ -231,19 +268,16 @@ if __name__ == "__main__": config = Config() screen = Screen(config.general["monitor"]) template_finder = TemplateFinder(screen) - search_templates = ["ELDRITCH_4", "ELDRITCH_3", "ELDRITCH_2", "ELDRITCH_1"] - scores = {} + search_templates = ["ELDRITCH_1", "ELDRITCH_4", "ELDRITCH_3", "ELDRITCH_2"] while 1: # img = cv2.imread("") img = screen.grab() display_img = img.copy() - for template_name in search_templates: - success, pos = template_finder.search(template_name, img) - scores[template_name] = template_finder.last_score - if success: - cv2.putText(display_img, str(template_name), pos, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2, cv2.LINE_AA) - cv2.circle(display_img, pos, 7, (255, 0, 0), thickness=5) + template_match = template_finder.search(search_templates,img,best_match=1) + if template_match.valid: + cv2.putText(display_img, str(template_match.name), template_match.position, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2, cv2.LINE_AA) + cv2.circle(display_img, template_match.position, 7, (255, 0, 0), thickness=5) + print(f"Name: {template_match.name} Pos: {template_match.position}, Score: {template_match.score}") display_img = cv2.resize(display_img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_NEAREST) - print(scores) cv2.imshow('test', display_img) key = cv2.waitKey(1) diff --git a/src/ui_manager.py b/src/ui_manager.py index bf3726a..846ea71 100644 --- a/src/ui_manager.py +++ b/src/ui_manager.py @@ -67,7 +67,7 @@ class UiManager(): self._config.ui_pos["skill_height"] ] for template in template_list: - if self._template_finder.search(template, self._screen.grab(), threshold=0.94, roi=roi)[0]: + if self._template_finder.search(template, self._screen.grab(), threshold=0.94, roi=roi).valid: return True return False @@ -109,7 +109,7 @@ class UiManager(): # TODO: Add hardcoded coordinates to ini file away_x_m, away_y_m = self._screen.convert_abs_to_monitor((-167, 0)) templates = ["SAVE_AND_EXIT_NO_HIGHLIGHT","SAVE_AND_EXIT_HIGHLIGHT"] - while self._template_finder.search_and_wait(templates, roi=self._config.ui_roi["save_and_exit"], time_out=1.5, take_ss=False)[0]: + while self._template_finder.search_and_wait(templates, roi=self._config.ui_roi["save_and_exit"], time_out=1.5, take_ss=False).valid: delay = [0.9, 1.1] if does_chicken: delay = [0.3, 0.4] @@ -133,6 +133,7 @@ class UiManager(): Starting a game. Will wait and retry on server connection issue. :return: Bool if action was successful """ + Logger.debug("Wait for active Play button") # To test the start_game() function seperatly, just run: # (botty) >> python src/ui_manager.py # then go to D2r window -> press "f11", you can exit with "f12" @@ -143,16 +144,10 @@ class UiManager(): # it returns a bool value (True or False) if the button was found, and the position of it # roi = Region of interest. It reduces the search area and can be adapted within game.ini # by running >> python src/screen.py you can visualize all of the currently set region of interests - found_btn, btn_pos = self._template_finder.search("PLAY_BTN", img, roi=self._config.ui_roi["go_btn"], threshold=0.8) - score_enabled = self._template_finder.last_score - # same as above, just with different template - self._template_finder.search("PLAY_BTN_GRAY", img, roi=self._config.ui_roi["go_btn"], threshold=0.8) - score_disabled = self._template_finder.last_score - # found_btn is a bool (True or False), if a play btn was found and it is not grayed out, then we can proceed - found_btn = found_btn and score_enabled > score_disabled - if found_btn: + found_btn = self._template_finder.search(["PLAY_BTN","PLAY_BTN_GRAY"], img, roi=self._config.ui_roi["go_btn"], threshold=0.8, best_match=True) + if found_btn.name == "PLAY_BTN": # We need to convert the position to monitor coordinates (e.g. if someone is using 2 monitors or windowed mode) - x, y = self._screen.convert_screen_to_monitor(btn_pos) + x, y = self._screen.convert_screen_to_monitor(found_btn.position) Logger.debug(f"Found Play Btn") # move the mouse to the play button and randomize the position a bit. +-35 pixel in x direction, +-7 pixel in y direction mouse.move(x, y, randomize=[35, 7], delay_factor=[1.0, 1.8]) @@ -162,28 +157,22 @@ class UiManager(): break else: # Might be in online mode? - found_btn, _ = self._template_finder.search("PLAY_BTN", img, roi=self._config.ui_roi["play_btn"], threshold=0.8) - if found_btn: + found_btn = self._template_finder.search("PLAY_BTN", img, roi=self._config.ui_roi["play_btn"], threshold=0.8) + if found_btn.valid: Logger.error("Botty only works for single player. Please switch to offline mode and restart botty!") return False time.sleep(3.0) - difficulty=self._config.general["difficulty"].lower() - Logger.debug(f"Searching for {difficulty} Btn...") + difficulty=self._config.general["difficulty"].upper() while 1: - # edge case: if a player hasn't unlocked nightmare difficulty, there won't be an option to select difficulty after clicking play button - wait(0.75,1.25) - if self._template_finder.search("LOADING", self._screen.grab())[0]: - Logger.debug("On loading screen, nightmare not unlocked") - return True - - found, pos = self._template_finder.search_and_wait("NORMAL_BTN", roi=self._config.ui_roi["normal_btn"], time_out=8) - - if not found: - Logger.debug("Could not find btn, try from start again") + template_match = self._template_finder.search_and_wait(["LOADING", f"{difficulty}_BTN"], time_out=8, threshold=0.9) + if not template_match.valid: + Logger.debug(f"Could not find, try from start again") return self.start_game() - - x, y = self._screen.convert_screen_to_monitor((self._config.ui_pos[f"{difficulty}_x"], self._config.ui_pos[f"{difficulty}_y"])) + if template_match.name == "LOADING": + Logger.debug(f"Found {template_match.name} screen") + return True + x, y = self._screen.convert_screen_to_monitor(template_match.position) Logger.debug(f"Found {difficulty} Btn -> clicking it") mouse.move(x, y, randomize=[50, 9], delay_factor=[1.0, 1.8]) wait(0.15, 0.2) @@ -192,7 +181,7 @@ class UiManager(): # check for server issue wait(2.0) - server_issue, _ = self._template_finder.search("SERVER_ISSUES", self._screen.grab()) + server_issue = self._template_finder.search("SERVER_ISSUES", self._screen.grab()).valid if server_issue: Logger.warning("Server connection issue. waiting 20s") x, y = self._screen.convert_screen_to_monitor((self._config.ui_pos["issue_occured_ok_x"], self._config.ui_pos["issue_occured_ok_y"])) @@ -276,8 +265,8 @@ class UiManager(): # TODO: Do not stash portal scrolls and potions but throw them out of inventory on the ground! # then the pickit check for potions and belt free can also be removed Logger.debug("Searching for inventory gold btn...") - found, pos_gold_btn = self._template_finder.search_and_wait("INVENTORY_GOLD_BTN", roi=self._config.ui_roi["gold_btn"], time_out=20) - if not found: + gold_btn = self._template_finder.search_and_wait("INVENTORY_GOLD_BTN", roi=self._config.ui_roi["gold_btn"], time_out=20) + if not gold_btn.valid: Logger.error("Could not determine to be in stash menu. Continue...") return Logger.debug("Found inventory gold btn") @@ -292,7 +281,7 @@ class UiManager(): wait(0.3, 0.4) # stash gold if self._config.char["stash_gold"]: - x, y = self._screen.convert_screen_to_monitor(pos_gold_btn) + x, y = self._screen.convert_screen_to_monitor(gold_btn.position) mouse.move(x, y, randomize=4) wait(0.1, 0.15) mouse.press(button="left") @@ -394,10 +383,10 @@ class UiManager(): Repair and fills up TP buy selling tomb and buying. Vendor inventory needs to be open! :return: Bool if success """ - found, pos_repair_abs = self._template_finder.search_and_wait("REPAIR_BTN", roi=self._config.ui_roi["repair_btn"], time_out=4) - if not found: + repair_btn = self._template_finder.search_and_wait("REPAIR_BTN", roi=self._config.ui_roi["repair_btn"], time_out=4) + if not repair_btn.valid: return False - x, y = self._screen.convert_screen_to_monitor(pos_repair_abs) + x, y = self._screen.convert_screen_to_monitor(repair_btn.position) mouse.move(x, y, randomize=12, delay_factor=[1.0, 1.5]) wait(0.1, 0.15) mouse.click(button="left") @@ -407,10 +396,10 @@ class UiManager(): wait(0.1, 0.15) mouse.click(button="left") wait(0.5, 0.6) - found, pos_tp_inventory = self._template_finder.search_and_wait("TP_TOMB", roi=self._config.ui_roi["inventory"], time_out=3) - if not found: + tp_tomb = self._template_finder.search_and_wait("TP_TOMB", roi=self._config.ui_roi["inventory"], time_out=3) + if not tp_tomb.valid: return False - x, y = self._screen.convert_screen_to_monitor(pos_tp_inventory) + x, y = self._screen.convert_screen_to_monitor(tp_tomb.position) keyboard.send('ctrl', do_release=False) mouse.move(x, y, randomize=8, delay_factor=[1.0, 1.5]) wait(0.1, 0.15) @@ -419,16 +408,20 @@ class UiManager(): mouse.release(button="left") wait(0.5, 0.6) keyboard.send('ctrl', do_press=False) - found, pos_tp_inventory = self._template_finder.search_and_wait("TP_TOMB", roi=self._config.ui_roi["vendor_stash"], time_out=3) - if not found: + tp_tomb = self._template_finder.search_and_wait("TP_TOMB", roi=self._config.ui_roi["vendor_stash"], time_out=3) + if not tp_tomb.valid: return False - x, y = self._screen.convert_screen_to_monitor(pos_tp_inventory) + x, y = self._screen.convert_screen_to_monitor(tp_tomb.position) keyboard.send('ctrl', do_release=False) mouse.move(x, y, randomize=8, delay_factor=[1.0, 1.5]) wait(0.1, 0.15) mouse.click(button="right") wait(0.1, 0.15) keyboard.send('ctrl', do_press=False) + # delay to make sure the tome has time to transfer to other inventory before closing window + tp_tomb = self._template_finder.search_and_wait("TP_TOMB", roi=self._config.ui_roi["inventory"], time_out=3) + if not tp_tomb.valid: + return False return True diff --git a/src/utils/graphic_debugger.py b/src/utils/graphic_debugger.py index 18e5f18..4984ead 100644 --- a/src/utils/graphic_debugger.py +++ b/src/utils/graphic_debugger.py @@ -29,11 +29,11 @@ def run_graphic_debugger(): # Show Town A5 template matches scores = {} for template_name in search_templates: - success, pos = template_finder.search(template_name, img, threshold=0.65) - if success: - scores[template_name] = template_finder.last_score - cv2.putText(combined_img, str(template_name), pos, cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA) - cv2.circle(combined_img, pos, 7, (255, 0, 0), thickness=5) + template_match = template_finder.search(template_name, img, threshold=0.65) + if template_match.valid: + scores[template_match.name] = template_match.score + cv2.putText(combined_img, str(template_name), template_match.position, cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA) + cv2.circle(combined_img, template_match.position, 7, (255, 0, 0), thickness=5) if len(scores) > 0: print(scores) # Show img diff --git a/src/utils/node_recorder.py b/src/utils/node_recorder.py index 3d0ec8a..c26cc3b 100644 --- a/src/utils/node_recorder.py +++ b/src/utils/node_recorder.py @@ -33,10 +33,10 @@ class NodeRecorder: img = self._screen.grab() for key in self._template_finder._templates: if key not in self._ref_points: - found, pos = self._template_finder.search(key, img) - if found: + found = self._template_finder.search(key, img) + if found.valid: print(f"Found Previous: {key}") - self._ref_points[key] = pos + self._ref_points[key] = found.position def hook(self, e): if e.event_type == "down":