lul
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
venv3/
|
venv3/
|
||||||
|
*.json
|
||||||
*.log
|
*.log
|
||||||
cvarlist.txt
|
cvarlist.txt
|
||||||
curztest.py
|
curztest.py
|
||||||
|
|||||||
@ -10,10 +10,10 @@ DEFAULT_HOST = 'tcp://127.0.0.1:27961'
|
|||||||
POLL_TIMEOUT = 100
|
POLL_TIMEOUT = 100
|
||||||
|
|
||||||
# UI dimensions
|
# UI dimensions
|
||||||
INFO_WINDOW_HEIGHT = 10
|
INFO_WINDOW_HEIGHT = 11
|
||||||
INFO_WINDOW_Y = 2
|
INFO_WINDOW_Y = 2
|
||||||
OUTPUT_WINDOW_Y = 12
|
OUTPUT_WINDOW_Y = 12
|
||||||
INPUT_WINDOW_HEIGHT = 1
|
INPUT_WINDOW_HEIGHT = 2
|
||||||
|
|
||||||
# Event deduplication
|
# Event deduplication
|
||||||
MAX_RECENT_EVENTS = 10
|
MAX_RECENT_EVENTS = 10
|
||||||
|
|||||||
42
parser.py
42
parser.py
@ -66,7 +66,7 @@ class EventParser:
|
|||||||
'PLAYER_STATS': self._handle_player_stats,
|
'PLAYER_STATS': self._handle_player_stats,
|
||||||
'PLAYER_CONNECT': lambda d: None,
|
'PLAYER_CONNECT': lambda d: None,
|
||||||
'PLAYER_DISCONNECT': lambda d: None,
|
'PLAYER_DISCONNECT': lambda d: None,
|
||||||
'ROUND_OVER': lambda d: None,
|
'ROUND_OVER': self._handle_round_over,
|
||||||
}
|
}
|
||||||
|
|
||||||
handler = handler_map.get(event_type)
|
handler = handler_map.get(event_type)
|
||||||
@ -148,9 +148,15 @@ class EventParser:
|
|||||||
|
|
||||||
victim_prefix = get_team_prefix(victim_name, self.game_state.player_tracker)
|
victim_prefix = get_team_prefix(victim_name, self.game_state.player_tracker)
|
||||||
warmup = " ^0^3(Warmup)^9" if data.get('WARMUP', False) else ""
|
warmup = " ^0^3(Warmup)^9" if data.get('WARMUP', False) else ""
|
||||||
|
score_prefix = ""
|
||||||
|
|
||||||
# Environmental death (no killer)
|
# Environmental death (no killer)
|
||||||
if 'KILLER' not in data or not data['KILLER']:
|
if 'KILLER' not in data or not data['KILLER']:
|
||||||
|
# -1 for environmental death
|
||||||
|
if not data.get('WARMUP', False):
|
||||||
|
self.game_state.player_tracker.update_score(victim_name, -1)
|
||||||
|
score_prefix = "^0^1[-1]^9 "
|
||||||
|
|
||||||
mod = data.get('MOD', 'UNKNOWN')
|
mod = data.get('MOD', 'UNKNOWN')
|
||||||
msg_template = DEATH_MESSAGES.get(mod, "%s^0%s^9 ^1DIED FROM %s^7")
|
msg_template = DEATH_MESSAGES.get(mod, "%s^0%s^9 ^1DIED FROM %s^7")
|
||||||
|
|
||||||
@ -159,7 +165,7 @@ class EventParser:
|
|||||||
else:
|
else:
|
||||||
msg = msg_template % (victim_prefix, victim_name, mod)
|
msg = msg_template % (victim_prefix, victim_name, mod)
|
||||||
|
|
||||||
return f"{msg}{warmup}\n"
|
return f"{score_prefix}{msg}{warmup}\n"
|
||||||
|
|
||||||
# Player killed by another player
|
# Player killed by another player
|
||||||
killer = data['KILLER']
|
killer = data['KILLER']
|
||||||
@ -174,17 +180,43 @@ class EventParser:
|
|||||||
|
|
||||||
# Suicide
|
# Suicide
|
||||||
if killer_name == victim_name:
|
if killer_name == victim_name:
|
||||||
|
# -1 for suicide
|
||||||
|
if not data.get('WARMUP', False):
|
||||||
|
self.game_state.player_tracker.update_score(victim_name, -1)
|
||||||
|
score_prefix = "^0^1[-1]^9 "
|
||||||
|
|
||||||
weapon = killer.get('WEAPON', 'OTHER_WEAPON')
|
weapon = killer.get('WEAPON', 'OTHER_WEAPON')
|
||||||
if weapon != 'OTHER_WEAPON':
|
if weapon != 'OTHER_WEAPON':
|
||||||
weapon_name = WEAPON_NAMES.get(weapon, weapon)
|
weapon_name = WEAPON_NAMES.get(weapon, weapon)
|
||||||
return f"{killer_prefix}^0{killer_name}^9 ^7committed suicide with the ^7{weapon_name}{warmup}\n"
|
return f"{score_prefix}{killer_prefix}^0{killer_name}^9 ^7committed suicide with the ^7{weapon_name}{warmup}\n"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Regular kill
|
# Regular kill: +1 for killer
|
||||||
|
if not data.get('WARMUP', False):
|
||||||
|
self.game_state.player_tracker.update_score(killer_name, 1)
|
||||||
|
score_prefix = "^0^2[+1]^9 "
|
||||||
|
|
||||||
|
else:
|
||||||
|
score_prefix = ""
|
||||||
|
|
||||||
weapon = killer.get('WEAPON', 'UNKNOWN')
|
weapon = killer.get('WEAPON', 'UNKNOWN')
|
||||||
weapon_name = WEAPON_KILL_NAMES.get(weapon, f'the {weapon}')
|
weapon_name = WEAPON_KILL_NAMES.get(weapon, f'the {weapon}')
|
||||||
|
|
||||||
return f"{killer_prefix}^0{killer_name}^9 ^7fragged^7 {victim_prefix}^0{victim_name}^9 ^7with {weapon_name}{warmup}\n"
|
return f"{score_prefix}{killer_prefix}^0{killer_name}^9 ^7fragged^7 {victim_prefix}^0{victim_name}^9 ^7with {weapon_name}{warmup}\n"
|
||||||
|
|
||||||
|
def _handle_round_over(self, data):
|
||||||
|
"""Handle ROUND_OVER events for CA"""
|
||||||
|
team_won = data.get('TEAM_WON')
|
||||||
|
round_num = data.get('ROUND', 0)
|
||||||
|
|
||||||
|
if team_won == 'RED':
|
||||||
|
self.game_state.server_info.red_rounds += 1
|
||||||
|
logger.info(f"Round {round_num}: RED wins (RED: {self.game_state.server_info.red_rounds}, BLUE: {self.game_state.server_info.blue_rounds})")
|
||||||
|
elif team_won == 'BLUE':
|
||||||
|
self.game_state.server_info.blue_rounds += 1
|
||||||
|
logger.info(f"Round {round_num}: BLUE wins (RED: {self.game_state.server_info.red_rounds}, BLUE: {self.game_state.server_info.blue_rounds})")
|
||||||
|
|
||||||
|
return None # Don't display in chat
|
||||||
|
|
||||||
def _handle_medal(self, data):
|
def _handle_medal(self, data):
|
||||||
"""Handle PLAYER_MEDAL event"""
|
"""Handle PLAYER_MEDAL event"""
|
||||||
|
|||||||
21
state.py
21
state.py
@ -25,7 +25,9 @@ class ServerInfo:
|
|||||||
self.maxclients = '0'
|
self.maxclients = '0'
|
||||||
self.curclients = '0'
|
self.curclients = '0'
|
||||||
self.red_score = 0
|
self.red_score = 0
|
||||||
|
self.red_rounds = 0
|
||||||
self.blue_score = 0
|
self.blue_score = 0
|
||||||
|
self.blue_rounds = 0
|
||||||
self.players = []
|
self.players = []
|
||||||
self.last_update = 0
|
self.last_update = 0
|
||||||
self.warmup = False
|
self.warmup = False
|
||||||
@ -34,6 +36,11 @@ class ServerInfo:
|
|||||||
"""Check if current gametype is a team mode"""
|
"""Check if current gametype is a team mode"""
|
||||||
return self.gametype in TEAM_MODES
|
return self.gametype in TEAM_MODES
|
||||||
|
|
||||||
|
def reset_round_scores(self):
|
||||||
|
"""Reset round scores (for new matches)"""
|
||||||
|
self.red_rounds = 0
|
||||||
|
self.blue_rounds = 0
|
||||||
|
|
||||||
def update_from_cvar(self, cvar_name, value):
|
def update_from_cvar(self, cvar_name, value):
|
||||||
"""Update server info from a cvar response"""
|
"""Update server info from a cvar response"""
|
||||||
# Normalize cvar name to lowercase for case-insensitive matching
|
# Normalize cvar name to lowercase for case-insensitive matching
|
||||||
@ -162,6 +169,20 @@ class PlayerTracker:
|
|||||||
|
|
||||||
logger.debug(f'Renamed player: {old_name} -> {new_name} (team: {team})')
|
logger.debug(f'Renamed player: {old_name} -> {new_name} (team: {team})')
|
||||||
|
|
||||||
|
def update_score(self, name, delta):
|
||||||
|
"""Update player's score by delta (+1 for kill, -1 for death/suicide)"""
|
||||||
|
clean_name = re.sub(r'\^\d', '', name)
|
||||||
|
|
||||||
|
for player in self.server_info.players:
|
||||||
|
player_clean = re.sub(r'\^\d', '', player['name'])
|
||||||
|
if player['name'] == name or player_clean == clean_name:
|
||||||
|
current_score = int(player.get('score', 0))
|
||||||
|
player['score'] = str(current_score + delta)
|
||||||
|
logger.debug(f"Score update: {name} {delta:+d} -> {player['score']}")
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.warning(f"Could not update score for {name} - player not found")
|
||||||
|
|
||||||
class EventDeduplicator:
|
class EventDeduplicator:
|
||||||
"""Prevents duplicate kill/death events"""
|
"""Prevents duplicate kill/death events"""
|
||||||
|
|
||||||
|
|||||||
131
ui.py
131
ui.py
@ -97,7 +97,6 @@ class UIManager:
|
|||||||
curses.use_default_colors()
|
curses.use_default_colors()
|
||||||
curses.cbreak()
|
curses.cbreak()
|
||||||
curses.curs_set(0)
|
curses.curs_set(0)
|
||||||
#curses.setsyx(-1, -1)
|
|
||||||
|
|
||||||
self.screen.addstr(f"Quake Live PyCon: {self.host}")
|
self.screen.addstr(f"Quake Live PyCon: {self.host}")
|
||||||
self.screen.noutrefresh()
|
self.screen.noutrefresh()
|
||||||
@ -308,7 +307,8 @@ class UIManager:
|
|||||||
|
|
||||||
# Line 1: Hostname
|
# Line 1: Hostname
|
||||||
hostname = server_info.hostname
|
hostname = server_info.hostname
|
||||||
print_colored(self.info_window, f"^3Name:^0 {hostname} ^7\n", 0)
|
warmup_display = "^3Warmup: ^2YES^9" if server_info.warmup else "^3Warmup: ^1NO^9"
|
||||||
|
print_colored(self.info_window, f"^3Name:^0 {hostname} {warmup_display}\n", 0)
|
||||||
|
|
||||||
# Line 2: Game info
|
# Line 2: Game info
|
||||||
gametype = server_info.gametype
|
gametype = server_info.gametype
|
||||||
@ -328,43 +328,134 @@ class UIManager:
|
|||||||
teams = game_state.player_tracker.get_players_by_team()
|
teams = game_state.player_tracker.get_players_by_team()
|
||||||
|
|
||||||
if server_info.gametype in TEAM_MODES:
|
if server_info.gametype in TEAM_MODES:
|
||||||
warmup_display = "^3Warmup: ^2YES^9" if server_info.warmup else "^3Warmup: ^1NO^9"
|
if server_info.gametype == 'CA':
|
||||||
print_colored(self.info_window, f"^0^1(RED) ^4(BLUE) ^3(SPEC) {warmup_display}\n", 0)
|
red_score = f"{server_info.red_rounds:>3} "
|
||||||
|
blue_score = f"{server_info.blue_rounds:>3} "
|
||||||
|
|
||||||
red_players = teams['RED'][:4]
|
else:
|
||||||
blue_players = teams['BLUE'][:4]
|
red_total = 0
|
||||||
|
blue_total = 0
|
||||||
|
for player in server_info.players:
|
||||||
|
player_name = player['name']
|
||||||
|
team = game_state.player_tracker.get_team(player_name)
|
||||||
|
score = int(player.get('score', 0))
|
||||||
|
|
||||||
|
if team == 'RED':
|
||||||
|
red_total += score
|
||||||
|
elif team == 'BLUE':
|
||||||
|
blue_total += score
|
||||||
|
|
||||||
|
red_score = f"{red_total:>3} "
|
||||||
|
blue_score = f"{blue_total:>3} "
|
||||||
|
|
||||||
|
print_colored(self.info_window, f"^0^1RED TEAM: ^7{red_score} ^4BLUE TEAM: ^7{blue_score} ^3(SPEC)\n", 0)
|
||||||
|
|
||||||
|
# Sort players by score within each team
|
||||||
|
red_players_with_scores = []
|
||||||
|
blue_players_with_scores = []
|
||||||
|
spec_players = []
|
||||||
|
|
||||||
|
for player_name in teams['RED']:
|
||||||
|
score = 0
|
||||||
|
for player in server_info.players:
|
||||||
|
if player['name'] == player_name:
|
||||||
|
score = int(player.get('score', 0))
|
||||||
|
break
|
||||||
|
red_players_with_scores.append((player_name, score))
|
||||||
|
|
||||||
|
for player_name in teams['BLUE']:
|
||||||
|
score = 0
|
||||||
|
for player in server_info.players:
|
||||||
|
if player['name'] == player_name:
|
||||||
|
score = int(player.get('score', 0))
|
||||||
|
break
|
||||||
|
blue_players_with_scores.append((player_name, score))
|
||||||
|
|
||||||
|
# Sort by score descending
|
||||||
|
red_players_with_scores.sort(key=lambda x: x[1], reverse=True)
|
||||||
|
blue_players_with_scores.sort(key=lambda x: x[1], reverse=True)
|
||||||
|
|
||||||
|
red_players = [name for name, score in red_players_with_scores[:4]]
|
||||||
|
blue_players = [name for name, score in blue_players_with_scores[:4]]
|
||||||
spec_players = teams['SPECTATOR'][:4]
|
spec_players = teams['SPECTATOR'][:4]
|
||||||
|
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
red = red_players[i] if i < len(red_players) else ''
|
red_name = red_players[i] if i < len(red_players) else ''
|
||||||
blue = blue_players[i] if i < len(blue_players) else ''
|
blue_name = blue_players[i] if i < len(blue_players) else ''
|
||||||
spec = spec_players[i] if i < len(spec_players) else ''
|
spec = spec_players[i] if i < len(spec_players) else ''
|
||||||
|
|
||||||
# Strip color codes for padding calculation
|
# Get scores for team players
|
||||||
|
red_score = ''
|
||||||
|
blue_score = ''
|
||||||
|
if red_name:
|
||||||
|
for player in server_info.players:
|
||||||
|
if player['name'] == red_name:
|
||||||
|
red_score = player.get('score', '0')
|
||||||
|
break
|
||||||
|
if blue_name:
|
||||||
|
for player in server_info.players:
|
||||||
|
if player['name'] == blue_name:
|
||||||
|
blue_score = player.get('score', '0')
|
||||||
|
break
|
||||||
|
|
||||||
|
# Format: " 9 PlayerName" with right-aligned score
|
||||||
|
red = f"{red_score:>3} {red_name}" if red_name else ''
|
||||||
|
blue = f"{blue_score:>3} {blue_name}" if blue_name else ''
|
||||||
|
|
||||||
from formatter import strip_color_codes
|
from formatter import strip_color_codes
|
||||||
red_clean = strip_color_codes(red)
|
red_clean = strip_color_codes(red)
|
||||||
blue_clean = strip_color_codes(blue)
|
blue_clean = strip_color_codes(blue)
|
||||||
spec_clean = strip_color_codes(spec)
|
spec_clean = strip_color_codes(spec)
|
||||||
|
|
||||||
# Calculate padding (24 chars per column)
|
|
||||||
red_pad = 24 - len(red_clean)
|
red_pad = 24 - len(red_clean)
|
||||||
blue_pad = 24 - len(blue_clean)
|
blue_pad = 24 - len(blue_clean)
|
||||||
|
|
||||||
line = f"^0{red}^9{' ' * red_pad}^0{blue}^9{' ' * blue_pad}^0{spec}^9\n"
|
line = f"^0{red}^9{' ' * red_pad}^0{blue}^9{' ' * blue_pad}^0{spec}^9\n"
|
||||||
print_colored(self.info_window, line, 0)
|
print_colored(self.info_window, line, 0)
|
||||||
else:
|
else:
|
||||||
warmup_display = "^3Warmup: ^2YES^9" if server_info.warmup else "^3Warmup: ^1NO^9"
|
print_colored(self.info_window, f" ^0^5(FREE) ^5(FREE) ^3(SPEC)\n", 0)
|
||||||
print_colored(self.info_window, f"^0^5(FREE) ^5(FREE) ^3(SPEC) {warmup_display}\n", 0)
|
# Sort FREE players by score (highest first)
|
||||||
free_players = teams['FREE']
|
free_players = teams['FREE']
|
||||||
|
free_players_with_scores = []
|
||||||
|
for player_name in free_players:
|
||||||
|
score = 0
|
||||||
|
for player in server_info.players:
|
||||||
|
if player['name'] == player_name:
|
||||||
|
score = int(player.get('score', 0))
|
||||||
|
break
|
||||||
|
free_players_with_scores.append((player_name, score))
|
||||||
|
|
||||||
|
# Sort by score descending
|
||||||
|
free_players_with_scores.sort(key=lambda x: x[1], reverse=True)
|
||||||
|
sorted_free_players = [name for name, score in free_players_with_scores]
|
||||||
|
|
||||||
spec_players = teams['SPECTATOR'][:4]
|
spec_players = teams['SPECTATOR'][:4]
|
||||||
free_col1 = free_players[:4]
|
free_col1 = sorted_free_players[:4]
|
||||||
free_col2 = free_players[4:8]
|
free_col2 = sorted_free_players[4:8]
|
||||||
|
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
col1 = free_col1[i] if i < len(free_col1) else ''
|
col1_name = free_col1[i] if i < len(free_col1) else ''
|
||||||
col2 = free_col2[i] if i < len(free_col2) else ''
|
col2_name = free_col2[i] if i < len(free_col2) else ''
|
||||||
spec = spec_players[i] if i < len(spec_players) else ''
|
spec = spec_players[i] if i < len(spec_players) else ''
|
||||||
|
|
||||||
|
# Get scores for FREE players
|
||||||
|
col1_score = ''
|
||||||
|
col2_score = ''
|
||||||
|
if col1_name:
|
||||||
|
for player in server_info.players:
|
||||||
|
if player['name'] == col1_name:
|
||||||
|
col1_score = player.get('score', '0')
|
||||||
|
break
|
||||||
|
if col2_name:
|
||||||
|
for player in server_info.players:
|
||||||
|
if player['name'] == col2_name:
|
||||||
|
col2_score = player.get('score', '0')
|
||||||
|
break
|
||||||
|
|
||||||
|
# Format: " 9 PlayerName" with right-aligned score
|
||||||
|
col1 = f"{col1_score:>3} {col1_name}" if col1_name else ''
|
||||||
|
col2 = f"{col2_score:>3} {col2_name}" if col2_name else ''
|
||||||
|
|
||||||
from formatter import strip_color_codes
|
from formatter import strip_color_codes
|
||||||
col1_clean = strip_color_codes(col1)
|
col1_clean = strip_color_codes(col1)
|
||||||
col2_clean = strip_color_codes(col2)
|
col2_clean = strip_color_codes(col2)
|
||||||
@ -377,7 +468,13 @@ class UIManager:
|
|||||||
print_colored(self.info_window, line, 0)
|
print_colored(self.info_window, line, 0)
|
||||||
|
|
||||||
# Blank lines to fill
|
# Blank lines to fill
|
||||||
self.info_window.addstr("\n\n")
|
self.info_window.addstr("\n")
|
||||||
|
|
||||||
|
line=f"^0^3(SPEC)^7^9\n"
|
||||||
|
print_colored(self.info_window, line, 0)
|
||||||
|
|
||||||
|
# Blank lines to fill
|
||||||
|
self.info_window.addstr("\n")
|
||||||
|
|
||||||
# Separator
|
# Separator
|
||||||
separator = "^7" + "═" * (max_x - 1) + "^7"
|
separator = "^7" + "═" * (max_x - 1) + "^7"
|
||||||
|
|||||||
Reference in New Issue
Block a user