VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
EntitySystem.cpp
Go to the documentation of this file.
2
7#include <entity2/entityidentity.h>
8#include <entity2/entityinstance.h>
9#include <entity2/entitysystem.h>
10#include <entityhandle.h>
11#include <string>
12#include <string_view>
13#include <vector>
14
15/**
16 * The SDK calls ::GameEntitySystem() without context. EntitySystem owns this pointer and clears it
17 * before the load cycle ends.
18 */
19static CGameEntitySystem* g_entitySystem = nullptr;
20
21// entity2 sources link against this accessor, so return the framework's resolved system.
22CGameEntitySystem* GameEntitySystem()
23{
24 return g_entitySystem;
25}
26
27namespace VoltMod
28{
29
30static constexpr std::string_view ControllerClass = "cs_player_controller";
31
32static_assert(MaxPlayers == ABSOLUTE_PLAYER_LIMIT);
33
35 : _interfaces(interfaces), _bindings(bindings)
36{
37 if (_interfaces.GameResourceService && _bindings.GameEntitySystem)
38 {
39 Log::Info("Gamedata loaded (entity system offset: {}).", _bindings.GameEntitySystem.Value());
40 Raw();
41 }
42}
43
45{
46 if (g_entitySystem == _interfaces.EntitySystem)
47 {
48 g_entitySystem = nullptr;
49 }
50}
51
52void EntitySystem::SetEntitySystem(CGameEntitySystem* system)
53{
54 _interfaces.EntitySystem = system;
55 g_entitySystem = system; // publish for ::GameEntitySystem()
56}
57
58CGameEntitySystem* EntitySystem::ReadEntitySystemPointer()
59{
60 // Retry until the pointer resolves; keep this path a plain read.
61 if (!_interfaces.GameResourceService || _isRealSystem == false)
62 {
63 return nullptr;
64 }
65
67 // Check the vtable before using a pointer from a drifted offset.
68 if (system && !_isRealSystem)
69 {
70 const void* table = FindVirtualTable("server", "CGameEntitySystem");
71 _isRealSystem = !table || IsInstanceOf(system, table);
72 if (!*_isRealSystem)
73 {
74 Log::Error("The GameEntitySystem offset {} does not reach a CGameEntitySystem; entity lookups are off.",
75 _bindings.GameEntitySystem.Value());
76 return nullptr;
77 }
78 }
79 return system;
80}
81
83{
84 // Each map creates a new system, so discard the old pointer before it is freed.
85 SetEntitySystem(nullptr);
86
87 if (Raw())
88 {
89 Log::Info("Entity system initialized.");
90 }
91 else
92 {
93 Log::Error("Entity system pointer could not be read from IGameResourceService.");
94 }
95}
96
98{
99 // The cache stays empty until the first server starts and is cleared for each new map.
100 if (!_interfaces.EntitySystem)
101 {
102 SetEntitySystem(ReadEntitySystemPointer());
103 }
104
105 return _interfaces.EntitySystem;
106}
107
109{
110 auto* system = Raw();
111 if (!ref || !system)
112 {
113 return {};
114 }
115
116 // The identity's serial must match, since a destroyed entity leaves a dangling instance pointer.
117 return {*this, system->GetEntityInstance(CEntityHandle(ref.Handle))};
118}
119
120CEntityInstance* EntitySystem::RawController(int slot)
121{
122 auto* system = Raw();
124 {
125 return nullptr;
126 }
127
128 // Controllers occupy indices 1..MaxPlayers; index 0 is worldspawn.
129 CEntityIdentity* identity = system->GetEntityIdentity(CEntityIndex(slot + 1));
130 if (!identity)
131 {
132 return nullptr;
133 }
134
135 // An index past the server's player limit holds an ordinary entity.
136 if (ControllerClass != identity->GetClassname())
137 {
138 return nullptr;
139 }
140 return identity->m_pInstance;
141}
142
144{
145 return {*this, RawController(slot), slot};
146}
147
152
157
159{
160 if (!Raw() || className.empty())
161 {
162 return {};
163 }
164
165 // Walk the active list: the SDK's CEntityClass layout is stale, so its per-class chain reads garbage.
166 const std::string name(className);
167 EntityInstanceByClassIter_t iter(nullptr, name.c_str());
168 return {*this, iter.Next()};
169}
170
171std::vector<Entity> EntitySystem::FindAll(std::string_view className)
172{
173 std::vector<Entity> found;
174 if (!Raw() || className.empty())
175 {
176 return found;
177 }
178
179 const std::string name(className);
180 EntityInstanceByClassIter_t iter(nullptr, name.c_str());
181 for (CEntityInstance* entity = iter.Next(); entity; entity = iter.Next())
182 {
183 found.emplace_back(*this, entity);
184 }
185 return found;
186}
187
188} // namespace VoltMod
CGameEntitySystem * GameEntitySystem()
static CGameEntitySystem * g_entitySystem
A player's identity behind a slot: name, money, team, scoreboard row. It survives death and respawn; ...
VoltMod::Pawn Pawn() const
std::vector< Entity > FindAll(std::string_view className)
Entity Find(std::string_view className)
VoltMod::Controller Controller(int slot)
VoltMod::Pawn Pawn(int slot)
CGameEntitySystem * Raw()
Entity Get(EntityRef ref)
EntitySystem(VoltMod::Interfaces &interfaces, const VoltMod::Bindings &bindings)
A live entity, valid for this frame only.
Definition Entity.hpp:29
Pawn AsPawn() const
Definition Pawn.cpp:178
constexpr int Value() const noexcept
T Read(const void *base) const
A player's body: health, armor, movement, aim, weapons. The engine replaces it on every spawn; the Co...
Definition Pawn.hpp:19
void Error(std::format_string< Args... > fmt, Args &&... args)
Definition Log.hpp:97
void Info(std::format_string< Args... > fmt, Args &&... args)
Definition Log.hpp:79
static constexpr std::string_view ControllerClass
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
constexpr int MaxPlayers
Definition Slot.hpp:7
void * FindVirtualTable(std::string_view moduleName, std::string_view className)
bool IsInstanceOf(const void *object, const void *table)
Offset< CGameEntitySystem * > GameEntitySystem
Definition Bindings.hpp:94
Centralized holder for all HL2SDK interface pointers.
IGameResourceService * GameResourceService
CGameEntitySystem * EntitySystem