From 7df2f8e0457ce00426f8fa414166db41939c3efd Mon Sep 17 00:00:00 2001 From: Benoist <14257866+Kenzzer@users.noreply.github.com> Date: Sat, 1 Jun 2024 16:07:55 +0200 Subject: [PATCH] Reconcile the concept of Edict & Networkable across the codebase (#1903) * Reconcile the concept of Edict & Networkable across the codebase * There's no need to check this, it's done elsewhere. Also could be null (segfault) * This was never needed * Pseudo review changes Re-added removed null checks, and added new ones. Changed the error messages in Get/SetProp natives to better reflect reality. * Don't change the behaviour of GetEntityNetClass * Overload IGameHelpers::FindServerClass * Make error messages more accurate * Fix a dev comment * Rename FindServerClass --------- Co-authored-by: Kenzzer --- core/HalfLife2.cpp | 11 +++++ core/HalfLife2.h | 1 + core/smn_entities.cpp | 52 +++++++++++------------- extensions/cstrike/natives.cpp | 9 ++-- extensions/sdkhooks/extension.cpp | 11 +++-- extensions/sdkhooks/natives.cpp | 41 ++++++++++--------- extensions/sdktools/gamerulesnatives.cpp | 27 ++++++------ extensions/sdktools/teamnatives.cpp | 18 ++++---- extensions/sdktools/vglobals.cpp | 19 ++++----- extensions/sdktools/vnatives.cpp | 14 ++++++- extensions/tf2/criticals.cpp | 36 +++++++++------- extensions/tf2/extension.cpp | 14 +++---- public/IGameHelpers.h | 9 +++- 13 files changed, 148 insertions(+), 114 deletions(-) diff --git a/core/HalfLife2.cpp b/core/HalfLife2.cpp index 7600917b40..f0e1013f75 100644 --- a/core/HalfLife2.cpp +++ b/core/HalfLife2.cpp @@ -413,6 +413,17 @@ ServerClass *CHalfLife2::FindServerClass(const char *classname) return pInfo->sc; } +ServerClass *CHalfLife2::FindEntityServerClass(CBaseEntity *pEntity) +{ + IServerNetworkable* pNetwork = ((IServerUnknown *)pEntity)->GetNetworkable(); + if (pNetwork == nullptr) + { + return nullptr; + } + + return pNetwork->GetServerClass(); +} + DataTableInfo *CHalfLife2::_FindServerClass(const char *classname) { DataTableInfo *pInfo = NULL; diff --git a/core/HalfLife2.h b/core/HalfLife2.h index b3f9b534ef..8497704a30 100644 --- a/core/HalfLife2.h +++ b/core/HalfLife2.h @@ -221,6 +221,7 @@ class CHalfLife2 : bool FindSendPropInfo(const char *classname, const char *offset, sm_sendprop_info_t *info); datamap_t *GetDataMap(CBaseEntity *pEntity); ServerClass *FindServerClass(const char *classname); + ServerClass *FindEntityServerClass(CBaseEntity *pEntity); typedescription_t *FindInDataMap(datamap_t *pMap, const char *offset); bool FindDataMapInfo(datamap_t *pMap, const char *offset, sm_datatable_info_t *pDataTable); void SetEdictStateChanged(edict_t *pEdict, unsigned short offset); diff --git a/core/smn_entities.cpp b/core/smn_entities.cpp index d2986ca437..929b4a4a8f 100644 --- a/core/smn_entities.cpp +++ b/core/smn_entities.cpp @@ -134,12 +134,10 @@ inline edict_t *BaseEntityToEdict(CBaseEntity *pEntity) { IServerUnknown *pUnk = (IServerUnknown *)pEntity; IServerNetworkable *pNet = pUnk->GetNetworkable(); - - if (!pNet) + if (pNet == nullptr) { - return NULL; + return nullptr; } - return pNet->GetEdict(); } @@ -363,14 +361,20 @@ static cell_t IsValidEntity(IPluginContext *pContext, const cell_t *params) static cell_t IsEntNetworkable(IPluginContext *pContext, const cell_t *params) { - edict_t *pEdict = GetEdict(params[1]); + IServerUnknown *pUnknown = (IServerUnknown *)g_HL2.ReferenceToEntity(params[1]); + if (!pUnknown) + { + return 0; + } - if (!pEdict) + IServerNetworkable *pNet = pUnknown->GetNetworkable(); + if (!pNet) { return 0; } - return (pEdict->GetNetworkable() != NULL) ? 1 : 0; + edict_t* edict = pNet->GetEdict(); + return (edict && !edict->IsFree()) ? 1 : 0; } static cell_t GetEntityCount(IPluginContext *pContext, const cell_t *params) @@ -436,14 +440,12 @@ static cell_t GetEntityNetClass(IPluginContext *pContext, const cell_t *params) return pContext->ThrowNativeError("Invalid entity (%d - %d)", g_HL2.ReferenceToIndex(params[1]), params[1]); } - IServerNetworkable *pNet = pUnk->GetNetworkable(); - if (!pNet) + ServerClass *pClass = g_HL2.FindEntityServerClass(pEntity); + if (!pClass) { return 0; } - ServerClass *pClass = pNet->GetServerClass(); - pContext->StringToLocal(params[2], params[3], pClass->GetName()); return 1; @@ -1270,13 +1272,11 @@ static cell_t SetEntDataString(IPluginContext *pContext, const cell_t *params) #define FIND_PROP_SEND(type, type_name) \ sm_sendprop_info_t info;\ SendProp *pProp; \ - IServerUnknown *pUnk = (IServerUnknown *)pEntity; \ - IServerNetworkable *pNet = pUnk->GetNetworkable(); \ - if (!pNet) \ - { \ - return pContext->ThrowNativeError("Edict %d (%d) is not networkable", g_HL2.ReferenceToIndex(params[1]), params[1]); \ + ServerClass *pServerClass = g_HL2.FindEntityServerClass(pEntity); \ + if (pServerClass == nullptr) { \ + pContext->ThrowNativeError("Failed to retrieve entity %d (%d) server class!", g_HL2.ReferenceToIndex(params[1]), params[1]); \ } \ - if (!g_HL2.FindSendPropInfo(pNet->GetServerClass()->GetName(), prop, &info)) \ + if (!g_HL2.FindSendPropInfo(pServerClass->GetName(), prop, &info)) \ { \ const char *class_name = g_HL2.GetEntityClassname(pEntity); \ return pContext->ThrowNativeError("Property \"%s\" not found (entity %d/%s)", \ @@ -1434,13 +1434,13 @@ static cell_t GetEntPropArraySize(IPluginContext *pContext, const cell_t *params { sm_sendprop_info_t info; - IServerUnknown *pUnk = (IServerUnknown *)pEntity; - IServerNetworkable *pNet = pUnk->GetNetworkable(); - if (!pNet) + ServerClass *pServerClass = g_HL2.FindEntityServerClass(pEntity); + if (pServerClass == nullptr) { - return pContext->ThrowNativeError("Edict %d (%d) is not networkable", g_HL2.ReferenceToIndex(params[1]), params[1]); + return pContext->ThrowNativeError("Failed to retrieve entity %d (%d) server class!", g_HL2.ReferenceToIndex(params[1]), params[1]); } - if (!g_HL2.FindSendPropInfo(pNet->GetServerClass()->GetName(), prop, &info)) + + if (!g_HL2.FindSendPropInfo(pServerClass->GetName(), prop, &info)) { const char *class_name = g_HL2.GetEntityClassname(pEntity); return pContext->ThrowNativeError("Property \"%s\" not found (entity %d/%s)", @@ -2079,13 +2079,7 @@ static cell_t SetEntPropEnt(IPluginContext *pContext, const cell_t *params) edict_t *pOtherEdict = NULL; if (pOther) { - IServerNetworkable *pNetworkable = ((IServerUnknown *) pOther)->GetNetworkable(); - if (!pNetworkable) - { - return pContext->ThrowNativeError("Entity %d (%d) does not have a valid edict", g_HL2.ReferenceToIndex(params[4]), params[4]); - } - - pOtherEdict = pNetworkable->GetEdict(); + pOtherEdict = BaseEntityToEdict(pOther); if (!pOtherEdict || pOtherEdict->IsFree()) { return pContext->ThrowNativeError("Entity %d (%d) does not have a valid edict", g_HL2.ReferenceToIndex(params[4]), params[4]); diff --git a/extensions/cstrike/natives.cpp b/extensions/cstrike/natives.cpp index a3fedb963e..aa6dd928fe 100644 --- a/extensions/cstrike/natives.cpp +++ b/extensions/cstrike/natives.cpp @@ -254,10 +254,13 @@ static cell_t CS_DropWeapon(IPluginContext *pContext, const cell_t *params) //Psychonic is awesome for this sm_sendprop_info_t spi; - IServerUnknown *pUnk = (IServerUnknown *)pWeapon; - IServerNetworkable *pNet = pUnk->GetNetworkable(); + ServerClass *pServerClass = gamehelpers->FindEntityServerClass(pWeapon); + if (pServerClass == nullptr) + { + return pContext->ThrowNativeError("Failed to retrieve entity %d server class!", params[2]); + } - if (!UTIL_FindDataTable(pNet->GetServerClass()->m_pTable, "DT_WeaponCSBase", &spi, 0)) + if (!UTIL_FindDataTable(pServerClass->m_pTable, "DT_WeaponCSBase", &spi, 0)) return pContext->ThrowNativeError("Entity index %d is not a weapon", params[2]); if (!gamehelpers->FindSendPropInfo("CBaseCombatWeapon", "m_hOwnerEntity", &spi)) diff --git a/extensions/sdkhooks/extension.cpp b/extensions/sdkhooks/extension.cpp index 2444fa6a37..3a457fb51c 100644 --- a/extensions/sdkhooks/extension.cpp +++ b/extensions/sdkhooks/extension.cpp @@ -614,11 +614,16 @@ HookReturn SDKHooks::Hook(int entity, SDKHookType type, IPluginFunction *callbac if (!!strcmp(g_HookTypes[type].dtReq, "")) { - IServerUnknown *pUnk = (IServerUnknown *)pEnt; + ServerClass *pServerClass = gamehelpers->FindEntityServerClass(pEnt); + if (pServerClass == nullptr) + { + return HookRet_BadEntForHookType; + } - IServerNetworkable *pNet = pUnk->GetNetworkable(); - if (pNet && !UTIL_ContainsDataTable(pNet->GetServerClass()->m_pTable, g_HookTypes[type].dtReq)) + if (!UTIL_ContainsDataTable(pServerClass->m_pTable, g_HookTypes[type].dtReq)) + { return HookRet_BadEntForHookType; + } } size_t entry; diff --git a/extensions/sdkhooks/natives.cpp b/extensions/sdkhooks/natives.cpp index e801747bda..526bc68622 100644 --- a/extensions/sdkhooks/natives.cpp +++ b/extensions/sdkhooks/natives.cpp @@ -60,15 +60,15 @@ cell_t Native_Hook(IPluginContext *pContext, const cell_t *params) break; case HookRet_BadEntForHookType: { - CBaseEntity *pEnt = gamehelpers->ReferenceToEntity(params[1]); - const char *pClassname = pEnt ? gamehelpers->GetEntityClassname(pEnt) : NULL; - if (!pClassname) - { - pContext->ThrowNativeError("Hook type not valid for this type of entity (%i).", entity); - } - else - { - pContext->ThrowNativeError("Hook type not valid for this type of entity (%i/%s)", entity, pClassname); + CBaseEntity *pEnt = gamehelpers->ReferenceToEntity(params[1]); + const char *pClassname = pEnt ? gamehelpers->GetEntityClassname(pEnt) : NULL; + if (!pClassname) + { + pContext->ThrowNativeError("Hook type not valid for this type of entity (%i).", entity); + } + else + { + pContext->ThrowNativeError("Hook type not valid for this type of entity (%i/%s)", entity, pClassname); } break; @@ -185,9 +185,9 @@ cell_t Native_TakeDamage(IPluginContext *pContext, const cell_t *params) if (!pCall) { int offset; - if (!g_pGameConf->GetOffset("OnTakeDamage", &offset)) - { - return pContext->ThrowNativeError("Could not find OnTakeDamage offset"); + if (!g_pGameConf->GetOffset("OnTakeDamage", &offset)) + { + return pContext->ThrowNativeError("Could not find OnTakeDamage offset"); } PassInfo pass[2]; @@ -230,10 +230,13 @@ cell_t Native_DropWeapon(IPluginContext *pContext, const cell_t *params) if (!pWeapon) return pContext->ThrowNativeError("Invalid entity index %d for weapon", params[2]); - IServerUnknown *pUnk = (IServerUnknown *)pWeapon; - IServerNetworkable *pNet = pUnk->GetNetworkable(); - - if (!UTIL_ContainsDataTable(pNet->GetServerClass()->m_pTable, "DT_BaseCombatWeapon")) + ServerClass *pClass = gamehelpers->FindEntityServerClass(pWeapon); + if (pClass == nullptr) + { + return pContext->ThrowNativeError("Failed to retrieve entity %d server class!", params[2]); + } + + if (!UTIL_ContainsDataTable(pClass->m_pTable, "DT_BaseCombatWeapon")) return pContext->ThrowNativeError("Entity index %d is not a weapon", params[2]); sm_sendprop_info_t spi; @@ -291,9 +294,9 @@ cell_t Native_DropWeapon(IPluginContext *pContext, const cell_t *params) if (!pCall) { int offset; - if (!g_pGameConf->GetOffset("Weapon_Drop", &offset)) - { - return pContext->ThrowNativeError("Could not find Weapon_Drop offset"); + if (!g_pGameConf->GetOffset("Weapon_Drop", &offset)) + { + return pContext->ThrowNativeError("Could not find Weapon_Drop offset"); } PassInfo pass[3]; diff --git a/extensions/sdktools/gamerulesnatives.cpp b/extensions/sdktools/gamerulesnatives.cpp index 07f4477a71..ca6c8728bc 100644 --- a/extensions/sdktools/gamerulesnatives.cpp +++ b/extensions/sdktools/gamerulesnatives.cpp @@ -45,26 +45,27 @@ static CBaseEntity *FindEntityByNetClass(int start, const char *classname) int maxEntities = gpGlobals->maxEntities; for (int i = start; i < maxEntities; i++) { - edict_t *current = gamehelpers->EdictOfIndex(i); - if (current == NULL || current->IsFree()) - continue; - - IServerNetworkable *network = current->GetNetworkable(); - if (network == NULL) + CBaseEntity *pEntity = gamehelpers->ReferenceToEntity(i); + if (pEntity == nullptr) + { continue; + } - IHandleEntity *pHandleEnt = network->GetEntityHandle(); - if (pHandleEnt == NULL) + ServerClass *pServerClass = gamehelpers->FindEntityServerClass(pEntity); + if (pServerClass == nullptr) + { continue; + } + - ServerClass *sClass = network->GetServerClass(); - const char *name = sClass->GetName(); - + const char *name = pServerClass->GetName(); if (!strcmp(name, classname)) - return gamehelpers->ReferenceToEntity(gamehelpers->IndexOfEdict(current)); + { + return pEntity; + } } - return NULL; + return nullptr; } static CBaseEntity* GetGameRulesProxyEnt() diff --git a/extensions/sdktools/teamnatives.cpp b/extensions/sdktools/teamnatives.cpp index 6334ca5e48..8f770fe5bd 100644 --- a/extensions/sdktools/teamnatives.cpp +++ b/extensions/sdktools/teamnatives.cpp @@ -50,19 +50,20 @@ void InitTeamNatives() int edictCount = gpGlobals->maxEntities; - for (int i=0; iIsFree()) + CBaseEntity *pEntity = gamehelpers->ReferenceToEntity(i); + if (pEntity == nullptr) { continue; } - if (!pEdict->GetNetworkable()) + + ServerClass *pClass = gamehelpers->FindEntityServerClass(pEntity); + if (pClass == nullptr) { continue; } - - ServerClass *pClass = pEdict->GetNetworkable()->GetServerClass(); + if (FindNestedDataTable(pClass->m_pTable, "DT_Team")) { SendProp *pTeamNumProp = g_pGameHelpers->FindInSendTable(pClass->GetName(), "m_iTeamNum"); @@ -70,15 +71,14 @@ void InitTeamNatives() if (pTeamNumProp != NULL) { int offset = pTeamNumProp->GetOffset(); - CBaseEntity *pEnt = pEdict->GetUnknown()->GetBaseEntity(); - int TeamIndex = *(int *)((unsigned char *)pEnt + offset); + int TeamIndex = *(int *)((unsigned char *)pEntity + offset); if (TeamIndex >= (int)g_Teams.size()) { g_Teams.resize(TeamIndex+1); } g_Teams[TeamIndex].ClassName = pClass->GetName(); - g_Teams[TeamIndex].pEnt = pEnt; + g_Teams[TeamIndex].pEnt = pEntity; } } } diff --git a/extensions/sdktools/vglobals.cpp b/extensions/sdktools/vglobals.cpp index 046fe75c5a..acfe10deac 100644 --- a/extensions/sdktools/vglobals.cpp +++ b/extensions/sdktools/vglobals.cpp @@ -321,28 +321,23 @@ void GetResourceEntity() { int edictCount = gpGlobals->maxEntities; - for (int i=0; iIsFree()) - { - continue; - } - if (!pEdict->GetNetworkable()) + CBaseEntity *pEntity = gamehelpers->ReferenceToEntity(i); + if (pEntity == nullptr) { continue; } - IHandleEntity *pHandleEnt = pEdict->GetNetworkable()->GetEntityHandle(); - if (!pHandleEnt) + ServerClass *pClass = gamehelpers->FindEntityServerClass(pEntity); + if (pClass == nullptr) { continue; } - - ServerClass *pClass = pEdict->GetNetworkable()->GetServerClass(); + if (FindNestedDataTable(pClass->m_pTable, "DT_PlayerResource")) { - g_ResourceEntity = pHandleEnt->GetRefEHandle(); + g_ResourceEntity = ((IHandleEntity *)pEntity)->GetRefEHandle(); break; } } diff --git a/extensions/sdktools/vnatives.cpp b/extensions/sdktools/vnatives.cpp index be41f65e05..e3d71ff3be 100644 --- a/extensions/sdktools/vnatives.cpp +++ b/extensions/sdktools/vnatives.cpp @@ -1689,7 +1689,12 @@ static cell_t LookupEntityAttachment(IPluginContext* pContext, const cell_t* par CBaseEntity* pEntity; ENTINDEX_TO_CBASEENTITY(params[1], pEntity); - ServerClass* pClass = ((IServerUnknown*)pEntity)->GetNetworkable()->GetServerClass(); + ServerClass* pClass = gamehelpers->FindEntityServerClass(pEntity); + if (pClass == nullptr) + { + return pContext->ThrowNativeError("Failed to retrieve entity %d (%d) server class!", gamehelpers->ReferenceToIndex(params[1]), params[1]); + } + if (!FindNestedDataTable(pClass->m_pTable, "DT_BaseAnimating")) { return pContext->ThrowNativeError("Entity %d (%d) is not a CBaseAnimating", gamehelpers->ReferenceToIndex(params[1]), params[1]); @@ -1735,7 +1740,12 @@ static cell_t GetEntityAttachment(IPluginContext* pContext, const cell_t* params CBaseEntity* pEntity; ENTINDEX_TO_CBASEENTITY(params[1], pEntity); - ServerClass* pClass = ((IServerUnknown*)pEntity)->GetNetworkable()->GetServerClass(); + ServerClass* pClass = gamehelpers->FindEntityServerClass(pEntity); + if (pClass == nullptr) + { + return pContext->ThrowNativeError("Failed to retrieve entity %d (%d) server class!", gamehelpers->ReferenceToIndex(params[1]), params[1]); + } + if (!FindNestedDataTable(pClass->m_pTable, "DT_BaseAnimating")) { return pContext->ThrowNativeError("Entity %d (%d) is not a CBaseAnimating", gamehelpers->ReferenceToIndex(params[1]), params[1]); diff --git a/extensions/tf2/criticals.cpp b/extensions/tf2/criticals.cpp index dac519cbe1..3a4621f773 100644 --- a/extensions/tf2/criticals.cpp +++ b/extensions/tf2/criticals.cpp @@ -76,16 +76,21 @@ bool CritManager::TryEnable() for (size_t i = playerhelpers->GetMaxClients() + 1; i < MAX_EDICTS; ++i) { CBaseEntity *pEntity = gamehelpers->ReferenceToEntity(i); - if (pEntity == NULL) + if (pEntity == nullptr) + { continue; + } - IServerUnknown *pUnknown = (IServerUnknown *)pEntity; - IServerNetworkable *pNetworkable = pUnknown->GetNetworkable(); - if (!pNetworkable) + ServerClass *pServerClass = gamehelpers->FindEntityServerClass(pEntity); + if (pServerClass == nullptr) + { continue; + } - if (!UTIL_ContainsDataTable(pNetworkable->GetServerClass()->m_pTable, TF_WEAPON_DATATABLE)) + if (!UTIL_ContainsDataTable(pServerClass->m_pTable, TF_WEAPON_DATATABLE)) + { continue; + } SH_ADD_MANUALHOOK(CalcIsAttackCriticalHelper, pEntity, SH_MEMBER(&g_CritManager, &CritManager::Hook_CalcIsAttackCriticalHelper), false); SH_ADD_MANUALHOOK(CalcIsAttackCriticalHelperNoCrits, pEntity, SH_MEMBER(&g_CritManager, &CritManager::Hook_CalcIsAttackCriticalHelperNoCrits), false); @@ -116,15 +121,20 @@ void CritManager::Disable() void CritManager::OnEntityCreated(CBaseEntity *pEntity, const char *classname) { if (!m_enabled) + { return; + } - IServerUnknown *pUnknown = (IServerUnknown *)pEntity; - IServerNetworkable *pNetworkable = pUnknown->GetNetworkable(); - if (!pNetworkable) + ServerClass *pServerClass = gamehelpers->FindEntityServerClass(pEntity); + if (pServerClass == nullptr) + { return; + } - if (!UTIL_ContainsDataTable(pNetworkable->GetServerClass()->m_pTable, TF_WEAPON_DATATABLE)) + if (!UTIL_ContainsDataTable(pServerClass->m_pTable, TF_WEAPON_DATATABLE)) + { return; + } SH_ADD_MANUALHOOK(CalcIsAttackCriticalHelper, pEntity, SH_MEMBER(&g_CritManager, &CritManager::Hook_CalcIsAttackCriticalHelper), false); SH_ADD_MANUALHOOK(CalcIsAttackCriticalHelperNoCrits, pEntity, SH_MEMBER(&g_CritManager, &CritManager::Hook_CalcIsAttackCriticalHelperNoCrits), false); @@ -164,11 +174,9 @@ bool CritManager::Hook_CalcIsAttackCriticalHelpers(bool noCrits) { CBaseEntity *pWeapon = META_IFACEPTR(CBaseEntity); - // If there's an invalid ent or invalid networkable here, we've got issues elsewhere. - - IServerNetworkable *pNetWeapon = ((IServerUnknown *)pWeapon)->GetNetworkable(); - ServerClass *pServerClass = pNetWeapon->GetServerClass(); - if (!pServerClass) + // If there's an invalid ent or invalid server class here, we've got issues elsewhere. + ServerClass *pServerClass = gamehelpers->FindEntityServerClass(pWeapon); + if (pServerClass == nullptr) { g_pSM->LogError(myself, "Invalid server class on weapon."); RETURN_META_VALUE(MRES_IGNORED, false); diff --git a/extensions/tf2/extension.cpp b/extensions/tf2/extension.cpp index ce745248f8..620eec5697 100644 --- a/extensions/tf2/extension.cpp +++ b/extensions/tf2/extension.cpp @@ -435,23 +435,19 @@ int FindEntityByNetClass(int start, const char *classname) for (int i = ((start != -1) ? start : 0); i < gpGlobals->maxEntities; i++) { - current = engine->PEntityOfEntIndex(i); - if (current == NULL || current->IsFree()) + CBaseEntity *pEntity = gamehelpers->ReferenceToEntity(i); + if (pEntity == nullptr) { continue; } - IServerNetworkable *network = current->GetNetworkable(); - - if (network == NULL) + ServerClass *pServerClass = gamehelpers->FindEntityServerClass(pEntity); + if (pServerClass == nullptr) { continue; } - - ServerClass *sClass = network->GetServerClass(); - const char *name = sClass->GetName(); - + const char *name = pServerClass->GetName(); if (strcmp(name, classname) == 0) { return i; diff --git a/public/IGameHelpers.h b/public/IGameHelpers.h index b05c98d403..bc2ae12462 100644 --- a/public/IGameHelpers.h +++ b/public/IGameHelpers.h @@ -40,7 +40,7 @@ */ #define SMINTERFACE_GAMEHELPERS_NAME "IGameHelpers" -#define SMINTERFACE_GAMEHELPERS_VERSION 11 +#define SMINTERFACE_GAMEHELPERS_VERSION 12 class CBaseEntity; class CBaseHandle; @@ -351,6 +351,13 @@ namespace SourceMod * @return 64-bit server Steam id. */ virtual uint64_t GetServerSteamId64() const =0; + + /** + * @brief Finds a given entity's server class. + * + * @return ServerClass pointer on success, nullptr on failure. + */ + virtual ServerClass *FindEntityServerClass(CBaseEntity *pEntity) = 0; }; }