Skip to content

Commit

Permalink
set edict limits per client
Browse files Browse the repository at this point in the history
This is done by detecting which version of the game a player is using (possible by querying client cvars). sv_max_client_edicts is still needed engine-side and should be set to the lowest expected max client edicts value (1365 for steam_legacy HL). A warning message is sent to steam_legacy clients when an object/effect was made invisible due to the lower edict limit on steam_legacy.

Also added detection for HLBugFixed and added/fixed some network message utils.
  • Loading branch information
wootguy committed Nov 5, 2024
1 parent a1d1c69 commit 73a61b4
Show file tree
Hide file tree
Showing 23 changed files with 593 additions and 675 deletions.
4 changes: 4 additions & 0 deletions dlls/CBaseEntity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -990,4 +990,8 @@ bool CBaseEntity::CanReach(CBaseEntity* toucher) {

bool CBaseEntity::IsVisibleTo(edict_t* player) {
return m_visiblePlayers & PLRBIT(player);
}

bool CBaseEntity::IsAudibleTo(edict_t* player) {
return m_audiblePlayers & PLRBIT(player);
}
78 changes: 78 additions & 0 deletions dlls/CBasePlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5610,4 +5610,82 @@ int CBasePlayer::GetNameColor() {
}

return DEFAULT_TEAM_COLOR;
}

void CBasePlayer::QueryClientType() {
if (IsBot()) {
m_clientEngineVersion = CLIENT_ENGINE_BOT;
m_clientModVersion = CLIENT_MOD_BOT;
return;
}

// first check for custom clients, next step depends on the output of this
g_engfuncs.pfnQueryClientCvarValue2(edict(), "aghl_version", 0); // BugfixedHL cvar
}

void CBasePlayer::HandleClientCvarResponse(int requestID, const char* pszCvarName, const char* pszValue) {
if (requestID == 0) {
bool hasCvar = strstr(pszValue, "Bad CVAR request") == 0;
if (hasCvar) {
m_clientModVersion = CLIENT_MOD_HLBUGFIXED;
m_clientModVersionString = ALLOC_STRING((std::string("HLBugFixed ") + pszValue).c_str());
}
else {
// could also be using an unknown custom client, no way to know...
m_clientModVersion = CLIENT_MOD_HL;
m_clientModVersionString = MAKE_STRING("Half-Life");
}

// sv_allow_shaders was added to HL 25, so if it's missing, then it must be the legacy client
g_engfuncs.pfnQueryClientCvarValue2(edict(), "sv_allow_shaders", 1);
}
else if (requestID == 1) {
bool hasCvar = strstr(pszValue, "Bad CVAR request") == 0;
m_clientEngineVersion = hasCvar ? CLIENT_ENGINE_HL_LATEST : CLIENT_ENGINE_HL_LEGACY;

UTIL_LogPlayerEvent(edict(), "Client version: %s\n", GetClientVersionString());
}
}

int CBasePlayer::GetMaxClientEdicts() {
// return the default from steam
// this value can be overridden in liblist.gam but there's no way to know if the client did that
// and they will get kicked if this value is overestimated

switch (m_clientEngineVersion) {
case CLIENT_ENGINE_HL_LATEST:
return MAX_CLIENT_ENTS;
case CLIENT_ENGINE_HL_LEGACY:
default: // better safe than sorry
return MAX_LEGACY_CLIENT_ENTS;
}
}

void CBasePlayer::SendLegacyClientWarning() {
if (m_sentClientWarning || m_clientEngineVersion != CLIENT_ENGINE_HL_LEGACY) {
return;
}

edict_t* e = edict();
m_sentClientWarning = true;
UTIL_ClientPrint(e, print_chat, "[info] This map does not function properly with steam_legacy clients. Check your console for more information.\n");

UTIL_ClientPrint(e, print_console, "\n-------------------------------------------------------------------------\n");
UTIL_ClientPrint(e, print_console, "This mod is not 100% compatible with the \"Pre-25th Anniversary Build\" of Half-Life.\n");
UTIL_ClientPrint(e, print_console, "Some objects and effects have been made invisible to you so that you aren't kicked.\n");
UTIL_ClientPrint(e, print_console, "To fix this, either set \"Beta Participation\" to \"None\" in Steam, or add \"edicts 2048\"\n");
UTIL_ClientPrint(e, print_console, "to your liblist.gam file. Editing liblist.gam should fix the problem, but won't remove\n");
UTIL_ClientPrint(e, print_console, "this message (the server can't know if you've made that edit or not).\n");
UTIL_ClientPrint(e, print_console, "-------------------------------------------------------------------------\n\n");

UTIL_LogPlayerEvent(e, "was sent the steam_legacy client warning\n");
}

