v0.6.5: size hero detail drawer from viewport; add PC post-lock item tips.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+88
-15
@@ -43,6 +43,7 @@ from capture import grab_frame, is_dota_foreground, raw_dir_for_match, save_fram
|
||||
from shared.grid import bans, hero_table, read_grid
|
||||
from modes import detect_mode, load_mode_templates
|
||||
from recognize import recognize_image
|
||||
from item_suggest import suggest_items
|
||||
from recommend import ally_keys, enemy_keys, load_relations, suggest_marks
|
||||
from roles import ROLES, detect_roles, load_role_templates
|
||||
|
||||
@@ -95,10 +96,18 @@ class DraftSession:
|
||||
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
|
||||
items_cfg = rec.get("items") or {}
|
||||
self.recommend_items_enabled = bool(items_cfg.get("enabled", True))
|
||||
self.recommend_items_core_n = int(items_cfg.get("core_n", 3))
|
||||
self.recommend_items_answer_n = int(items_cfg.get("answer_n", 3))
|
||||
self.recommend_items_max_total = int(items_cfg.get("max_total", 6))
|
||||
self.recommend_hero_items_path = items_cfg.get("hero_items_path")
|
||||
self.recommend_items_meta_path = items_cfg.get("items_meta_path")
|
||||
self._rec_warned = False
|
||||
self._last_rec_sig: tuple | None = None
|
||||
self._last_enemy_profile: dict = {}
|
||||
self._last_rec_meta: dict = {}
|
||||
self._last_item_sig: tuple | None = None
|
||||
|
||||
def _push_overlay(self, confirmed: dict[int, str]) -> None:
|
||||
if self.overlay is None:
|
||||
@@ -113,6 +122,7 @@ class DraftSession:
|
||||
cells: dict | None,
|
||||
picks: list[dict],
|
||||
analysis: str = "",
|
||||
items: list[dict] | None = None,
|
||||
) -> None:
|
||||
if self.overlay is None:
|
||||
return
|
||||
@@ -121,6 +131,8 @@ class DraftSession:
|
||||
self.overlay.set_grid_marks(cells or {}, marks)
|
||||
if hasattr(self.overlay, "set_analysis"):
|
||||
self.overlay.set_analysis(analysis or "")
|
||||
if hasattr(self.overlay, "set_items"):
|
||||
self.overlay.set_items(items if items is not None else [])
|
||||
except Exception as e: # noqa: BLE001
|
||||
self.log(f"[draft] rec overlay update failed: {e}")
|
||||
|
||||
@@ -134,9 +146,12 @@ class DraftSession:
|
||||
self.overlay.set_grid_marks({}, {})
|
||||
if hasattr(self.overlay, "set_analysis"):
|
||||
self.overlay.set_analysis("")
|
||||
if hasattr(self.overlay, "set_items"):
|
||||
self.overlay.set_items([])
|
||||
self.overlay.show()
|
||||
except Exception as e: # noqa: BLE001
|
||||
self.log(f"[draft] overlay show failed: {e}")
|
||||
self._last_item_sig = None
|
||||
if self.recommend_enabled and not self._rec_warned:
|
||||
rel = self.relations or {}
|
||||
if not rel.get("counters") and not rel.get("synergies"):
|
||||
@@ -157,6 +172,7 @@ class DraftSession:
|
||||
"cells": None,
|
||||
"mode": None,
|
||||
"recommendations": [],
|
||||
"item_recommendations": [],
|
||||
}
|
||||
polls = 0
|
||||
last_frame = None
|
||||
@@ -248,8 +264,8 @@ class DraftSession:
|
||||
event["frame"] = save_frame(frame, self.frame_dir, prefix="draft")
|
||||
timeline.append(event)
|
||||
self._log_event(event, info)
|
||||
if state == HERO_SELECTION:
|
||||
self._refresh_recommendations(confirmed, info, gsi_fn)
|
||||
# Hero marks in selection; item bar after self-lock (incl. strategy tail).
|
||||
self._refresh_recommendations(confirmed, info, gsi_fn)
|
||||
|
||||
n = len(confirmed)
|
||||
if self.keep_frames and n in (4, 8, 10) and n not in saved_milestones:
|
||||
@@ -276,6 +292,8 @@ class DraftSession:
|
||||
self.overlay.set_grid_marks({}, {})
|
||||
if hasattr(self.overlay, "set_analysis"):
|
||||
self.overlay.set_analysis("")
|
||||
if hasattr(self.overlay, "set_items"):
|
||||
self.overlay.set_items([])
|
||||
self.overlay.hide()
|
||||
except Exception as e: # noqa: BLE001
|
||||
self.log(f"[draft] overlay hide failed: {e}")
|
||||
@@ -383,26 +401,70 @@ class DraftSession:
|
||||
self.log(f"[draft] grid: {len(res['unavailable'])} heroes unavailable "
|
||||
f"(contrast margin {res['margin']}) - {names}")
|
||||
|
||||
def _refresh_item_recommendations(
|
||||
self,
|
||||
confirmed: dict,
|
||||
info: dict,
|
||||
*,
|
||||
self_hero: str,
|
||||
enemies: list[str],
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
"""After self-lock: clear hero marks and show core + answer items."""
|
||||
analysis = (self._last_rec_meta or {}).get("analysis") or ""
|
||||
if not self.recommend_items_enabled:
|
||||
if info.get("recommendations") or info.get("item_recommendations") or self._last_rec_sig != ("locked",):
|
||||
info["recommendations"] = []
|
||||
info["item_recommendations"] = []
|
||||
self._last_rec_sig = ("locked",)
|
||||
self._last_item_sig = ("locked-off",)
|
||||
self._push_rec_overlay(info.get("cells"), [], analysis, [])
|
||||
return
|
||||
sig = (self_hero, tuple(enemies))
|
||||
if not force and sig == self._last_item_sig:
|
||||
return
|
||||
self._last_item_sig = sig
|
||||
self._last_rec_sig = ("locked",)
|
||||
info["recommendations"] = []
|
||||
items = suggest_items(
|
||||
self_hero,
|
||||
enemies,
|
||||
core_n=self.recommend_items_core_n,
|
||||
answer_n=self.recommend_items_answer_n,
|
||||
max_total=self.recommend_items_max_total,
|
||||
hero_items_path=self.recommend_hero_items_path,
|
||||
items_meta_path=self.recommend_items_meta_path,
|
||||
)
|
||||
info["item_recommendations"] = items
|
||||
self._push_rec_overlay(info.get("cells"), [], analysis, items)
|
||||
if items:
|
||||
bits = [
|
||||
f"{it.get('name_loc') or it['key']}({it.get('reason') or it.get('kind')})"
|
||||
for it in items
|
||||
]
|
||||
self.log(
|
||||
f"[rec:items] {self.hero_names.get(self_hero, self_hero)} vs "
|
||||
f"{[self.hero_names.get(e, e) for e in enemies]}: {', '.join(bits)}"
|
||||
)
|
||||
|
||||
def _refresh_recommendations(self, confirmed: dict, info: dict, gsi_fn, *, force: bool = False) -> None:
|
||||
if not self.recommend_enabled:
|
||||
return
|
||||
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")
|
||||
self_team = gsi.get("team") or info.get("self_team")
|
||||
role = info["roles"].get(self_slot) if self_slot else None
|
||||
position = role["position"] if role else None
|
||||
# Stop suggesting once you have locked a hero.
|
||||
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"), [], "")
|
||||
return
|
||||
self_hero = (confirmed.get(self_slot) if self_slot else None) or gsi.get("hero")
|
||||
enemies = enemy_keys(confirmed, self_team)
|
||||
# Once locked, switch from hero marks to item icon bar.
|
||||
if self_slot and self_hero:
|
||||
self._refresh_item_recommendations(
|
||||
confirmed, info, self_hero=self_hero, enemies=enemies, force=force,
|
||||
)
|
||||
return
|
||||
if not self.relations and not self.recommend_archetypes:
|
||||
return
|
||||
allies = ally_keys(confirmed, self_team, self_slot)
|
||||
exclude = set(confirmed.values())
|
||||
if info.get("unavailable"):
|
||||
@@ -411,11 +473,13 @@ class DraftSession:
|
||||
if not force and sig == self._last_rec_sig:
|
||||
return
|
||||
self._last_rec_sig = sig
|
||||
self._last_item_sig = None
|
||||
if len(enemies) < self.recommend_min_enemies:
|
||||
info["recommendations"] = []
|
||||
info["item_recommendations"] = []
|
||||
self._last_enemy_profile = {}
|
||||
self._last_rec_meta = {}
|
||||
self._push_rec_overlay(info.get("cells"), [], "")
|
||||
self._push_rec_overlay(info.get("cells"), [], "", [])
|
||||
return
|
||||
result = suggest_marks(
|
||||
position=position,
|
||||
@@ -433,6 +497,7 @@ class DraftSession:
|
||||
profile = result.get("enemy_profile") or {}
|
||||
analysis = result.get("analysis") or ""
|
||||
info["recommendations"] = picks
|
||||
info["item_recommendations"] = []
|
||||
self._last_enemy_profile = profile
|
||||
self._last_rec_meta = {
|
||||
"analysis": analysis,
|
||||
@@ -441,7 +506,7 @@ class DraftSession:
|
||||
"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)
|
||||
self._push_rec_overlay(info.get("cells"), picks, analysis, [])
|
||||
if picks or analysis:
|
||||
bits = []
|
||||
for p in picks[:12]:
|
||||
@@ -597,6 +662,7 @@ class DraftSession:
|
||||
"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 [],
|
||||
"items": info.get("item_recommendations") or [],
|
||||
},
|
||||
}
|
||||
if info["unavailable"] is not None:
|
||||
@@ -680,6 +746,13 @@ def describe(summary: dict) -> list[str]:
|
||||
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)}")
|
||||
items = rec.get("items") or []
|
||||
if items:
|
||||
bits = [
|
||||
f"{it.get('name_loc') or it.get('key')}({it.get('reason') or it.get('kind')})"
|
||||
for it in items
|
||||
]
|
||||
lines.append(f"items : {', '.join(bits)}")
|
||||
return lines
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user