VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
GameEvents.cpp
Go to the documentation of this file.
7#include <bit>
8#include <cstdint>
9#include <playerslot.h>
10
11namespace VoltMod
12{
13
15{
16 if (_bindings.LegacyGameEventListener)
17 {
18 _getLegacyListener = std::bit_cast<GetLegacyGameEventListenerFn>(_bindings.LegacyGameEventListener.Ptr());
19 }
20 else
21 {
22 Log::Warn("LegacyGameEventListener signature not found; per-client event delivery unavailable.");
23 }
24
25 if (_bindings.GameEventManager)
26 {
28 }
29}
30
32{
33 // Remove the listener before destroying it.
35}
36
38{
39 if (!_bindings.GameEventManager)
40 {
41 return std::unexpected(Error::Unsupported("the GameEventManager address did not bind"));
42 }
43 if (!_interfaces.GameEventManager)
44 {
45 return std::unexpected(Error::Engine("the game event manager pointer is null"));
46 }
47 return {};
48}
49
50IGameEventListener2* GameEvents::GetClientLegacyListener(int slot) const
51{
52 if (!_getLegacyListener || !IsValidSlot(slot))
53 {
54 return nullptr;
55 }
56
57 return _getLegacyListener(CPlayerSlot(slot));
58}
59
60bool GameEvents::ClientListensTo(int slot, std::string_view eventName) const
61{
62 auto* mgr = _interfaces.GameEventManager;
64 if (!mgr || !listener || eventName.empty())
65 {
66 return false;
67 }
68
69 return mgr->FindListener(listener, std::string(eventName).c_str());
70}
71
72IGameEvent* GameEvents::CreateEvent(std::string_view name)
73{
74 auto* mgr = _interfaces.GameEventManager;
75 if (!mgr || name.empty())
76 {
77 return nullptr;
78 }
79
80 // The manager resolves the descriptor during the call and keeps no pointer.
81 return mgr->CreateEvent(std::string(name).c_str());
82}
83
85{
86 auto* mgr = _interfaces.GameEventManager;
87 if (!mgr || !event)
88 {
89 return false;
90 }
91
92 return mgr->FireEvent(event, !broadcast);
93}
94
96{
97 auto* mgr = _interfaces.GameEventManager;
98 if (mgr && event)
99 {
100 mgr->FreeEvent(event);
101 }
102}
103
104Subscription GameEvents::Add(std::string_view eventName, EventCallback callback)
105{
106 auto* mgr = _interfaces.GameEventManager;
107 if (!mgr)
108 {
109 return {};
110 }
111
112 auto it = _events.find(eventName);
113 if (it == _events.end())
114 {
115 it = _events.emplace(std::string(eventName), EventHandlers{}).first;
116 }
117
118 // The engine drops this late attachment at the next map startup, where it is re-attached.
119 EventHandlers& entry = it->second;
120 if (!entry.Attached)
121 {
122 entry.Attached = mgr->AddListener(this, it->first.c_str(), true);
123 }
124
125 return entry.Handlers.AddOwned(std::move(callback));
126}
127
129{
130 auto* mgr = _interfaces.GameEventManager;
131 if (!mgr || _events.empty())
132 {
133 return;
134 }
135
136 // Detach first, so a surviving listener is not added twice. The engine has no per-event
137 // detach, so an event whose handlers all dropped stops here.
138 mgr->RemoveListener(this);
139
140 int wanted = 0;
141 int attached = 0;
142 for (auto& [name, entry] : _events)
143 {
144 entry.Attached = false;
145 if (entry.Handlers.Empty())
146 {
147 continue;
148 }
149 ++wanted;
150 entry.Attached = mgr->AddListener(this, name.c_str(), true);
151 if (entry.Attached)
152 {
153 ++attached;
154 }
155 else
156 {
157 Log::Warn("Game event listener failed to attach: {}.", name);
158 }
159 }
160 Log::Info("Attached {}/{} game event listener(s) at map start.", attached, wanted);
161}
162
164{
165 // Both Runtime and the destructor call this, so it must be idempotent.
166 if (auto* mgr = _interfaces.GameEventManager; mgr && !_events.empty())
167 {
168 mgr->RemoveListener(this);
169 }
170
171 for (auto& [name, entry] : _events)
172 {
173 entry.Handlers.Clear();
174 entry.Attached = false;
175 }
176}
177
179{
180 if (!event)
181 {
182 return;
183 }
184
185 const char* eventName = event->GetName();
186 if (!eventName)
187 {
188 return;
189 }
190
191 const auto it = _events.find(std::string_view(eventName));
192 if (it != _events.end())
193 {
194 it->second.Handlers.Dispatch([event](EventCallback& callback) { callback(event); });
195 }
196}
197
198} // namespace VoltMod
Typed read/write of engine fields at a base pointer + byte offset.
void * Ptr() const noexcept
~GameEvents() override
Status Available() const
void FreeEvent(IGameEvent *event)
bool FireEvent(IGameEvent *event, bool broadcast=true)
IGameEvent * CreateEvent(std::string_view name)
GameEvents(Interfaces &interfaces, const Bindings &bindings)
IGameEventListener2 * GetClientLegacyListener(int slot) const
bool ClientListensTo(int slot, std::string_view eventName) const
void FireGameEvent(IGameEvent *event) override
Owns one registration and releases it on destruction.
void Info(std::format_string< Args... > fmt, Args &&... args)
Definition Log.hpp:79
void Warn(std::format_string< Args... > fmt, Args &&... args)
Definition Log.hpp:88
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
constexpr bool IsValidSlot(int slot)
Definition Slot.hpp:13
std::expected< void, Error > Status
Definition Result.hpp:67
Address GameEventManager
Definition Bindings.hpp:70
Address LegacyGameEventListener
Definition Bindings.hpp:43
static Error Engine(std::string detail)
Definition Result.hpp:58
static Error Unsupported(std::string detail)
Definition Result.hpp:57
Centralized holder for all HL2SDK interface pointers.
IGameEventManager2 * GameEventManager