diff --git a/src/__init__.py b/src/__init__.py index aec74368..47adc4c0 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -2,4 +2,4 @@ TP = concurrent.futures.ThreadPoolExecutor() -__version__ = "6.0.0alpha3" +__version__ = "6.0.0alpha4" diff --git a/src/item/data/item_type.py b/src/item/data/item_type.py index 8aa4777d..950881f4 100644 --- a/src/item/data/item_type.py +++ b/src/item/data/item_type.py @@ -32,6 +32,7 @@ class ItemType(Enum): Tome = "tome" Wand = "wand" # Custom Types + Cache = "cache" Compass = "compass" Consumable = "consumable" Gem = "gem" @@ -41,6 +42,7 @@ class ItemType(Enum): Sigil = "nightmare sigil" TemperManual = "temper manual" Tribute = "tribute" + WhisperingWood = "whispering wood" def is_armor(item_type: ItemType) -> bool: @@ -63,10 +65,7 @@ def is_consumable(item_type: ItemType) -> bool: def is_mapping(item_type: ItemType) -> bool: - return item_type in [ - ItemType.Compass, - ItemType.Sigil, - ] + return item_type in [ItemType.Compass, ItemType.Sigil, ItemType.WhisperingWood] def is_jewelry(item_type: ItemType) -> bool: diff --git a/src/item/descr/read_descr_tts.py b/src/item/descr/read_descr_tts.py index 6190a67f..dbba2ba7 100644 --- a/src/item/descr/read_descr_tts.py +++ b/src/item/descr/read_descr_tts.py @@ -123,6 +123,10 @@ def _create_base_item_from_tts(tts_item: list[str]) -> Item | None: return Item(item_type=ItemType.Material) if any(tts_item[1].lower().endswith(x) for x in ["gem"]): return Item(item_type=ItemType.Gem) + if any(tts_item[1].lower().endswith(x) for x in ["cache"]): + return Item(item_type=ItemType.Cache) + if any(tts_item[1].lower().endswith(x) for x in ["whispering wood"]): + return Item(item_type=ItemType.WhisperingWood) if "rune of" in tts_item[1].lower(): item = Item(item_type=ItemType.Rune) search_string_split = tts_item[1].lower().split(" rune of ") @@ -232,7 +236,10 @@ def _get_item_type(data: str): def _is_codex_upgrade(tts_section: list[str], item: Item) -> bool: - return any("upgrades an aspect in the codex of power on salvage" in line.lower() for line in tts_section) + for line in tts_section: + if "upgrades an aspect in the codex of power on salvage" in line.lower() or "unlocks new aspect" in line.lower(): + return True + return False def read_descr_mixed(img_item_descr: np.ndarray) -> Item | None: @@ -320,11 +327,13 @@ def read_descr() -> Item | None: is_consumable(item.item_type), is_mapping(item.item_type), is_socketable(item.item_type), - item.item_type in [ItemType.Material, ItemType.Tribute], + item.item_type in [ItemType.Material, ItemType.Tribute, ItemType.Cache], ] ): return item - if all([not is_armor(item.item_type), not is_jewelry(item.item_type), not is_weapon(item.item_type)]): + if all( + [not is_armor(item.item_type), not is_jewelry(item.item_type), not is_weapon(item.item_type), item.item_type != ItemType.Shield] + ): return None if item.rarity not in [ItemRarity.Legendary, ItemRarity.Mythic, ItemRarity.Unique]: return item diff --git a/src/scripts/common.py b/src/scripts/common.py index ff417278..6e9cabe6 100644 --- a/src/scripts/common.py +++ b/src/scripts/common.py @@ -33,13 +33,14 @@ def reset_canvas(root, canvas): def reset_item_status(occupied, inv): for item_slot in occupied: if item_slot.is_fav: - inv.hover_item(item_slot) + inv.hover_item_with_delay(item_slot) keyboard.send("space") if item_slot.is_junk: - inv.hover_item(item_slot) + inv.hover_item_with_delay(item_slot) keyboard.send("space") - time.sleep(0.13) + time.sleep(0.15) keyboard.send("space") + time.sleep(0.15) if occupied: mouse.move(*Cam().abs_window_to_monitor((0, 0))) diff --git a/src/scripts/handler.py b/src/scripts/handler.py index 3660d0c7..a6bca0fe 100644 --- a/src/scripts/handler.py +++ b/src/scripts/handler.py @@ -31,6 +31,7 @@ class ScriptHandler: def __init__(self): self.loot_interaction_thread = None self.script_threads = [] + self.vision_mode = src.scripts.vision_mode_tts.VisionMode() self.setup_key_binds() if IniConfigLoader().general.run_vision_mode_on_startup: @@ -71,6 +72,8 @@ def _start_or_stop_loot_interaction_thread(self, loot_interaction_method: typing LOGGER.info("Stopping filter or move process") kill_thread(self.loot_interaction_thread) self.loot_interaction_thread = None + if self.did_stop_scripts and IniConfigLoader().general.use_tts == UseTTSType.full and not self.vision_mode.running(): + self.vision_mode.start() else: self.loot_interaction_thread = threading.Thread( target=self._wrapper_run_loot_interaction_method, args=(loot_interaction_method, method_args), daemon=True @@ -84,17 +87,22 @@ def _start_or_stop_loot_interaction_thread(self, loot_interaction_method: typing def _wrapper_run_loot_interaction_method(self, loot_interaction_method: typing.Callable, method_args=()): try: # We will stop all scripts if they are currently running and restart them afterward if needed - did_stop_scripts = False - if len(self.script_threads) > 0: - LOGGER.info("Stopping Scripts") - for script_thread in self.script_threads: - kill_thread(script_thread) - self.script_threads = [] - did_stop_scripts = True + self.did_stop_scripts = False + if IniConfigLoader().general.use_tts == UseTTSType.full: + if self.vision_mode.running(): + self.vision_mode.stop() + self.did_stop_scripts = True + else: + if len(self.script_threads) > 0: + LOGGER.info("Stopping Scripts") + for script_thread in self.script_threads: + kill_thread(script_thread) + self.script_threads = [] + self.did_stop_scripts = True loot_interaction_method(*method_args) - if did_stop_scripts: + if self.did_stop_scripts: self.run_scripts() finally: self.loot_interaction_thread = None @@ -102,23 +110,28 @@ def _wrapper_run_loot_interaction_method(self, loot_interaction_method: typing.C def run_scripts(self): if LOCK.acquire(blocking=False): try: - if len(self.script_threads) > 0: - LOGGER.info("Stopping Vision Mode") - for script_thread in self.script_threads: - kill_thread(script_thread) - self.script_threads = [] + if not IniConfigLoader().advanced_options.scripts: + LOGGER.info("No scripts configured") + return + + # TODO Probably just remove the "scripts" concept and change to a checkbox for vision mode + if IniConfigLoader().general.use_tts == UseTTSType.full: + if self.vision_mode.running(): + self.vision_mode.stop() + else: + self.vision_mode.start() else: - if not IniConfigLoader().advanced_options.scripts: - LOGGER.info("No scripts configured") - return - for name in IniConfigLoader().advanced_options.scripts: - if name == "vision_mode": - if IniConfigLoader().general.use_tts == UseTTSType.full: - vision_mode_thread = threading.Thread(target=src.scripts.vision_mode_tts.VisionMode().start, daemon=True) - else: + if len(self.script_threads) > 0: + LOGGER.info("Stopping Vision Mode") + for script_thread in self.script_threads: + kill_thread(script_thread) + self.script_threads = [] + else: + for name in IniConfigLoader().advanced_options.scripts: + if name == "vision_mode": vision_mode_thread = threading.Thread(target=src.scripts.vision_mode.vision_mode, daemon=True) - vision_mode_thread.start() - self.script_threads.append(vision_mode_thread) + vision_mode_thread.start() + self.script_threads.append(vision_mode_thread) finally: LOCK.release() else: diff --git a/src/scripts/loot_filter_tts.py b/src/scripts/loot_filter_tts.py index 74f3638d..ab23e559 100644 --- a/src/scripts/loot_filter_tts.py +++ b/src/scripts/loot_filter_tts.py @@ -32,24 +32,35 @@ def check_items(inv: InventoryBase, force_refresh: ItemRefreshType): if item.is_junk or item.is_fav: continue inv.hover_item(item) - time.sleep(0.15) + time.sleep(0.10) img = Cam().grab() item_descr = None + item_descr_previous_check = None try: - item_descr = src.item.descr.read_descr_tts.read_descr() + item_descr_previous_check = src.item.descr.read_descr_tts.read_descr() LOGGER.debug(f"Parsed item based on TTS: {item_descr}") except Exception: screenshot("tts_error", img=img) LOGGER.exception(f"Error in TTS read_descr. {src.tts.LAST_ITEM=}") - if item_descr is None: - LOGGER.info("Retry item detection") - time.sleep(0.3) + + retry_count = 0 + while item_descr is None and retry_count != 5: + # Check again to make sure the item is what we think. + # Move off of the item then back on again + inv.hover_left_of_item(item) + inv.hover_item(item) + time.sleep(0.10) try: item_descr = src.item.descr.read_descr_tts.read_descr() LOGGER.debug(f"Parsed item based on TTS: {item_descr}") + if item_descr != item_descr_previous_check: + item_descr_previous_check = item_descr + item_descr = None except Exception: screenshot("tts_error", img=img) LOGGER.exception(f"Error in TTS read_descr. {src.tts.LAST_ITEM=}") + retry_count += 1 + if item_descr is None: continue diff --git a/src/scripts/vision_mode.py b/src/scripts/vision_mode.py index b332b653..6dbc0f3c 100644 --- a/src/scripts/vision_mode.py +++ b/src/scripts/vision_mode.py @@ -118,7 +118,7 @@ def vision_mode(): canvas = tk.Canvas(root, bg="black", highlightthickness=0) canvas.pack(fill=tk.BOTH, expand=True) - LOGGER.info("Starting Vision Filter") + LOGGER.info("Starting Vision Mode") inv = CharInventory() chest = Chest() img = Cam().grab() diff --git a/src/scripts/vision_mode_tts.py b/src/scripts/vision_mode_tts.py index 4a43bf9c..0988fc7e 100644 --- a/src/scripts/vision_mode_tts.py +++ b/src/scripts/vision_mode_tts.py @@ -37,6 +37,8 @@ def __init__(self): self.clear_timer_id = None self.queue = queue.Queue() self.draw_from_queue() + self.stop_thread = None + self.is_running = False def adjust_textbox_size(self): self.textbox.config(state=tk.NORMAL) @@ -157,8 +159,18 @@ def on_tts(self, _): LOGGER.exception("Error in vision mode. Please create a bug report") def start(self): - LOGGER.info("Starting Vision Filter") + LOGGER.info("Starting Vision Mode") Publisher().subscribe(self.on_tts) + self.is_running = True + + def stop(self): + LOGGER.info("Stopping Vision Mode") + self.request_clear() + Publisher().unsubscribe(self.on_tts) + self.is_running = False + + def running(self): + return self.is_running def create_match_text(matches: list[MatchedFilter]): diff --git a/src/ui/inventory_base.py b/src/ui/inventory_base.py index b9cbdcdc..d63fb63f 100644 --- a/src/ui/inventory_base.py +++ b/src/ui/inventory_base.py @@ -84,3 +84,13 @@ def get_item_slots(self, img: np.ndarray | None = None) -> tuple[list[ItemSlot], def hover_item(self, item: ItemSlot): mouse.move(*Cam().window_to_monitor(item.center), randomize=15) + + # Needed for double checking a TTS + def hover_left_of_item(self, item: ItemSlot): + mouse.move( + *Cam().window_to_monitor([item.bounding_box[0] - item.bounding_box[2] / 2, item.bounding_box[1] + item.bounding_box[3] / 2]), + randomize=15, + ) + + def hover_item_with_delay(self, item: ItemSlot, delay_factor: tuple[float, float] = (1.5, 2.0)): + mouse.move(*Cam().window_to_monitor(item.center), randomize=15, delay_factor=delay_factor) diff --git a/src/utils/custom_mouse.py b/src/utils/custom_mouse.py index e0550c10..46a10058 100644 --- a/src/utils/custom_mouse.py +++ b/src/utils/custom_mouse.py @@ -244,7 +244,7 @@ def move(x: int, y: int, absolute: bool = True, randomize: int | tuple[int, int] from_point, (x, y), offsetBoundaryX=offsetBoundaryX, offsetBoundaryY=offsetBoundaryY, targetPoints=targetPoints ) - duration = min(0.3, max(0.05, dist * 0.0004) * random.uniform(delay_factor[0], delay_factor[1])) + duration = min(0.5, max(0.05, dist * 0.0004) * random.uniform(delay_factor[0], delay_factor[1])) delta = duration / len(human_curve.points) for point in human_curve.points: