VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
Targeting.cpp
Go to the documentation of this file.
2
5#include <algorithm>
6#include <utility>
7
8namespace VoltMod
9{
10
12{
13 const std::string raw(token);
14 const std::string lower = Strings::ToLower(raw);
15 using Kind = TargetKind;
16
17 if (lower == "@all" || lower == "@*")
18 {
19 return {.Kind = Kind::All};
20 }
21 if (lower == "@me")
22 {
23 return {.Kind = Kind::Me};
24 }
25 if (lower == "@!me")
26 {
27 return {.Kind = Kind::NotMe};
28 }
29 if (lower == "@t")
30 {
31 return {.Kind = Kind::Team, .Team = Team::T};
32 }
33 if (lower == "@ct")
34 {
35 return {.Kind = Kind::Team, .Team = Team::CT};
36 }
37 if (lower == "@spec")
38 {
39 return {.Kind = Kind::Team, .Team = Team::Spectator};
40 }
41 if (lower == "@dead")
42 {
43 return {.Kind = Kind::Dead};
44 }
45 if (lower == "@alive")
46 {
47 return {.Kind = Kind::Alive};
48 }
49 if (lower == "@bot" || lower == "@bots")
50 {
51 return {.Kind = Kind::Bots};
52 }
53 if (lower == "@human" || lower == "@humans")
54 {
55 return {.Kind = Kind::Humans};
56 }
57 if (lower == "@random")
58 {
59 return {.Kind = Kind::Random};
60 }
61 if (lower == "@randomt")
62 {
63 return {.Kind = Kind::RandomTeam, .Team = Team::T};
64 }
65 if (lower == "@randomct")
66 {
67 return {.Kind = Kind::RandomTeam, .Team = Team::CT};
68 }
69
70 if (raw.size() > 1 && raw[0] == '#')
71 {
72 // Check the range before casting to avoid wrapping into another slot.
73 if (auto slot = ParseInt64(std::string_view(raw).substr(1)); slot && std::in_range<int>(*slot) && *slot >= 0)
74 {
75 return {.Kind = Kind::Slot, .Slot = static_cast<int>(*slot)};
76 }
77 }
78
79 if (raw.size() >= 15)
80 {
81 if (auto id = ParseInt64(raw); id && SteamId::IsValid(*id))
82 {
83 return {.Kind = Kind::SteamId, .SteamId = *id};
84 }
85 }
86 if (Strings::StartsWith(raw, "[U:1:"))
87 {
88 if (auto id = SteamId::FromSteamId3(raw))
89 {
90 return {.Kind = Kind::SteamId, .SteamId = *id};
91 }
92 }
93 if (Strings::StartsWith(raw, "STEAM_"))
94 {
95 if (auto id = SteamId::FromSteamId(raw))
96 {
97 return {.Kind = Kind::SteamId, .SteamId = *id};
98 }
99 }
100
101 return {.Kind = Kind::Name, .Needle = lower};
102}
103
104std::expected<std::vector<int>, TargetFailure> FilterPlayers(std::span<const PlayerView> players,
105 const TargetQuery& query, const TargetRules& rules,
106 int callerSlot,
107 const std::function<std::size_t(std::size_t)>& randomIndex)
108{
109 using Kind = TargetKind;
110
111 std::vector<const PlayerView*> candidates;
112 auto collect = [&](auto&& pred) {
113 for (const auto& p : players)
114 {
115 if (pred(p))
116 {
117 candidates.push_back(&p);
118 }
119 }
120 };
121
122 switch (query.Kind)
123 {
124 case Kind::All:
125 case Kind::Random:
126 collect([](const PlayerView&) { return true; });
127 break;
128 case Kind::Me:
129 collect([&](const PlayerView& p) { return p.Slot == callerSlot; });
130 break;
131 case Kind::NotMe:
132 collect([&](const PlayerView& p) { return p.Slot != callerSlot; });
133 break;
134 case Kind::Team:
135 case Kind::RandomTeam:
136 collect([&](const PlayerView& p) { return p.Team == query.Team; });
137 break;
138 case Kind::Dead:
139 collect([](const PlayerView& p) { return !p.Alive; });
140 break;
141 case Kind::Alive:
142 collect([](const PlayerView& p) { return p.Alive; });
143 break;
144 case Kind::Bots:
145 collect([](const PlayerView& p) { return p.Bot; });
146 break;
147 case Kind::Humans:
148 collect([](const PlayerView& p) { return !p.Bot; });
149 break;
150 case Kind::Slot:
151 collect([&](const PlayerView& p) { return p.Slot == query.Slot; });
152 break;
153 case Kind::SteamId:
154 collect([&](const PlayerView& p) { return p.SteamId == query.SteamId; });
155 break;
156 case Kind::Name:
157 {
158 // Prefer exact names, then prefixes, then substrings.
159 auto tier = [&](auto&& pred) {
160 candidates.clear();
161 collect(pred);
162 return !candidates.empty();
163 };
164 tier([&](const PlayerView& p) { return Strings::ToLower(p.Name) == query.Needle; }) ||
165 tier([&](const PlayerView& p) { return Strings::StartsWith(Strings::ToLower(p.Name), query.Needle); }) ||
166 tier([&](const PlayerView& p) { return Strings::ToLower(p.Name).find(query.Needle) != std::string::npos; });
167 break;
168 }
169 }
170
171 if (candidates.empty())
172 {
173 return std::unexpected(TargetFailure{TargetError::NoMatch});
174 }
175
176 auto drop = [&](auto&& pred) { std::erase_if(candidates, pred); };
177
178 if (!rules.AllowBots)
179 {
180 drop([](const PlayerView* p) { return p->Bot; });
181 if (candidates.empty())
182 {
183 return std::unexpected(TargetFailure{TargetError::BotNotAllowed});
184 }
185 }
186 if (!rules.AllowDead)
187 {
188 drop([](const PlayerView* p) { return !p->Alive; });
189 if (candidates.empty())
190 {
191 return std::unexpected(TargetFailure{TargetError::DeadNotAllowed});
192 }
193 }
194
195 // Blocked players are removed; report immunity only when none remain.
196 drop([](const PlayerView* p) { return !p->Targetable; });
197 if (candidates.empty())
198 {
199 return std::unexpected(TargetFailure{TargetError::Immune});
200 }
201
202 if (query.Kind == Kind::Random || query.Kind == Kind::RandomTeam)
203 {
204 std::size_t pick = randomIndex ? randomIndex(candidates.size()) % candidates.size() : 0;
206 }
207
208 // Single-target commands reject ambiguous names and multi-selectors.
209 if (candidates.size() > 1 && !rules.AllowMultiple)
210 {
211 auto error = (query.Kind == Kind::Name) ? TargetError::Ambiguous : TargetError::MultiNotAllowed;
212 return std::unexpected(TargetFailure{error, static_cast<int>(candidates.size())});
213 }
214
215 std::vector<int> slots;
216 slots.reserve(candidates.size());
217 for (const auto* p : candidates)
218 {
219 slots.push_back(p->Slot);
220 }
221 return slots;
222}
223
224} // namespace VoltMod
static std::optional< int64_t > FromSteamId3(const std::string &steamId3)
Definition SteamId.cpp:23
static bool IsValid(int64_t steamId64)
Definition SteamId.cpp:76
static std::optional< int64_t > FromSteamId(const std::string &steamId)
Definition SteamId.cpp:45
static bool StartsWith(std::string_view str, std::string_view prefix)
Definition Strings.cpp:148
static std::string ToLower(std::string_view str)
Definition Strings.cpp:100
std::optional< int64_t > ParseInt64(std::string_view text)
Definition Strings.cpp:13
TargetQuery ParseTargetToken(std::string_view token)
Definition Targeting.cpp:11
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
@ Ambiguous
a name fragment matched more than one player
@ MultiNotAllowed
a multi-selector was used where the command takes a single target
@ Immune
matches existed, but the policy blocked all of them
std::expected< std::vector< int >, TargetFailure > FilterPlayers(std::span< const PlayerView > players, const TargetQuery &query, const TargetRules &rules, int callerSlot, const std::function< std::size_t(std::size_t)> &randomIndex)