From 51653ab31410379e00b5a01f180fc0f67bcfc67f Mon Sep 17 00:00:00 2001 From: Celio Lozatto Date: Tue, 1 Sep 2026 20:00:42 -0300 Subject: [PATCH 1/4] feat: Add Garbage Collector to prevent ED_Alloc crashes --- regamedll/dlls/client.cpp | 58 +++++++++++++++++++++++++++++++++++++++ regamedll/dlls/game.cpp | 2 ++ regamedll/dlls/game.h | 1 + 3 files changed, 61 insertions(+) diff --git a/regamedll/dlls/client.cpp b/regamedll/dlls/client.cpp index daee11d12..db4597b64 100644 --- a/regamedll/dlls/client.cpp +++ b/regamedll/dlls/client.cpp @@ -1,4 +1,6 @@ #include "precompiled.h" +#include "weapons.h" +#include "gamerules.h" int gmsgWeapPickup = 0; int gmsgHudText = 0; @@ -3924,6 +3926,60 @@ void EXT_FUNC ParmsChangeLevel() } } +extern cvar_t entity_gc; + +void CollectGarbage() +{ + if (entity_gc.value <= 0.0f) + return; + + // Check if we are near the max entities limit + if (NUMBER_OF_ENTITIES() < (gpGlobals->maxEntities - ENTITY_INTOLERANCE)) + return; + + int count = 0; + // We iterate from oldest (lowest index) to newest + for (int i = 1; i < gpGlobals->maxEntities; i++) + { + edict_t *pEdict = INDEXENT(i); + if (!pEdict || pEdict->free) + continue; + + CBaseEntity *pEntity = CBaseEntity::Instance(pEdict); + if (!pEntity) + continue; + + if (FClassnameIs(pEdict, "weaponbox")) + { + CWeaponBox *pBox = (CWeaponBox *)pEntity; + if (pBox->m_bIsBomb) + continue; // DO NOT remove the C4! + + UTIL_Remove(pEntity); + count++; + } + else if (FClassnameIs(pEdict, "weapon_shield")) + { + UTIL_Remove(pEntity); + count++; + } + else if (FClassnameIs(pEdict, "gib")) + { + UTIL_Remove(pEntity); + count++; + } + + // Stop if we cleared enough entities (e.g., 50 entities) to avoid lagging the server by doing too much at once + if (count >= 50) + break; + } + + if (count > 0) + { + ALERT(at_console, "Garbage Collector: Removed %d old entities to prevent server crash.\n", count); + } +} + void EXT_FUNC StartFrame() { if (g_pGameRules) @@ -3933,6 +3989,8 @@ void EXT_FUNC StartFrame() return; } + CollectGarbage(); + CLocalNav::Think(); gpGlobals->teamplay = 1.0f; diff --git a/regamedll/dlls/game.cpp b/regamedll/dlls/game.cpp index 7f3e8102f..6fa532f2e 100644 --- a/regamedll/dlls/game.cpp +++ b/regamedll/dlls/game.cpp @@ -22,6 +22,7 @@ cvar_t fragsleft = { "mp_fragsleft", "0", FCVAR_SERVER | FCVAR_UNLOG cvar_t timeleft = { "mp_timeleft", "0", FCVAR_SERVER | FCVAR_UNLOGGED, 0.0f, nullptr }; cvar_t friendlyfire = { "mp_friendlyfire", "0", FCVAR_SERVER, 0.0f, nullptr }; +cvar_t entity_gc = { "mp_entity_gc", "1", 0, 0.0f, nullptr }; cvar_t infiniteAmmo = { "mp_infinite_ammo", "0", FCVAR_SERVER, 0.0f, nullptr }; cvar_t infiniteGrenades = { "mp_infinite_grenades", "0", FCVAR_SERVER, 0.0f, nullptr }; cvar_t allowmonsters = { "mp_allowmonsters", "0", FCVAR_SERVER, 0.0f, nullptr }; @@ -279,6 +280,7 @@ void EXT_FUNC GameDLLInit() CVAR_REGISTER(&displaysoundlist); CVAR_REGISTER(&timelimit); CVAR_REGISTER(&friendlyfire); + CVAR_REGISTER(&entity_gc); #ifdef BUILD_LATEST CVAR_REGISTER(&infiniteAmmo); diff --git a/regamedll/dlls/game.h b/regamedll/dlls/game.h index 32e6ae015..8af25a450 100644 --- a/regamedll/dlls/game.h +++ b/regamedll/dlls/game.h @@ -60,6 +60,7 @@ extern cvar_t fadetoblack; extern cvar_t fragsleft; extern cvar_t timeleft; extern cvar_t friendlyfire; +extern cvar_t entity_gc; extern cvar_t infiniteAmmo; extern cvar_t infiniteGrenades; extern cvar_t allowmonsters; From 48795f9577def0c510449ea9f7a5b55a1965e57f Mon Sep 17 00:00:00 2001 From: Celio Lozatto Date: Tue, 1 Sep 2026 20:09:26 -0300 Subject: [PATCH 2/4] feat: Native HUD Messages Queue to prevent overflow & overlapping --- regamedll/dlls/API/CAPI_Impl.cpp | 14 ++++ regamedll/dlls/API/CAPI_Impl.h | 1 + regamedll/dlls/client.cpp | 3 +- regamedll/dlls/util.cpp | 85 +++++++++++++++++++++- regamedll/dlls/util.h | 31 ++++++++ regamedll/public/regamedll/regamedll_api.h | 1 + 6 files changed, 133 insertions(+), 2 deletions(-) diff --git a/regamedll/dlls/API/CAPI_Impl.cpp b/regamedll/dlls/API/CAPI_Impl.cpp index 398c8a136..a9b52472e 100644 --- a/regamedll/dlls/API/CAPI_Impl.cpp +++ b/regamedll/dlls/API/CAPI_Impl.cpp @@ -402,4 +402,18 @@ bool CReGameApi::BGetIGameRules(const char *pchVersion) const return false; } +void CReGameApi::QueueHudMessage(int client, const struct hudtextparms_s &textparms, const char *pMessage) +{ + if (client == 0) + { + UTIL_HudMessageAll(textparms, pMessage); + } + else + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex(client); + if (pPlayer) + UTIL_HudMessage(pPlayer, textparms, pMessage); + } +} + EXPOSE_SINGLE_INTERFACE(CReGameApi, IReGameApi, VRE_GAMEDLL_API_VERSION); diff --git a/regamedll/dlls/API/CAPI_Impl.h b/regamedll/dlls/API/CAPI_Impl.h index 4bb154a9a..855097c6b 100644 --- a/regamedll/dlls/API/CAPI_Impl.h +++ b/regamedll/dlls/API/CAPI_Impl.h @@ -1116,4 +1116,5 @@ class CReGameApi: public IReGameApi { EXT_FUNC virtual AmmoInfoStruct *GetAmmoInfoEx(const char *ammoName); EXT_FUNC virtual bool BGetICSEntity(const char *pchVersion) const; EXT_FUNC virtual bool BGetIGameRules(const char *pchVersion) const; + EXT_FUNC virtual void QueueHudMessage(int client, const struct hudtextparms_s &textparms, const char *pMessage); }; diff --git a/regamedll/dlls/client.cpp b/regamedll/dlls/client.cpp index daee11d12..ec1a0829e 100644 --- a/regamedll/dlls/client.cpp +++ b/regamedll/dlls/client.cpp @@ -3910,7 +3910,7 @@ void EXT_FUNC PlayerPostThink(edict_t *pEntity) void EXT_FUNC ParmsNewLevel() { - ; + g_HudQueue.Reset(); } void EXT_FUNC ParmsChangeLevel() @@ -3933,6 +3933,7 @@ void EXT_FUNC StartFrame() return; } + g_HudQueue.Think(); CLocalNav::Think(); gpGlobals->teamplay = 1.0f; diff --git a/regamedll/dlls/util.cpp b/regamedll/dlls/util.cpp index e0ffed532..070c01154 100644 --- a/regamedll/dlls/util.cpp +++ b/regamedll/dlls/util.cpp @@ -592,7 +592,82 @@ void UTIL_ScreenFade(CBaseEntity *pEntity, const Vector &color, float fadeTime, UTIL_ScreenFadeWrite(fade, pEntity); } -void UTIL_HudMessage(CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage) +CHudMessageQueue g_HudQueue; + +CHudMessageQueue::CHudMessageQueue() { + Reset(); +} + +void CHudMessageQueue::Reset() { + for (int i = 0; i < 33; i++) { + for (int j = 0; j < NUM_HUD_CHANNELS; j++) { + m_players[i].channelFreeTime[j] = 0.0f; + } + for (int j = 0; j < MAX_HUD_QUEUE; j++) { + m_players[i].messages[j].inUse = false; + } + } +} + +void CHudMessageQueue::QueueMessage(int client, const hudtextparms_t &textparms, const char *pMessage) { + if (client < 1 || client >= 33) return; + + int freeSlot = -1; + for (int i = 0; i < MAX_HUD_QUEUE; i++) { + if (!m_players[client].messages[i].inUse) { + freeSlot = i; + break; + } + } + + if (freeSlot == -1) { + return; + } + + m_players[client].messages[freeSlot].textparms = textparms; + Q_strlcpy(m_players[client].messages[freeSlot].message, pMessage, sizeof(m_players[client].messages[freeSlot].message)); + m_players[client].messages[freeSlot].inUse = true; +} + +void CHudMessageQueue::Think() { + float currentTime = gpGlobals->time; + + for (int i = 1; i <= gpGlobals->maxClients; i++) { + CBaseEntity *pPlayer = UTIL_PlayerByIndex(i); + if (!pPlayer || !pPlayer->IsNetClient()) + continue; + + for (int j = 0; j < MAX_HUD_QUEUE; j++) { + if (!m_players[i].messages[j].inUse) + continue; + + hudtextparms_t parms = m_players[i].messages[j].textparms; + + int targetChannel = parms.channel - 1; + if (parms.channel <= 0 || parms.channel > NUM_HUD_CHANNELS) { + int bestChannel = 0; + float oldestTime = m_players[i].channelFreeTime[0]; + for (int c = 1; c < NUM_HUD_CHANNELS; c++) { + if (m_players[i].channelFreeTime[c] < oldestTime) { + oldestTime = m_players[i].channelFreeTime[c]; + bestChannel = c; + } + } + targetChannel = bestChannel; + parms.channel = bestChannel + 1; + } + + if (currentTime >= m_players[i].channelFreeTime[targetChannel]) { + UTIL_SendHudMessage(pPlayer, parms, m_players[i].messages[j].message); + m_players[i].channelFreeTime[targetChannel] = currentTime + parms.fadeinTime + parms.holdTime + parms.fadeoutTime + 0.2f; + + m_players[i].messages[j].inUse = false; + } + } + } +} + +void UTIL_SendHudMessage(CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage) { if (!pEntity || !pEntity->IsNetClient()) return; @@ -636,6 +711,14 @@ void UTIL_HudMessage(CBaseEntity *pEntity, const hudtextparms_t &textparms, cons MESSAGE_END(); } +void UTIL_HudMessage(CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage) +{ + if (!pEntity || !pEntity->IsNetClient()) + return; + + g_HudQueue.QueueMessage(pEntity->entindex(), textparms, pMessage); +} + void UTIL_HudMessageAll(const hudtextparms_t &textparms, const char *pMessage) { for (int i = 1; i <= gpGlobals->maxClients; i++) diff --git a/regamedll/dlls/util.h b/regamedll/dlls/util.h index f96be669b..a96c791b2 100644 --- a/regamedll/dlls/util.h +++ b/regamedll/dlls/util.h @@ -227,8 +227,39 @@ void UTIL_ScreenFadeBuild(ScreenFade &fade, const Vector &color, float fadeTime, void UTIL_ScreenFadeWrite(const ScreenFade &fade, CBaseEntity *pEntity); void UTIL_ScreenFadeAll(const Vector &color, float fadeTime, float fadeHold, int alpha, int flags); void UTIL_ScreenFade(CBaseEntity *pEntity, const Vector &color, float fadeTime, float fadeHold = 0.0f, int alpha = 0, int flags = 0); +// ---------------------------------------------------- +// Native HUD Messages Queue +// ---------------------------------------------------- +#define MAX_HUD_QUEUE 10 +#define NUM_HUD_CHANNELS 4 + +struct QueuedHudMessage_t { + hudtextparms_t textparms; + char message[512]; + bool inUse; +}; + +class CHudMessageQueue { +public: + CHudMessageQueue(); + + void QueueMessage(int client, const hudtextparms_t &textparms, const char *pMessage); + void Think(); + void Reset(); + +private: + struct PlayerHudQueue { + QueuedHudMessage_t messages[MAX_HUD_QUEUE]; + float channelFreeTime[NUM_HUD_CHANNELS]; + }; + PlayerHudQueue m_players[33]; +}; + +extern CHudMessageQueue g_HudQueue; + void UTIL_HudMessage(CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage); void UTIL_HudMessageAll(const hudtextparms_t &textparms, const char *pMessage); +void UTIL_SendHudMessage(CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage); void UTIL_ClientPrintAll(int msg_dest, const char *msg_name, const char *param1 = nullptr, const char *param2 = nullptr, const char *param3 = nullptr, const char *param4 = nullptr); void ClientPrint(entvars_t *client, int msg_dest, const char *msg_name, const char *param1 = nullptr, const char *param2 = nullptr, const char *param3 = nullptr, const char *param4 = nullptr); void UTIL_Log(const char *fmt, ...); diff --git a/regamedll/public/regamedll/regamedll_api.h b/regamedll/public/regamedll/regamedll_api.h index 466ec4b42..2cbb06a3d 100644 --- a/regamedll/public/regamedll/regamedll_api.h +++ b/regamedll/public/regamedll/regamedll_api.h @@ -856,6 +856,7 @@ class IReGameApi { virtual struct AmmoInfoStruct *GetAmmoInfoEx(const char *ammoName) = 0; virtual bool BGetICSEntity(const char *pchVersion) const = 0; virtual bool BGetIGameRules(const char *pchVersion) const = 0; + virtual void QueueHudMessage(int client, const struct hudtextparms_s &textparms, const char *pMessage) = 0; }; #define VRE_GAMEDLL_API_VERSION "VRE_GAMEDLL_API_VERSION001" From 92aef80fc8abd40c568627a77f9e9b834048a132 Mon Sep 17 00:00:00 2001 From: Celio Lozatto Date: Tue, 1 Sep 2026 20:18:55 -0300 Subject: [PATCH 3/4] feat: implement HUD message queue system to manage prioritized UI notifications --- regamedll/dlls/hud_queue.cpp | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 regamedll/dlls/hud_queue.cpp diff --git a/regamedll/dlls/hud_queue.cpp b/regamedll/dlls/hud_queue.cpp new file mode 100644 index 000000000..e69de29bb From ff5632db2cd815617af97ce4e59f93f87e2e896e Mon Sep 17 00:00:00 2001 From: Celio Lozatto Date: Tue, 1 Sep 2026 20:32:03 -0300 Subject: [PATCH 4/4] fix: Restore UTIL_HudMessage to fix CPlayingEngExtInterceptor unit test --- regamedll/dlls/API/CAPI_Impl.cpp | 9 ++-- regamedll/dlls/util.cpp | 82 ++++++++++++++++---------------- regamedll/dlls/util.h | 1 - 3 files changed, 45 insertions(+), 47 deletions(-) diff --git a/regamedll/dlls/API/CAPI_Impl.cpp b/regamedll/dlls/API/CAPI_Impl.cpp index a9b52472e..7708d1f83 100644 --- a/regamedll/dlls/API/CAPI_Impl.cpp +++ b/regamedll/dlls/API/CAPI_Impl.cpp @@ -406,13 +406,14 @@ void CReGameApi::QueueHudMessage(int client, const struct hudtextparms_s &textpa { if (client == 0) { - UTIL_HudMessageAll(textparms, pMessage); + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + g_HudQueue.QueueMessage(i, textparms, pMessage); + } } else { - CBaseEntity *pPlayer = UTIL_PlayerByIndex(client); - if (pPlayer) - UTIL_HudMessage(pPlayer, textparms, pMessage); + g_HudQueue.QueueMessage(client, textparms, pMessage); } } diff --git a/regamedll/dlls/util.cpp b/regamedll/dlls/util.cpp index 070c01154..36c8d442f 100644 --- a/regamedll/dlls/util.cpp +++ b/regamedll/dlls/util.cpp @@ -658,7 +658,7 @@ void CHudMessageQueue::Think() { } if (currentTime >= m_players[i].channelFreeTime[targetChannel]) { - UTIL_SendHudMessage(pPlayer, parms, m_players[i].messages[j].message); + UTIL_HudMessage(pPlayer, parms, m_players[i].messages[j].message); m_players[i].channelFreeTime[targetChannel] = currentTime + parms.fadeinTime + parms.holdTime + parms.fadeoutTime + 0.2f; m_players[i].messages[j].inUse = false; @@ -667,58 +667,56 @@ void CHudMessageQueue::Think() { } } -void UTIL_SendHudMessage(CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage) +void UTIL_HudMessage(CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage) { if (!pEntity || !pEntity->IsNetClient()) return; MESSAGE_BEGIN(MSG_ONE, SVC_TEMPENTITY, nullptr, pEntity->edict()); - WRITE_BYTE(TE_TEXTMESSAGE); - WRITE_BYTE(textparms.channel & 0xFF); - WRITE_SHORT(FixedSigned16(textparms.x, (1<<13))); - WRITE_SHORT(FixedSigned16(textparms.y, (1<<13))); - WRITE_BYTE(textparms.effect); - WRITE_BYTE(textparms.r1); - WRITE_BYTE(textparms.g1); - WRITE_BYTE(textparms.b1); - WRITE_BYTE(textparms.a1); - WRITE_BYTE(textparms.r2); - WRITE_BYTE(textparms.g2); - WRITE_BYTE(textparms.b2); - WRITE_BYTE(textparms.a2); - WRITE_SHORT(FixedUnsigned16(textparms.fadeinTime, (1<<8))); - WRITE_SHORT(FixedUnsigned16(textparms.fadeoutTime, (1<<8))); - WRITE_SHORT(FixedUnsigned16(textparms.holdTime, (1<<8))); - - if (textparms.effect == 2) - WRITE_SHORT(FixedUnsigned16(textparms.fxTime, (1<<8))); - - if (!pMessage) - WRITE_STRING(" "); + WRITE_BYTE(TE_TEXTMESSAGE); + WRITE_BYTE(textparms.channel & 0xFF); + + WRITE_SHORT(FixedSigned16(textparms.x, 1 << 13)); + WRITE_SHORT(FixedSigned16(textparms.y, 1 << 13)); + WRITE_BYTE(textparms.effect); + + WRITE_BYTE(textparms.r1); + WRITE_BYTE(textparms.g1); + WRITE_BYTE(textparms.b1); + WRITE_BYTE(textparms.a1); + + WRITE_BYTE(textparms.r2); + WRITE_BYTE(textparms.g2); + WRITE_BYTE(textparms.b2); + WRITE_BYTE(textparms.a2); + + WRITE_SHORT(FixedUnsigned16(textparms.fadeinTime, 1 << 8)); + WRITE_SHORT(FixedUnsigned16(textparms.fadeoutTime, 1 << 8)); + WRITE_SHORT(FixedUnsigned16(textparms.holdTime, 1 << 8)); + + if (textparms.effect == 2) + WRITE_SHORT(FixedUnsigned16(textparms.fxTime, 1 << 8)); + + if (!pMessage) + { + WRITE_STRING(" "); + } + else + { + if (Q_strlen(pMessage) >= 512) + { + char tmp[512]; + Q_strlcpy(tmp, pMessage); + WRITE_STRING(tmp); + } else { - if (Q_strlen(pMessage) >= 512) - { - char tmp[512]; - Q_strlcpy(tmp, pMessage); - WRITE_STRING(tmp); - } - else - { - WRITE_STRING(pMessage); - } + WRITE_STRING(pMessage); } + } MESSAGE_END(); } -void UTIL_HudMessage(CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage) -{ - if (!pEntity || !pEntity->IsNetClient()) - return; - - g_HudQueue.QueueMessage(pEntity->entindex(), textparms, pMessage); -} - void UTIL_HudMessageAll(const hudtextparms_t &textparms, const char *pMessage) { for (int i = 1; i <= gpGlobals->maxClients; i++) diff --git a/regamedll/dlls/util.h b/regamedll/dlls/util.h index a96c791b2..6b55681c7 100644 --- a/regamedll/dlls/util.h +++ b/regamedll/dlls/util.h @@ -259,7 +259,6 @@ extern CHudMessageQueue g_HudQueue; void UTIL_HudMessage(CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage); void UTIL_HudMessageAll(const hudtextparms_t &textparms, const char *pMessage); -void UTIL_SendHudMessage(CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage); void UTIL_ClientPrintAll(int msg_dest, const char *msg_name, const char *param1 = nullptr, const char *param2 = nullptr, const char *param3 = nullptr, const char *param4 = nullptr); void ClientPrint(entvars_t *client, int msg_dest, const char *msg_name, const char *param1 = nullptr, const char *param2 = nullptr, const char *param3 = nullptr, const char *param4 = nullptr); void UTIL_Log(const char *fmt, ...);