1/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */
2#include "gamecontext.h"
3
4#include <base/log.h>
5
6#include <engine/shared/config.h>
7
8static NETADDR KeyAddress(NETADDR Addr)
9{
10 if(Addr.type == NETTYPE_WEBSOCKET_IPV4)
11 {
12 Addr.type = NETTYPE_IPV4;
13 }
14 else if(Addr.type == NETTYPE_WEBSOCKET_IPV6)
15 {
16 Addr.type = NETTYPE_IPV6;
17 }
18 Addr.port = 0;
19 return Addr;
20}
21
22int CMute::SecondsLeft() const
23{
24 return m_Expire - time_timestamp();
25}
26
27CMutes::CMutes(const char *pSystemName) :
28 m_pSystemName(pSystemName)
29{
30}
31
32bool CMutes::Mute(const NETADDR *pAddr, int Seconds, const char *pReason, const char *pClientName, bool InitialDelay)
33{
34 if(!in_range(a: Seconds, lower: 1, upper: 365 * 24 * 60 * 60))
35 {
36 log_info(m_pSystemName, "Invalid mute duration: %d", Seconds);
37 return false;
38 }
39
40 // Find a matching mute for this IP, update expiration time if found
41 const int64_t Expire = time_timestamp() + Seconds;
42 CMute &Mute = m_Mutes[KeyAddress(Addr: *pAddr)];
43 if(!Mute.m_Initialized)
44 {
45 Mute.m_Initialized = true;
46 Mute.m_Expire = Expire;
47 str_copy(dst&: Mute.m_aReason, src: pReason);
48 Mute.m_InitialDelay = InitialDelay;
49 if(pClientName)
50 {
51 str_copy(dst&: Mute.m_aClientName, src: pClientName);
52 Mute.m_NameKnown = true;
53 }
54 else
55 {
56 Mute.m_aClientName[0] = '\0';
57 Mute.m_NameKnown = false;
58 }
59
60 return true;
61 }
62 if(Expire > Mute.m_Expire)
63 {
64 Mute.m_Expire = Expire;
65 str_copy(dst&: Mute.m_aReason, src: pReason);
66 Mute.m_InitialDelay = InitialDelay;
67 }
68 return true;
69}
70
71void CMutes::UnmuteIndex(int Index)
72{
73 if(Index < 0 || Index >= (int)m_Mutes.size())
74 {
75 log_info(m_pSystemName, "Invalid index to unmute: %d", Index);
76 return;
77 }
78 auto It = std::next(x: m_Mutes.begin(), n: Index);
79 char aAddrString[NETADDR_MAXSTRSIZE];
80 net_addr_str(addr: &It->first, string: aAddrString, max_length: sizeof(aAddrString), add_port: false);
81 if(It->second.m_NameKnown)
82 log_info(m_pSystemName, "Unmuted: '%s' (<{%s}>)", It->second.m_aClientName, aAddrString);
83 else
84 log_info(m_pSystemName, "Unmuted: (unknown) (<{%s}>)", aAddrString);
85 m_Mutes.erase(position: It);
86}
87
88void CMutes::UnmuteAddr(const NETADDR *pAddr)
89{
90 NETADDR KeyAddr = KeyAddress(Addr: *pAddr);
91 char aAddrString[NETADDR_MAXSTRSIZE];
92 net_addr_str(addr: &KeyAddr, string: aAddrString, max_length: sizeof(aAddrString), add_port: false);
93 auto It = m_Mutes.find(x: KeyAddr);
94 if(It == m_Mutes.end())
95 {
96 log_info(m_pSystemName, "No mutes for this IP address found: %s", aAddrString);
97 return;
98 }
99 if(It->second.m_NameKnown)
100 log_info(m_pSystemName, "Unmuted: '%s' (<{%s}>)", It->second.m_aClientName, aAddrString);
101 else
102 log_info(m_pSystemName, "Unmuted: (unknown) (<{%s}>)", aAddrString);
103 m_Mutes.erase(position: It);
104}
105
106std::optional<CMute> CMutes::IsMuted(const NETADDR *pAddr, bool RespectInitialDelay) const
107{
108 const auto It = m_Mutes.find(x: KeyAddress(Addr: *pAddr));
109 if(It == m_Mutes.end())
110 {
111 return std::nullopt;
112 }
113 if(!RespectInitialDelay && !It->second.m_InitialDelay)
114 {
115 return std::nullopt;
116 }
117 return It->second;
118}
119
120void CMutes::UnmuteExpired()
121{
122 const int64_t Now = time_timestamp();
123 for(auto It = m_Mutes.begin(); It != m_Mutes.end();)
124 {
125 if(It->second.m_Expire <= Now)
126 {
127 It = m_Mutes.erase(position: It);
128 }
129 else
130 {
131 ++It;
132 }
133 }
134}
135
136void CMutes::Print(int Page) const
137{
138 if(m_Mutes.empty())
139 {
140 log_info(m_pSystemName, "The mute list is empty.");
141 return;
142 }
143
144 static constexpr int ENTRIES_PER_PAGE = 20;
145 const int NumPages = std::ceil(x: m_Mutes.size() / (float)ENTRIES_PER_PAGE);
146 if(!in_range(a: Page, lower: 1, upper: NumPages))
147 {
148 log_info(m_pSystemName, "Invalid page number. There %s %d %s available.",
149 NumPages == 1 ? "is" : "are", NumPages, NumPages == 1 ? "page" : "pages");
150 return;
151 }
152
153 const int Start = (Page - 1) * ENTRIES_PER_PAGE;
154 const int End = Page * ENTRIES_PER_PAGE;
155
156 int Count = 0;
157 for(const auto &[Addr, MuteEntry] : m_Mutes)
158 {
159 if(Count < Start)
160 {
161 Count++;
162 continue;
163 }
164 else if(Count >= End)
165 {
166 break;
167 }
168
169 char aAddrString[NETADDR_MAXSTRSIZE];
170 net_addr_str(addr: &Addr, string: aAddrString, max_length: sizeof(aAddrString), add_port: false);
171
172 if(MuteEntry.m_NameKnown)
173 {
174 log_info(
175 m_pSystemName,
176 "#%d '%s' (<{%s}>) muted for %d seconds (%s)",
177 Count,
178 MuteEntry.m_aClientName,
179 aAddrString,
180 MuteEntry.SecondsLeft(),
181 MuteEntry.m_aReason[0] == '\0' ? "No reason given" : MuteEntry.m_aReason);
182 }
183 else
184 {
185 log_info(
186 m_pSystemName,
187 "#%d (unknown) (<{%s}>) muted for %d seconds (%s)",
188 Count,
189 aAddrString,
190 MuteEntry.SecondsLeft(),
191 MuteEntry.m_aReason[0] == '\0' ? "No reason given" : MuteEntry.m_aReason);
192 }
193
194 Count++;
195 }
196 log_info(m_pSystemName, "%d %s, showing entries %d - %d (page %d/%d)",
197 (int)m_Mutes.size(), m_Mutes.size() == 1 ? "mute" : "mutes", Start, Count - 1, Page, NumPages);
198}
199
200void CGameContext::MuteWithMessage(const NETADDR *pAddr, int Seconds, const char *pReason, const char *pDisplayName)
201{
202 if(!m_Mutes.Mute(pAddr, Seconds, pReason, pClientName: pDisplayName, InitialDelay: false))
203 {
204 return;
205 }
206
207 char aChatMessage[256];
208 if(pReason[0] != '\0')
209 {
210 str_format(buffer: aChatMessage, buffer_size: sizeof(aChatMessage), format: "'%s' has been muted for %d seconds (%s)", pDisplayName, Seconds, pReason);
211 }
212 else
213 {
214 str_format(buffer: aChatMessage, buffer_size: sizeof(aChatMessage), format: "'%s' has been muted for %d seconds", pDisplayName, Seconds);
215 }
216 SendChat(ClientId: -1, Team: TEAM_ALL, pText: aChatMessage);
217}
218
219void CGameContext::VoteMuteWithMessage(const NETADDR *pAddr, int Seconds, const char *pReason, const char *pDisplayName)
220{
221 if(!m_VoteMutes.Mute(pAddr, Seconds, pReason, pClientName: pDisplayName, InitialDelay: false))
222 {
223 return;
224 }
225
226 char aChatMessage[256];
227 if(pReason[0] != '\0')
228 {
229 str_format(buffer: aChatMessage, buffer_size: sizeof(aChatMessage), format: "'%s' has been banned from voting for %d seconds (%s)", pDisplayName, Seconds, pReason);
230 }
231 else
232 {
233 str_format(buffer: aChatMessage, buffer_size: sizeof(aChatMessage), format: "'%s' has been banned from voting for %d seconds", pDisplayName, Seconds);
234 }
235 SendChat(ClientId: -1, Team: TEAM_ALL, pText: aChatMessage);
236}
237
238void CGameContext::ConMute(IConsole::IResult *pResult, void *pUserData)
239{
240 log_info("mutes", "This command is deprecated. Use either 'muteid <client_id> <seconds> <reason>' or 'muteip <ip> <seconds> <reason>'");
241}
242
243void CGameContext::ConMuteId(IConsole::IResult *pResult, void *pUserData)
244{
245 CGameContext *pSelf = (CGameContext *)pUserData;
246
247 const int Victim = pResult->GetVictim();
248 if(!CheckClientId(ClientId: Victim) || !pSelf->m_apPlayers[Victim])
249 {
250 log_info("mutes", "Client ID not found: %d", Victim);
251 return;
252 }
253
254 const char *pReason = pResult->NumArguments() > 2 ? pResult->GetString(Index: 2) : "";
255 pSelf->MuteWithMessage(pAddr: pSelf->Server()->ClientAddr(ClientId: Victim), Seconds: pResult->GetInteger(Index: 1), pReason, pDisplayName: pSelf->Server()->ClientName(ClientId: Victim));
256}
257
258void CGameContext::ConMuteIp(IConsole::IResult *pResult, void *pUserData)
259{
260 CGameContext *pSelf = (CGameContext *)pUserData;
261
262 NETADDR Addr;
263 if(net_addr_from_str(addr: &Addr, string: pResult->GetString(Index: 0)) != 0)
264 {
265 log_info("mutes", "Invalid IP address to mute: %s", pResult->GetString(0));
266 return;
267 }
268
269 const char *pReason = pResult->NumArguments() > 2 ? pResult->GetString(Index: 2) : "";
270
271 pSelf->m_Mutes.Mute(pAddr: &Addr, Seconds: pResult->GetInteger(Index: 1), pReason, pClientName: nullptr, InitialDelay: false);
272}
273
274void CGameContext::ConUnmute(IConsole::IResult *pResult, void *pUserData)
275{
276 CGameContext *pSelf = (CGameContext *)pUserData;
277
278 pSelf->m_Mutes.UnmuteIndex(Index: pResult->GetInteger(Index: 0));
279}
280
281void CGameContext::ConUnmuteId(IConsole::IResult *pResult, void *pUserData)
282{
283 CGameContext *pSelf = (CGameContext *)pUserData;
284
285 const int Victim = pResult->GetVictim();
286 if(!CheckClientId(ClientId: Victim) || !pSelf->m_apPlayers[Victim])
287 {
288 log_info("mutes", "Client ID not found: %d", Victim);
289 return;
290 }
291
292 pSelf->m_Mutes.UnmuteAddr(pAddr: pSelf->Server()->ClientAddr(ClientId: Victim));
293}
294
295void CGameContext::ConUnmuteIp(IConsole::IResult *pResult, void *pUserData)
296{
297 CGameContext *pSelf = (CGameContext *)pUserData;
298
299 NETADDR Addr;
300 if(net_addr_from_str(addr: &Addr, string: pResult->GetString(Index: 0)) != 0)
301 {
302 log_info("mutes", "Invalid IP address to unmute: %s", pResult->GetString(0));
303 return;
304 }
305
306 pSelf->m_Mutes.UnmuteAddr(pAddr: &Addr);
307}
308
309void CGameContext::ConMutes(IConsole::IResult *pResult, void *pUserData)
310{
311 CGameContext *pSelf = (CGameContext *)pUserData;
312
313 pSelf->m_Mutes.Print(Page: pResult->NumArguments() > 0 ? pResult->GetInteger(Index: 0) : 1);
314}
315
316void CGameContext::ConVoteMute(IConsole::IResult *pResult, void *pUserData)
317{
318 log_info("votemutes", "Use either 'vote_muteid <client_id> <seconds> <reason>' or 'vote_muteip <ip> <seconds> <reason>'");
319}
320
321void CGameContext::ConVoteMuteId(IConsole::IResult *pResult, void *pUserData)
322{
323 CGameContext *pSelf = (CGameContext *)pUserData;
324
325 const int Victim = pResult->GetVictim();
326 if(!CheckClientId(ClientId: Victim) || !pSelf->m_apPlayers[Victim])
327 {
328 log_info("votemutes", "Client ID not found: %d", Victim);
329 return;
330 }
331
332 const char *pReason = pResult->NumArguments() > 2 ? pResult->GetString(Index: 2) : "";
333 pSelf->VoteMuteWithMessage(pAddr: pSelf->Server()->ClientAddr(ClientId: Victim), Seconds: pResult->GetInteger(Index: 1), pReason, pDisplayName: pSelf->Server()->ClientName(ClientId: Victim));
334}
335
336void CGameContext::ConVoteMuteIp(IConsole::IResult *pResult, void *pUserData)
337{
338 CGameContext *pSelf = (CGameContext *)pUserData;
339
340 NETADDR Addr;
341 if(net_addr_from_str(addr: &Addr, string: pResult->GetString(Index: 0)) != 0)
342 {
343 log_info("votemutes", "Invalid IP address to mute: %s", pResult->GetString(0));
344 return;
345 }
346
347 const char *pReason = pResult->NumArguments() > 2 ? pResult->GetString(Index: 2) : "";
348
349 pSelf->m_VoteMutes.Mute(pAddr: &Addr, Seconds: pResult->GetInteger(Index: 1), pReason, pClientName: nullptr, InitialDelay: false);
350}
351
352void CGameContext::ConVoteUnmute(IConsole::IResult *pResult, void *pUserData)
353{
354 CGameContext *pSelf = (CGameContext *)pUserData;
355
356 pSelf->m_VoteMutes.UnmuteIndex(Index: pResult->GetInteger(Index: 0));
357}
358
359void CGameContext::ConVoteUnmuteId(IConsole::IResult *pResult, void *pUserData)
360{
361 CGameContext *pSelf = (CGameContext *)pUserData;
362
363 const int Victim = pResult->GetVictim();
364 if(!CheckClientId(ClientId: Victim) || !pSelf->m_apPlayers[Victim])
365 {
366 log_info("votemutes", "Client ID not found: %d", Victim);
367 return;
368 }
369
370 pSelf->m_VoteMutes.UnmuteAddr(pAddr: pSelf->Server()->ClientAddr(ClientId: Victim));
371}
372
373void CGameContext::ConVoteUnmuteIp(IConsole::IResult *pResult, void *pUserData)
374{
375 CGameContext *pSelf = (CGameContext *)pUserData;
376
377 NETADDR Addr;
378 if(net_addr_from_str(addr: &Addr, string: pResult->GetString(Index: 0)) != 0)
379 {
380 log_info("votemutes", "Invalid IP address to unmute: %s", pResult->GetString(0));
381 return;
382 }
383
384 pSelf->m_VoteMutes.UnmuteAddr(pAddr: &Addr);
385}
386
387void CGameContext::ConVoteMutes(IConsole::IResult *pResult, void *pUserData)
388{
389 CGameContext *pSelf = (CGameContext *)pUserData;
390
391 pSelf->m_VoteMutes.Print(Page: pResult->NumArguments() > 0 ? pResult->GetInteger(Index: 0) : 1);
392}
393