feat(domination): complete match + career DomObj pipeline, fix rankings, fix DB keys, improve UI layout
This commit is contained in:
parent
077d7a7783
commit
16f8032654
3 changed files with 88 additions and 52 deletions
|
|
@ -167,12 +167,12 @@ class DashLeagueGUI:
|
||||||
|
|
||||||
top_frame = ttk.Frame(main_frame)
|
top_frame = ttk.Frame(main_frame)
|
||||||
top_frame.grid(row=0, column=0, columnspan=2, sticky="ew", pady=(0, 10))
|
top_frame.grid(row=0, column=0, columnspan=2, sticky="ew", pady=(0, 10))
|
||||||
|
|
||||||
|
self.build_db_button = ttk.Button(top_frame, text="Build Career Database", command=self.on_build_career_db)
|
||||||
|
self.build_db_button.grid(row=0, column=0, padx=(0, 10))
|
||||||
|
|
||||||
self.sync_button = ttk.Button(top_frame, text="Sync Stats", command=self.on_sync_stats)
|
self.sync_button = ttk.Button(top_frame, text="Sync Stats", command=self.on_sync_stats)
|
||||||
self.sync_button.grid(row=0, column=0, padx=(0, 10))
|
self.sync_button.grid(row=0, column=1, padx=(0, 10))
|
||||||
|
|
||||||
self.build_db_button = ttk.Button(top_frame, text="Build Career Database", command=self.on_build_career_db)
|
|
||||||
self.build_db_button.grid(row=0, column=1, padx=(0, 10))
|
|
||||||
|
|
||||||
self.spotlight_button = ttk.Button(top_frame, text="Player Spotlight", command=self.on_player_spotlight)
|
self.spotlight_button = ttk.Button(top_frame, text="Player Spotlight", command=self.on_player_spotlight)
|
||||||
self.spotlight_button.grid(row=0, column=2, padx=(0, 10))
|
self.spotlight_button.grid(row=0, column=2, padx=(0, 10))
|
||||||
|
|
@ -374,11 +374,36 @@ class DashLeagueGUI:
|
||||||
p["slayer"] = pdata.get("slayer", {})
|
p["slayer"] = pdata.get("slayer", {})
|
||||||
p["sharpshooter"] = pdata.get("sharpshooter", {})
|
p["sharpshooter"] = pdata.get("sharpshooter", {})
|
||||||
p["objective_payload"] = pdata.get("objective_payload", {})
|
p["objective_payload"] = pdata.get("objective_payload", {})
|
||||||
p["objective_domination"] = pdata.get("objective_domination", {})
|
|
||||||
|
# DO NOT overwrite match-based DomObj
|
||||||
|
if "objective_domination_components" not in p:
|
||||||
|
p["objective_domination_components"] = pdata.get(
|
||||||
|
"objective_domination_components",
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
p["consistency"] = pdata.get("consistency", {})
|
p["consistency"] = pdata.get("consistency", {})
|
||||||
p["clutch"] = pdata.get("clutch", {})
|
p["clutch"] = pdata.get("clutch", {})
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# 4.5 Always attach match-based Domination Objective
|
||||||
|
# -----------------------------
|
||||||
|
for p in all_players:
|
||||||
|
dom = compute_dom_objective_for_match_player(p)
|
||||||
|
caps = dom.get("captures", 0)
|
||||||
|
counters = dom.get("counters", 0)
|
||||||
|
|
||||||
|
# Simple scoring model
|
||||||
|
dom_score = caps * 0.4 + counters * 0.6
|
||||||
|
|
||||||
|
p["objective_domination_components"] = {
|
||||||
|
"raw": dom_score,
|
||||||
|
"captures": caps,
|
||||||
|
"counters": counters,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
# 5. Write OBS slot files
|
# 5. Write OBS slot files
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
|
|
@ -398,11 +423,6 @@ class DashLeagueGUI:
|
||||||
write_text(os.path.join(slot_dir, "Captures.txt"), p.get("DOM_Captures", ""))
|
write_text(os.path.join(slot_dir, "Captures.txt"), p.get("DOM_Captures", ""))
|
||||||
write_text(os.path.join(slot_dir, "Counters.txt"), p.get("DOM_Counters", ""))
|
write_text(os.path.join(slot_dir, "Counters.txt"), p.get("DOM_Counters", ""))
|
||||||
|
|
||||||
# Tag exports (use enriched match player dict)
|
|
||||||
map_type = self.map_var.get()
|
|
||||||
tags_text = self.generate_player_tags(p, map_type)
|
|
||||||
write_text(os.path.join(slot_dir, "Tags.txt"), tags_text)
|
|
||||||
|
|
||||||
self.set_status("Player slots generated.")
|
self.set_status("Player slots generated.")
|
||||||
messagebox.showinfo("Success", "Player slots generated into p0–p9.")
|
messagebox.showinfo("Success", "Player slots generated into p0–p9.")
|
||||||
|
|
||||||
|
|
@ -416,8 +436,8 @@ class DashLeagueGUI:
|
||||||
try:
|
try:
|
||||||
self.set_status("Building career database...")
|
self.set_status("Building career database...")
|
||||||
|
|
||||||
# Modernized output path
|
# Use the same file the rest of the tool reads
|
||||||
output_path = os.path.join(BASE_DIR, "final_db.json")
|
output_path = os.path.join(BASE_DIR, "career_stats.json")
|
||||||
|
|
||||||
build_career_database(output_path)
|
build_career_database(output_path)
|
||||||
|
|
||||||
|
|
@ -429,8 +449,8 @@ class DashLeagueGUI:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
messagebox.showerror("Error", f"Failed to build career database:\n{e}")
|
messagebox.showerror("Error", f"Failed to build career database:\n{e}")
|
||||||
self.set_status("Career DB build failed.")
|
self.set_status("Career DB build failed.")
|
||||||
|
|
||||||
|
|
||||||
def on_player_spotlight(self):
|
def on_player_spotlight(self):
|
||||||
career_path = os.path.join(BASE_DIR, "career_stats.json")
|
career_path = os.path.join(BASE_DIR, "career_stats.json")
|
||||||
if not os.path.exists(career_path):
|
if not os.path.exists(career_path):
|
||||||
|
|
@ -472,21 +492,21 @@ class DashLeagueGUI:
|
||||||
|
|
||||||
text = f"""PLAYER SPOTLIGHT — {name}
|
text = f"""PLAYER SPOTLIGHT — {name}
|
||||||
|
|
||||||
Slayer Tier: {slayer_strength}
|
Slayer Tier: {slayer_strength}
|
||||||
Payload Objective Specialist: {obj_pl if obj_pl is not None else "N/A"}
|
Payload Objective Specialist: {obj_pl if obj_pl is not None else "N/A"}
|
||||||
|
|
||||||
Career Stats:
|
Career Stats:
|
||||||
KD: {career['KD']:.2f}
|
KD: {career['KD']:.2f}
|
||||||
Accuracy: {career['accuracy']:.2f}
|
Accuracy: {career['accuracy']:.2f}
|
||||||
Kills: {career['kills']}
|
Kills: {career['kills']}
|
||||||
Deaths: {career['deaths']}
|
Deaths: {career['deaths']}
|
||||||
Maps Played: {career['maps']}
|
Maps Played: {career['maps']}
|
||||||
|
|
||||||
Derived Metrics:
|
Derived Metrics:
|
||||||
Kills/Map: {derived.get('kills_per_map', 0):.2f}
|
Kills/Map: {derived.get('kills_per_map', 0):.2f}
|
||||||
Deaths/Map: {derived.get('deaths_per_map', 0):.2f}
|
Deaths/Map: {derived.get('deaths_per_map', 0):.2f}
|
||||||
PushTime/Season: {derived.get('push_time_per_season', 0):.2f}
|
PushTime/Season: {derived.get('push_time_per_season', 0):.2f}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
output.delete("1.0", tk.END)
|
output.delete("1.0", tk.END)
|
||||||
output.insert(tk.END, text)
|
output.insert(tk.END, text)
|
||||||
|
|
@ -540,9 +560,10 @@ PushTime/Season: {derived.get('push_time_per_season', 0):.2f}
|
||||||
def generate_player_tags(self, player_entry, map_type):
|
def generate_player_tags(self, player_entry, map_type):
|
||||||
tags_out = []
|
tags_out = []
|
||||||
|
|
||||||
# Slayer
|
# Slayer (career)
|
||||||
slayer_strength = player_entry.get("slayer_strength")
|
slayer = player_entry.get("slayer", {})
|
||||||
if slayer_strength:
|
slayer_strength = slayer.get("strength") or player_entry.get("slayer_strength")
|
||||||
|
if isinstance(slayer_strength, str):
|
||||||
tags_out.append(slayer_strength)
|
tags_out.append(slayer_strength)
|
||||||
|
|
||||||
# Payload Objective Specialist (match-based)
|
# Payload Objective Specialist (match-based)
|
||||||
|
|
@ -554,14 +575,16 @@ PushTime/Season: {derived.get('push_time_per_season', 0):.2f}
|
||||||
# Domination Objective Specialist (match-based)
|
# Domination Objective Specialist (match-based)
|
||||||
if map_type == "Domination":
|
if map_type == "Domination":
|
||||||
dom_info = player_entry.get("objective_domination_components", {})
|
dom_info = player_entry.get("objective_domination_components", {})
|
||||||
dom_score = dom_info.get("dom_score_raw", 0.0)
|
dom_score = dom_info.get("raw", 0.0)
|
||||||
tags_out.append(f"ObjDOM {dom_score:.2f}")
|
tags_out.append(f"ObjDOM {dom_score:.2f}")
|
||||||
|
|
||||||
# Sharpshooter
|
# Sharpshooter (career)
|
||||||
sharp = player_entry.get("sharpshooter_score")
|
sharp = player_entry.get("sharpshooter", {}).get("score") or player_entry.get("sharpshooter_score")
|
||||||
if sharp is not None:
|
if isinstance(sharp, (int, float)):
|
||||||
tags_out.append(f"Sharp {sharp:.2f}")
|
tags_out.append(f"Sharp {sharp:.2f}")
|
||||||
|
|
||||||
|
print("DEBUG DOM:", player_entry.get("objective_domination_components"))
|
||||||
|
|
||||||
return ", ".join(tags_out)
|
return ", ".join(tags_out)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -648,7 +671,7 @@ PushTime/Season: {derived.get('push_time_per_season', 0):.2f}
|
||||||
|
|
||||||
win = tk.Toplevel(self.root)
|
win = tk.Toplevel(self.root)
|
||||||
win.title("Rankings")
|
win.title("Rankings")
|
||||||
win.geometry("500x600")
|
win.geometry("800x600")
|
||||||
|
|
||||||
tags = [
|
tags = [
|
||||||
"Slayer",
|
"Slayer",
|
||||||
|
|
@ -906,21 +929,14 @@ PushTime/Season: {derived.get('push_time_per_season', 0):.2f}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Prediction export failed:", e)
|
print("Prediction export failed:", e)
|
||||||
|
|
||||||
# --- PLAYER TAG EXPORTS ---
|
# --- PLAYER TAG EXPORTS ---
|
||||||
try:
|
try:
|
||||||
career_path = os.path.join(BASE_DIR, "career_stats.json")
|
for idx, p in enumerate(self.current_match_players):
|
||||||
if os.path.exists(career_path):
|
# p IS the enriched match player object
|
||||||
with open(career_path, "r", encoding="utf-8") as f:
|
tags_text = self.generate_player_tags(p, map_type)
|
||||||
career_db = json.load(f)
|
|
||||||
|
|
||||||
for idx, p in enumerate(self.current_match_players):
|
slot_path = os.path.join(PLAYERS_DIR, f"p{idx}", "Tags.txt")
|
||||||
pid = p.get("id")
|
write_text(slot_path, tags_text)
|
||||||
if pid and pid in career_db["players"]:
|
|
||||||
player_entry = career_db["players"][pid]
|
|
||||||
tags_text = self.generate_player_tags(player_entry, map_type)
|
|
||||||
|
|
||||||
slot_path = os.path.join(PLAYERS_DIR, f"p{idx}", "Tags.txt")
|
|
||||||
write_text(slot_path, tags_text)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Tag export failed:", e)
|
print("Tag export failed:", e)
|
||||||
|
|
|
||||||
|
|
@ -80,14 +80,16 @@ def build_career_database(output_path):
|
||||||
"KD": KD,
|
"KD": KD,
|
||||||
"accuracy": accuracy,
|
"accuracy": accuracy,
|
||||||
"push_time": career_raw["PAY_PushTime"],
|
"push_time": career_raw["PAY_PushTime"],
|
||||||
"captures": career_raw["DOM_captures"],
|
"DOM_Captures": int(career_raw["DOM_captures"]),
|
||||||
"counters": career_raw["DOM_counters"],
|
"DOM_Counters": int(career_raw["DOM_counters"]),
|
||||||
"maps": career_raw["maps"],
|
"maps": career_raw["maps"],
|
||||||
"damage": career_raw["damage"],
|
"damage": career_raw["damage"],
|
||||||
"shots": career_raw["shots"],
|
"shots": career_raw["shots"],
|
||||||
"shots_hit": career_raw["shots_hit"],
|
"shots_hit": career_raw["shots_hit"],
|
||||||
"headshots": career_raw["headshots"],
|
"headshots": career_raw["headshots"],
|
||||||
},
|
},
|
||||||
|
"DOM_Captures": int(career_raw["DOM_captures"]), # 👈 add this
|
||||||
|
"DOM_Counters": int(career_raw["DOM_counters"]), # 👈 and this
|
||||||
"derived": derived,
|
"derived": derived,
|
||||||
"matches": matches,
|
"matches": matches,
|
||||||
}
|
}
|
||||||
|
|
@ -138,7 +140,7 @@ def build_career_database(output_path):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Domination Objective Specialist
|
# Domination Objective Specialist
|
||||||
pdata["objective_domination"] = compute_dom_objective_for_career_player(
|
pdata["objective_domination_components"] = compute_dom_objective_for_career_player(
|
||||||
pdata,
|
pdata,
|
||||||
league_averages,
|
league_averages,
|
||||||
distributions["domination"]
|
distributions["domination"]
|
||||||
|
|
|
||||||
|
|
@ -162,8 +162,26 @@ def rank_match_players_objpl(match_players):
|
||||||
|
|
||||||
|
|
||||||
def rank_match_players_objdom(match_players):
|
def rank_match_players_objdom(match_players):
|
||||||
return _add_score_fields(rank_match_players_by_tag(match_players, "objective_domination"))
|
ranked = []
|
||||||
|
|
||||||
|
for p in match_players:
|
||||||
|
comp = p.get("objective_domination_components", {})
|
||||||
|
raw = comp.get("raw", 0.0)
|
||||||
|
|
||||||
|
ranked.append({
|
||||||
|
"id": p.get("id"),
|
||||||
|
"name": p.get("name"),
|
||||||
|
"team": p.get("team"),
|
||||||
|
"score_raw": raw,
|
||||||
|
"score_display": f"{raw:.2f}",
|
||||||
|
})
|
||||||
|
|
||||||
|
ranked.sort(key=lambda x: x["score_raw"], reverse=True)
|
||||||
|
|
||||||
|
for i, r in enumerate(ranked, 1):
|
||||||
|
r["rank"] = i
|
||||||
|
|
||||||
|
return ranked
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
# ---------------------------------------------------------
|
||||||
# Legacy Slayer Prediction Wrapper
|
# Legacy Slayer Prediction Wrapper
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue