Display responses to cvars the user queries from the console

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 13:57:19 +02:00
co-authored by Sisyphus
parent f8a71e7906
commit 016384ef12
2 changed files with 36 additions and 12 deletions
+1
View File
@@ -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 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_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 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 STATS_CONNECTION_DELAY = 0.5 # Initial stats connection delay
# UI dimensions # UI dimensions
+35 -12
View File
@@ -15,7 +15,7 @@ import signal
import sys import sys
import os 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.state import GameState
from lib.network import RconConnection, StatsConnection from lib.network import RconConnection, StatsConnection
from lib.parser import EventParser from lib.parser import EventParser
@@ -43,6 +43,7 @@ STATUS_ROW_PATTERN = re.compile(
ROSTER_CHAT_GUARD_PATTERN = re.compile(r'^.{1,40}:\s') ROSTER_CHAT_GUARD_PATTERN = re.compile(r'^.{1,40}:\s')
VOTE_CAST_PATTERN = re.compile(r'^(.+?) voted for (.+)\.$') VOTE_CAST_PATTERN = re.compile(r'^(.+?) voted for (.+)\.$')
CHAT_SHAPE_PATTERN = re.compile(r'^[\w(][^:]*:\s') 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( GAME_END_PATTERN = re.compile(
r'((?:timelimit|fraglimit|capturelimit|roundlimit)\s+hit' r'((?:timelimit|fraglimit|capturelimit|roundlimit)\s+hit'
r'|hit\s+the\s+(?:timelimit|fraglimit|capturelimit|roundlimit)' r'|hit\s+the\s+(?:timelimit|fraglimit|capturelimit|roundlimit)'
@@ -72,6 +73,16 @@ quit_confirm_time = None
_QUIT_CONFIRM_TIMEOUT = QUIT_CONFIRM_TIMEOUT _QUIT_CONFIRM_TIMEOUT = QUIT_CONFIRM_TIMEOUT
_RESPAWN_DELAY = RESPAWN_DELAY _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) # Global shutdown flag (set by handle_ctrl_c, checked by main_loop)
shutdown_requested = False shutdown_requested = False
@@ -102,20 +113,27 @@ def handle_ctrl_c():
def parse_cvar_response(message, game_state, ui): def parse_cvar_response(message, game_state, ui):
""" """
Parse server cvar responses and update state Parse server cvar responses: update state for known cvars, and display the
Returns True if message should be suppressed from display 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:...) match = CVAR_RESPONSE_PATTERN.search(message)
cvar_match = CVAR_RESPONSE_PATTERN.search(message) if not match:
if cvar_match: return False
cvar_name = cvar_match.group(1)
value = cvar_match.group(2)
if game_state.server_info.update_from_cvar(cvar_name, value): cvar_name = match.group(1)
ui.update_server_info(game_state) value = match.group(2)
return True
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): def is_command_echo(message):
@@ -356,6 +374,11 @@ def handle_user_input(ui, rcon):
timestamp = time.strftime('%H:%M:%S') timestamp = time.strftime('%H:%M:%S')
ui.print_message(f"^3[^7{timestamp}^3]^7 Server: {stripped[4:]}\n") 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): def handle_stats_stream(stats_conn, stats_check_counter, event_parser, ui, game_state):
"""Poll and process stats stream events""" """Poll and process stats stream events"""