Populate team rosters from a LIVESTATS team token and tag the damage list

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <[email protected]>
This commit is contained in:
Debian
2026-09-18 14:26:06 +02:00
co-authored by Sisyphus
parent cbeab1ae45
commit c1b5f6d86a
3 changed files with 38 additions and 15 deletions
+12 -3
View File
@@ -149,16 +149,25 @@ class PlayerTracker:
return True
return False
def update_livestats(self, clean_name, score, dealt, taken, ping):
"""Update live score/damage/ping from a LIVESTATS console print (clean name)"""
def update_livestats(self, clean_name, score, dealt, taken, ping, team=None):
"""Update live score/damage/ping from a LIVESTATS console print (clean name).
With a team token (extended livedamage format) the team is authoritative —
it sorts tracked players and adds unknown ones to the roster, which is
what populates teams correctly during warmup."""
for player_name, player_data in self.server_info.players.items():
if strip_color_codes(player_name) == clean_name:
player_data['score'] = str(score)
player_data['ping'] = str(ping)
player_data['dealt'] = str(dealt)
player_data['taken'] = str(taken)
if team is not None:
self.update_team(player_name, team)
return True
return False
if team is None:
return False
self.add_player(clean_name, score=str(score), ping=str(ping))
self.update_team(clean_name, team)
return True
def remove_player(self, name):
"""Remove player from tracking"""
+16 -8
View File
@@ -18,9 +18,10 @@ import time
from collections import deque
from .constants import (INFO_WINDOW_HEIGHT, INFO_WINDOW_Y, OUTPUT_WINDOW_Y, INPUT_WINDOW_HEIGHT,
MIN_ROWS, MIN_COLS, OUTPUT_SCROLLBACK_LINES, TEAM_MODES, MAX_COMMAND_HISTORY)
MIN_ROWS, MIN_COLS, OUTPUT_SCROLLBACK_LINES, TEAM_MODES, MAX_COMMAND_HISTORY,
TEAM_COLORS)
from .cvars import autocomplete, COMMAND_SIGNATURES, get_signature_with_highlight, get_argument_suggestions, COMMAND_ARGUMENTS
from .formatter import strip_color_codes
from .formatter import get_team_prefix, strip_color_codes
logger = logging.getLogger('ui')
@@ -431,9 +432,11 @@ def _draw_server_menu(screen, servers, names, selected):
screen.refresh()
def format_damage_summary(server_info):
"""One-line damage summary (top 3 by damage dealt) from LIVESTATS counters;
None when no damage data exists yet. Dealt/taken are stored as strings."""
def format_damage_summary(server_info, player_tracker=None):
"""One-line damage summary (top 3 by damage dealt) from LIVESTATS counters,
tagged with the tracked team of each player (data-driven, so tags work
during warmup too); None when no damage data exists yet. Dealt/taken are
stored as strings."""
entries = []
for player_name, player_data in server_info.players.items():
try:
@@ -447,8 +450,13 @@ def format_damage_summary(server_info):
if not entries:
return None
entries.sort(key=lambda x: x[1], reverse=True)
summary = ' '.join(f"{name} {dealt}/{taken}" for name, dealt, taken in entries[:3])
return f"^8^3DMG^7 {summary}\n"
parts = []
for name, dealt, taken in entries[:3]:
prefix = ''
if player_tracker is not None:
prefix = TEAM_COLORS.get(player_tracker.get_team(name), '')
parts.append(f"{prefix}{name} {dealt}/{taken}")
return f"^8^3DMG^7 {' '.join(parts)}\n"
class UIManager:
@@ -893,7 +901,7 @@ class UIManager:
print_colored(self.info_window, line, 0)
# Damage summary from LIVESTATS counters (top 3 by damage dealt)
damage_line = format_damage_summary(server_info)
damage_line = format_damage_summary(server_info, game_state.player_tracker)
if damage_line:
print_colored(self.info_window, damage_line, 0)
+10 -4
View File
@@ -37,7 +37,8 @@ RENAME_PATTERN = re.compile(r'^(.+?)\s+renamed to\s+(.+?)$')
STATUS_HEAD_PATTERN = re.compile(r'^\s*\d+\s+-?\d+\s+(bot|\d+)\s+\S', re.MULTILINE)
STATUS_MAP_PATTERN = re.compile(r'^map:\s+\S+\s*$', re.MULTILINE)
STEAMID_PATTERN = re.compile(r'\b\d{16,}\b')
LIVESTATS_PATTERN = re.compile(r'^LIVESTATS\s+(.+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s*$')
LIVESTATS_PATTERN = re.compile(
r'^LIVESTATS\s+(.+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)(?:\s+([A-Za-z]+|\d))?\s*$')
STATUS_ROW_PATTERN = re.compile(
r'^\s*(\d+)\s+(-?\d+)\s+(\d+)\s+(.+?)\s+(\d+)\s+(\S+)\s+(\d+)\s+(\d+)(?:\s+(\d{15,}))?\s*$')
ROSTER_CHAT_GUARD_PATTERN = re.compile(r'^.{1,40}:\s')
@@ -179,7 +180,9 @@ def handle_livestats(message, game_state, ui, verbose=0):
Consume every LIVESTATS heartbeat line (engine damage counters emitted
every 10s by the livedamage plugin, plus its summary variants). Lines
matching the full player shape update live score/damage/ping; names may
contain spaces, so the counters anchor from the end. The raw lines
contain spaces, so the counters anchor from the end. An optional trailing
team token (extended livedamage format) is authoritative and populates
the roster with correct teams including during warmup. The raw lines
display only at -v or higher verbosity; they are never treated as chat.
"""
clean = strip_color_codes(message).strip()
@@ -187,8 +190,11 @@ def handle_livestats(message, game_state, ui, verbose=0):
return False
match = LIVESTATS_PATTERN.match(clean)
if match:
name, score, dealt, taken, ping = match.groups()
game_state.player_tracker.update_livestats(name, int(score), int(dealt), int(taken), int(ping))
name, score, dealt, taken, ping, team = match.groups()
if team is not None and team.isdigit():
team = int(team)
game_state.player_tracker.update_livestats(
name, int(score), int(dealt), int(taken), int(ping), team)
ui.update_server_info(game_state)
if verbose > 0:
timestamp = time.strftime('%H:%M:%S')