diff --git a/src/transmute/transmute.py b/src/transmute/transmute.py index 6f26935..7bd7eb4 100644 --- a/src/transmute/transmute.py +++ b/src/transmute/transmute.py @@ -199,14 +199,19 @@ class Transmute: # ── New-UI (D2R named-tab stash) gem helpers ────────────────────────── - # Approximate screen position of the GEMS tab button in the new stash UI. - # The stash left panel spans x=33..415 at 1280x720. With 5 named tabs - # (PERSONAL, SHARED, GEMS, MATERIALS, RUNES) each ~76px wide: + # Named-tab stash UI (1280x720). 5 tabs ~76px wide starting at x=33: # PERSONAL≈71 SHARED≈147 GEMS≈223 MATERIALS≈299 RUNES≈375 - # Hermes measured GEMS at ~175 so we split the difference at ~200, y≈100. + PERSONAL_TAB_X = 71 GEMS_TAB_X = 200 GEMS_TAB_Y = 100 + def _switch_to_personal_tab(self) -> None: + x, y = convert_screen_to_monitor((self.PERSONAL_TAB_X, self.GEMS_TAB_Y)) + mouse.move(x, y) + self._wait() + mouse.click("left") + wait(0.4, 0.5) + def _switch_to_gems_tab(self) -> None: x, y = convert_screen_to_monitor((self.GEMS_TAB_X, self.GEMS_TAB_Y)) mouse.move(x, y) @@ -214,6 +219,18 @@ class Transmute: mouse.click("left") wait(0.4, 0.5) + def _open_cube_from_stash(self) -> bool: + """Switch to PERSONAL tab and right-click the Horadric Cube to open its UI.""" + self._switch_to_personal_tab() + if (match := detect_screen_object(ScreenObjects.CubeStash)).valid: + mouse.move(*match.center_monitor) + self._wait() + mouse.click("right") + wait(0.3, 0.4) + return True + Logger.error("Cube not found in PERSONAL stash tab") + return False + def _open_cube_from_inventory(self) -> bool: if (match := wait_until_visible(ScreenObjects.CubeInventory, 3.0)).valid: mouse.move(*match.center_monitor) @@ -234,6 +251,19 @@ class Transmute: keyboard.release('ctrl') self._wait() + def _ctrl_shift_click_monitor(self, x: int, y: int, label: str = "") -> None: + """Ctrl+Shift+Click — moves item from current stash tab into the open cube.""" + Logger.info(f" ctrl+shift+click {label} @ monitor ({x},{y})") + mouse.move(x, y) + self._wait() + keyboard.send('shift', do_release=False) + keyboard.send('ctrl', do_release=False) + self._wait() + mouse.click("left") + keyboard.release('ctrl') + keyboard.release('shift') + self._wait() + def _gems_tab_pull_3(self, gems_in: list) -> int: """Find a gem type in the GEMS tab (stacked) and ctrl+click it 3 times to pull 3 gems.""" img = grab() @@ -477,61 +507,49 @@ class Transmute: total = sum(n for n, *_ in plan) Logger.info(f"convert_all_gems: {len(plan)} gem types to process, {total} total transmutes planned") - # Step 3: execute — for each planned gem type, do N transmutes + # Step 3: execute. + # Flow per gem type: open cube from PERSONAL stash tab once, switch to + # GEMS tab (cube UI stays open), then for each batch: + # ctrl+shift+click gem stack 3x -> gems land directly in open cube + # transmute + # ctrl+click result from cube -> goes back to active GEMS tab + # Close cube when done with this gem type. done = 0 for n_transmutes, gem_template, gems_out, tier_name in plan: - Logger.info(f"convert_all_gems: executing {n_transmutes}x {gem_template}") - for i in range(n_transmutes): - self._switch_to_gems_tab() + Logger.info(f"convert_all_gems: {n_transmutes}x {gem_template}") - # Pull 3 of this specific gem from GEMS tab. - # GEMS tab uses stacked slots: one slot = many gems. - # Click the same stack position 3 times to pull 3 individual gems. + # Open cube from PERSONAL tab + if not self._open_cube_from_stash(): + Logger.error(" cube not found in stash — aborting") + return + + # Switch to GEMS tab (cube UI stays open) + self._switch_to_gems_tab() + + for i in range(n_transmutes): img = grab() roi = Config().ui_roi["left_inventory"] matches = template_finder.search_all(gem_template, img, threshold=0.85, roi=roi) if not matches: - Logger.warning(f" no {gem_template} found in GEMS tab, stopping this type") + Logger.info(f" no {gem_template} left in GEMS tab, stopping") break - Logger.info(f" [{i+1}/{n_transmutes}] pulling 3x {gem_template} from stack @ {matches[0].center_monitor}") + Logger.info(f" [{i+1}/{n_transmutes}] ctrl+shift+click {gem_template} x3 -> cube @ {matches[0].center_monitor}") for _ in range(3): - self._ctrl_click_monitor(*matches[0].center_monitor, label=gem_template) + self._ctrl_shift_click_monitor(*matches[0].center_monitor, label=gem_template) wait(0.15, 0.2) - # Open cube, load gems via search_all on right inventory, transmute, pick result - if not self._open_cube_from_inventory(): - Logger.error(" cube missing from inventory — aborting") - return - right_roi = Config().ui_roi["right_inventory"] - img = grab() - inv_hits = template_finder.search_all(gem_template, img, threshold=0.80, roi=right_roi) - loaded = 0 - for m in inv_hits[:3]: - Logger.info(f" inv->cube: {gem_template}") - self._ctrl_click_monitor(*m.center_monitor, label=gem_template) - loaded += 1 - Logger.info(f" transmuting ({loaded} gems in cube)") + Logger.info(f" [{i+1}/{n_transmutes}] transmuting") self.transmute() wait(0.5, 0.5) - # Find result in cube and take it + # ctrl+click result from cube -> goes to active GEMS tab if not self._pick_result_from_cube(gems_out): - Logger.warning(f" result not found in cube, skipping pickup") - self.close_cube() - wait(0.3, 0.4) - - # Put result back to GEMS tab via search_all on right inventory - self._switch_to_gems_tab() - img = grab() - result_hits = [] - for gem in gems_out: - for m in template_finder.search_all(gem, img, threshold=0.80, roi=right_roi): - result_hits.append((gem, m)) - Logger.info(f" putting {len(result_hits)} result gem(s) back to GEMS tab") - for gem, m in result_hits: - self._ctrl_click_monitor(*m.center_monitor, label=f"result {gem}") + Logger.warning(f" [{i+1}/{n_transmutes}] result not found in cube") done += 1 + self.close_cube() + wait(0.3, 0.4) + Logger.info(f"convert_all_gems: done — {done}/{total} transmutes completed") def should_transmute(self) -> bool: