Game: properly implement entity list base classes

Properly implemented and now mostly useable in the SDK.
This commit is contained in:
Kawe Mazidjatari 2024-07-31 20:49:19 +02:00
parent 892f415425
commit 084f94aefd
9 changed files with 291 additions and 53 deletions

View File

@ -8,9 +8,6 @@
//===========================================================================//
#if !defined( CLIENTENTITYLIST_H )
#define CLIENTENTITYLIST_H
#ifdef _WIN32
#pragma once
#endif
#include "tier1/utlvector.h"
#include "tier1/utllinkedlist.h"
@ -28,6 +25,10 @@ public:
virtual void OnEntityDeleted(C_BaseEntity* pEntity) {};
};
//-----------------------------------------------------------------------------
// Purpose: a global list of all the entities in the game. All iteration through
// entities is done through this object.
//-----------------------------------------------------------------------------
class CClientEntityList : public C_BaseEntityList, public IClientEntityList
{
protected:
@ -56,7 +57,6 @@ private:
// For fast iteration.
CUtlLinkedList<C_BaseEntity*, unsigned short> m_BaseEntities;
};
COMPILE_TIME_ASSERT(sizeof(CClientEntityList) == 0x3800C0);
inline IClientEntityList* g_pClientEntityList = nullptr;

View File

@ -18,6 +18,27 @@ public:
class C_BaseEntityList
{
public:
// Get an ehandle from a networkable entity's index (note: if there is no entity in that slot,
// then the ehandle will be invalid and produce NULL).
CBaseHandle GetNetworkableHandle(const int iEntity) const;
// ehandles use this in their Get() function to produce a pointer to the entity.
IHandleEntity* LookupEntity(const CBaseHandle& handle) const;
IHandleEntity* LookupEntityByNetworkIndex(const int edictIndex) const;
// Use these to iterate over all the entities.
CBaseHandle FirstHandle() const;
CBaseHandle NextHandle(const CBaseHandle& hEnt) const;
static CBaseHandle InvalidHandle();
const C_EntInfo* FirstEntInfo() const;
const C_EntInfo* NextEntInfo(const C_EntInfo* pInfo) const;
const C_EntInfo* GetEntInfoPtr(const CBaseHandle& hEnt) const;
const C_EntInfo* GetEntInfoPtrByIndex(const int index) const;
// Used by Foundry when an entity is respawned/edited.
// We force the new entity's ehandle to be the same so anyone pointing at it still gets a valid CBaseEntity out of their ehandle.
void ForceEntSerialNumber(const int iEntIndex, const int iSerialNumber);
// Overridables.
protected:
@ -33,7 +54,11 @@ private:
class C_EntInfoList
{
public:
C_EntInfoList();
C_EntInfoList()
{
m_pHead = NULL;
m_pTail = NULL;
}
const C_EntInfo* Head() const { return m_pHead; }
const C_EntInfo* Tail() const { return m_pTail; }
@ -52,6 +77,8 @@ private:
C_EntInfo* m_pTail;
};
int GetEntInfoIndex(const C_EntInfo* pEntInfo) const;
// The first MAX_EDICTS entities are networkable. The rest are client-only.
C_EntInfo m_EntPtrArray[NUM_ENT_ENTRIES];
C_EntInfoList m_activeList;
@ -63,4 +90,101 @@ private:
ssize_t m_soundEntCount;
};
// ------------------------------------------------------------------------------------ //
// Inlines.
// ------------------------------------------------------------------------------------ //
inline int C_BaseEntityList::GetEntInfoIndex(const C_EntInfo* pEntInfo) const
{
Assert(pEntInfo);
const int index = (int)(pEntInfo - m_EntPtrArray);
Assert(index >= 0 && index < NUM_ENT_ENTRIES);
return index;
}
inline CBaseHandle C_BaseEntityList::GetNetworkableHandle(const int iEntity) const
{
Assert(iEntity >= 0 && iEntity < MAX_EDICTS);
if (m_EntPtrArray[iEntity].m_pEntity)
return CBaseHandle(iEntity, m_EntPtrArray[iEntity].m_SerialNumber);
else
return CBaseHandle();
}
inline IHandleEntity* C_BaseEntityList::LookupEntity(const CBaseHandle& handle) const
{
if (handle.m_Index == INVALID_EHANDLE_INDEX)
return NULL;
const C_EntInfo* pInfo = &m_EntPtrArray[handle.GetEntryIndex()];
if (pInfo->m_SerialNumber == handle.GetSerialNumber())
return pInfo->m_pEntity;
else
return NULL;
}
inline IHandleEntity* C_BaseEntityList::LookupEntityByNetworkIndex(const int edictIndex) const
{
// (Legacy support).
if (edictIndex < 0)
return NULL;
Assert(edictIndex < NUM_ENT_ENTRIES);
return m_EntPtrArray[edictIndex].m_pEntity;
}
inline CBaseHandle C_BaseEntityList::FirstHandle() const
{
if (!m_activeList.Head())
return INVALID_EHANDLE_INDEX;
const int index = GetEntInfoIndex(m_activeList.Head());
return CBaseHandle(index, m_EntPtrArray[index].m_SerialNumber);
}
inline CBaseHandle C_BaseEntityList::NextHandle(const CBaseHandle& hEnt) const
{
const int iSlot = hEnt.GetEntryIndex();
const C_EntInfo* pNext = m_EntPtrArray[iSlot].m_pNext;
if (!pNext)
return INVALID_EHANDLE_INDEX;
const int index = GetEntInfoIndex(pNext);
return CBaseHandle(index, m_EntPtrArray[index].m_SerialNumber);
}
inline CBaseHandle C_BaseEntityList::InvalidHandle()
{
return INVALID_EHANDLE_INDEX;
}
inline const C_EntInfo* C_BaseEntityList::FirstEntInfo() const
{
return m_activeList.Head();
}
inline const C_EntInfo* C_BaseEntityList::NextEntInfo(const C_EntInfo* pInfo) const
{
return pInfo->m_pNext;
}
inline const C_EntInfo* C_BaseEntityList::GetEntInfoPtr(const CBaseHandle& hEnt) const
{
const int iSlot = hEnt.GetEntryIndex();
return &m_EntPtrArray[iSlot];
}
inline const C_EntInfo* C_BaseEntityList::GetEntInfoPtrByIndex(const int index) const
{
return &m_EntPtrArray[index];
}
inline void C_BaseEntityList::ForceEntSerialNumber(const int iEntIndex, const int iSerialNumber)
{
m_EntPtrArray[iEntIndex].m_SerialNumber = iSerialNumber;
}
#endif // ENTITYLIST_CLIENTBASE_H

View File

@ -6,12 +6,8 @@
#include "core/stdafx.h"
#include "entitylist.h"
IHandleEntity* LookupEntityByIndex(int iEntity)
{
Assert(iEntity >= 0 && iEntity < NUM_ENT_ENTRIES); // Programmer error!
IHandleEntity* pHandle = reinterpret_cast<IHandleEntity*>(*&g_pEntityList[6 * iEntity]);
return pHandle; // !TODO: implement CBaseEntityList properly.
}
CEntInfo** g_pEntityList = nullptr;
//-----------------------------------------------------------------------------
// Purpose: a global list of all the entities in the game. All iteration through
// entities is done through this object.
//-----------------------------------------------------------------------------
CGlobalEntityList* g_serverEntityList = nullptr;

View File

@ -37,13 +37,9 @@ private:
bool m_bClearingEntities;
CUtlVector<IEntityListener*> m_entityListeners;
};
COMPILE_TIME_ASSERT(sizeof(CGlobalEntityList) == 0x380088);
IHandleEntity* LookupEntityByIndex(int iEntity);
extern CEntInfo** g_pEntityList;
extern CGlobalEntityList* g_serverEntityList;
///////////////////////////////////////////////////////////////////////////////
@ -51,14 +47,13 @@ class VServerEntityList : public IDetour
{
virtual void GetAdr(void) const
{
LogVarAdr("g_serverEntityList", g_pEntityList);
LogVarAdr("g_serverEntityList", g_serverEntityList);
}
virtual void GetFun(void) const { }
virtual void GetVar(void) const
{
void* CBaseEntity__GetBaseEntity;
g_GameDll.FindPatternSIMD("8B 91 ?? ?? ?? ?? 83 FA FF 74 1F 0F B7 C2 48 8D 0D ?? ?? ?? ?? C1 EA 10 48 8D 04 40 48 03 C0 39 54 C1 08 75 05 48 8B 04 C1 C3 33 C0 C3 CC CC CC 48 8B 41 30").GetPtr(CBaseEntity__GetBaseEntity);
g_pEntityList = CMemory(CBaseEntity__GetBaseEntity).FindPattern("48 8D 0D").ResolveRelativeAddressSelf(0x3, 0x7).RCast<CEntInfo**>();
g_GameDll.FindPatternSIMD("48 8D 0D ?? ?? ?? ?? 66 0F 7F 05 ?? ?? ?? ?? 44 89 0D").
ResolveRelativeAddressSelf(3, 7).ResolveRelativeAddressSelf(3, 7).GetPtr(g_serverEntityList);
}
virtual void GetCon(void) const { }
virtual void Detour(const bool bAttach) const { }

View File

@ -21,6 +21,27 @@ public:
class CBaseEntityList
{
public:
// Get an ehandle from a networkable entity's index (note: if there is no entity in that slot,
// then the ehandle will be invalid and produce NULL).
CBaseHandle GetNetworkableHandle(const int iEntity) const;
// ehandles use this in their Get() function to produce a pointer to the entity.
IHandleEntity* LookupEntity(const CBaseHandle& handle) const;
IHandleEntity* LookupEntityByNetworkIndex(const int edictIndex) const;
// Use these to iterate over all the entities.
CBaseHandle FirstHandle() const;
CBaseHandle NextHandle(const CBaseHandle& hEnt) const;
static CBaseHandle InvalidHandle();
const CEntInfo* FirstEntInfo() const;
const CEntInfo* NextEntInfo(const CEntInfo* pInfo) const;
const CEntInfo* GetEntInfoPtr(const CBaseHandle& hEnt) const;
const CEntInfo* GetEntInfoPtrByIndex(const int index) const;
// Used by Foundry when an entity is respawned/edited.
// We force the new entity's ehandle to be the same so anyone pointing at it still gets a valid CBaseEntity out of their ehandle.
void ForceEntSerialNumber(const int iEntIndex, const int iSerialNumber);
// Overridables.
protected:
@ -36,7 +57,11 @@ private:
class CEntInfoList
{
public:
CEntInfoList();
CEntInfoList()
{
m_pHead = NULL;
m_pTail = NULL;
}
const CEntInfo* Head() const { return m_pHead; }
const CEntInfo* Tail() const { return m_pTail; }
@ -55,6 +80,8 @@ private:
CEntInfo* m_pTail;
};
int GetEntInfoIndex(const CEntInfo* pEntInfo) const;
// The first MAX_EDICTS entities are networkable. The rest are server-only.
CEntInfo m_EntPtrArray[NUM_ENT_ENTRIES];
CEntInfoList m_activeList;
@ -66,4 +93,101 @@ private:
ssize_t m_soundEntCount;
};
// ------------------------------------------------------------------------------------ //
// Inlines.
// ------------------------------------------------------------------------------------ //
inline int CBaseEntityList::GetEntInfoIndex(const CEntInfo* pEntInfo) const
{
Assert(pEntInfo);
const int index = (int)(pEntInfo - m_EntPtrArray);
Assert(index >= 0 && index < NUM_ENT_ENTRIES);
return index;
}
inline CBaseHandle CBaseEntityList::GetNetworkableHandle(const int iEntity) const
{
Assert(iEntity >= 0 && iEntity < MAX_EDICTS);
if (m_EntPtrArray[iEntity].m_pEntity)
return CBaseHandle(iEntity, m_EntPtrArray[iEntity].m_SerialNumber);
else
return CBaseHandle();
}
inline IHandleEntity* CBaseEntityList::LookupEntity(const CBaseHandle& handle) const
{
if (handle.m_Index == INVALID_EHANDLE_INDEX)
return NULL;
const CEntInfo* pInfo = &m_EntPtrArray[handle.GetEntryIndex()];
if (pInfo->m_SerialNumber == handle.GetSerialNumber())
return pInfo->m_pEntity;
else
return NULL;
}
inline IHandleEntity* CBaseEntityList::LookupEntityByNetworkIndex(const int edictIndex) const
{
// (Legacy support).
if (edictIndex < 0)
return NULL;
Assert(edictIndex < NUM_ENT_ENTRIES);
return m_EntPtrArray[edictIndex].m_pEntity;
}
inline CBaseHandle CBaseEntityList::FirstHandle() const
{
if (!m_activeList.Head())
return INVALID_EHANDLE_INDEX;
const int index = GetEntInfoIndex(m_activeList.Head());
return CBaseHandle(index, m_EntPtrArray[index].m_SerialNumber);
}
inline CBaseHandle CBaseEntityList::NextHandle(const CBaseHandle& hEnt) const
{
const int iSlot = hEnt.GetEntryIndex();
const CEntInfo* pNext = m_EntPtrArray[iSlot].m_pNext;
if (!pNext)
return INVALID_EHANDLE_INDEX;
const int index = GetEntInfoIndex(pNext);
return CBaseHandle(index, m_EntPtrArray[index].m_SerialNumber);
}
inline CBaseHandle CBaseEntityList::InvalidHandle()
{
return INVALID_EHANDLE_INDEX;
}
inline const CEntInfo* CBaseEntityList::FirstEntInfo() const
{
return m_activeList.Head();
}
inline const CEntInfo* CBaseEntityList::NextEntInfo(const CEntInfo* pInfo) const
{
return pInfo->m_pNext;
}
inline const CEntInfo* CBaseEntityList::GetEntInfoPtr(const CBaseHandle& hEnt) const
{
int iSlot = hEnt.GetEntryIndex();
return &m_EntPtrArray[iSlot];
}
inline const CEntInfo* CBaseEntityList::GetEntInfoPtrByIndex(const int index) const
{
return &m_EntPtrArray[index];
}
inline void CBaseEntityList::ForceEntSerialNumber(const int iEntIndex, const int iSerialNumber)
{
m_EntPtrArray[iEntIndex].m_SerialNumber = iSerialNumber;
}
#endif // ENTITYLIST_SERVERBASE_H

View File

@ -118,8 +118,8 @@ void __fastcall CServerGameDLL::OnReceivedSayTextMessage(void* thisptr, int send
void DrawServerHitbox(int iEntity)
{
IHandleEntity* pEntity = LookupEntityByIndex(iEntity);
CBaseAnimating* pAnimating = dynamic_cast<CBaseAnimating*>(pEntity);
const CEntInfo* pInfo = g_serverEntityList->GetEntInfoPtrByIndex(iEntity);
CBaseAnimating* pAnimating = dynamic_cast<CBaseAnimating*>(pInfo->m_pEntity);
if (pAnimating)
{

View File

@ -120,8 +120,9 @@ inline const CHandle<CBaseEntity> &variant_t::Entity( void ) const
if ( fieldType == FIELD_EHANDLE )
return eVal;
static const CHandle<CBaseEntity> hNull( INVALID_EHANDLE );
return( hNull );
static CHandle<CBaseEntity> hNull;
hNull.Set(NULL);
return(hNull);
}
#endif // VARIANT_T_H

View File

@ -53,8 +53,8 @@ public:
CHandle();
CHandle( int iEntry, int iSerialNumber );
/*implicit*/ CHandle( T *pVal );
/*implicit*/ CHandle( INVALID_EHANDLE_tag );
CHandle( const CBaseHandle &handle );
CHandle( T *pVal );
// NOTE: The following two constructor functions are not type-safe, and can allow creating a
// CHandle<T> that doesn't actually point to an object of type T.
@ -90,18 +90,19 @@ inline CHandle<T>::CHandle()
{
}
template<class T>
inline CHandle<T>::CHandle( INVALID_EHANDLE_tag )
: CBaseHandle( INVALID_EHANDLE )
{
}
template<class T>
inline CHandle<T>::CHandle( int iEntry, int iSerialNumber )
{
Init( iEntry, iSerialNumber );
}
template<class T>
CHandle<T>::CHandle( const CBaseHandle &handle )
: CBaseHandle( handle )
{
}
template<class T>
inline CHandle<T>::CHandle( T *pObj )
: CBaseHandle( INVALID_EHANDLE )

View File

@ -22,19 +22,15 @@ class IHandleEntity;
// CBaseHandle.
// -------------------------------------------------------------------------------------------------- //
enum INVALID_EHANDLE_tag
{
INVALID_EHANDLE
};
class CBaseHandle
{
friend class CBaseEntityList;
friend class C_BaseEntityList;
public:
CBaseHandle();
CBaseHandle( INVALID_EHANDLE_tag );
CBaseHandle( unsigned long value );
CBaseHandle( const CBaseHandle &other );
explicit CBaseHandle( IHandleEntity* pHandleObj );
CBaseHandle( int iEntry, int iSerialNumber );
@ -89,11 +85,12 @@ inline CBaseHandle::CBaseHandle()
m_Index = INVALID_EHANDLE_INDEX;
}
inline CBaseHandle::CBaseHandle( INVALID_EHANDLE_tag )
inline CBaseHandle::CBaseHandle( unsigned long value )
{
m_Index = INVALID_EHANDLE_INDEX;
m_Index = value;
}
inline CBaseHandle::CBaseHandle( const CBaseHandle &other )
{
m_Index = other.m_Index;
@ -138,7 +135,7 @@ inline int CBaseHandle::GetEntryIndex() const
{
// There is a hack here: due to a bug in the original implementation of the
// entity handle system, an attempt to look up an invalid entity index in
// certain cirumstances might fall through to the the mask operation below.
// certain circumstances might fall through to the mask operation below.
// This would mask an invalid index to be in fact a lookup of entity number
// NUM_ENT_ENTRIES, so invalid ent indexes end up actually looking up the
// last slot in the entities array. Since this slot is always empty, the
@ -150,7 +147,7 @@ inline int CBaseHandle::GetEntryIndex() const
// retains the prior (bug-submarining) behavior.
if ( !IsValid() )
return NUM_ENT_ENTRIES-1;
return m_Index & ENT_ENTRY_MASK;
return m_Index;
}
inline int CBaseHandle::GetSerialNumber() const
@ -193,12 +190,12 @@ inline bool CBaseHandle::operator <( const CBaseHandle &other ) const
// uint32 otherIndex = (pEntity) ? pEntity->GetRefEHandle().m_Index : INVALID_EHANDLE_INDEX;
// return m_Index < otherIndex;
//}
inline const CBaseHandle& CBaseHandle::operator=( const IHandleEntity *pEntity )
{
return Set( pEntity );
}
//
//inline const CBaseHandle& CBaseHandle::operator=( const IHandleEntity *pEntity )
//{
// return Set( pEntity );
//}
//
//inline const CBaseHandle& CBaseHandle::Set( const IHandleEntity *pEntity )
//{
// if ( pEntity )