const char* CBasePlayer::GetClientVersionString() {
const char* engineVersion = "";

if (m_clientEngineVersion == CLIENT_ENGINE_HL_LEGACY)
engineVersion = " (steam_legacy)";

return UTIL_VarArgs("%s%s", STRING(m_clientModVersionString), engineVersion);
}
34 changes: 34 additions & 0 deletions dlls/CBasePlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,23 @@ enum sbar_data
SBAR_END,
};

#define MAX_CLIENT_ENTS 1665 // default for the latest HL client from steam
#define MAX_LEGACY_CLIENT_ENTS 1365 // default when using the steam_legacy beta

enum HL_CLIENT_ENGINE_VERSION {
CLIENT_ENGINE_NOT_CHECKED, // player hasn't responded to cvar queries yet
CLIENT_ENGINE_HL_LATEST, // the latest version of the steam HL client from steam
CLIENT_ENGINE_HL_LEGACY, // the legacy version of HL from steam
CLIENT_ENGINE_BOT, // bot's don't use a client
};

enum HL_CLIENT_MOD_VERSION {
CLIENT_MOD_NOT_CHECKED, // player hasn't responded to cvar queries yet
CLIENT_MOD_HL, // the vanilla half-life mod from steam (or an undetected custom client)
CLIENT_MOD_HLBUGFIXED, // a popular custom client (for cheating!!! but also cool stuff...)
CLIENT_MOD_BOT, // bot's don't use mods
};

#define CHAT_INTERVAL 1.0f

class EXPORT CBasePlayer : public CBaseMonster
Expand Down Expand Up @@ -384,7 +401,24 @@ class EXPORT CBasePlayer : public CBaseMonster

float m_initSoundTime;

HL_CLIENT_ENGINE_VERSION m_clientEngineVersion; // which game engine is the is this player using?
HL_CLIENT_MOD_VERSION m_clientModVersion; // which mod is this player using?
string_t m_clientModVersionString; // version string for the client mod
bool m_sentClientWarning; // has this client been warned about their client incompatability?

int GetNameColor();

// checks client cvars to determine which engine and mod is being used. Called when the player first enters the server.
void QueryClientType();

void HandleClientCvarResponse(int requestID, const char* pszCvarName, const char* pszValue);

// get the default edict count for the player's client, to avoid sending invalid indexes
int GetMaxClientEdicts();

void SendLegacyClientWarning();

const char* GetClientVersionString();

// for sven-style monster info
//void UpdateMonsterInfo();
Expand Down
2 changes: 1 addition & 1 deletion dlls/cbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ gamedll_funcs_t* gpGamedllFuncs = &GameDllFuncs;

extern "C" {

int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion )
int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion )
{
if ( !pFunctionTable || interfaceVersion != INTERFACE_VERSION )
{
Expand Down
4 changes: 4 additions & 0 deletions dlls/cbase.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ EXPORT extern gamedll_funcs_t* gpGamedllFuncs; // for ease of porting to/from me

extern "C" DLLEXPORT int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion );
extern "C" DLLEXPORT int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion );
extern "C" DLLEXPORT int GetNewDLLFunctions(NEW_DLL_FUNCTIONS * pNewFunctionTable, int* interfaceVersion);

EXPORT extern int DispatchSpawn( edict_t *pent );
EXPORT extern void DispatchKeyValue( edict_t *pentKeyvalue, KeyValueData *pkvd );
Expand Down Expand Up @@ -390,6 +391,9 @@ class EXPORT CBaseEntity
// true if the player can potentially see this entity
bool IsVisibleTo(edict_t* player);

// true if the player can potentially hear this entity
bool IsAudibleTo(edict_t* player);

//We use this variables to store each ammo count.
int ammo_9mm;
int ammo_357;
Expand Down
25 changes: 18 additions & 7 deletions dlls/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,17 @@ void ClientKill( edict_t *pEntity )
// respawn( pev );
}


void CvarValue2(const edict_t* pEnt, int requestID, const char* pszCvarName, const char* pszValue) {
CBasePlayer* plr = UTIL_PlayerByIndex(ENTINDEX(pEnt));

if (!plr || strstr(pszValue, "Bad Player")) {
return;
}

plr->HandleClientCvarResponse(requestID, pszCvarName, pszValue);
}

