From 05c80d6351506e73103aa0ec5680de78a041f9a1 Mon Sep 17 00:00:00 2001 From: Dino <8dino2@gmail.com> Date: Sat, 23 May 2026 20:03:50 -0400 Subject: [PATCH 1/2] chore: cleanup esp_respawn_uvtime log + dead atl/etv pointers + quit_on_empty rewrite (#316 FIX-15, FIX-17, FIX-18) FIX-15: esp_respawn_uvtime clamp log said "setting to 2 seconds" but the code sets the value to 20. Fix the message; enforcement was already correct. FIX-17 (g_main.c portion): The LRCON quit_on_empty timer shared level.emptyTime with empty_rotate, which uses it as an accumulator. Result: when a player leaves a long-running server, level.emptyTime was ~FRAMETIME but level.time was large, so `level.time - emptyTime > 5.0` fired after one frame instead of waiting 5 seconds. Rewrite the quit_on_empty branch to use a new dedicated field with -1.0f sentinel. The g_local.h field add and g_spawn.c init are in the next commit. FIX-18: Commit b8b34840 (2024 "Refactored setting and determining espionage modes, removed atl/etv cvars") removed the cvar registrations and replaced them with esp_atl + EVIP detection from the .esp map file. But the global cvar pointers atl and etv were left declared/defined as dead code in g_main.c, g_local.h, and a_esp.h. They are never registered and never dereferenced. Delete them. Refs #316 --- src/action/a_esp.h | 4 ---- src/action/g_local.h | 3 +-- src/action/g_main.c | 18 +++++++++++------- src/action/g_save.c | 2 +- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/action/a_esp.h b/src/action/a_esp.h index 501e32d42..53f13cc5b 100644 --- a/src/action/a_esp.h +++ b/src/action/a_esp.h @@ -1,10 +1,6 @@ // This is set to 1 if either atl or etv are 1 extern cvar_t *esp; -// Discrete game modes -extern cvar_t *atl; -extern cvar_t *etv; - #define IS_LEADER(ent) (teams[(ent)->client->resp.team].leader == (ent)) #define HAVE_LEADER(teamNum) (teams[(teamNum)].leader) #define MAX_ESP_STRLEN 32 diff --git a/src/action/g_local.h b/src/action/g_local.h index 409384ad5..1d7bc17e3 100644 --- a/src/action/g_local.h +++ b/src/action/g_local.h @@ -976,6 +976,7 @@ typedef struct int timeoutFrames; float matchTime; float emptyTime; + float quit_empty_time; // LRCON quit_on_empty: level.time when server first emptied, or -1 if not empty. Separate from emptyTime which empty_rotate uses as an accumulator. int abandonFrames; // Countdown for abandon forfeit int weapon_sound_framenum; int pic_teamplay_timer_icon; @@ -1396,8 +1397,6 @@ extern cvar_t *medkit_value; // BEGIN AQ2 ETE extern cvar_t *esp; // Enable or disable Espionage mode -extern cvar_t *atl; // Enable or disable Assassinate the Leader mode (do not set this manually) -extern cvar_t *etv; // Enable or disable Escort the VIP mode (do not set this manually) extern cvar_t *esp_atl; // Prefer ATL mode even if ETV mode is available extern cvar_t *esp_punish; // Enable or disable punishment for losing the around extern cvar_t *esp_etv_halftime; // Enable or disable halftime in ETV mode diff --git a/src/action/g_main.c b/src/action/g_main.c index 3077f82cc..be5bc212b 100644 --- a/src/action/g_main.c +++ b/src/action/g_main.c @@ -533,8 +533,6 @@ cvar_t *jump; // jumping mod // BEGIN AQ2 ETE cvar_t *esp; -cvar_t *atl; -cvar_t *etv; cvar_t *esp_atl; cvar_t *esp_punish; cvar_t *esp_etv_halftime; @@ -1344,18 +1342,24 @@ void G_RunFrame (void) return; } - // LRCON quit_on_empty logic + // LRCON quit_on_empty logic. + // Uses its own quit_empty_time field — sharing level.emptyTime with the + // empty_rotate accumulator above caused the old comparison + // `level.time - level.emptyTime > 5.0` to fire after one frame when a + // player left a long-running server (level.time was already large but + // emptyTime had just incremented from 0). Sentinel -1.0f means "not + // currently empty" so we can distinguish from level.time == 0 at start. if (game.lrcon_config.quit_on_empty) { if (empty) { - if (level.emptyTime == 0) { - level.emptyTime = level.time; + if (level.quit_empty_time < 0) { + level.quit_empty_time = level.time; gi.dprintf("LRCON: Server empty, will quit in 5 seconds\n"); - } else if (level.time - level.emptyTime > 5.0) { + } else if (level.time - level.quit_empty_time > 5.0f) { gi.dprintf("LRCON: Quitting server (empty for 5+ seconds)\n"); gi.AddCommandString("quit\n"); } } else { - level.emptyTime = 0; + level.quit_empty_time = -1.0f; } } diff --git a/src/action/g_save.c b/src/action/g_save.c index 4d93bdd3f..d4b2a68a4 100644 --- a/src/action/g_save.c +++ b/src/action/g_save.c @@ -618,7 +618,7 @@ void InitGame( void ) esp_matchmode = gi.cvar("esp_matchmode", "0", 0); esp_respawn_uvtime = gi.cvar("esp_respawn_uvtime", "10", 0); if (esp_respawn_uvtime->value > 20) { - gi.dprintf("esp_respawn_uvtime was set too high, setting to 2 seconds\n"); + gi.dprintf("esp_respawn_uvtime was set too high, setting to 20 seconds\n"); gi.cvar_forceset("esp_respawn_uvtime", "20"); } esp_debug = gi.cvar("esp_debug", "0", 0); // Set to 1 to enable debug messages for Espionage From e3250e83413660b234aea49bdb4cb0b13d5b6adb Mon Sep 17 00:00:00 2001 From: Dino <8dino2@gmail.com> Date: Sat, 23 May 2026 20:05:07 -0400 Subject: [PATCH 2/2] fix(server,lrcon): SV_BotInit call + quit_empty_time sentinel init (#316 FIX-16, FIX-17 part 2) FIX-16: SV_BotInit was declared and exported in server.h but never called. bot_clients[] is zero-initialized at process start via BSS so cold-start works, but SV_InitGame can be called on restart/map-change where in-progress bot state could otherwise persist. Add the call in SV_InitGame after game DLL init. FIX-17 (final): Initialize level.quit_empty_time to -1.0f at SpawnEntities time. memset zero-fills the level struct but 0 is a valid level.time at start of map, so we need the explicit sentinel. Refs #316 --- src/action/g_spawn.c | 4 ++++ src/server/init.c | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/action/g_spawn.c b/src/action/g_spawn.c index 804326367..94683a5d9 100644 --- a/src/action/g_spawn.c +++ b/src/action/g_spawn.c @@ -1580,6 +1580,10 @@ void SpawnEntities (const char *mapname, const char *entities, const char *spawn memset(&level, 0, sizeof (level)); memset(g_edicts, 0, game.maxentities * sizeof (g_edicts[0])); + // quit_empty_time uses -1 as the "not currently empty" sentinel (0 is a + // valid level.time at start of map). + level.quit_empty_time = -1.0f; + Q_strncpyz(level.mapname, mapname, sizeof(level.mapname)); Q_strncpyz(game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)); diff --git a/src/server/init.c b/src/server/init.c index 818145ee3..432d64593 100644 --- a/src/server/init.c +++ b/src/server/init.c @@ -481,6 +481,11 @@ void SV_InitGame(unsigned mvd_spawn) SV_MvdPostInit(); } + // Reset bot client slots on game (re)initialization. Cold-start gets + // zeroed bot_clients[] via BSS, but SV_InitGame can be called on + // restart/map-change where in-progress state could otherwise persist. + SV_BotInit(); + if (svs.csr.extended && IS_NEW_GAME_API) PmoveEnableExt(&svs.pmp);