v0.5.59: item counter evidence for fears, catch up Web features to site version.
Ship OpenDota counter-stats reordering for feared items, finalize SITE_VERSION/docs for rankings/streamers/trends/matches/mechanics and draft archetypes, and ignore regenerable Web data caches. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+73
-15
@@ -38,7 +38,7 @@ from capture import grab_frame, is_dota_foreground, raw_dir_for_match, save_fram
|
||||
from grid import bans, hero_table, read_grid
|
||||
from modes import detect_mode, load_mode_templates
|
||||
from recognize import recognize_image
|
||||
from recommend import ally_keys, enemy_keys, load_relations, suggest_top
|
||||
from recommend import ally_keys, enemy_keys, load_relations, suggest_marks
|
||||
from roles import ROLES, detect_roles, load_role_templates
|
||||
|
||||
HERO_SELECTION = "DOTA_GAMERULES_STATE_HERO_SELECTION"
|
||||
@@ -84,11 +84,16 @@ class DraftSession:
|
||||
self.frame_dir = raw_dir_for_match(None)
|
||||
rec = cfg.get("recommend") or {}
|
||||
self.recommend_enabled = bool(rec.get("enabled", True))
|
||||
self.recommend_top_n = int(rec.get("top_n", 3))
|
||||
self.recommend_top_n = int(rec.get("top_n", 0))
|
||||
self.recommend_min_enemies = int(rec.get("min_enemies", 1))
|
||||
self.recommend_min_heroes_for_gaps = int(rec.get("min_heroes_for_gaps", 2))
|
||||
self.recommend_archetypes = bool(rec.get("archetypes", True))
|
||||
self.recommend_role_tags = rec.get("role_tags")
|
||||
self.relations = load_relations(rec.get("relations_path")) if self.recommend_enabled else None
|
||||
self._rec_warned = False
|
||||
self._last_rec_sig: tuple | None = None
|
||||
self._last_enemy_profile: dict = {}
|
||||
self._last_rec_meta: dict = {}
|
||||
|
||||
def _push_overlay(self, confirmed: dict[int, str]) -> None:
|
||||
if self.overlay is None:
|
||||
@@ -98,12 +103,19 @@ class DraftSession:
|
||||
except Exception as e: # noqa: BLE001
|
||||
self.log(f"[draft] overlay update failed: {e}")
|
||||
|
||||
def _push_rec_overlay(self, cells: dict | None, picks: list[dict]) -> None:
|
||||
def _push_rec_overlay(
|
||||
self,
|
||||
cells: dict | None,
|
||||
picks: list[dict],
|
||||
analysis: str = "",
|
||||
) -> None:
|
||||
if self.overlay is None:
|
||||
return
|
||||
try:
|
||||
marks = {p["key"]: p["rank"] for p in picks}
|
||||
marks = {p["key"]: list(p.get("labels") or []) for p in picks if p.get("key")}
|
||||
self.overlay.set_grid_marks(cells or {}, marks)
|
||||
if hasattr(self.overlay, "set_analysis"):
|
||||
self.overlay.set_analysis(analysis or "")
|
||||
except Exception as e: # noqa: BLE001
|
||||
self.log(f"[draft] rec overlay update failed: {e}")
|
||||
|
||||
@@ -115,6 +127,8 @@ class DraftSession:
|
||||
try:
|
||||
self.overlay.set_roster({})
|
||||
self.overlay.set_grid_marks({}, {})
|
||||
if hasattr(self.overlay, "set_analysis"):
|
||||
self.overlay.set_analysis("")
|
||||
self.overlay.show()
|
||||
except Exception as e: # noqa: BLE001
|
||||
self.log(f"[draft] overlay show failed: {e}")
|
||||
@@ -255,6 +269,8 @@ class DraftSession:
|
||||
if self.overlay is not None:
|
||||
try:
|
||||
self.overlay.set_grid_marks({}, {})
|
||||
if hasattr(self.overlay, "set_analysis"):
|
||||
self.overlay.set_analysis("")
|
||||
self.overlay.hide()
|
||||
except Exception as e: # noqa: BLE001
|
||||
self.log(f"[draft] overlay hide failed: {e}")
|
||||
@@ -365,7 +381,7 @@ class DraftSession:
|
||||
def _refresh_recommendations(self, confirmed: dict, info: dict, gsi_fn, *, force: bool = False) -> None:
|
||||
if not self.recommend_enabled:
|
||||
return
|
||||
if not self.relations:
|
||||
if not self.relations and not self.recommend_archetypes:
|
||||
return
|
||||
gsi = (gsi_fn() or {}) if gsi_fn else {}
|
||||
self_slot = gsi_slot(gsi) or info.get("self_slot")
|
||||
@@ -376,8 +392,10 @@ class DraftSession:
|
||||
if self_slot and confirmed.get(self_slot):
|
||||
if info.get("recommendations") or self._last_rec_sig is not None:
|
||||
info["recommendations"] = []
|
||||
self._last_enemy_profile = {}
|
||||
self._last_rec_meta = {}
|
||||
self._last_rec_sig = ("locked",)
|
||||
self._push_rec_overlay(info.get("cells"), [])
|
||||
self._push_rec_overlay(info.get("cells"), [], "")
|
||||
return
|
||||
enemies = enemy_keys(confirmed, self_team)
|
||||
allies = ally_keys(confirmed, self_team, self_slot)
|
||||
@@ -388,11 +406,13 @@ class DraftSession:
|
||||
if not force and sig == self._last_rec_sig:
|
||||
return
|
||||
self._last_rec_sig = sig
|
||||
if position is None or (not enemies and not allies):
|
||||
if len(enemies) < self.recommend_min_enemies:
|
||||
info["recommendations"] = []
|
||||
self._push_rec_overlay(info.get("cells"), [])
|
||||
self._last_enemy_profile = {}
|
||||
self._last_rec_meta = {}
|
||||
self._push_rec_overlay(info.get("cells"), [], "")
|
||||
return
|
||||
picks = suggest_top(
|
||||
result = suggest_marks(
|
||||
position=position,
|
||||
enemies=enemies,
|
||||
allies=allies,
|
||||
@@ -400,14 +420,38 @@ class DraftSession:
|
||||
relations=self.relations,
|
||||
top_n=self.recommend_top_n,
|
||||
role_tags=self.recommend_role_tags,
|
||||
min_enemies=self.recommend_min_enemies,
|
||||
min_heroes_for_gaps=self.recommend_min_heroes_for_gaps,
|
||||
archetypes_enabled=self.recommend_archetypes,
|
||||
)
|
||||
picks = result.get("marks") or []
|
||||
profile = result.get("enemy_profile") or {}
|
||||
analysis = result.get("analysis") or ""
|
||||
info["recommendations"] = picks
|
||||
self._push_rec_overlay(info.get("cells"), picks)
|
||||
if picks:
|
||||
names = ", ".join(f"{p['rank']}.{p['name_loc']}({p['score']:+d})" for p in picks)
|
||||
self._last_enemy_profile = profile
|
||||
self._last_rec_meta = {
|
||||
"analysis": analysis,
|
||||
"enemy_archetypes": list(result.get("enemy_archetypes") or []),
|
||||
"enemy_gaps": list(result.get("enemy_gaps") or []),
|
||||
"ally_gaps": list(result.get("ally_gaps") or []),
|
||||
"ally_profile": dict(result.get("ally_profile") or {}),
|
||||
}
|
||||
self._push_rec_overlay(info.get("cells"), picks, analysis)
|
||||
if picks or analysis:
|
||||
bits = []
|
||||
for p in picks[:12]:
|
||||
labs = "".join(p.get("labels") or [])
|
||||
why = "、".join(p.get("reasons") or [])
|
||||
extra = f":{why}" if why else ""
|
||||
bits.append(f"{p['name_loc']}[{labs}]{extra}")
|
||||
pos_s = f"pos{position}" if position is not None else "all"
|
||||
ally_n = [self.hero_names.get(a, a) for a in allies]
|
||||
enemy_n = [self.hero_names.get(e, e) for e in enemies]
|
||||
self.log(f"[rec] pos{position} with {ally_n} vs {enemy_n}: {names}")
|
||||
analysis_s = analysis or "-"
|
||||
self.log(
|
||||
f"[rec] {pos_s} {analysis_s} | with {ally_n} vs {enemy_n}: "
|
||||
f"{len(picks)} marks — {', '.join(bits)}"
|
||||
)
|
||||
|
||||
def _absorb_picks(self, result: dict, pending: dict, confirmed: dict,
|
||||
scores: dict, *, allow_revise: bool = True,
|
||||
@@ -541,6 +585,12 @@ class DraftSession:
|
||||
"position": role["position"] if role else None,
|
||||
"enemies": enemies,
|
||||
"allies": allies,
|
||||
"enemy_profile": dict(self._last_enemy_profile or {}),
|
||||
"ally_profile": dict((self._last_rec_meta or {}).get("ally_profile") or {}),
|
||||
"enemy_archetypes": list((self._last_rec_meta or {}).get("enemy_archetypes") or []),
|
||||
"enemy_gaps": list((self._last_rec_meta or {}).get("enemy_gaps") or []),
|
||||
"ally_gaps": list((self._last_rec_meta or {}).get("ally_gaps") or []),
|
||||
"analysis": (self._last_rec_meta or {}).get("analysis") or "",
|
||||
"picks": info.get("recommendations") or [],
|
||||
},
|
||||
}
|
||||
@@ -613,10 +663,18 @@ def describe(summary: dict) -> list[str]:
|
||||
if summary.get("bans_loc"):
|
||||
lines.append(f"bans : {len(summary['bans_loc'])} - {', '.join(summary['bans_loc'])}")
|
||||
rec = summary.get("recommendations") or {}
|
||||
analysis = rec.get("analysis") or ""
|
||||
if analysis:
|
||||
lines.append(f"draft : {analysis}")
|
||||
picks = rec.get("picks") or []
|
||||
if picks:
|
||||
bits = ", ".join(f"{p['rank']}.{p.get('name_loc') or loc(p['key'], names)}" for p in picks)
|
||||
lines.append(f"rec : {bits}")
|
||||
bits = []
|
||||
for p in picks[:15]:
|
||||
labs = "".join(p.get("labels") or []) or "?"
|
||||
why = "、".join(p.get("reasons") or [])
|
||||
name = p.get("name_loc") or loc(p["key"], names)
|
||||
bits.append(f"{name}[{labs}]" + (f"({why})" if why else ""))
|
||||
lines.append(f"rec : {len(picks)} — {', '.join(bits)}")
|
||||
return lines
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user