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:
+107
-28
@@ -1,7 +1,7 @@
|
||||
"""Transparent click-through overlay: role tags + Top-3 recommend badges.
|
||||
"""Transparent click-through overlay: role tags + 克/搭/补 marks + analysis bar.
|
||||
|
||||
Runs a Tk root on a background thread. DraftSession calls set_roster() and
|
||||
set_grid_marks(); geometry uses the same relative coords as recognition.
|
||||
Runs a Tk root on a background thread. DraftSession calls set_roster(),
|
||||
set_grid_marks(), and set_analysis(); geometry uses relative coords.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -10,7 +10,6 @@ import json
|
||||
import sys
|
||||
import threading
|
||||
import tkinter as tk
|
||||
from pathlib import Path
|
||||
|
||||
import mss
|
||||
|
||||
@@ -30,8 +29,18 @@ ROLE_ORDER = [
|
||||
]
|
||||
|
||||
CHROMA = "#ff00ff"
|
||||
DEFAULT_REC_COLOR = "#2ec4b6"
|
||||
DEFAULT_REC_TEXT = "#0b1220"
|
||||
DEFAULT_COUNTER_COLOR = "#2ec4b6"
|
||||
DEFAULT_SYNERGY_COLOR = "#e9a825"
|
||||
DEFAULT_FILL_COLOR = "#9b7ebd"
|
||||
DEFAULT_MARK_TEXT = "#0b1220"
|
||||
DEFAULT_ANALYSIS_BG = "#1a2332"
|
||||
DEFAULT_ANALYSIS_FG = "#e8eef7"
|
||||
LABEL_ORDER = ("克", "搭", "补")
|
||||
LABEL_COLORS = {
|
||||
"克": "counter",
|
||||
"搭": "synergy",
|
||||
"补": "fill",
|
||||
}
|
||||
|
||||
|
||||
def _load_roles_by_key() -> dict[str, list[str]]:
|
||||
@@ -75,12 +84,21 @@ class DraftOverlay:
|
||||
self.icon_gap_rel = float(o.get("icon_gap_rel", 0.002))
|
||||
self.mark_size_rel = float(o.get("mark_size_rel", 0.018))
|
||||
self.mark_pad_rel = float(o.get("mark_pad_rel", 0.004))
|
||||
self.rec_color = str(o.get("rec_color", DEFAULT_REC_COLOR))
|
||||
self.rec_text = str(o.get("rec_text_color", DEFAULT_REC_TEXT))
|
||||
self.mark_gap_rel = float(o.get("mark_gap_rel", 0.002))
|
||||
self.counter_color = str(o.get("counter_color", o.get("rec_color", DEFAULT_COUNTER_COLOR)))
|
||||
self.synergy_color = str(o.get("synergy_color", DEFAULT_SYNERGY_COLOR))
|
||||
self.fill_color = str(o.get("fill_color", DEFAULT_FILL_COLOR))
|
||||
self.mark_text = str(o.get("mark_text_color", o.get("rec_text_color", DEFAULT_MARK_TEXT)))
|
||||
self.analysis_y_rel = float(o.get("analysis_y_rel", 0.12))
|
||||
self.analysis_h_rel = float(o.get("analysis_h_rel", 0.028))
|
||||
self.analysis_font_rel = float(o.get("analysis_font_rel", 0.014))
|
||||
self.analysis_bg = str(o.get("analysis_bg", DEFAULT_ANALYSIS_BG))
|
||||
self.analysis_fg = str(o.get("analysis_fg", DEFAULT_ANALYSIS_FG))
|
||||
self.roles_by_key = _load_roles_by_key()
|
||||
self._roster: dict[int, str] = {}
|
||||
self._cells: dict[str, dict] = {}
|
||||
self._marks: dict[str, int] = {}
|
||||
self._marks: dict[str, list[str]] = {}
|
||||
self._analysis = ""
|
||||
self._ready = threading.Event()
|
||||
self._closed = False
|
||||
self._root: tk.Tk | None = None
|
||||
@@ -91,6 +109,14 @@ class DraftOverlay:
|
||||
self._thread.start()
|
||||
self._ready.wait(timeout=5.0)
|
||||
|
||||
def _label_fill(self, label: str) -> str:
|
||||
kind = LABEL_COLORS.get(label)
|
||||
if kind == "synergy":
|
||||
return self.synergy_color
|
||||
if kind == "fill":
|
||||
return self.fill_color
|
||||
return self.counter_color
|
||||
|
||||
def _run(self) -> None:
|
||||
sw, sh = _primary_monitor_size()
|
||||
root = tk.Tk()
|
||||
@@ -160,23 +186,47 @@ class DraftOverlay:
|
||||
self._roster = dict(roster)
|
||||
self._schedule_redraw()
|
||||
|
||||
def set_analysis(self, text: str | None) -> None:
|
||||
"""Short lineup analysis banner (empty clears)."""
|
||||
value = (text or "").strip()
|
||||
if value == self._analysis:
|
||||
return
|
||||
self._analysis = value
|
||||
self._schedule_redraw()
|
||||
|
||||
def set_grid_marks(
|
||||
self,
|
||||
cells: dict[str, dict] | None,
|
||||
marks: dict[str, int] | list[dict] | None,
|
||||
marks: dict[str, list[str] | int] | list[dict] | None,
|
||||
) -> None:
|
||||
"""Cyan Top-3 badges on hero-grid cells. marks: key->rank or suggest_top list."""
|
||||
"""克/搭/补 badges on hero-grid cells.
|
||||
|
||||
marks: key->labels list, key->legacy rank int, or suggest_marks list.
|
||||
"""
|
||||
cells = {str(k): dict(v) for k, v in (cells or {}).items()}
|
||||
parsed: dict[str, int] = {}
|
||||
parsed: dict[str, list[str]] = {}
|
||||
if isinstance(marks, list):
|
||||
for item in marks:
|
||||
key = item.get("key")
|
||||
rank = item.get("rank")
|
||||
if key and rank:
|
||||
parsed[str(key)] = int(rank)
|
||||
if not key:
|
||||
continue
|
||||
labels = item.get("labels")
|
||||
if labels:
|
||||
parsed[str(key)] = [str(x) for x in labels if x in LABEL_ORDER]
|
||||
elif item.get("rank"):
|
||||
parsed[str(key)] = ["克"]
|
||||
elif marks:
|
||||
for key, rank in marks.items():
|
||||
parsed[str(key)] = int(rank)
|
||||
for key, val in marks.items():
|
||||
if isinstance(val, (list, tuple)):
|
||||
labs = [str(x) for x in val if x in LABEL_ORDER]
|
||||
elif isinstance(val, int) and val > 0:
|
||||
labs = ["克"]
|
||||
elif isinstance(val, str) and val in LABEL_ORDER:
|
||||
labs = [val]
|
||||
else:
|
||||
labs = []
|
||||
if labs:
|
||||
parsed[str(key)] = labs
|
||||
if cells == self._cells and parsed == self._marks:
|
||||
return
|
||||
self._cells = cells
|
||||
@@ -210,6 +260,7 @@ class DraftOverlay:
|
||||
if self._canvas is not None:
|
||||
self._canvas.delete("all")
|
||||
self._photos.clear()
|
||||
self._analysis = ""
|
||||
root.withdraw()
|
||||
|
||||
try:
|
||||
@@ -252,6 +303,26 @@ class DraftOverlay:
|
||||
gap = max(0, int(round(self.icon_gap_rel * sh)))
|
||||
y_gap = int(round(self.y_gap_rel * sh))
|
||||
|
||||
if self._analysis:
|
||||
bar_h = max(18, int(round(self.analysis_h_rel * sh)))
|
||||
bar_y = int(round(self.analysis_y_rel * sh))
|
||||
font_size = max(10, int(round(self.analysis_font_rel * sh)))
|
||||
pad_x = max(12, int(round(0.01 * sw)))
|
||||
# Estimate text width roughly; keep banner centered and readable.
|
||||
approx_w = min(sw - 2 * pad_x, max(200, int(len(self._analysis) * font_size * 0.95) + 2 * pad_x))
|
||||
x0 = (sw - approx_w) // 2
|
||||
y0 = bar_y
|
||||
x1 = x0 + approx_w
|
||||
y1 = y0 + bar_h
|
||||
canvas.create_rectangle(x0, y0, x1, y1, fill=self.analysis_bg, outline=self.analysis_bg)
|
||||
canvas.create_text(
|
||||
(x0 + x1) / 2,
|
||||
(y0 + y1) / 2,
|
||||
text=self._analysis,
|
||||
fill=self.analysis_fg,
|
||||
font=("Microsoft YaHei UI", font_size, "bold"),
|
||||
)
|
||||
|
||||
for slot in self.cfg.get("slots") or []:
|
||||
idx = int(slot["index"])
|
||||
hero = self._roster.get(idx)
|
||||
@@ -275,22 +346,30 @@ class DraftOverlay:
|
||||
|
||||
mark = max(12, int(round(self.mark_size_rel * sh)))
|
||||
pad = max(2, int(round(self.mark_pad_rel * sh)))
|
||||
mark_gap = max(1, int(round(self.mark_gap_rel * sh)))
|
||||
font_size = max(8, int(round(mark * 0.55)))
|
||||
for key, rank in self._marks.items():
|
||||
for key, labels in self._marks.items():
|
||||
cell = self._cells.get(key)
|
||||
if not cell:
|
||||
continue
|
||||
ordered = [lab for lab in LABEL_ORDER if lab in labels]
|
||||
if not ordered:
|
||||
continue
|
||||
x0 = int(cell["x0"])
|
||||
y0 = int(cell["y0"])
|
||||
x1 = x0 + pad
|
||||
y1 = y0 + pad
|
||||
x2 = x1 + mark
|
||||
y2 = y1 + mark
|
||||
canvas.create_rectangle(x1, y1, x2, y2, fill=self.rec_color, outline=self.rec_color)
|
||||
canvas.create_text(
|
||||
(x1 + x2) / 2,
|
||||
(y1 + y2) / 2,
|
||||
text=str(rank),
|
||||
fill=self.rec_text,
|
||||
font=("Segoe UI", font_size, "bold"),
|
||||
)
|
||||
for i, lab in enumerate(ordered):
|
||||
bx1 = x1 + i * (mark + mark_gap)
|
||||
by1 = y1
|
||||
bx2 = bx1 + mark
|
||||
by2 = by1 + mark
|
||||
fill = self._label_fill(lab)
|
||||
canvas.create_rectangle(bx1, by1, bx2, by2, fill=fill, outline=fill)
|
||||
canvas.create_text(
|
||||
(bx1 + bx2) / 2,
|
||||
(by1 + by2) / 2,
|
||||
text=lab,
|
||||
fill=self.mark_text,
|
||||
font=("Microsoft YaHei UI", font_size, "bold"),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user