v0.2.0: relations preview, item shop, abilities, overlay recommend, GSI enhancements

- Add relations/item/abilities preview (serve_relations.py + web/relations/)
- Add fetch scripts: hero_items, item_shop, items_meta, hero_abilities,
  ability_videos, patches, stratz, matchups, portraits
- Add overlay.py (role tags + Top-3 cyan marks), recommend.py
- Add http_utils.py, loc_format.py, hero_tags.py, item_fears.py
- GSI: full payload JSONL dump, foreground window detection
- Drop real template library; CDN-only matching
- Update docs: CHANGELOG 0.2.0, DESIGN config table, AGENTS module table
- .gitignore: exclude large regenerable assets (icons/portraits/videos)
This commit is contained in:
voson
2026-07-27 11:56:51 +08:00
parent e567a5cdfc
commit a91789b72f
76 changed files with 109860 additions and 996 deletions
+21 -4
View File
@@ -33,6 +33,7 @@ import mss
from capture import append_gsi_payload, grab_frame, is_dota_foreground, raw_dir_for_match, save_frame
from common import ROOT, load_config, load_template_library
from draft_session import HERO_SELECTION, DraftSession, describe, gsi_slot, loc
from overlay import DraftOverlay
from recognize import recognize_image
from roles import detect_roles
@@ -68,6 +69,15 @@ class Watcher:
self.dump_lock = threading.Lock()
self.self_info: dict = {}
self._dump_announced: set[str] = set()
# One overlay for the whole process - Tk does not like create/destroy per match.
self.overlay = None
if calibrated and bool((cfg.get("overlay") or {}).get("enabled", True)):
try:
self.overlay = DraftOverlay(cfg)
print("[draft] role-tag overlay ready", flush=True)
except Exception as e: # noqa: BLE001
print(f"[draft] overlay disabled: {e}", flush=True)
self.overlay = None
def on_payload(self, payload: dict) -> None:
if not self.connected:
@@ -82,6 +92,8 @@ class Watcher:
"team": p.get("team_name"),
"team_slot": p.get("team_slot"),
"hero": (h.get("name") or "").replace("npc_dota_hero_", "") or None,
"accountid": p.get("accountid"),
"steamid": p.get("steamid"),
}
state = (payload.get("map") or {}).get("game_state")
@@ -142,9 +154,9 @@ class Watcher:
late = " (joined late)" if state != HERO_SELECTION else ""
print(f"[draft] tracking match {match_id} from {state}{late}", flush=True)
session = DraftSession(self.cfg, self.library)
session = DraftSession(self.cfg, self.library, overlay=self.overlay)
summary = session.run(match_id, lambda: self.last_state, lambda: self.self_info)
if summary["recognized"] == 0:
if not summary or summary["recognized"] == 0:
print("[draft] nothing recognized - no result written", flush=True)
return None
self.report_session(summary)
@@ -207,11 +219,14 @@ class Watcher:
print(f"[run] {result['recognized']}/10 slots, {result['elapsed_ms']}ms", flush=True)
print(f"radiant: {', '.join(loc(r['hero']) for r in result['radiant'])}", flush=True)
print(f"dire : {', '.join(loc(r['hero']) for r in result['dire'])}", flush=True)
slot = gsi_slot(self.self_info) or found["self_slot"]
slot = gsi_slot(self.self_info)
team = self.self_info.get("team") or found.get("self_team")
if slot:
role = found["roles"].get(slot)
pos = f"position {role['position']} ({role['label']})" if role else "position unknown"
print(f"you : slot {slot} {found['self_team']} - {pos}", flush=True)
print(f"you : slot {slot} {team} - {pos}", flush=True)
elif not slot:
print("you : slot unknown (waiting for GSI team_slot)", flush=True)
result["roles"] = found
return result
@@ -306,6 +321,8 @@ def main() -> None:
except KeyboardInterrupt:
print("\nstopped.")
finally:
if watcher.overlay is not None:
watcher.overlay.close()
server.server_close()