From ec64804fecce724923763c25dda873a012e1cdd3 Mon Sep 17 00:00:00 2001 From: Debian Date: Fri, 18 Sep 2026 14:39:21 +0200 Subject: [PATCH] Infer kill event teams without relying on the livedamage plugin Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- lib/parser.py | 48 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/lib/parser.py b/lib/parser.py index b6a4e94..e652505 100644 --- a/lib/parser.py +++ b/lib/parser.py @@ -167,6 +167,34 @@ class EventParser: team_prefix = get_team_prefix(name, self.game_state.player_tracker) return f"^8^5[SWITCH]^7 {team_prefix}^8{name}^0{team_msg} from {old_team_msg}{warmup}\n" + def _opposite_team(self, team): + """The other active team in a two-team mode; None when not applicable""" + return {1: 2, 2: 1, 'RED': 'BLUE', 'BLUE': 'RED'}.get(team) + + def _resolve_event_teams(self, data, killer, victim, killer_name, victim_name): + """ + Resolve (killer_team, victim_team) for a kill/death event. Event TEAM + fields win; when a server omits them (some do during warmup), the + missing side is derived from the counterpart player and the + TEAMKILL/SUICIDE flags. Solo modes always resolve to FREE — every + participant is FREE by definition. None means unknowable from this + event (leave the tracked team untouched). + """ + if not self.game_state.server_info.is_team_mode(): + return ('FREE', 'FREE') + + killer_team = killer.get('TEAM') if killer else None + victim_team = victim.get('TEAM') if victim else None + same_team = (bool(data.get('SUICIDE', False)) + or strip_color_codes(killer_name).strip() == strip_color_codes(victim_name).strip() + or bool(data.get('TEAMKILL', False))) + + if killer_team is not None and victim_team is None: + victim_team = killer_team if same_team else self._opposite_team(killer_team) + elif victim_team is not None and killer_team is None: + killer_team = victim_team if same_team else self._opposite_team(victim_team) + return (killer_team, victim_team) + def _handle_death(self, data): """Handle PLAYER_DEATH and PLAYER_KILL events""" @@ -190,6 +218,11 @@ class EventParser: if 'TEAM' in victim: self.game_state.player_tracker.update_team(victim_name, victim['TEAM']) self.game_state.player_tracker.add_player(victim_name) + elif not self.game_state.server_info.is_team_mode(): + # Solo modes: every participant is FREE by definition, even when + # the event carries no TEAM field + self.game_state.player_tracker.update_team(victim_name, 'FREE') + self.game_state.player_tracker.add_player(victim_name) # Mark as dead if not data.get('WARMUP', False): @@ -224,10 +257,17 @@ class EventParser: killer = data['KILLER'] killer_name = killer.get('NAME', 'Unknown') - # Update killer team - if 'TEAM' in killer: - self.game_state.player_tracker.update_team(killer_name, killer['TEAM']) - self.game_state.player_tracker.add_player(killer_name) + # Team resolution with inference: prefer the event's TEAM fields; when + # a server omits them (some do during warmup), derive the missing side + # from the counterpart and the TEAMKILL/SUICIDE flags + killer_team, victim_team = self._resolve_event_teams( + data, killer, victim, killer_name, victim_name) + if killer_team is not None: + self.game_state.player_tracker.update_team(killer_name, killer_team) + if victim_team is not None: + self.game_state.player_tracker.update_team(victim_name, victim_team) + self.game_state.player_tracker.add_player(killer_name) + self.game_state.player_tracker.add_player(victim_name) killer_prefix = get_team_prefix(killer_name, self.game_state.player_tracker)