/*
===========
ClientPutInServer
Expand Down Expand Up @@ -285,6 +296,8 @@ void ClientPutInServer( edict_t *pEntity )
// Reset interpolation during first frame
pPlayer->pev->effects |= EF_NOINTERP;

pPlayer->QueryClientType();

CALL_HOOKS_VOID(pfnClientPutInServer, pPlayer);

// Allocate a CBasePlayer for pev, and call spawn
Expand Down Expand Up @@ -1049,8 +1062,8 @@ void SetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char **pv

int pnum = g_packClientIdx - 1;
if (g_numEdictOverflows[pnum] > 0) {
ALERT(at_console, "Overflowed %d edicts for client %s\n",
g_numEdictOverflows[pnum], STRING(pClient->v.netname));
ALERT(at_console, "Overflowed %d edicts for \"%s\", Client: %s\n",
g_numEdictOverflows[pnum], STRING(pClient->v.netname), plr->GetClientVersionString());
}

g_numEdictOverflows[pnum] = 0;
Expand Down Expand Up @@ -1270,11 +1283,10 @@ int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *h

}

// prevent disconnects with this error:
// Host_Error: CL_EntityNum: 1665 is an invalid number, cl.max_edicts is 1665
if (sv_max_client_edicts && e >= (int)sv_max_client_edicts->value) {
ALERT(at_console, "Can't send edict %d '%s' (index too high)\n", e, STRING(ent->v.classname));
if (e >= plr->GetMaxClientEdicts()) {
//ALERT(at_console, "Can't send edict %d '%s' (index too high)\n", e, STRING(ent->v.classname));
g_numEdictOverflows[player]++;
plr->SendLegacyClientWarning();
return 0;
}

Expand Down Expand Up @@ -1957,4 +1969,3 @@ int ShouldCollide(edict_t* pentTouched, edict_t* pentOther) {

void CvarValue(const edict_t* pEnt, const char* pszValue) {}

void CvarValue2(const edict_t* pEnt, int requestID, const char* pszCvarName, const char* pszValue) {}
9 changes: 1 addition & 8 deletions dlls/env/CBubbling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,7 @@ void CBubbling::KeyValue(KeyValueData* pkvd)

void CBubbling::FizzThink(void)
{
if (UTIL_isSafeEntIndex(ENTINDEX(edict()), "create TE_FIZZ")) {
MESSAGE_BEGIN(MSG_PAS, SVC_TEMPENTITY, VecBModelOrigin(pev));
WRITE_BYTE(TE_FIZZ);
WRITE_SHORT((short)ENTINDEX(edict()));
WRITE_SHORT((short)m_bubbleModel);
WRITE_BYTE(m_density);
MESSAGE_END();
}
UTIL_Fizz(entindex(), m_bubbleModel, m_density, MSG_PAS, VecBModelOrigin(pev));

if (m_frequency > 19)
pev->nextthink = gpGlobals->time + 0.5;
Expand Down
23 changes: 8 additions & 15 deletions dlls/env/CDecal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "util.h"
#include "cbase.h"
#include "decals.h"
#include "CBasePlayer.h"

//
// This must match the list in util.h
Expand Down Expand Up @@ -107,23 +108,10 @@ void CDecal::TriggerDecal(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYP
// this is set up as a USE function for infodecals that have targetnames, so that the
// decal doesn't get applied until it is fired. (usually by a scripted sequence)
TraceResult trace;
int entityIndex;

UTIL_TraceLine(pev->origin - Vector(5, 5, 5), pev->origin + Vector(5, 5, 5), ignore_monsters, ENT(pev), &trace);

if (UTIL_isSafeEntIndex(ENTINDEX(trace.pHit), "apply decal")) {
MESSAGE_BEGIN(MSG_BROADCAST, SVC_TEMPENTITY);
WRITE_BYTE(TE_BSPDECAL);
WRITE_COORD(pev->origin.x);
WRITE_COORD(pev->origin.y);
WRITE_COORD(pev->origin.z);
WRITE_SHORT((int)pev->skin);
entityIndex = (short)ENTINDEX(trace.pHit);
WRITE_SHORT(entityIndex);
if (entityIndex)
WRITE_SHORT((int)VARS(trace.pHit)->modelindex);
MESSAGE_END();
}
UTIL_BSPDecal(ENTINDEX(trace.pHit), pev->origin, pev->skin);

SetThink(&CDecal::SUB_Remove);
pev->nextthink = gpGlobals->time + 0.1;
Expand All @@ -143,9 +131,14 @@ void CDecal::StaticDecal(void)
else
modelIndex = 0;

if (UTIL_isSafeEntIndex(entityIndex, "apply decal")) {
// TODO: selectively send decal messages when clients join instead
// of calling the engine function which does that automatically
if (entityIndex < MAX_LEGACY_CLIENT_ENTS) {
g_engfuncs.pfnStaticDecal(pev->origin, (int)pev->skin, entityIndex, modelIndex);
}
else {
ALERT(at_error, "Failed to apply static decal. Entity index too high.");
}

SUB_Remove();
}
Expand Down
54 changes: 12 additions & 42 deletions dlls/env/CLightning.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,8 @@ void CLightning::StrikeThink(void)
}
}

MESSAGE_BEGIN(MSG_BROADCAST, SVC_TEMPENTITY);
float life = m_life * 10.0f;

if (IsPointEntity(pStart) || IsPointEntity(pEnd))
{
if (!IsPointEntity(pEnd)) // One point entity must be in pEnd
Expand All @@ -338,57 +339,26 @@ void CLightning::StrikeThink(void)
}
if (!IsPointEntity(pStart)) // One sided
{
if (!UTIL_isSafeEntIndex(pStart->entindex(), "create lightning")) {
return;
}
WRITE_BYTE(TE_BEAMENTPOINT);
WRITE_SHORT(pStart->entindex());
WRITE_COORD(pEnd->pev->origin.x);
WRITE_COORD(pEnd->pev->origin.y);
WRITE_COORD(pEnd->pev->origin.z);
UTIL_BeamEntPoint(pStart->entindex(), 0, pEnd->pev->origin, m_spriteTexture,
m_frameStart, pev->framerate, life, m_boltWidth, m_noiseAmplitude,
RGBA(pev->rendercolor, pev->renderamt), m_speed);
}
else
{
WRITE_BYTE(TE_BEAMPOINTS);
WRITE_COORD(pStart->pev->origin.x);
WRITE_COORD(pStart->pev->origin.y);
WRITE_COORD(pStart->pev->origin.z);
WRITE_COORD(pEnd->pev->origin.x);
WRITE_COORD(pEnd->pev->origin.y);
WRITE_COORD(pEnd->pev->origin.z);
UTIL_BeamPoints(pStart->pev->origin, pEnd->pev->origin, m_spriteTexture,
m_frameStart, pev->framerate, life, m_boltWidth, m_noiseAmplitude,
RGBA(pev->rendercolor, pev->renderamt), m_speed);
}


}
else
{
if (!UTIL_isSafeEntIndex(pStart->entindex(), "create lightning")) {
return;
}
if (!UTIL_isSafeEntIndex(pEnd->entindex(), "create lightning")) {
return;
}
bool ringMode = pev->spawnflags & SF_BEAM_RING;

if (pev->spawnflags & SF_BEAM_RING)
WRITE_BYTE(TE_BEAMRING);
else
WRITE_BYTE(TE_BEAMENTS);
WRITE_SHORT(pStart->entindex());
WRITE_SHORT(pEnd->entindex());
UTIL_BeamEnts(pStart->entindex(), 0, pEnd->entindex(), 0, ringMode, m_spriteTexture,
m_frameStart, pev->framerate, life, m_boltWidth, m_noiseAmplitude,
RGBA(pev->rendercolor, pev->renderamt), m_speed);
}

WRITE_SHORT(m_spriteTexture);
WRITE_BYTE(m_frameStart); // framestart
WRITE_BYTE((int)pev->framerate); // framerate
WRITE_BYTE((int)(m_life * 10.0)); // life
WRITE_BYTE(m_boltWidth); // width
WRITE_BYTE(m_noiseAmplitude); // noise
WRITE_BYTE((int)pev->rendercolor.x); // r, g, b
WRITE_BYTE((int)pev->rendercolor.y); // r, g, b
WRITE_BYTE((int)pev->rendercolor.z); // r, g, b
WRITE_BYTE(pev->renderamt); // brightness
WRITE_BYTE(m_speed); // speed
MESSAGE_END();
DoSparks(pStart->pev->origin, pEnd->pev->origin);
if (pev->dmg > 0)
{
Expand Down
17 changes: 1 addition & 16 deletions dlls/monster/CApache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1143,22 +1143,7 @@ void CApacheHVR :: IgniteThink( void )
// make rocket sound
EMIT_SOUND( ENT(pev), CHAN_VOICE, "weapons/rocket1.wav", 1, 0.5 );

if (UTIL_isSafeEntIndex(entindex(), "create HVR rocket trail")) {
// rocket trail
MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY );

WRITE_BYTE( TE_BEAMFOLLOW );
WRITE_SHORT(entindex()); // entity
WRITE_SHORT(m_iTrail ); // model
WRITE_BYTE( 15 ); // life
WRITE_BYTE( 5 ); // width
WRITE_BYTE( 224 ); // r, g, b
WRITE_BYTE( 224 ); // r, g, b
WRITE_BYTE( 255 ); // r, g, b
WRITE_BYTE( 255 ); // brightness

MESSAGE_END(); // move PHS/PVS data sending into here (SEND_ALL, SEND_PVS, SEND_PHS)
}
UTIL_BeamFollow(entindex(), m_iTrail, 15, 5, RGBA(224, 224, 255, 255), MSG_BROADCAST, NULL);

// set to accelerate
SetThink( &CApacheHVR::AccelerateThink );
Expand Down
Loading

0 comments on commit 73a61b4

Please sign in to comment.