VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
Entity.cpp
Go to the documentation of this file.
4#include <algorithm>
5#include <bit>
6#include <entity2/entityclass.h>
7#include <entity2/entityidentity.h>
8#include <entity2/entityinstance.h>
9#include <entity2/entitysystem.h>
10#include <schemasystem/schematypes.h>
11#include <shareddefs.h>
12#include <string>
13#include <string_view>
14#include <utility>
15#include <variant.h>
16
17namespace VoltMod
18{
19
20static_assert(std::to_underlying(Team::None) == TEAM_UNASSIGNED);
21static_assert(std::to_underlying(Team::Spectator) == TEAM_SPECTATOR);
22static_assert(std::to_underlying(Team::T) == CS_TEAM_T);
23static_assert(std::to_underlying(Team::CT) == CS_TEAM_CT);
24
25// CS2's EmitSound_t; the SDK still declares the Source 1 layout. The engine reads it by offset.
27{
28 const char* SoundName = nullptr;
29 Vector SoundOrigin{0.0f, 0.0f, 0.0f};
30 float Volume = 1.0f;
31 float SoundTime = 0.0f;
32 uint8_t Pad1C[0x4]{};
34 uint8_t Pad24[0x4]{};
37};
38static_assert(offsetof(EmitSoundParams, Volume) == 0x14);
39static_assert(offsetof(EmitSoundParams, ForceGuid) == 0x20);
40static_assert(offsetof(EmitSoundParams, Pitch) == 0x28);
41
42// The filtered EmitSound returns this through the hidden sret ABI.
43#pragma pack(push, 1)
51#pragma pack(pop)
52static_assert(sizeof(StartSoundEventInfo) == 20);
53
55 const EmitSoundParams& params);
56
57static constexpr float MinScale = 0.05f;
58static constexpr float MaxScale = 3.0f;
59
60// The engine keeps neither pointer past the call.
61static void FireInput(const Bindings& bindings, CEntityInstance* entity, std::string_view input, variant_t& value,
62 CEntityInstance* activator)
63{
64 if (!bindings.AcceptInput || !entity || input.empty())
65 {
66 return;
67 }
68 bindings.AcceptInput(entity, std::string(input).c_str(), activator, nullptr, &value);
69}
70
71// Origin and rotation live on the scene node, not on CBaseEntity.
73{
74 return entity.BodyComponent().SceneNode();
75}
76
77int Entity::Index() const
78{
79 return (_e && _e->m_pEntity) ? _e->GetEntityIndex().Get() : -1;
80}
81
83{
84 return {(_e && _e->m_pEntity) ? static_cast<uint32_t>(_e->GetRefEHandle().ToInt()) : EntityRef::Unset};
85}
86
87std::string_view Entity::ClassName() const
88{
89 if (!_e || !_e->m_pEntity)
90 {
91 return {};
92 }
93 const char* name = _e->GetClassname();
94 return name ? std::string_view(name) : std::string_view{};
95}
96
98{
100 return node ? node.AbsOrigin() : Vector(0.0f, 0.0f, 0.0f);
101}
102
104{
106 return node ? node.AbsRotation() : QAngle(0.0f, 0.0f, 0.0f);
107}
108
109void Entity::Teleport(std::optional<Vector> origin, std::optional<QAngle> angles, std::optional<Vector> velocity) const
110{
111 if (!_e || !_sys || !_sys->Bindings().Teleport)
112 {
113 return;
114 }
115
116 _sys->Bindings().Teleport(_e, origin ? &*origin : nullptr, angles ? &*angles : nullptr,
117 velocity ? &*velocity : nullptr);
118}
119
121{
122 SetMoveTypeRaw(type);
123 SetActualMoveType(type);
124}
125
127{
128 if (_e && _sys && _sys->Bindings().DispatchSpawn)
129 {
130 _sys->Bindings().DispatchSpawn(_e, values.Detach());
131 }
132}
133
134void Entity::AcceptInput(std::string_view input, std::string_view value, const Entity& activator) const
135{
136 if (!_sys)
137 {
138 return;
139 }
140 // The variant keeps the pointer, not a copy, so the text must outlive the call.
141 const std::string text(value);
142 variant_t variant(text.c_str());
144}
145
146// Walks the entity's schema classes, since designer names such as "prop_dynamic" do not say it.
147static bool IsModelEntity(CEntityInstance* entity)
148{
149 const CEntityIdentity* identity = entity ? entity->m_pEntity : nullptr;
150 if (!identity || !identity->m_pClass || !identity->m_pClass->m_pClassInfo)
151 {
152 return false;
153 }
154 const CSchemaClassInfo* klass = identity->m_pClass->GetSchemaBinding();
155 for (; klass; klass = klass->m_nBaseClassCount > 0 ? klass->m_pBaseClasses[0].m_pClass : nullptr)
156 {
157 if (klass->m_pszName && std::string_view(klass->m_pszName) == "CBaseModelEntity")
158 {
159 return true;
160 }
161 }
162 return false;
163}
164
165void Entity::Remove() const
166{
167 CEntityIdentity* identity = _e ? _e->m_pEntity : nullptr;
168 if (!identity || !identity->m_pClass)
169 {
170 return;
171 }
172
173 // The class's think lookup searches its bases, so every entity reaches CBaseEntity's removal.
174 const BASEPTR remove = identity->m_pClass->m_NameToThinkFunc("CBaseEntitySUB_Remove");
175 if (remove)
176 {
177 remove(_e);
178 }
179}
180
182{
183 if (!_e || !_sys || !_sys->Bindings().AddEntityIOEvent)
184 {
185 return;
186 }
188 if (!system)
189 {
190 return;
191 }
192
193 // The queue copies the input name and the value.
194 const variant_t value("");
195 _sys->Bindings().AddEntityIOEvent(system, _e, "Kill", nullptr, nullptr, &value, seconds, nullptr, nullptr);
196}
197
198void Entity::SetModel(std::string_view path) const
199{
200 if (_sys && _sys->Bindings().SetModel && !path.empty() && IsModelEntity(_e))
201 {
202 _sys->Bindings().SetModel(_e, std::string(path).c_str());
203 }
204}
205
206void Entity::SetScale(float scale) const
207{
208 if (!_sys)
209 {
210 return;
211 }
212 variant_t value(std::clamp(scale, MinScale, MaxScale));
213 FireInput(_sys->Bindings(), _e, "SetScale", value, nullptr);
214}
215
217{
218 if (!IsModelEntity(_e))
219 {
220 return;
221 }
224 model.SetRenderColor(color);
225}
226
232
233void Entity::PlayAnimation(std::string_view animation, std::string_view idle) const
234{
235 if (!idle.empty())
236 {
237 AcceptInput("SetIdleAnimationLooping", idle);
238 }
239 AcceptInput("SetAnimationNotLooping", animation);
240}
241
242void Entity::EmitSound(std::string_view soundEvent, float volume) const
243{
244 if (_e && _sys && _sys->Bindings().EmitSoundParams && !soundEvent.empty())
245 {
246 _sys->Bindings().EmitSoundParams(_e, std::string(soundEvent).c_str(), 100, volume, 0.0f);
247 }
248}
249
250void Entity::EmitSound(std::string_view soundEvent, IRecipientFilter& recipients, float volume) const
251{
252 if (!_e || !_sys || !_sys->Bindings().EmitSoundFilter || soundEvent.empty())
253 {
254 return;
255 }
256
257 // The params borrow the name; the engine reads it during the call.
258 const std::string sound(soundEvent);
260 params.SoundName = sound.c_str();
261 params.Volume = volume;
262
263 std::bit_cast<EmitSoundFilterFn>(_sys->Bindings().EmitSoundFilter.Ptr())(recipients, _e->GetEntityIndex(), params);
264}
265
266void Entity::StopSound(std::string_view soundEvent) const
267{
268 if (_e && _sys && _sys->Bindings().StopSound && !soundEvent.empty())
269 {
270 _sys->Bindings().StopSound(_e, std::string(soundEvent).c_str());
271 }
272}
273
274} // namespace VoltMod
void * Ptr() const noexcept
const VoltMod::Bindings & Bindings() const noexcept
CGameEntitySystem * Raw()
A live entity, valid for this frame only.
Definition Entity.hpp:29
void SetRender(Schema::RenderMode_t mode, Color color) const
Definition Entity.cpp:216
QAngle Angles() const
Definition Entity.cpp:103
void SetModel(std::string_view path) const
Definition Entity.cpp:198
void SetMoveType(Schema::MoveType_t type) const
Definition Entity.cpp:120
int Index() const
Definition Entity.cpp:77
void SetVisible(bool visible, uint8_t alpha=0) const
Definition Entity.cpp:227
void RemoveAfter(float seconds) const
Definition Entity.cpp:181
std::string_view ClassName() const
Definition Entity.cpp:87
void AcceptInput(std::string_view input, std::string_view value={}, const Entity &activator={}) const
Definition Entity.cpp:134
void EmitSound(std::string_view soundEvent, float volume=1.0f) const
Definition Entity.cpp:242
EntitySystem * _sys
Definition Entity.hpp:31
EntityRef Ref() const
Definition Entity.cpp:82
void Spawn(KeyValues &values) const
Definition Entity.cpp:126
void PlayAnimation(std::string_view animation, std::string_view idle={}) const
Definition Entity.cpp:233
void Remove() const
Definition Entity.cpp:165
void Teleport(std::optional< Vector > origin, std::optional< QAngle > angles, std::optional< Vector > velocity) const
Definition Entity.cpp:109
Vector Origin() const
Definition Entity.cpp:97
void SetScale(float scale) const
Definition Entity.cpp:206
void StopSound(std::string_view soundEvent) const
Definition Entity.cpp:266
CEntityInstance * _e
Definition Entity.hpp:32
Spawn keyvalues such as "origin", "spawnflags" or "effect_name".
Definition KeyValues.hpp:16
void SetRenderMode(RenderMode_t value) const
static constexpr float MinScale
Definition Entity.cpp:57
static constexpr float MaxScale
Definition Entity.cpp:58
static bool IsModelEntity(CEntityInstance *entity)
Definition Entity.cpp:147
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
::QAngle QAngle
Definition Math.hpp:14
static void FireInput(const Bindings &bindings, CEntityInstance *entity, std::string_view input, variant_t &value, CEntityInstance *activator)
Definition Entity.cpp:61
static Schema::CGameSceneNode SceneNode(const Entity &entity)
Definition Entity.cpp:72
::Vector Vector
Definition Math.hpp:11
StartSoundEventInfo(*)(IRecipientFilter &filter, CEntityIndex sourceIndex, const EmitSoundParams &params) EmitSoundFilterFn
Definition Entity.cpp:55
Fn< void(CEntityInstance *, const char *)> StopSound
Definition Bindings.hpp:39
Fn< void(CEntityInstance *, CEntityKeyValues *)> DispatchSpawn
Definition Bindings.hpp:27
Address EmitSoundFilter
Definition Bindings.hpp:41
Fn< void(CEntityInstance *, const char *, int, float, float)> EmitSoundParams
Definition Bindings.hpp:37
Fn< void(void *, CEntityInstance *, const char *, CEntityInstance *, CEntityInstance *, const void *, float, const void *, const void *)> AddEntityIOEvent
Definition Bindings.hpp:34
Fn< void(CEntityInstance *, const char *)> SetModel
Definition Bindings.hpp:35
VirtualFn< void(CEntityInstance *, const Vector *, const QAngle *, const Vector *)> Teleport
Definition Bindings.hpp:77
An RGBA color in the engine's byte order, as m_clrRender and glow colors hold it.
Definition Color.hpp:15
uint8_t A
Definition Color.hpp:19
const char * SoundName
Definition Entity.cpp:28
static constexpr uint32_t Unset
Definition EntityRef.hpp:12