Gem transmute: use cube from stash (PERSONAL tab) + ctrl+shift to send gems to cube
- _open_cube_from_stash(): switch to PERSONAL tab, right-click cube from stash - _ctrl_shift_click_monitor(): ctrl+shift+click sends item from active stash tab directly into the open cube (no inventory roundtrip) - convert_all_gems_to_perfect: open cube once per gem type from stash, switch to GEMS tab, ctrl+shift+click gem stack 3x -> into cube, transmute, ctrl+click result -> back to GEMS tab; close cube when type is done - Keeps _open_cube_from_inventory as fallback for other callers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user