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, 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 return true;
50 }
51 if(Expire > Mute.m_Expire)
52 {
53 Mute.m_Expire = Expire;
54 str_copy(dst&: Mute.m_aReason, src: pReason);
55 Mute.m_InitialDelay = InitialDelay;
56 }
57 return true;
58}
59
60void CMutes::UnmuteIndex(int Index)
61{
62 if(Index < 0 || Index >= (int)m_Mutes.size())
63 {
64 log_info(m_pSystemName, "Invalid index to unmute: %d", Index);
65 return;
66 }
67 auto It = std::next(x: m_Mutes.begin(), n: Index);
68 char aAddrString[NETADDR_MAXSTRSIZE];
69 net_addr_str(addr: &It->first, string: aAddrString, max_length: sizeof(aAddrString), add_port: false);
70 log_info(m_pSystemName, "Unmuted: %s", aAddrString);
71 m_Mutes.erase(position: It);
72}
73
74void CMutes::UnmuteAddr(const NETADDR *pAddr)
75{
76 NETADDR KeyAddr = KeyAddress(Addr: *pAddr);
77 char aAddrString[NETADDR_MAXSTRSIZE];
78 net_addr_str(addr: &KeyAddr, string: aAddrString, max_length: sizeof(aAddrString), add_port: false);
79 auto It = m_Mutes.find(x: KeyAddr);
80 if(It == m_Mutes.end())
81 {
82 log_info(m_pSystemName, "No mutes for this IP address found: %s", aAddrString);
83 return;
84 }
85 log_info(m_pSystemName, "Unmuted: %s", aAddrString);
86 m_Mutes.erase(position: It);
87}
88
89std::optional<CMute> CMutes::IsMuted(const NETADDR *pAddr, bool RespectInitialDelay) const
90{
91 const auto It = m_Mutes.find(x: KeyAddress(Addr: *pAddr));
92 if(It == m_Mutes.end())
93 {
94 return std::nullopt;
95 }
96 if(!RespectInitialDelay && !It->second.m_InitialDelay)
97 {
98 return std::nullopt;
99 }
100 return It->second;
101}
102
103void CMutes::UnmuteExpired()
104{
105 const int64_t Now = time_timestamp();
106 for(auto It = m_Mutes.begin(); It != m_Mutes.end();)
107 {
108 if(It->second.m_Expire <= Now)
109 {
110 It = m_Mutes.erase(position: It);
111 }
112 else
113 {
114 ++It;
115 }
116 }
117}
118
119void CMutes::Print(int Page) const
120{
121 if(m_Mutes.empty())
122 {
123 log_info(m_pSystemName, "The mute list is empty.");
124 return;
125 }
126
127 static constexpr int ENTRIES_PER_PAGE = 20;
128 const int NumPages = std::ceil(x: m_Mutes.size() / (float)ENTRIES_PER_PAGE);
129 if(!in_range(a: Page, lower: 1, upper: NumPages))
130 {
131 log_info(m_pSystemName, "Invalid page number. There %s %d %s available.",
132 NumPages == 1 ? "is" : "are", NumPages, NumPages == 1 ? "page" : "pages");
133 return;
134 }
135
136 const int Start = (Page - 1) * ENTRIES_PER_PAGE;
137 const int End = Page * ENTRIES_PER_PAGE;
138
139 int Count = 0;
140 for(const auto &[Addr, MuteEntry] : m_Mutes)
141 {
142 if(Count < Start)
143 {
144 Count++;
145 continue;
146 }
147 else if(Count >= End)
148 {
149 break;
150 }
151 char aAddrString[NETADDR_MAXSTRSIZE];
152 net_addr_str(addr: &Addr, string: aAddrString, max_length: sizeof(aAddrString), add_port: false);
153 log_info(m_pSystemName, "#%d '%s' muted for %d seconds (%s)",
154 Count, aAddrString, MuteEntry.SecondsLeft(), MuteEntry.m_aReason[0] == '\0' ? "No reason given" : MuteEntry.m_aReason);
155 Count++;
156 }
157 log_info(m_pSystemName, "%d %s, showing entries %d - %d (page %d/%d)",
158 (int)m_Mutes.size(), m_Mutes.size() == 1 ? "mute" : "mutes", Start, Count - 1, Page, NumPages);
159}
160
161void CGameContext::MuteWithMessage(const NETADDR *pAddr, int Seconds, const char *pReason, const char *pDisplayName)
162{
163 if(!m_Mutes.Mute(pAddr, Seconds, pReason, InitialDelay: false))
164 {
165 return;
166 }
167
168 char aChatMessage[256];
169 if(pReason[0] != '\0')
170 {
171 str_format(buffer: aChatMessage, buffer_size: sizeof(aChatMessage), format: "'%s' has been muted for %d seconds (%s)", pDisplayName, Seconds, pReason);
172 }
173 else
174 {
175 str_format(buffer: aChatMessage, buffer_size: sizeof(aChatMessage), format: "'%s' has been muted for %d seconds", pDisplayName, Seconds);
176 }
177 SendChat(ClientId: -1, Team: TEAM_ALL, pText: aChatMessage);
178}
179
180void CGameContext::VoteMuteWithMessage(const NETADDR *pAddr, int Seconds, const char *pReason, const char *pDisplayName)
181{
182 if(!m_VoteMutes.Mute(pAddr, Seconds, pReason, InitialDelay: false))
183 {
184 return;
185 }
186
187 char aChatMessage[256];
188 if(pReason[0] != '\0')
189 {
190 str_format(buffer: aChatMessage, buffer_size: sizeof(aChatMessage), format: "'%s' has been banned from voting for %d seconds (%s)", pDisplayName, Seconds, pReason);
191 }
192 else
193 {
194 str_format(buffer: aChatMessage, buffer_size: sizeof(aChatMessage), format: "'%s' has been banned from voting for %d seconds", pDisplayName, Seconds);
195 }
196 SendChat(ClientId: -1, Team: TEAM_ALL, pText: aChatMessage);
197}
198
199void CGameContext::ConMute(IConsole::IResult *pResult, void *pUserData)
200{
201 log_info("mutes", "This command is deprecated. Use either 'muteid <client_id> <seconds> <reason>' or 'muteip <ip> <seconds> <reason>'");
202}
203
204void CGameContext::ConMuteId(IConsole::IResult *pResult, void *pUserData)
205{
206 CGameContext *pSelf = (CGameContext *)pUserData;
207
208 const int Victim = pResult->GetVictim();
209 if(!CheckClientId(ClientId: Victim) || !pSelf->m_apPlayers[Victim])
210 {
211 log_info("mutes", "Client ID not found: %d", Victim);
212 return;
213 }
214
215 const char *pReason = pResult->NumArguments() > 2 ? pResult->GetString(Index: 2) : "";
216 pSelf->MuteWithMessage(pAddr: pSelf->Server()->ClientAddr(ClientId: Victim), Seconds: pResult->GetInteger(Index: 1), pReason, pDisplayName: pSelf->Server()->ClientName(ClientId: Victim));
217}
218
219void CGameContext::ConMuteIp(IConsole::IResult *pResult, void *pUserData)
220{
221 CGameContext *pSelf = (CGameContext *)pUserData;
222
223 NETADDR Addr;
224 if(net_addr_from_str(addr: &Addr, string: pResult->GetString(Index: 0)) != 0)
225 {
226 log_info("mutes", "Invalid IP address to mute: %s", pResult->GetString(0));
227 return;
228 }
229
230 const char *pReason = pResult->NumArguments() > 2 ? pResult->GetString(Index: 2) : "";
231 pSelf->m_Mutes.Mute(pAddr: &Addr, Seconds: pResult->GetInteger(Index: 1), pReason, InitialDelay: false);
232}
233
234void CGameContext::ConUnmute(IConsole::IResult *pResult, void *pUserData)
235{
236 CGameContext *pSelf = (CGameContext *)pUserData;
237
238 pSelf->m_Mutes.UnmuteIndex(Index: pResult->GetInteger(Index: 0));
239}
240
241void CGameContext::ConUnmuteId(IConsole::IResult *pResult, void *pUserData)
242{
243 CGameContext *pSelf = (CGameContext *)pUserData;
244
245 const int Victim = pResult->GetVictim();
246 if(!CheckClientId(ClientId: Victim) || !pSelf->m_apPlayers[Victim])
247 {
248 log_info("mutes", "Client ID not found: %d", Victim);
249 return;
250 }
251
252 pSelf->m_Mutes.UnmuteAddr(pAddr: pSelf->Server()->ClientAddr(ClientId: Victim));
253}
254
255void CGameContext::ConUnmuteIp(IConsole::IResult *pResult, void *pUserData)
256{
257 CGameContext *pSelf = (CGameContext *)pUserData;
258
259 NETADDR Addr;
260 if(net_addr_from_str(addr: &Addr, string: pResult->GetString(Index: 0)) != 0)
261 {
262 log_info("mutes", "Invalid IP address to unmute: %s", pResult->GetString(0));
263 return;
264 }
265
266 pSelf->m_Mutes.UnmuteAddr(pAddr: &Addr);
267}
268
269void CGameContext::ConMutes(IConsole::IResult *pResult, void *pUserData)
270{
271 CGameContext *pSelf = (CGameContext *)pUserData;
272
273 pSelf->m_Mutes.Print(Page: pResult->NumArguments() > 0 ? pResult->GetInteger(Index: 0) : 1);
274}
275
276void CGameContext::ConVoteMute(IConsole::IResult *pResult, void *pUserData)
277{
278 log_info("votemutes", "Use either 'vote_muteid <client_id> <seconds> <reason>' or 'vote_muteip <ip> <seconds> <reason>'");
279}
280
281void CGameContext::ConVoteMuteId(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("votemutes", "Client ID not found: %d", Victim);
289 return;
290 }
291
292 const char *pReason = pResult->NumArguments() > 2 ? pResult->GetString(Index: 2) : "";
293 pSelf->VoteMuteWithMessage(pAddr: pSelf->Server()->ClientAddr(ClientId: Victim), Seconds: pResult->GetInteger(Index: 1), pReason, pDisplayName: pSelf->Server()->ClientName(ClientId: Victim));
294}
295
296void CGameContext::ConVoteMuteIp(IConsole::IResult *pResult, void *pUserData)
297{
298 CGameContext *pSelf = (CGameContext *)pUserData;
299
300 NETADDR Addr;
301 if(net_addr_from_str(addr: &Addr, string: pResult->GetString(Index: 0)) != 0)
302 {
303 log_info("votemutes", "Invalid IP address to mute: %s", pResult->GetString(0));
304 return;
305 }
306
307 const char *pReason = pResult->NumArguments() > 2 ? pResult->GetString(Index: 2) : "";
308 pSelf->m_VoteMutes.Mute(pAddr: &Addr, Seconds: pResult->GetInteger(Index: 1), pReason, InitialDelay: false);
309}
310
311void CGameContext::ConVoteUnmute(IConsole::IResult *pResult, void *pUserData)
312{
313 CGameContext *pSelf = (CGameContext *)pUserData;
314
315 pSelf->m_VoteMutes.UnmuteIndex(Index: pResult->GetInteger(Index: 0));
316}
317
318void CGameContext::ConVoteUnmuteId(IConsole::IResult *pResult, void *pUserData)
319{
320 CGameContext *pSelf = (CGameContext *)pUserData;
321
322 const int Victim = pResult->GetVictim();
323 if(!CheckClientId(ClientId: Victim) || !pSelf->m_apPlayers[Victim])
324 {
325 log_info("votemutes", "Client ID not found: %d", Victim);
326 return;
327 }
328
329 pSelf->m_VoteMutes.UnmuteAddr(pAddr: pSelf->Server()->ClientAddr(ClientId: Victim));
330}
331
332void CGameContext::ConVoteUnmuteIp(IConsole::IResult *pResult, void *pUserData)
333{
334 CGameContext *pSelf = (CGameContext *)pUserData;
335
336 NETADDR Addr;
337 if(net_addr_from_str(addr: &Addr, string: pResult->GetString(Index: 0)) != 0)
338 {
339 log_info("votemutes", "Invalid IP address to unmute: %s", pResult->GetString(0));
340 return;
341 }
342
343 pSelf->m_VoteMutes.UnmuteAddr(pAddr: &Addr);
344}
345
346void CGameContext::ConVoteMutes(IConsole::IResult *pResult, void *pUserData)
347{
348 CGameContext *pSelf = (CGameContext *)pUserData;
349
350 pSelf->m_VoteMutes.Print(Page: pResult->NumArguments() > 0 ? pResult->GetInteger(Index: 0) : 1);
351}
352