newnewnew
This commit is contained in:
15
formatter.py
15
formatter.py
@ -164,17 +164,24 @@ def format_chat_message(message, player_tracker):
|
|||||||
|
|
||||||
return message
|
return message
|
||||||
|
|
||||||
|
|
||||||
def format_powerup_message(message, player_tracker):
|
def format_powerup_message(message, player_tracker):
|
||||||
"""
|
"""
|
||||||
Format powerup pickup and carrier kill messages
|
Format powerup pickup and carrier kill messages
|
||||||
Returns formatted message or None if not a powerup message
|
Returns formatted message or None if not a powerup message
|
||||||
"""
|
"""
|
||||||
from config import POWERUP_COLORS
|
from config import POWERUP_COLORS
|
||||||
|
import time
|
||||||
|
|
||||||
if message.startswith("broadcast:"):
|
if message.startswith("broadcast:"):
|
||||||
message = message[11:].strip()
|
message = message[11:].strip()
|
||||||
|
|
||||||
|
# Strip print " wrapper
|
||||||
|
if message.startswith('print "'):
|
||||||
|
message = message[7:]
|
||||||
|
if message.endswith('"'):
|
||||||
|
message = message[:-1]
|
||||||
|
message = message.strip()
|
||||||
|
|
||||||
# Powerup pickup: "PlayerName got the PowerupName!"
|
# Powerup pickup: "PlayerName got the PowerupName!"
|
||||||
pickup_match = re.match(r'^(.+?)\s+got the\s+(.+?)!', message)
|
pickup_match = re.match(r'^(.+?)\s+got the\s+(.+?)!', message)
|
||||||
if pickup_match:
|
if pickup_match:
|
||||||
@ -185,7 +192,8 @@ def format_powerup_message(message, player_tracker):
|
|||||||
team_prefix = get_team_prefix(player_clean, player_tracker)
|
team_prefix = get_team_prefix(player_clean, player_tracker)
|
||||||
|
|
||||||
colored_powerup = POWERUP_COLORS.get(powerup_name, f'^6{powerup_name}^7')
|
colored_powerup = POWERUP_COLORS.get(powerup_name, f'^6{powerup_name}^7')
|
||||||
return f"{team_prefix}^0{player_name}^9 ^7got the {colored_powerup}!\n"
|
timestamp = time.strftime('%H:%M:%S')
|
||||||
|
return f"^3[^7{timestamp}^3]^7 {team_prefix}^0{player_name}^9 ^7got the {colored_powerup}!\n"
|
||||||
|
|
||||||
# Powerup carrier kill: "PlayerName killed the PowerupName carrier!"
|
# Powerup carrier kill: "PlayerName killed the PowerupName carrier!"
|
||||||
carrier_match = re.match(r'^(.+?)\s+killed the\s+(.+?)\s+carrier!', message)
|
carrier_match = re.match(r'^(.+?)\s+killed the\s+(.+?)\s+carrier!', message)
|
||||||
@ -197,6 +205,7 @@ def format_powerup_message(message, player_tracker):
|
|||||||
team_prefix = get_team_prefix(player_clean, player_tracker)
|
team_prefix = get_team_prefix(player_clean, player_tracker)
|
||||||
|
|
||||||
colored_powerup = POWERUP_COLORS.get(powerup_name, f'^6{powerup_name}^7')
|
colored_powerup = POWERUP_COLORS.get(powerup_name, f'^6{powerup_name}^7')
|
||||||
return f"{team_prefix}^0{player_name}^9 ^7killed the {colored_powerup} ^7carrier!\n"
|
timestamp = time.strftime('%H:%M:%S')
|
||||||
|
return f"^3[^7{timestamp}^3]^7 {team_prefix}^0{player_name}^9 ^7killed the {colored_powerup} ^7carrier!\n"
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|||||||
147
main.py
147
main.py
@ -98,56 +98,120 @@ def parse_player_events(message, game_state, ui):
|
|||||||
Returns True if message should be suppressed
|
Returns True if message should be suppressed
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
msg = message
|
||||||
|
|
||||||
|
# Strip broadcast: print "..." wrapper with regex
|
||||||
|
broadcast_match = re.match(r'^broadcast:\s*print\s*"(.+?)(?:\\n)?"\s*$', msg)
|
||||||
|
if broadcast_match:
|
||||||
|
msg = broadcast_match.group(1)
|
||||||
|
|
||||||
|
# Strip timestamp: [HH:MM:SS] or ^3[^7HH:MM:SS^3]^7
|
||||||
|
msg = re.sub(r'\^\d\[\^\d[0-9:]+\^\d\]\^\d\s*', '', msg)
|
||||||
|
msg = re.sub(r'\[[0-9:]+\]\s*', '', msg)
|
||||||
|
msg = msg.strip()
|
||||||
|
|
||||||
|
if not msg:
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.debug(f'parse_player_events: {repr(msg)}')
|
||||||
|
|
||||||
# Strip color codes for matching
|
# Strip color codes for matching
|
||||||
from formatter import strip_color_codes
|
from formatter import strip_color_codes
|
||||||
clean_msg = strip_color_codes(message)
|
clean_msg = strip_color_codes(msg)
|
||||||
|
|
||||||
# Match connects: "NAME connected"
|
# Match connects: "NAME connected" or "NAME connected with Steam ID"
|
||||||
connect_match = re.match(r'^(.+?)\s+connected(?:\s+with Steam ID)?', clean_msg)
|
connect_match = re.match(r'^(.+?)\s+connected', clean_msg)
|
||||||
if connect_match:
|
if connect_match:
|
||||||
player_name = message.split(' connected')[0].strip() # Keep color codes
|
player_name_match = re.match(r'^(.+?)\s+connected', msg)
|
||||||
|
player_name = player_name_match.group(1).strip() if player_name_match else connect_match.group(1).strip()
|
||||||
|
player_name = re.sub(r'\^\d+$', '', player_name)
|
||||||
|
|
||||||
|
logger.info(f'CONNECT: {repr(player_name)}')
|
||||||
if game_state.server_info.is_team_mode():
|
if game_state.server_info.is_team_mode():
|
||||||
game_state.player_tracker.update_team(player_name, 'SPECTATOR')
|
game_state.player_tracker.update_team(player_name, 'SPECTATOR')
|
||||||
game_state.player_tracker.add_player(player_name)
|
game_state.player_tracker.add_player(player_name)
|
||||||
ui.update_server_info(game_state)
|
ui.update_server_info(game_state)
|
||||||
logger.debug(f'Player connected: {player_name}')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Match disconnects: "NAME disconnected", "NAME was kicked"
|
# Only print if this is NOT the Steam ID line
|
||||||
disconnect_patterns = [
|
if 'Steam ID' not in message:
|
||||||
r'^(.+?)\s+disconnected',
|
timestamp = time.strftime('%H:%M:%S')
|
||||||
r'^(.+?)\s+was kicked',
|
ui.print_message(f"^3[^7{timestamp}^3]^7 ^0{player_name}^9 ^2connected^7\n")
|
||||||
]
|
|
||||||
|
|
||||||
for pattern in disconnect_patterns:
|
return True
|
||||||
match = re.match(pattern, clean_msg)
|
|
||||||
if match:
|
|
||||||
player_name_clean = match.group(1).strip()
|
|
||||||
# Try to find the original name with color codes
|
|
||||||
original_match = re.match(pattern, message)
|
|
||||||
player_name = original_match.group(1).strip() if original_match else player_name_clean
|
|
||||||
|
|
||||||
|
# Regular disconnect
|
||||||
|
disconnect_match = re.match(r'^(.+?)\s+disconnected', clean_msg)
|
||||||
|
if disconnect_match:
|
||||||
|
original_match = re.match(r'^(.+?)\s+disconnected', msg)
|
||||||
|
player_name = original_match.group(1).strip() if original_match else disconnect_match.group(1).strip()
|
||||||
|
player_name = re.sub(r'\^\d+$', '', player_name)
|
||||||
|
player_name = re.sub(r'^\^\d+', '', player_name)
|
||||||
|
|
||||||
|
logger.info(f'DISCONNECT: {repr(player_name)}')
|
||||||
game_state.player_tracker.remove_player(player_name)
|
game_state.player_tracker.remove_player(player_name)
|
||||||
game_state.player_tracker.remove_player(player_name_clean) # Try both
|
|
||||||
ui.update_server_info(game_state)
|
ui.update_server_info(game_state)
|
||||||
logger.debug(f'Player disconnected: {player_name}')
|
|
||||||
return False
|
timestamp = time.strftime('%H:%M:%S')
|
||||||
|
ui.print_message(f"^3[^7{timestamp}^3]^7 ^0{player_name}^9 ^1disconnected^7\n")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Kick
|
||||||
|
kick_match = re.match(r'^(.+?)\s+was kicked', clean_msg)
|
||||||
|
if kick_match:
|
||||||
|
original_match = re.match(r'^(.+?)\s+was kicked', msg)
|
||||||
|
player_name = original_match.group(1).strip() if original_match else kick_match.group(1).strip()
|
||||||
|
player_name = re.sub(r'\^\d+$', '', player_name)
|
||||||
|
player_name = re.sub(r'^\^\d+', '', player_name)
|
||||||
|
|
||||||
|
logger.info(f'KICK: {repr(player_name)}')
|
||||||
|
game_state.player_tracker.remove_player(player_name)
|
||||||
|
ui.update_server_info(game_state)
|
||||||
|
|
||||||
|
timestamp = time.strftime('%H:%M:%S')
|
||||||
|
ui.print_message(f"^3[^7{timestamp}^3]^7 ^0{player_name}^9 ^1was kicked^7\n")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Inactivity
|
||||||
|
inactivity_match = re.match(r'^(.+?)\s+Dropped due to inactivity', clean_msg)
|
||||||
|
if inactivity_match:
|
||||||
|
original_match = re.match(r'^(.+?)\s+Dropped due to inactivity', msg)
|
||||||
|
player_name = original_match.group(1).strip() if original_match else inactivity_match.group(1).strip()
|
||||||
|
player_name = re.sub(r'\^\d+$', '', player_name)
|
||||||
|
player_name = re.sub(r'^\^\d+', '', player_name)
|
||||||
|
|
||||||
|
logger.info(f'INACTIVITY DROP: {repr(player_name)}')
|
||||||
|
game_state.player_tracker.remove_player(player_name)
|
||||||
|
ui.update_server_info(game_state)
|
||||||
|
|
||||||
|
timestamp = time.strftime('%H:%M:%S')
|
||||||
|
ui.print_message(f"^3[^7{timestamp}^3]^7 ^0{player_name}^9 ^3dropped due to inactivity^7\n")
|
||||||
|
return True
|
||||||
|
|
||||||
# Match renames: "OldName renamed to NewName"
|
# Match renames: "OldName renamed to NewName"
|
||||||
rename_match = re.match(r'^(.+?)\s+renamed to\s+(.+?)$', clean_msg)
|
rename_match = re.match(r'^(.+?)\s+renamed to\s+(.+?)$', clean_msg)
|
||||||
if rename_match:
|
if rename_match:
|
||||||
old_name_clean = rename_match.group(1).strip()
|
# Extract from original message
|
||||||
new_name_clean = rename_match.group(2).strip()
|
original_match = re.match(r'^(.+?)\s+renamed to\s+(.+?)$', msg)
|
||||||
|
if original_match:
|
||||||
|
old_name = original_match.group(1).strip()
|
||||||
|
new_name = original_match.group(2).strip()
|
||||||
|
else:
|
||||||
|
old_name = rename_match.group(1).strip()
|
||||||
|
new_name = rename_match.group(2).strip()
|
||||||
|
|
||||||
# Get names with color codes
|
# Remove trailing color codes from both names
|
||||||
original_match = re.match(r'^(.+?)\s+renamed to\s+(.+?)$', message)
|
old_name = re.sub(r'\^\d+$', '', old_name)
|
||||||
old_name = original_match.group(1).strip() if original_match else old_name_clean
|
new_name = re.sub(r'^\^\d+', '', new_name) # Remove leading ^7 from new name
|
||||||
new_name = original_match.group(2).strip() if original_match else new_name_clean
|
new_name = re.sub(r'\^\d+$', '', new_name) # Remove trailing too
|
||||||
|
old_name = old_name.rstrip('\n\r') # Remove trailing newline
|
||||||
|
new_name = new_name.rstrip('\n\r') # Remove trailing newline
|
||||||
|
|
||||||
|
logger.info(f'RENAME: {repr(old_name)} -> {repr(new_name)}')
|
||||||
game_state.player_tracker.rename_player(old_name, new_name)
|
game_state.player_tracker.rename_player(old_name, new_name)
|
||||||
ui.update_server_info(game_state)
|
ui.update_server_info(game_state)
|
||||||
logger.debug(f'Player renamed: {old_name} -> {new_name}')
|
timestamp = time.strftime('%H:%M:%S')
|
||||||
return False
|
ui.print_message(f"^3[^7{timestamp}^3]^7 ^0{old_name}^9 ^6renamed to^7 ^0{new_name}^9\n")
|
||||||
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f'Error in parse_player_events: {e}')
|
logger.error(f'Error in parse_player_events: {e}')
|
||||||
@ -291,14 +355,33 @@ def main_loop(screen):
|
|||||||
|
|
||||||
logger.debug(f'Received message ({len(message)} bytes): {repr(message[:100])}')
|
logger.debug(f'Received message ({len(message)} bytes): {repr(message[:100])}')
|
||||||
|
|
||||||
|
# Check for player connect/disconnect/rename events
|
||||||
|
if parse_player_events(message, game_state, ui):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if '------- Game Initialization -------' in message or 'Game Initialization' in message:
|
||||||
|
logger.info('Game initialization detected - refreshing server info')
|
||||||
|
ui.print_message("^6New Game - Refreshing Server Info^7\n")
|
||||||
|
|
||||||
|
rcon.send_command(b'qlx_serverBrandName')
|
||||||
|
rcon.send_command(b'g_factoryTitle')
|
||||||
|
rcon.send_command(b'mapname')
|
||||||
|
rcon.send_command(b'timelimit')
|
||||||
|
rcon.send_command(b'fraglimit')
|
||||||
|
rcon.send_command(b'roundlimit')
|
||||||
|
rcon.send_command(b'capturelimit')
|
||||||
|
rcon.send_command(b'sv_maxclients')
|
||||||
|
|
||||||
|
# Clear player list since map changed
|
||||||
|
game_state.server_info.players = []
|
||||||
|
game_state.player_tracker.player_teams = {}
|
||||||
|
ui.update_server_info(game_state)
|
||||||
|
|
||||||
# Try to parse as cvar response
|
# Try to parse as cvar response
|
||||||
if parse_cvar_response(message, game_state, ui):
|
if parse_cvar_response(message, game_state, ui):
|
||||||
logger.debug('Suppressed cvar response')
|
logger.debug('Suppressed cvar response')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check for player connect/disconnect/rename events
|
|
||||||
parse_player_events(message, game_state, ui)
|
|
||||||
|
|
||||||
# Check for stats connection info
|
# Check for stats connection info
|
||||||
port, password = handle_stats_connection(message, rcon, ui, game_state)
|
port, password = handle_stats_connection(message, rcon, ui, game_state)
|
||||||
if port:
|
if port:
|
||||||
|
|||||||
51
state.py
51
state.py
@ -93,7 +93,14 @@ class PlayerTracker:
|
|||||||
|
|
||||||
def add_player(self, name, score='0', ping='0'):
|
def add_player(self, name, score='0', ping='0'):
|
||||||
"""Add player to server's player list if not exists"""
|
"""Add player to server's player list if not exists"""
|
||||||
if not any(p['name'] == name for p in self.server_info.players):
|
clean_name = re.sub(r'\^\d', '', name)
|
||||||
|
|
||||||
|
# Check if player already exists (by either name or clean name)
|
||||||
|
for existing in self.server_info.players:
|
||||||
|
existing_clean = re.sub(r'\^\d', '', existing['name'])
|
||||||
|
if existing['name'] == name or existing_clean == clean_name:
|
||||||
|
return # Already exists
|
||||||
|
|
||||||
self.server_info.players.append({
|
self.server_info.players.append({
|
||||||
'name': name,
|
'name': name,
|
||||||
'score': score,
|
'score': score,
|
||||||
@ -115,34 +122,48 @@ class PlayerTracker:
|
|||||||
"""Remove player from tracking"""
|
"""Remove player from tracking"""
|
||||||
clean_name = re.sub(r'\^\d', '', name)
|
clean_name = re.sub(r'\^\d', '', name)
|
||||||
|
|
||||||
# Remove from player list (check both name and clean name)
|
# Count before removal
|
||||||
|
before_count = len(self.server_info.players)
|
||||||
|
|
||||||
|
# Remove from player list - check both original and clean names
|
||||||
self.server_info.players = [
|
self.server_info.players = [
|
||||||
p for p in self.server_info.players
|
p for p in self.server_info.players
|
||||||
if p['name'] != name and re.sub(r'\^\d', '', p['name']) != clean_name
|
if p['name'] != name and re.sub(r'\^\d', '', p['name']) != clean_name
|
||||||
]
|
]
|
||||||
|
|
||||||
# Remove from team tracking
|
# Log if anything was actually removed
|
||||||
if name in self.player_teams:
|
after_count = len(self.server_info.players)
|
||||||
del self.player_teams[name]
|
if before_count != after_count:
|
||||||
if clean_name in self.player_teams and clean_name != name:
|
logger.info(f'Removed player: {name} (clean: {clean_name}) - {before_count} -> {after_count}')
|
||||||
del self.player_teams[clean_name]
|
else:
|
||||||
|
logger.warning(f'Player not found for removal: {name} (clean: {clean_name})')
|
||||||
|
|
||||||
logger.debug(f'Removed player: {name} (clean: {clean_name})')
|
# Remove from team tracking - both versions
|
||||||
|
self.player_teams.pop(name, None)
|
||||||
|
self.player_teams.pop(clean_name, None)
|
||||||
|
|
||||||
def rename_player(self, old_name, new_name):
|
def rename_player(self, old_name, new_name):
|
||||||
"""Rename a player while maintaining their team"""
|
"""Rename a player while maintaining their team"""
|
||||||
# Get current team
|
old_clean = re.sub(r'\^\d', '', old_name)
|
||||||
team = self.player_teams.get(old_name, 'SPECTATOR')
|
|
||||||
|
|
||||||
# Remove old entries
|
# Get current team (try both names)
|
||||||
self.remove_player(old_name)
|
team = self.player_teams.get(old_name) or self.player_teams.get(old_clean, 'SPECTATOR')
|
||||||
|
|
||||||
# Add with new name and team
|
# Find and update player in server list
|
||||||
|
for player in self.server_info.players:
|
||||||
|
if player['name'] == old_name or re.sub(r'\^\d', '', player['name']) == old_clean:
|
||||||
|
player['name'] = new_name
|
||||||
|
break
|
||||||
|
|
||||||
|
# Remove old team entries
|
||||||
|
self.player_teams.pop(old_name, None)
|
||||||
|
self.player_teams.pop(old_clean, None)
|
||||||
|
|
||||||
|
# Add new team entries with color codes preserved
|
||||||
if self.server_info.is_team_mode():
|
if self.server_info.is_team_mode():
|
||||||
self.update_team(new_name, team)
|
self.update_team(new_name, team)
|
||||||
self.add_player(new_name)
|
|
||||||
|
|
||||||
logger.debug(f'Renamed player: {old_name} -> {new_name}')
|
logger.debug(f'Renamed player: {old_name} -> {new_name} (team: {team})')
|
||||||
|
|
||||||
class EventDeduplicator:
|
class EventDeduplicator:
|
||||||
"""Prevents duplicate kill/death events"""
|
"""Prevents duplicate kill/death events"""
|
||||||
|
|||||||
Reference in New Issue
Block a user