diff --git a/lib/constants.py b/lib/constants.py index febbd9d..7a0c3e2 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -20,6 +20,7 @@ QUIT_CONFIRM_TIMEOUT = 3.0 # Seconds to confirm quit (Ctrl-C twice) RESPAWN_DELAY = 3.0 # Seconds before players respawn after death STATUS_OUTPUT_WINDOW = 1.5 # Seconds a 'status' command echo suppresses its chunked table output STATUS_SEED_WINDOW = 2.0 # Seconds the roster seeder collects the 'status' dump after connect +CVAR_RESPONSE_TTL = 10.0 # Seconds a user-queried cvar response may arrive after the query STATS_CONNECTION_DELAY = 0.5 # Initial stats connection delay # UI dimensions diff --git a/main.py b/main.py index 84b9f18..1fdf904 100644 --- a/main.py +++ b/main.py @@ -15,7 +15,7 @@ import signal import sys import os -from lib.constants import VERSION, DEFAULT_HOST, POLL_TIMEOUT, QUIT_CONFIRM_TIMEOUT, RESPAWN_DELAY, MAX_COMMAND_HISTORY, STATUS_OUTPUT_WINDOW, STATUS_SEED_WINDOW +from lib.constants import VERSION, DEFAULT_HOST, POLL_TIMEOUT, QUIT_CONFIRM_TIMEOUT, RESPAWN_DELAY, MAX_COMMAND_HISTORY, STATUS_OUTPUT_WINDOW, STATUS_SEED_WINDOW, CVAR_RESPONSE_TTL from lib.state import GameState from lib.network import RconConnection, StatsConnection from lib.parser import EventParser @@ -43,6 +43,7 @@ STATUS_ROW_PATTERN = re.compile( ROSTER_CHAT_GUARD_PATTERN = re.compile(r'^.{1,40}:\s') VOTE_CAST_PATTERN = re.compile(r'^(.+?) voted for (.+)\.$') CHAT_SHAPE_PATTERN = re.compile(r'^[\w(][^:]*:\s') +CVAR_QUERY_PATTERN = re.compile(r'^[A-Za-z_][A-Za-z0-9_]*$') GAME_END_PATTERN = re.compile( r'((?:timelimit|fraglimit|capturelimit|roundlimit)\s+hit' r'|hit\s+the\s+(?:timelimit|fraglimit|capturelimit|roundlimit)' @@ -72,6 +73,16 @@ quit_confirm_time = None _QUIT_CONFIRM_TIMEOUT = QUIT_CONFIRM_TIMEOUT _RESPAWN_DELAY = RESPAWN_DELAY +# Cvars the user queried from the console (lowercased name -> request time): +# their responses display verbatim; everything else stays suppressed +expected_cvar_responses = {} + + +def consume_expected_cvar(cvar_name): + """True when the user queried this cvar from the console recently""" + requested_at = expected_cvar_responses.pop(cvar_name.lower(), None) + return requested_at is not None and (time.time() - requested_at) < CVAR_RESPONSE_TTL + # Global shutdown flag (set by handle_ctrl_c, checked by main_loop) shutdown_requested = False @@ -102,20 +113,27 @@ def handle_ctrl_c(): def parse_cvar_response(message, game_state, ui): """ - Parse server cvar responses and update state - Returns True if message should be suppressed from display + Parse server cvar responses: update state for known cvars, and display the + response when the user queried it from the console. Returns True when the + message is consumed (known-cvar update or a displayed query result). """ - # Parse cvar responses (format: "cvar_name" is:"value" default:...) - cvar_match = CVAR_RESPONSE_PATTERN.search(message) - if cvar_match: - cvar_name = cvar_match.group(1) - value = cvar_match.group(2) + match = CVAR_RESPONSE_PATTERN.search(message) + if not match: + return False - if game_state.server_info.update_from_cvar(cvar_name, value): - ui.update_server_info(game_state) - return True + cvar_name = match.group(1) + value = match.group(2) - return False + state_updated = game_state.server_info.update_from_cvar(cvar_name, value) + if state_updated: + ui.update_server_info(game_state) + + displayed = consume_expected_cvar(cvar_name) + if displayed: + timestamp = time.strftime('%H:%M:%S') + ui.print_message(f"^3[^7{timestamp}^3]^7 {message.rstrip()}\n") + + return state_updated or displayed def is_command_echo(message): @@ -356,6 +374,11 @@ def handle_user_input(ui, rcon): timestamp = time.strftime('%H:%M:%S') ui.print_message(f"^3[^7{timestamp}^3]^7 Server: {stripped[4:]}\n") + # A bare cvar name queries its value; the response displays verbatim + # when it arrives (other clients' query output stays suppressed) + if CVAR_QUERY_PATTERN.match(stripped): + expected_cvar_responses[stripped.lower()] = time.time() + def handle_stats_stream(stats_conn, stats_check_counter, event_parser, ui, game_state): """Poll and process stats stream events"""