diff --git a/config/params.ini b/config/params.ini index f2b55d0..dacbb9e 100644 --- a/config/params.ini +++ b/config/params.ini @@ -19,6 +19,7 @@ discord_status_condensed=0 info_screenshots=1 loot_screenshots=0 saved_games_folder= +find_window_via_win32_api=0 d2r_path=C:\Program Files (x86)\Diablo II Resurrected\D2R.exe ; if you set this field to 1, botty will save the top-most visible character template just after ; the online/offline tab and upon restart will attempt to find the same template to select the char diff --git a/src/config.py b/src/config.py index 4de67d9..30d70a9 100644 --- a/src/config.py +++ b/src/config.py @@ -112,7 +112,8 @@ class Config: "info_screenshots": bool(int(Config._select_val("general", "info_screenshots"))), "loot_screenshots": bool(int(Config._select_val("general", "loot_screenshots"))), "d2r_path": Config._select_val("general", "d2r_path"), - "restart_d2r_when_stuck": bool(int(Config._select_val("general", "restart_d2r_when_stuck"))) + "restart_d2r_when_stuck": bool(int(Config._select_val("general", "restart_d2r_when_stuck"))), + "find_window_via_win32_api": bool(int(Config._select_val("general", "find_window_via_win32_api"))) } # Added for dclone ip hunting diff --git a/src/screen.py b/src/screen.py index 52b579b..cb315f3 100644 --- a/src/screen.py +++ b/src/screen.py @@ -5,6 +5,7 @@ import time from logger import Logger from typing import Tuple from config import Config +from utils import misc from utils.misc import load_template import os @@ -24,12 +25,42 @@ class Screen: self._config = Config() self._monitor_roi = self._sct.monitors[monitor_idx] # auto find offests + self.found_offsets = False + position = None + if self._config.general["find_window_via_win32_api"] : + Logger.debug("Using WinAPI to search for window under D2R.exe process") + position = self.find_window_via_winapi() + if position is None: + Logger.debug("Can't find any window owned by D2R.exe falling back to matching via assets. Make sure D2R is in focus and you are on the hero selection screen") + + if position is None: + position = self.find_window_via_assets(wait) + + if position is not None: + self._set_window_position(*position) + else: + if self._config.general["info_screenshots"]: + cv2.imwrite("./info_screenshots/error_d2r_window_not_found_" + time.strftime("%Y%m%d_%H%M%S") + ".png", self.grab()) + Logger.error("Could not find hero selection or template for ingame, shutting down") + Logger.error("Could not determine window offset. Please make sure you have the D2R window " + + f"focused and that you are on the hero selection screen when pressing {self._config.general['resume_key']}") + + def _set_window_position(self, offset_x: int, offset_y: int): + Logger.debug(f"Set offsets: left {offset_x}px, top {offset_y}px") + self._monitor_roi["top"] += offset_y + self._monitor_roi["left"] += offset_x + self._monitor_x_range = (self._monitor_roi["left"] + 10, self._monitor_roi["left"] + self._monitor_roi["width"] - 10) + self._monitor_y_range = (self._monitor_roi["top"] + 10, self._monitor_roi["top"] + self._monitor_roi["height"] - 10) + self._monitor_roi["width"] = self._config.ui_pos["screen_width"] + self._monitor_roi["height"] = self._config.ui_pos["screen_height"] + self.found_offsets = True + + + def find_window_via_assets(self, wait: int) -> Tuple[int, int]: template = load_template(f"assets/templates/main_menu_top_left.png", 1.0) template_ingame = load_template(f"assets/templates/window_ingame_offset_reference.png", 1.0) - start = time.time() - self.found_offsets = False - Logger.info("Searching for window offsets. Make sure D2R is in focus and you are on the hero selection screen") debug_max_val = 0 + start = time.time() while time.time() - start < wait: img = self.grab() self._sct = mss() @@ -53,25 +84,18 @@ class Screen: if max_val < 0.93: Logger.warning(f"Your template match score to calc corner was lower then usual ({max_val*100:.1f}% confidence). " + "You might run into template matching issues along the way!") - offset_left, offset_top = max_pos - Logger.debug(f"Set offsets: left {offset_left}px, top {offset_top}px") - self._monitor_roi["top"] += offset_top - self._monitor_roi["left"] += offset_left - self._monitor_x_range = (self._monitor_roi["left"] + 10, self._monitor_roi["left"] + self._monitor_roi["width"] - 10) - self._monitor_y_range = (self._monitor_roi["top"] + 10, self._monitor_roi["top"] + self._monitor_roi["height"] - 10) - self._monitor_roi["width"] = self._config.ui_pos["screen_width"] - self._monitor_roi["height"] = self._config.ui_pos["screen_height"] - self.found_offsets = True - break - if not self.found_offsets: - if self._config.general["info_screenshots"]: - cv2.imwrite("./info_screenshots/error_d2r_window_not_found_" + time.strftime("%Y%m%d_%H%M%S") + ".png", self.grab()) - Logger.error("Could not find hero selection or template for ingame, shutting down") - Logger.error(f"The max score that could be found was: ({debug_max_val*100:.1f}% confidence)") - Logger.error("Could not determine window offset. Please make sure you have the D2R window " + - f"focused and that you are on the hero selection screen when pressing {self._config.general['resume_key']}") - + return max_pos + Logger.error(f"The max score that could be found was: ({debug_max_val*100:.1f}% confidence)") + return None + def find_window_via_winapi(self) -> Tuple[int, int]: + position = misc.find_d2r_window() + if position is None: + return None + + offset_x, offset_y, _, _ = position + return offset_x, offset_y + def convert_monitor_to_screen(self, screen_coord: Tuple[float, float]) -> Tuple[float, float]: return (screen_coord[0] - self._monitor_roi["left"], screen_coord[1] - self._monitor_roi["top"]) diff --git a/src/utils/misc.py b/src/utils/misc.py index e5342c4..5e15690 100644 --- a/src/utils/misc.py +++ b/src/utils/misc.py @@ -11,12 +11,25 @@ import os from math import cos, sin, dist import subprocess from win32con import HWND_TOPMOST, SWP_NOMOVE, SWP_NOSIZE, HWND_TOP, HWND_BOTTOM, SWP_NOZORDER, SWP_NOOWNERZORDER, HWND_DESKTOP, SWP_NOSENDCHANGING, SWP_SHOWWINDOW, HWND_NOTOPMOST -from win32gui import GetWindowText, SetWindowPos, EnumWindows - +from win32gui import GetWindowText, SetWindowPos, EnumWindows, GetClientRect, ClientToScreen +from win32process import GetWindowThreadProcessId +import psutil def close_down_d2(): subprocess.call(["taskkill","/F","/IM","D2R.exe"], stderr=subprocess.DEVNULL) + +def find_d2r_window(): + if os.name == 'nt': + window_list = [] + EnumWindows(lambda w, l: l.append((w, *GetWindowThreadProcessId(w))), window_list) + for (hwnd, _, process_id) in window_list: + if psutil.Process(process_id).name() == "D2R.exe": + left, top, right, bottom = GetClientRect(hwnd) + (left, top), (right, bottom) = ClientToScreen(hwnd, (left, top)), ClientToScreen(hwnd, (right, bottom)) + return (left, top, right, bottom) + return None + def set_d2r_always_on_top(): if os.name == 'nt': windows_list = []