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]>
88 lines
3.4 KiB
Python
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()
|