Files
my-botty/tools/capture_skill_hotkeys.py
alexpolo1andClaude Fable 5 40e4dd8a4f Skill hotkeys select the RIGHT slot - fix cast flow, prove auto-binding live
Live finding (fresh game, 2026-06-12): D2R skill hotkeys select onto the
RIGHT skill slot, not the left as the old comments assumed. The left slot
permanently holds Blessed Hammer. Consequence: _cast_hammers pressing the
hammer hotkey after activating an aura was REPLACING the aura on the right
slot every cast cycle - the true root cause of fights running without
Concentration. _cast_hammers no longer touches the hammer hotkey: select
aura (lands on right, stays active), hold stand-still, spam left-click.
Verified live: full Diablo kill at 09:04, ~55s from last seal to kill.

Auto skill binding proven end-to-end (tools/set_binds_from_params.py):
blessed_hammer/concentration/redemption/vigor/holy_shield/teleport all
bound via the in-game picker and visually verified (6/7 OK; conviction
correctly reported missing - not skilled on this char).

- capture tool + preflight verify now watch the RIGHT slot
- fresh skill slot templates + clean PICKER_* cell templates captured at
  current settings (blessed_hammer, concentration, redemption, vigor,
  holy_shield, teleport)
- removed bogus conviction.png (had captured vigor's icon)

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-12 09:13:52 +02:00

88 lines
3.4 KiB
Python

"""
Verify that the skill hotkeys in config/params.ini match the in-game skill
assignments, using screenshots of the left skill slot.
For each configured skill hotkey this tool:
1. presses the hotkey (which selects that skill on left-click in D2R),
2. screenshots the left skill slot ROI,
3. saves a labeled crop to assets/templates/skills_capture/<skill>.png.
Review the saved icons: if e.g. concentration.png shows the Blessed Hammer
icon, the in-game bind does not match params.ini. The crops double as
skill-icon templates for skills.is_left/right_skill_selected() checks.
Usage: start D2R, enter a game (town is fine), then from the repo root:
<botty-env-python> tools/capture_skill_hotkeys.py
The tool waits 5s so you can focus the D2R window, then runs by itself.
"""
import os
import sys
import time
import ctypes
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
ctypes.windll.user32.SetProcessDPIAware()
import cv2
from config import Config
from screen import start_detecting_window, stop_detecting_window, grab
from utils.misc import cut_roi, wait
from input_layer import keyboard
# Skills selectable on left-click without a weapon swap. BO/BC live on the CTA
# swap and are validated by the prebuff flow instead.
SKILLS = [
"blessed_hammer", "holy_shield", "redemption", "vigor",
"conviction", "concentration", "teleport",
]
def main():
# save straight into the live template dir: crops auto-load as templates
# (CONCENTRATION etc.) for both the startup preflight verify and the
# skill_hotkey_setter picker fallback
out_dir = os.path.join(os.path.dirname(__file__), "..", "assets", "templates", "ui", "skills")
os.makedirs(out_dir, exist_ok=True)
print("Focus the D2R window (in a game). Capturing starts in 5s...")
time.sleep(5)
start_detecting_window()
wait(0.5)
# hotkeys select onto the RIGHT slot (verified live 2026-06-12)
roi = Config().ui_roi["skill_right"]
results = []
prev_icon = None
try:
build_cfg = getattr(Config(), Config().char.get("type", ""), {})
for skill in SKILLS:
# build skills live in the [hammerdin]-style section; teleport in [char]
hotkey = build_cfg.get(skill) or Config().char.get(skill)
if not hotkey:
results.append((skill, "-", "no hotkey configured"))
continue
keyboard.send(hotkey)
wait(0.5, 0.6)
icon = cut_roi(grab(), roi)
path = os.path.join(out_dir, f"{skill}.png")
cv2.imwrite(path, icon)
# sanity: icon should differ from the previous skill's icon
note = "ok"
if prev_icon is not None and icon.shape == prev_icon.shape:
diff = cv2.norm(icon, prev_icon, cv2.NORM_L1) / icon.size
if diff < 1.0:
note = "WARNING: icon identical to previous - hotkey may be unbound"
prev_icon = icon
results.append((skill, hotkey, note))
print(f" {skill:16s} [{hotkey:>4s}] -> {os.path.basename(path)} {note}")
finally:
stop_detecting_window()
print("\nSummary:")
for skill, hotkey, note in results:
print(f" {skill:16s} {hotkey:>4s} {note}")
print(f"\nReview the icons in {os.path.abspath(out_dir)} - each file must show")
print("the skill it is named after, otherwise fix the bind in-game or in params.ini.")
if __name__ == "__main__":
main()