VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
PlayerManager.cpp
Go to the documentation of this file.
2#include <algorithm>
3#include <utility>
4
5namespace VoltMod
6{
7
8// Bots share SteamID 0, so indexing them would make the map's single 0 entry mean whichever
9// bot connected last - and removing any one of them would unindex a live player. Get(slot) is
10// the only sensible lookup for a bot.
11void PlayerManager::IndexBySteamId(Player* player)
12{
13 if (!player->IsBot())
14 {
15 _playersBySteamId[player->SteamId()] = player;
16 }
17}
18
19void PlayerManager::UnindexBySteamId(const Player* player)
20{
21 if (player->IsBot())
22 {
23 return;
24 }
25
26 // Only drop the entry if it still points at this player: a reconnect under the same SteamID
27 // into a different slot must not be unindexed by the old occupant leaving.
28 auto it = _playersBySteamId.find(player->SteamId());
29 if (it != _playersBySteamId.end() && it->second == player)
30 {
31 _playersBySteamId.erase(it);
32 }
33}
34
35void PlayerManager::Reindex()
36{
37 _ordered.clear();
38 _ordered.reserve(_playersBySlot.size());
39 for (const auto& [slot, player] : _playersBySlot)
40 {
41 _ordered.push_back(player.get());
42 }
43 std::sort(_ordered.begin(), _ordered.end(), [](const Player* a, const Player* b) { return a->Slot() < b->Slot(); });
44}
45
46void PlayerManager::Drop(int slot)
47{
48 auto it = _playersBySlot.find(slot);
49 if (it == _playersBySlot.end())
50 {
51 return;
52 }
53
54 // Raised while the player is still in the roster, so a handler may still look them up.
55 Disconnected.Raise(*it->second);
56
57 // Re-find: a handler is allowed to touch the roster, and the node may have moved or gone.
58 it = _playersBySlot.find(slot);
59 if (it == _playersBySlot.end())
60 {
61 return;
62 }
63
64 UnindexBySteamId(it->second.get());
65 _playersBySlot.erase(it);
66 Reindex();
67}
68
69Player* PlayerManager::Add(int slot, int64_t steamId, std::string name, std::string ip)
70{
71 // A slot can be re-occupied without a disconnect ever reaching us. The previous occupant
72 // leaves the same way any other does, or the SteamID map keeps a dangling pointer and
73 // whatever a plugin keyed on them is never released.
74 Drop(slot);
75
76 auto player = std::make_unique<Player>(slot, steamId, std::move(name), std::move(ip), _entities);
77 Player* playerPtr = player.get();
78
79 _playersBySlot[slot] = std::move(player);
80 IndexBySteamId(playerPtr);
81 Reindex();
82
83 // Slot change first: it value-resets the per-slot caches, so a Connected handler that writes
84 // per-slot state is not wiped a moment later.
85 _slots.Raise(slot);
86 Connected.Raise(*playerPtr);
87 return playerPtr;
88}
89
91{
92 if (!_playersBySlot.contains(slot))
93 {
94 return;
95 }
96
97 Drop(slot);
98 _slots.Raise(slot);
99}
100
102{
103 std::vector<int> slots;
104 slots.reserve(_playersBySlot.size());
105 for (const auto& [slot, player] : _playersBySlot)
106 {
107 slots.push_back(slot);
108 }
109 std::sort(slots.begin(), slots.end());
110
111 for (int slot : slots)
112 {
113 Drop(slot);
114 }
115
116 // Anything left is a player a Disconnected handler added; drop it without a second raise.
117 _playersBySlot.clear();
118 _playersBySteamId.clear();
119 _ordered.clear();
120
121 for (int slot : slots)
122 {
123 _slots.Raise(slot);
124 }
125}
126
128{
129 if (Player* player = Get(slot))
130 {
131 // First, as in Add: per-slot caches drop before a handler writes.
132 _slots.FullyConnected.Raise(slot);
133 FullyConnected.Raise(*player);
134 }
135}
136
138{
139 if (Player* player = Get(slot))
140 {
141 SettingsChanged.Raise(*player);
142 }
143}
144
146{
147 auto it = _playersBySlot.find(slot);
148 return it != _playersBySlot.end() ? it->second.get() : nullptr;
149}
150
152{
153 Player* player = Get(ref.Slot);
154 return (player && player->SteamId() == ref.SteamId) ? player : nullptr;
155}
156
158{
159 auto it = _playersBySteamId.find(steamId);
160 return it != _playersBySteamId.end() ? it->second : nullptr;
161}
162
164{
165 Player* player = Get(slot);
166 return player ? player->Ref() : PlayerRef{};
167}
168
169} // namespace VoltMod
void Raise(Args... args)
Definition Event.hpp:98
Event< Player & > FullyConnected
A player finished connecting (post ClientFullyConnect) - the first point their name and replicated co...
void OnClientFullyConnected(int slot)
Player * Add(int slot, int64_t steamId, std::string name, std::string ip)
Player * Get(int slot)
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)
Player * BySteamId(int64_t steamId)
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...
One connected player, owned by PlayerManager for the length of the connection.
Definition Player.hpp:23
Event< int > FullyConnected
void Raise(int slot)
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
A storable reference to a player: the slot plus the SteamID that occupied it.
Definition PlayerRef.hpp:19