diff --git a/.gitignore b/.gitignore index 3514997e..c76ac1e5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,6 @@ main.build/ main.dist/ main.onefile-build/ generated/ -botty_v*/ custom.ini config/custom.ini config/custom.*.ini diff --git a/environment.yml b/environment.yml index f5fa6922..42480a1c 100644 --- a/environment.yml +++ b/environment.yml @@ -13,6 +13,7 @@ dependencies: - pytesseract==0.3.10 - rapidfuzz - mss + - tk - numpy - mouse - coverage diff --git a/src/item/read_descr.py b/src/item/read_descr.py index 90a53d27..20fe4465 100644 --- a/src/item/read_descr.py +++ b/src/item/read_descr.py @@ -46,12 +46,21 @@ def _find_number(s): return None +def _remove_text_after_first_keyword(text, keywords): + for keyword in keywords: + match = re.search(re.escape(keyword), text) + if match: + return text[: match.start()] + return text + + def _clean_str(s): cleaned_str = re.sub(r"(\+)?\d+(\.\d+)?%?", "", s) # Remove numbers and trailing % or preceding + cleaned_str = re.sub(r"[\[\]+\-:%\']", "", cleaned_str) # Remove [ and ] and leftover +, -, %, :, ' cleaned_str = re.sub( r"\((rogue|barbarian|druid|sorcerer|necromancer) only\)", "", cleaned_str ) # this is not included in our affix table + cleaned_str = _remove_text_after_first_keyword(cleaned_str, ["requires level", "account", "sell value"]) cleaned_str = re.sub( r"(scroll up|account bound|requires level|sell value|durability|barbarian|rogue|sorceress|druid|necromancer|not useable|by your class|by your clas)", "", diff --git a/src/logger.py b/src/logger.py index 2071f298..870cd73b 100644 --- a/src/logger.py +++ b/src/logger.py @@ -98,7 +98,7 @@ def init(lvl=logging.DEBUG): Logger.logger.removeHandler(hdlr) # Create the logger - Logger.logger = logging.getLogger("botty") + Logger.logger = logging.getLogger("d4lf") for hdlr in Logger.logger.handlers: Logger.logger.removeHandler(hdlr) Logger.logger.setLevel(Logger._logger_level) diff --git a/src/main.py b/src/main.py index c7b1e51f..ef378963 100644 --- a/src/main.py +++ b/src/main.py @@ -1,6 +1,4 @@ -import keyboard import os -import threading from utils.window import start_detecting_window from beautifultable import BeautifulTable import logging @@ -9,9 +7,10 @@ from version import __version__ from config import Config from logger import Logger -from loot_filter import run_loot_filter from utils.misc import wait from cam import Cam +from overlay import Overlay +import keyboard def main(): @@ -30,18 +29,22 @@ def main(): else: print(f"ERROR: Unkown log_lvl {Config().advanced_options['log_lvl']}. Must be one of [info, debug]") + overlay = None + print(f"============ D4 Loot Filter {__version__} ============") table = BeautifulTable() table.set_style(BeautifulTable.STYLE_BOX_ROUNDED) - table.rows.append([Config().advanced_options["run_key"], "Run Loot Filter"]) + table.rows.append([Config().advanced_options["run_key"], "Run/Stop Loot Filter"]) table.rows.append([Config().advanced_options["exit_key"], "Stop"]) table.columns.header = ["hotkey", "action"] print(table) print("\n") - keyboard.add_hotkey(Config().advanced_options["run_key"], lambda: threading.Thread(target=run_loot_filter, daemon=True).start()) + keyboard.add_hotkey(Config().advanced_options["run_key"], lambda: overlay.filter_items()) keyboard.add_hotkey(Config().advanced_options["exit_key"], lambda: safe_exit()) - keyboard.wait() + + overlay = Overlay() + overlay.run() if __name__ == "__main__": @@ -49,5 +52,5 @@ def main(): main() except: traceback.print_exc() - print("Press Enter to exit ...") - input() + print("Press Enter to exit ...") + input() diff --git a/src/overlay.py b/src/overlay.py new file mode 100644 index 00000000..2f8a0193 --- /dev/null +++ b/src/overlay.py @@ -0,0 +1,96 @@ +import tkinter as tk +import threading +from utils.window import move_window_to_foreground +from loot_filter import run_loot_filter +from version import __version__ +from utils.process_handler import kill_thread +from logger import Logger +import logging + + +class ListboxHandler(logging.Handler): + def __init__(self, listbox): + logging.Handler.__init__(self) + self.listbox = listbox + + def emit(self, record): + log_entry = self.format(record) + padded_text = " " * 1 + log_entry + " " * 1 + self.listbox.insert(tk.END, padded_text) + self.listbox.yview(tk.END) # Auto-scroll to the end + + +class Overlay: + def __init__(self): + self.loot_filter_thread = None + self.is_minimized = True + self.root = tk.Tk() + self.root.title("LootFilter Overlay") + self.root.attributes("-alpha", 0.8) + self.root.overrideredirect(True) + # self.root.wm_attributes("-transparentcolor", "white") + self.root.wm_attributes("-topmost", True) + + self.screen_width = self.root.winfo_screenwidth() + self.screen_height = self.root.winfo_screenheight() + self.initial_height = int(30) + self.initial_width = int(self.screen_width * 0.18) + self.maximized_height = int(160) + + self.canvas = tk.Canvas(self.root, bg="black", height=self.initial_height, width=self.initial_width, highlightthickness=0) + self.root.geometry(f"{self.initial_width}x{self.initial_height}+{self.screen_width//2 - self.initial_width//2}+0") + self.canvas.pack() + + self.toggle_button = tk.Button(self.root, text="toggle", bg="#222222", fg="#555555", borderwidth=0, command=self.toggle_size) + self.canvas.create_window(28, 15, window=self.toggle_button) + + self.filter_button = tk.Button(self.root, text="filter", bg="#222222", fg="#555555", borderwidth=0, command=self.filter_items) + self.canvas.create_window(70, 15, window=self.filter_button) + + self.terminal_listbox = tk.Listbox( + self.canvas, + bg="black", + fg="white", + highlightcolor="white", + highlightthickness=0, + selectbackground="#222222", + activestyle=tk.NONE, + borderwidth=0, + font=("Courier New", 9), + ) + self.terminal_listbox.place(relx=0, rely=0, relwidth=1, relheight=1, y=30) + + # Setup the listbox logger handler + listbox_handler = ListboxHandler(self.terminal_listbox) + listbox_handler.setLevel(Logger._logger_level) + Logger.logger.addHandler(listbox_handler) + + def toggle_size(self): + if not self.is_minimized: + self.canvas.config(height=self.initial_height, width=self.initial_width) + self.root.geometry(f"{self.initial_width}x{self.initial_height}+{self.screen_width//2 - self.initial_width//2}+0") + else: + self.canvas.config(height=self.maximized_height, width=self.initial_width) + self.root.geometry(f"{self.initial_width}x{self.maximized_height}+{self.screen_width//2 - self.initial_width//2}+0") + self.is_minimized = not self.is_minimized + move_window_to_foreground() + + def filter_items(self): + if self.loot_filter_thread is not None: + Logger.info("Stoping Filter process") + kill_thread(self.loot_filter_thread) + self.loot_filter_thread = None + return + if self.is_minimized: + self.toggle_size() + self.loot_filter_thread = threading.Thread(target=self._wrapper_run_loot_filter, daemon=True) + self.loot_filter_thread.start() + + def _wrapper_run_loot_filter(self): + try: + run_loot_filter() + finally: + self.loot_filter_thread = None + + def run(self): + self.root.mainloop() diff --git a/src/utils/custom_mouse.py b/src/utils/custom_mouse.py index 6104eded..24a4c97b 100644 --- a/src/utils/custom_mouse.py +++ b/src/utils/custom_mouse.py @@ -253,18 +253,6 @@ def move(x: int, y: int, absolute: bool = True, randomize: int | tuple[int, int] @staticmethod def _is_clicking_safe(): - # TODO: Implement - # # Because of reports that botty lost equiped items, let's check if the inventory is open, and if it is, restrict the mouse move - # mouse_pos = Cam().monitor_to_window(_mouse.get_position()) - # is_inventory_open = template_finder.search( - # "INVENTORY_GOLD_BTN", Cam().grab(), threshold=0.8, roi=Config().ui_roi["gold_btn"], use_grayscale=True - # ).valid - # if is_inventory_open: - # is_in_equipped_area = is_in_roi(Config().ui_roi["equipped_inventory_area"], mouse_pos) - # is_in_restricted_inventory_area = is_in_roi(Config().ui_roi["restricted_inventory_area"], mouse_pos) - # if is_in_restricted_inventory_area or is_in_equipped_area: - # Logger.error("Mouse wants to click in equipped area. Cancel action.") - # return False return True @staticmethod diff --git a/src/utils/window.py b/src/utils/window.py index 9cebed44..644e1cd3 100644 --- a/src/utils/window.py +++ b/src/utils/window.py @@ -94,6 +94,7 @@ def stop_detecting_window(): def move_window_to_foreground(window_spec: WindowSpec = D4_WINDOW): hwnd = get_window_spec_id(window_spec) if hwnd is not None: + ctypes.windll.user32.ShowWindow(hwnd, 5) ctypes.windll.user32.SetForegroundWindow(hwnd)