VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
PluginContext.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <format>
5#include <utility>
6
7namespace VoltMod
8{
9
10PluginContext::PluginContext(SharedState& state, std::string name, std::string logTag, std::string version,
12 : _state(state), _name(std::move(name)), _logTag(std::move(logTag)), _version(std::move(version)), _order(order)
13{}
14
15std::string_view PluginContext::Name() const
16{
17 return _name;
18}
19
20std::string_view PluginContext::Version() const
21{
22 return _version;
23}
24
25void* PluginContext::EngineInterface(const char* version) const
26{
27 return _state.Start.EngineFactory(version, nullptr);
28}
29
30void* PluginContext::ServerInterface(const char* version) const
31{
32 return _state.Start.ServerFactory(version, nullptr);
33}
34
35std::string_view PluginContext::BaseDir() const
36{
37 return _state.Start.GameDir;
38}
39
40KHook::IKHook* PluginContext::HookDispatcher() const
41{
42 return _state.Start.HookDispatcher;
43}
44
46{
47 return *this;
48}
49
51{
52 return *this;
53}
54
56{
57 return _state.GameData;
58}
59
60bool PluginContext::RegisterCommand(std::string_view name)
61{
62 if (_state.Commands.Register(this, _name, name))
63 {
64 return true;
65 }
66
67 Log::Error("Command '{}' is already registered by {}, so {} cannot have it.", name, _state.Commands.OwnerOf(name),
68 _name);
69 return false;
70}
71
72bool PluginContext::IsCommandRegistered(std::string_view name) const
73{
74 return !_state.Commands.OwnerOf(name).empty();
75}
76
81
83{
84 return *this;
85}
86
88{
89 if (!_state.Addons || !_state.Addons->Add(addonId))
90 {
91 return false;
92 }
93 _addons.push_back(addonId);
94 return true;
95}
96
98{
99 const auto held = std::ranges::find(_addons, addonId);
100 if (held != _addons.end())
101 {
102 _addons.erase(held);
103 _state.Addons->Remove(addonId);
104 }
105}
106
108{
109 return !_state.Addons || _state.Addons->IsReady(slot);
110}
111
113{
114 const auto wanted = static_cast<LogLevel>(level);
115 if (wanted < _minLevel)
116 {
117 return;
118 }
119
120 // Every plugin prints through the host's own handler, so a server reads one stream.
121 Log::Emit(wanted, std::format("[{}] {}", _logTag, text));
122}
123
125{
126 return static_cast<uint8_t>(_minLevel);
127}
128
133
135{
136 return _state.SchemaVerified;
137}
138
139template <class Fn>
140uint64_t PluginContext::Subscribe(std::string_view event, CallbackList<Fn>& list, Fn callback, void* context)
141{
142 if (callback == nullptr)
143 {
144 return 0;
145 }
146
147 const uint64_t token = _state.NextToken++;
148 list.Add(token, _order, callback, context);
149 _subscriptions.push_back({.Token = token, .Event = event, .Remove = [&list, token] { list.Remove(token); }});
150 return token;
151}
152
154{
155 return Subscribe("frame", _state.Frame, callback, context);
156}
157
159{
160 return Subscribe("server_startup", _state.ServerStartup, callback, context);
161}
162
164{
165 return Subscribe("client_connecting", _state.ClientConnecting, callback, context);
166}
167
169{
170 return Subscribe("client_connected", _state.ClientConnected, callback, context);
171}
172
174{
175 return Subscribe("client_disconnected", _state.ClientDisconnected, callback, context);
176}
177
179{
180 return Subscribe("client_fully_connected", _state.ClientFullyConnected, callback, context);
181}
182
184{
185 return Subscribe("client_settings_changed", _state.ClientSettingsChanged, callback, context);
186}
187
189{
190 return Subscribe("console_command", _state.ConsoleCommand, callback, context);
191}
192
194{
195 return Subscribe("check_transmit", _state.CheckTransmit, callback, context);
196}
197
199{
200 return Subscribe("build_game_session_manifest", _state.BuildGameSessionManifest, callback, context);
201}
202
204{
205 return Subscribe("player_command", _state.PlayerCommand, callback, context);
206}
207
209{
210 return Subscribe("player_command_done", _state.PlayerCommandDone, callback, context);
211}
212
214{
215 return Subscribe("button_press", _state.ButtonPress, callback, context);
216}
217
218void PluginContext::Publish(std::string_view name, void* implementation)
219{
220 _state.Services.Publish(this, name, implementation);
221}
222
223void PluginContext::Unpublish(std::string_view name)
224{
225 _state.Services.Unpublish(this, name);
226}
227
228void* PluginContext::Find(std::string_view name)
229{
230 return _state.Services.Find(name);
231}
232
234{
235 if (_state.CurrentMap.empty())
236 {
237 return;
238 }
239
241 _order, [&](ServerStartupFn callback, void* context) { callback(context, _state.CurrentMap); });
242 // Each slot checked before each event: a handler may kick, which empties it.
243 for (int slot = 0; slot < MaxPlayers; ++slot)
244 {
245 if (!_state.Clients[slot])
246 {
247 continue;
248 }
249 const ConnectedClient client = *_state.Clients[slot];
250 _state.ClientConnected.DispatchTo(_order, [&](ClientConnectedFn callback, void* context) {
251 callback(context, slot, client.SteamId, client.Name, client.Address);
252 });
253
254 if (_state.Clients[slot] && _state.Clients[slot]->FullyConnected)
255 {
257 _order, [&](ClientFullyConnectedFn callback, void* context) { callback(context, slot); });
258 }
259 }
260}
261
263{
264 for (int slot = 0; slot < MaxPlayers; ++slot)
265 {
266 if (_state.Clients[slot])
267 {
269 _order, [&](ClientDisconnectedFn callback, void* context) { callback(context, slot); });
270 }
271 }
272}
273
275{
276 const auto held = std::ranges::find(_subscriptions, token, &Subscribed::Token);
277 if (held == _subscriptions.end())
278 {
279 return;
280 }
281
282 held->Remove();
283 _subscriptions.erase(held);
284}
285
287{
289 // Subscriptions first, so the plugin on its way out is not told about its own withdrawals.
290 for (const Subscribed& held : _subscriptions)
291 {
292 held.Remove();
293 leaks.Subscriptions.push_back(held.Event);
294 }
295 _subscriptions.clear();
296
297 leaks.Services = _state.Services.RemoveAll(this);
298 for (const uint64_t addonId : std::exchange(_addons, {}))
299 {
300 _state.Addons->Remove(addonId);
301 leaks.Addons.push_back(addonId);
302 }
303 _state.Commands.RemoveAll(this);
304 return leaks;
305}
306
307} // namespace VoltMod
void DispatchTo(uint64_t order, Visit &&visit)
void RemoveAll(OwnerId owner)
bool Register(OwnerId owner, std::string_view ownerName, std::string_view name)
std::string_view OwnerOf(std::string_view name) const
uint64_t OnButtonPress(ButtonPressFn callback, void *context) override
void * ServerInterface(const char *version) const override
IPluginGameData * GameData() const override
uint64_t OnClientSettingsChanged(ClientSettingsChangedFn callback, void *context) override
std::string_view BaseDir() const override
IPluginAddons & Addons() override
void * EngineInterface(const char *version) const override
uint64_t OnFrame(FrameFn callback, void *context) override
uint64_t OnClientDisconnected(ClientDisconnectedFn callback, void *context) override
void Unsubscribe(uint64_t token) override
uint64_t OnPlayerCommand(PlayerCommandFn callback, void *context) override
std::string_view Name() const override
uint64_t SchemaLayoutStamp() const override
bool SchemaVerified() const override
bool IsReady(int slot) override
PluginContext(SharedState &state, std::string name, std::string logTag, std::string version, uint64_t order)
IPluginLanguages & Languages() override
uint64_t OnClientFullyConnected(ClientFullyConnectedFn callback, void *context) override
KHook::IKHook * HookDispatcher() const override
uint64_t OnClientConnected(ClientConnectedFn callback, void *context) override
bool IsCommandRegistered(std::string_view name) const override
bool RegisterCommand(std::string_view name) override
void * Find(std::string_view name) override
uint64_t OnCheckTransmit(CheckTransmitFn callback, void *context) override
uint8_t MinLogLevel() const override
void Publish(std::string_view name, void *implementation) override
uint64_t OnPlayerCommandDone(PlayerCommandFn callback, void *context) override
void Remove(uint64_t addonId) override
uint64_t OnConsoleCommand(ConsoleCommandFn callback, void *context) override
uint64_t OnClientConnecting(ClientConnectingFn callback, void *context) override
void WriteLog(uint8_t level, std::string_view text) override
IPluginEvents & Events() override
std::string_view Version() const override
uint64_t OnServerStartup(ServerStartupFn callback, void *context) override
bool Add(uint64_t addonId) override
uint64_t OnBuildGameSessionManifest(BuildGameSessionManifestFn callback, void *context) override
void Unpublish(std::string_view name) override
IPluginServices & Services() override
void Publish(OwnerId owner, std::string_view name, void *implementation)
void * Find(std::string_view name) const
std::vector< std::string > RemoveAll(OwnerId owner)
void Unpublish(OwnerId owner, std::string_view name)
void Error(std::format_string< Args... > fmt, Args &&... args)
Definition Log.hpp:97
void Emit(LogLevel level, std::string message)
Definition Logger.cpp:47
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
LogLevel
Definition Log.hpp:13
constexpr int MaxPlayers
Definition Slot.hpp:7
virtual bool IsReady(int slot)=0
virtual bool Add(uint64_t addonId)=0
virtual void Remove(uint64_t addonId)=0
void(*)(void *context, int slot) ClientFullyConnectedFn
void(*)(void *context, int slot) ClientSettingsChangedFn
void(*)(void *context, int slot, std::string_view buttonId) ButtonPressFn
void(*)(void *context, std::string_view mapName) ServerStartupFn
void(*)(void *context, int slot, int64_t steamId, std::string_view name, std::string_view address) ClientConnectedFn
bool(*)(void *context, std::string_view name, std::string_view arguments, int slot) ConsoleCommandFn
void(*)(void *context, CCheckTransmitInfo **infoList, int infoCount) CheckTransmitFn
void(*)(void *context, IEntityResourceManifest *manifest) BuildGameSessionManifestFn
void(*)(void *context) FrameFn
void(*)(void *context, int slot, const PlayerInput &input) PlayerCommandFn
bool(*)(void *context, int slot, int64_t steamId, std::string_view name, char *reason, size_t reasonSize) ClientConnectingFn
void(*)(void *context, int slot) ClientDisconnectedFn
std::vector< std::string_view > Subscriptions
the event each was taken on
InterfaceFactory EngineFactory
const char * GameDir
the absolute csgo directory
InterfaceFactory ServerFactory
the game's own server library, never a loader in front of it
KHook::IKHook * HookDispatcher
the loader's KHook, which every module's hooks go through
CallbackList< IPluginEvents::BuildGameSessionManifestFn > BuildGameSessionManifest
IPluginGameData * GameData
CallbackList< IPluginEvents::PlayerCommandFn > PlayerCommand
CallbackList< IPluginEvents::ClientFullyConnectedFn > ClientFullyConnected
CallbackList< IPluginEvents::ClientSettingsChangedFn > ClientSettingsChanged
CallbackList< IPluginEvents::FrameFn > Frame
PublishedInterfaces Services
PerSlot< std::optional< ConnectedClient > > Clients
CallbackList< IPluginEvents::ClientConnectingFn > ClientConnecting
IPluginAddons * Addons
null downloads nothing
uint64_t NextToken
unique across every event and the service table, never zero, never reused
LanguageTable Languages
CallbackList< IPluginEvents::CheckTransmitFn > CheckTransmit
CallbackList< IPluginEvents::ButtonPressFn > ButtonPress
CallbackList< IPluginEvents::ConsoleCommandFn > ConsoleCommand
CallbackList< IPluginEvents::PlayerCommandFn > PlayerCommandDone
CallbackList< IPluginEvents::ClientDisconnectedFn > ClientDisconnected
CallbackList< IPluginEvents::ClientConnectedFn > ClientConnected
uint64_t SchemaLayoutStamp
zero until the host has checked its own layout
CallbackList< IPluginEvents::ServerStartupFn > ServerStartup