VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
PlayerManager.hpp
Go to the documentation of this file.
1#pragma once
2
7#include <cstdint>
8#include <memory>
9#include <span>
10#include <string>
11#include <string_view>
12#include <unordered_map>
13#include <vector>
14
15namespace VoltMod
16{
17
18/** A player asking to join. Set Rejected to refuse them; they see Reason. */
20{
21 int Slot = -1;
23 std::string_view Name;
24 bool Rejected = false;
25 std::string Reason;
26};
27
28/** A chat line that was neither menu input nor a command. Set Blocked to keep it out of chat. */
30{
32 std::string_view Text;
33 bool TeamOnly = false;
34 bool Blocked = false;
35};
36
37/**
38 * @brief The roster: every connected player, and the signals for the connection lifecycle.
39 *
40 * Main-thread-only (no mutex) - the engine callbacks that drive it and everything that reads it
41 * run on the game thread. The framework owns the mutations; plugins look players up and
42 * subscribe to its events.
43 *
44 * A returned `Player*` is null when nobody matched, and lives only as long as that connection.
45 * Keep a @ref PlayerRef instead of a pointer or a bare slot.
46 */
48{
49public:
50 /**
51 * @p slots is the Core-level "this slot changed hands" signal this manager raises; subscribe
52 * on that feed (`runtime.Slots.Changed`) rather than here when all you need is to drop
53 * per-slot state - it lives in Core precisely so services below Players can hear it.
54 * @p entities builds the wrappers @ref Player::Controller and @ref Player::Pawn return. Null
55 * only in the framework's SDK-free unit tests, where there is no engine.
56 * Both must outlive the manager.
57 */
59
60 PlayerManager(const PlayerManager&) = delete;
62
63 /** @brief A player is asking to join, before the engine admits them and before they are in
64 * the roster. A handler that sets `Rejected` keeps them out. */
66
67 /** @brief A player joined and is now in the roster. Their name is not meaningful yet -
68 * @ref FullyConnected is the first point it is. */
70
71 /** @brief A player is leaving. Raised while they are still in the roster, so the handler can
72 * read their identity and flush whatever it keyed on them; the @ref Player is destroyed
73 * immediately afterwards. */
75
76 /** @brief A player finished connecting (post ClientFullyConnect) - the first point their
77 * name and replicated convars mean anything. */
79
80 /** @brief A player changed a replicated setting (name, userinfo cvars). Fires on every
81 * change, including the ones the engine sends at connect. */
83
84 /** @brief A player said something in chat that no menu or command took. */
86
87 /** The player in @p slot, or null when it is empty. */
88 Player* Get(int slot);
89
90 /**
91 * The player @p ref names: the same slot **and** the same SteamID. Null when the slot is
92 * empty or has changed hands, rather than the wrong player - which is the whole reason a
93 * slot captured earlier is stored as a ref. A bot ref (`SteamId == 0`) only says "a bot is
94 * still in that slot"; no reference can pin one down across a reconnect.
95 */
97
98 /** The connected player with this SteamID, or null. Bots are never reachable this way -
99 * they all share SteamID 0. */
101
102 /**
103 * Every connected player, in slot order. Backed by a vector the manager keeps up to date, so
104 * this allocates nothing; it is invalidated by the next Add/Remove/Clear, which means a loop
105 * over it must not connect or disconnect anybody.
106 */
107 std::span<Player* const> All() const { return _ordered; }
108
109 /** The ref for whoever occupies @p slot, or an unset ref when it is empty. The way a
110 * transient slot - a menu row, an engine callback - becomes a storable identity. */
111 PlayerRef RefFor(int slot);
112
113 /** @internal Roster mutation and lifecycle raising belong to the framework's host
114 * callbacks (`Plugin`); a plugin that calls these desynchronizes the roster from
115 * the engine. */
116 /** @{ */
117 Player* Add(int slot, int64_t steamId, std::string name, std::string ip);
118 void Remove(int slot);
119 /** Drops everyone, raising @ref Disconnected for each first. */
120 void Clear();
123 /** @} */
124
125private:
126 void IndexBySteamId(Player* player);
127 void UnindexBySteamId(const Player* player);
128 /** Rebuild the slot-ordered view. Called by every mutation, which is per connect/disconnect
129 * rather than per lookup. */
130 void Reindex();
131 /** Raise Disconnected, unindex and erase @p slot's occupant. The shared body of Remove and
132 * of Add taking over an occupied slot. */
133 void Drop(int slot);
134
135 SlotEvents& _slots;
136 EntitySystem* _entities;
137 std::unordered_map<int, std::unique_ptr<Player>> _playersBySlot;
138 std::unordered_map<int64_t, Player*> _playersBySteamId;
139 std::vector<Player*> _ordered;
140};
141
142} // namespace VoltMod
runtime.Entities: finds and creates entities. Everything it returns is valid for this frame only; see...
A multicast signal with a fixed handler signature: the one way to subscribe in VoltMod.
Definition Event.hpp:54
The roster: every connected player, and the signals for the connection lifecycle.
Event< Player & > FullyConnected
A player finished connecting (post ClientFullyConnect) - the first point their name and replicated co...
void OnClientFullyConnected(int slot)
Event< ChatMessage & > Said
A player said something in chat that no menu or command took.
Player * Add(int slot, int64_t steamId, std::string name, std::string ip)
std::span< Player *const > All() const
void OnClientSettingsChanged(int slot)
Event< Player & > Disconnected
A player is leaving. Raised while they are still in the roster, so the handler can read their identit...
PlayerRef RefFor(int slot)
PlayerManager(const PlayerManager &)=delete
PlayerManager(SlotEvents &slots, EntitySystem *entities)
Player * BySteamId(int64_t steamId)
PlayerManager & operator=(const PlayerManager &)=delete
Event< Player & > SettingsChanged
A player changed a replicated setting (name, userinfo cvars). Fires on every change,...
Event< Player & > Connected
A player joined and is now in the roster. Their name is not meaningful yet - FullyConnected is the fi...
Event< ConnectRequest & > Connecting
A player is asking to join, before the engine admits them and before they are in the roster....
One connected player, owned by PlayerManager for the length of the connection.
Definition Player.hpp:23
"The occupant of this slot changed" - raised by PlayerManager, consumed below it.
Static utilities for converting between SteamID formats (64-bit, SteamID2, SteamID3).
Definition SteamId.hpp:12
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
std::string_view Text
A storable reference to a player: the slot plus the SteamID that occupied it.
Definition PlayerRef.hpp:19