diff --git a/lib/constants.py b/lib/constants.py index 28ab9d0..0ae82ab 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -18,7 +18,7 @@ POLL_TIMEOUT = 100 # Timing constants 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 = 3.0 # Seconds a 'status' command echo hides its chunked table output +STATUS_OUTPUT_WINDOW = 1.5 # Seconds a 'status' command echo suppresses its chunked table output STATS_CONNECTION_DELAY = 0.5 # Initial stats connection delay # UI dimensions diff --git a/main.py b/main.py index 5417f71..abd7e8d 100644 --- a/main.py +++ b/main.py @@ -134,24 +134,38 @@ def is_command_output(message): return False -def is_status_chunk(message): +CHAT_SHAPE_PATTERN = re.compile(r'^[\w(][^:]*:\s') + + +def is_real_content(message): """ - Detect one chunk of 'status' command output. The server streams the table - as many small messages (row heads, '0 16384', the steamid, the address), - so per-chunk shape checks are loose; they only apply inside the - suppression window opened by the command echo. + True for messages that are definitely not status table debris: JSON, + chat-shaped lines and game initialization. Used to end the status + suppression window early; everything else inside the window is treated + as table debris, because the server splits rows into arbitrary chunks + (names, flag columns, '0 16384', bare steamids) that no shape check + can distinguish from real content. The status table's own 'map:' line + is chat-shaped and explicitly excluded, or it would end the window + on the first chunk. """ - clean = strip_color_codes(message) - stripped = clean.strip() - if not stripped or stripped[:1] in ('{', '['): + clean = strip_color_codes(message).strip() + if not clean: return False - if 'num score ping' in clean or '--- -----' in clean: + if clean[:1] in ('{', '['): return True - if stripped.startswith('map:'): + if clean.startswith('zmq'): + return False + if clean.startswith('map:'): + return False + if clean.startswith('broadcast:'): + clean = clean[10:].lstrip() + if clean.startswith('print "'): + clean = clean[7:] + if clean.endswith('"'): + clean = clean[:-1] + if 'Game Initialization' in clean: return True - if STATUS_HEAD_PATTERN.search(clean): - return True - if re.match(r'^[\d.:-]+(\s+[\w.:-]+)*\s*$', stripped): + if CHAT_SHAPE_PATTERN.match(clean): return True return False @@ -519,14 +533,16 @@ def main_loop(screen, args): logger.debug('Suppressed RCON command echo') continue - # Hide status table chunks following a 'status' command + # Suppress everything after a 'status' echo until real + # content arrives (chat, JSON, game init) or the window closes if status_suppress_until: - if time.time() < status_suppress_until: - if is_status_chunk(message): - logger.debug('Suppressed status output chunk') - continue - else: + if time.time() >= status_suppress_until: status_suppress_until = 0.0 + elif is_real_content(message): + status_suppress_until = 0.0 + else: + logger.debug('Suppressed status output chunk') + continue if 'Game Initialization' in message: logger.info('Game initialization detected - refreshing server info')