VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
Damage.cpp
Go to the documentation of this file.
2
9#include <cstdint>
10#include <igameevents.h>
11#include <mathlib/vector.h>
12#include <shareddefs.h>
13#include <string>
14#include <string_view>
15#include <utility>
16
17namespace VoltMod
18{
19
20static_assert(DamageGeneric == DMG_GENERIC);
21static_assert(DamageCrush == DMG_CRUSH);
22static_assert(DamageBullet == DMG_BULLET);
23static_assert(DamageSlash == DMG_SLASH);
24static_assert(DamageBurn == DMG_BURN);
25static_assert(DamageFall == DMG_FALL);
26static_assert(DamageBlast == DMG_BLAST);
27static_assert(DamageClub == DMG_CLUB);
28static_assert(DamageShock == DMG_SHOCK);
29static_assert(DamageHeadshot == DMG_HEADSHOT);
30
31static void SetWeapon(IGameEvent& event, std::string_view weapon)
32{
33 event.SetString("weapon", std::string(weapon).c_str());
34}
35
37 : _hook("Damage", [this] { return Install(); }),
38 Before(_hook.ForEvent()),
39 _entities(entities),
40 _bindings(bindings),
41 _interfaces(interfaces),
42 _events(events)
43{}
44
45Result<Subscription> Damage::Install()
46{
47 const auto onTakeDamage = [this](CEntityInstance& victim, void* info, void*) { return OnTakeDamage(victim, info); };
48 return HookFunction("Damage", _bindings.TakeDamage, onTakeDamage);
49}
50
51HookResult<int64_t> Damage::OnTakeDamage(CEntityInstance& victim, void* rawInfo)
52{
53 auto* info = static_cast<EngineDamageInfo*>(rawInfo);
54 DamageHit hit{.Victim = Entity{_entities, &victim},
55 .Attacker = {static_cast<uint32_t>(info->Attacker.ToInt())},
56 .Inflictor = {static_cast<uint32_t>(info->Inflictor.ToInt())},
57 .Amount = info->Damage,
58 .Type = static_cast<uint32_t>(info->DamageType)};
59 Before.Raise(hit);
60 if (hit.Blocked)
61 {
63 }
64
65 if (hit.Amount != info->Damage)
66 {
67 // Keep the totalled damage in step with the edited amount, as CS2Fixes does.
68 if (info->Damage != 0.0f)
69 {
70 info->TotalledDamage *= hit.Amount / info->Damage;
71 }
72 info->Damage = hit.Amount;
73 }
74 info->DamageType = static_cast<int32_t>(hit.Type);
75 return {};
76}
77
79{
80 if (!_bindings.TakeDamage)
81 {
82 return std::unexpected(Error::Unsupported("the CBaseEntity::TakeDamageOld signature did not bind"));
83 }
84 if (!_bindings.BuildDamageInfo)
85 {
86 return std::unexpected(Error::Unsupported("the CTakeDamageInfo constructor signature did not bind"));
87 }
88 return {};
89}
90
92{
93 if (!victim || !Available())
94 {
95 return;
96 }
97
98 CEntityInstance* attacker = _entities.Get(info.Attacker).Raw();
99 CEntityInstance* inflictor = _entities.Get(info.Inflictor).Raw();
100 // The engine drops a hit with no inflictor.
101 if (!inflictor)
102 {
104 }
105
106 // The engine warns about a hit with no position or push, and pushes nothing.
107 const Vector position = victim.Origin();
108 Vector push = position - Entity{_entities, inflictor}.Origin();
109 push.NormalizeInPlace();
110 push *= info.Amount;
111
113 _bindings.BuildDamageInfo(&damage, inflictor, attacker, nullptr, &push, &position, info.Amount,
114 static_cast<int>(info.Type), 0, nullptr);
115
118 result.HealthLost = static_cast<int32_t>(info.Amount);
119 result.DamageDealt = info.Amount;
120 result.PreModifiedDamage = info.Amount;
121 result.TotalledHealthLost = static_cast<int32_t>(info.Amount);
122 result.TotalledDamageDealt = info.Amount;
123
124 if (!info.Weapon.empty())
125 {
126 HookDeathEvents();
127 }
128 // A death handler may deal its own hit inside this one.
129 const DamageInfo* outer = std::exchange(_hit, &info);
130 _bindings.TakeDamage(victim.Raw(), &damage, &result);
131 _hit = outer;
132}
133
134void Damage::HookDeathEvents()
135{
136 if (_deathEvents || !_interfaces.GameEventManager)
137 {
138 return;
139 }
141 return OnFireEvent(manager, event, dontBroadcast);
142 };
143 _deathEvents = HookInterface(&IGameEventManager2::FireEvent, _interfaces.GameEventManager, onFireEvent);
144}
145
147{
148 // Every server event passes here, mostly outside Apply.
149 if (!_hit || _hit->Weapon.empty())
150 {
151 return {};
152 }
153 if (!event || event->GetName() != PlayerDeath::EventName)
154 {
155 return {};
156 }
157 if (_hit->VictimWeapon.empty() || dontBroadcast)
158 {
159 SetWeapon(*event, _hit->Weapon);
160 return {};
161 }
162 SendDeath(*event, *_hit);
163 // Only the server's listeners: the clients have theirs.
164 return HookResult<bool>::Block(CallOriginal(&IGameEventManager2::FireEvent, &manager, event, true));
165}
166
167void Damage::SendDeath(IGameEvent& death, const DamageInfo& info)
168{
169 const int victim = death.GetPlayerSlot("userid").Get();
170 for (int slot = 0; slot < MaxPlayers; ++slot)
171 {
172 IGameEventListener2* listener = _events.GetClientLegacyListener(slot);
173 if (!listener)
174 {
175 continue;
176 }
177 SetWeapon(death, slot == victim ? info.VictimWeapon : info.Weapon);
178 listener->FireGameEvent(&death);
179 }
180 // The server's listeners read the icon path.
181 SetWeapon(death, info.Weapon);
182}
183
184} // namespace VoltMod
Event< DamageHit & > Before
Definition Damage.hpp:94
Status Available() const
Definition Damage.cpp:78
void Apply(const Entity &victim, const DamageInfo &info)
Definition Damage.cpp:91
Damage(EntitySystem &entities, const Bindings &bindings, Interfaces &interfaces, GameEvents &events)
Definition Damage.cpp:36
runtime.Entities: finds and creates entities. Everything it returns is valid for this frame only; see...
Entity Get(EntityRef ref)
A live entity, valid for this frame only.
Definition Entity.hpp:29
CEntityInstance * Raw() const noexcept
Definition Entity.hpp:47
Creates, fires and listens to game events.
IGameEventListener2 * GetClientLegacyListener(int slot) const
static HookResult Block(Ret value)
constexpr uint32_t DamageCrush
Definition Damage.hpp:23
constexpr uint32_t DamageBullet
Definition Damage.hpp:24
constexpr uint32_t DamageFall
Definition Damage.hpp:27
constexpr uint32_t DamageHeadshot
Definition Damage.hpp:31
constexpr uint32_t DamageBurn
Definition Damage.hpp:26
constexpr uint32_t DamageShock
Definition Damage.hpp:30
constexpr uint32_t DamageSlash
Definition Damage.hpp:25
constexpr uint32_t DamageClub
Definition Damage.hpp:29
constexpr uint32_t DamageGeneric
Definition Damage.hpp:22
constexpr uint32_t DamageBlast
Definition Damage.hpp:28
Ret CallOriginal(Ret(Iface::*method)(Args...), Iface *instance, Passed &&... args)
Definition Hook.hpp:218
Result< Subscription > HookFunction(std::string_view name, const Fn< Ret(Object *, Args...)> &function, Before &&before, After &&after=nullptr)
Hook a signature-bound function at its entry.
Definition Hook.hpp:200
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
static void SetWeapon(IGameEvent &event, std::string_view weapon)
Definition Damage.cpp:31
std::expected< void, Error > Status
Definition Result.hpp:67
::Vector Vector
Definition Math.hpp:11
Subscription HookInterface(Ret(Iface::*method)(Args...), Iface *instance, Before &&before, After &&after=nullptr)
Hook one virtual method of an engine interface for a subscription's lifetime.
Definition Hook.hpp:142
constexpr int MaxPlayers
Definition Slot.hpp:7
Fn< void(void *, CEntityInstance *, CEntityInstance *, CEntityInstance *, const Vector *, const Vector *, float, int, int, void *)> BuildDamageInfo
Definition Bindings.hpp:49
Fn< int64_t(CEntityInstance *, void *, void *)> TakeDamage
Definition Bindings.hpp:45
std::string_view Weapon
Definition Damage.hpp:43
std::string_view VictimWeapon
Definition Damage.hpp:45
EngineDamageInfo * OriginatingInfo
static Error Unsupported(std::string detail)
Definition Result.hpp:57
Centralized holder for all HL2SDK interface pointers.
IGameEventManager2 * GameEventManager
static constexpr std::string_view EventName