feat(v2): 13 improvements — functional, visual, UX, backend
Functional:
1. Chat: "▼ New messages below" indicator when scrolled up, click to jump
2. Combat stats: "Clear Session" button (red, with confirm dialog)
3. Inventory: live updates via inventory_delta WS (re-fetches on change)
4. Inventory: real mana time from equipment_cantrip_state WS (live
countdown with state dot: green=active, red=inactive, yellow=unknown)
Visual:
5. Thin separator line between tool links and sort buttons
6. Selected player row highlighted with darker background (#2a3344)
7. Scroll-to-top button (▲) appears when scrolled past 200px in player list
UX:
8. Double-click player dot on map opens their chat window
9. Right-click player dot shows context menu (Chat/Stats/Inv/Char/Combat/Radar)
10. Ctrl+D keyboard shortcut toggles between map and dashboard views
11. Sound notification on rare drops (880Hz sine beep via Web Audio API)
Backend:
12. Deep-merge lifetime offense/defense per element — accumulates
total_attacks, failed_attacks, crits, damage per AttackType×Element
instead of overwriting with latest session data
13. Startup cleanup: deletes stale combat_stats records from before
the lifetime fix (pre-2026-04-14T09:00Z)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0b64c6ccff
commit
0112c59514
41 changed files with 404 additions and 112 deletions
40
main.py
40
main.py
|
|
@ -1242,6 +1242,13 @@ async def on_startup():
|
|||
# Seed default users on first run
|
||||
await seed_users()
|
||||
|
||||
# Clean stale combat_stats lifetime data (pre-fix records where lifetime == session)
|
||||
try:
|
||||
await database.execute("DELETE FROM combat_stats WHERE timestamp < '2026-04-14T09:00:00Z'")
|
||||
logger.info("Cleaned stale pre-fix combat_stats lifetime data")
|
||||
except Exception as e:
|
||||
logger.debug(f"Combat stats cleanup: {e}")
|
||||
|
||||
|
||||
@app.on_event("shutdown")
|
||||
async def on_shutdown():
|
||||
|
|
@ -2545,12 +2552,33 @@ def _combat_merge_into_lifetime(lifetime: dict, delta: dict) -> dict:
|
|||
lm["damage_received"] = (lm.get("damage_received", 0) or 0) + (dm.get("damage_received", 0) or 0)
|
||||
lm["aetheria_surges"] = (lm.get("aetheria_surges", 0) or 0) + (dm.get("aetheria_surges", 0) or 0)
|
||||
lm["cloak_surges"] = (lm.get("cloak_surges", 0) or 0) + (dm.get("cloak_surges", 0) or 0)
|
||||
# For offense/defense, use latest from delta (nested merge is complex and not needed
|
||||
# since the backend doesn't need per-element lifetime accuracy — session view has that)
|
||||
if dm.get("offense"):
|
||||
lm["offense"] = dm["offense"]
|
||||
if dm.get("defense"):
|
||||
lm["defense"] = dm["defense"]
|
||||
# Deep-merge offense/defense per-element stats
|
||||
for side_key in ("offense", "defense"):
|
||||
delta_side = dm.get(side_key, {})
|
||||
if not delta_side:
|
||||
continue
|
||||
lt_side = lm.setdefault(side_key, {})
|
||||
for atk_type, by_el in delta_side.items():
|
||||
lt_by_el = lt_side.setdefault(atk_type, {})
|
||||
for el, stats in by_el.items():
|
||||
if el not in lt_by_el:
|
||||
lt_by_el[el] = {
|
||||
"total_attacks": 0, "failed_attacks": 0, "crits": 0,
|
||||
"total_normal_damage": 0, "max_normal_damage": 0,
|
||||
"total_crit_damage": 0, "max_crit_damage": 0,
|
||||
}
|
||||
lt_s = lt_by_el[el]
|
||||
lt_s["total_attacks"] += stats.get("total_attacks", 0) or 0
|
||||
lt_s["failed_attacks"] += stats.get("failed_attacks", 0) or 0
|
||||
lt_s["crits"] += stats.get("crits", 0) or 0
|
||||
lt_s["total_normal_damage"] += stats.get("total_normal_damage", 0) or 0
|
||||
lt_s["max_normal_damage"] = max(
|
||||
lt_s["max_normal_damage"], stats.get("max_normal_damage", 0) or 0
|
||||
)
|
||||
lt_s["total_crit_damage"] += stats.get("total_crit_damage", 0) or 0
|
||||
lt_s["max_crit_damage"] = max(
|
||||
lt_s["max_crit_damage"], stats.get("max_crit_damage", 0) or 0
|
||||
)
|
||||
return lifetime
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue