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#include "player.h"
4#include "score.h"
5
6#include <base/log.h>
7#include <base/time.h>
8
9#include <engine/shared/config.h>
10#include <engine/shared/protocol.h>
11
12#include <game/mapitems.h>
13#include <game/server/entities/character.h>
14#include <game/server/gamemodes/ddnet.h>
15#include <game/server/teams.h>
16#include <game/team_state.h>
17#include <game/teamscore.h>
18#include <game/version.h>
19
20void CGameContext::ConInfo(IConsole::IResult *pResult, void *pUserData)
21{
22 log_info("chatresp", "DDraceNetwork Mod. Version: " GAME_VERSION);
23 if(GIT_SHORTREV_HASH)
24 {
25 char aBuf[64];
26 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Git revision hash: %s", GIT_SHORTREV_HASH);
27 log_info("chatresp", "%s", aBuf);
28 }
29 log_info("chatresp", "Official site: DDNet.org");
30 log_info("chatresp", "For more info: /cmdlist");
31 log_info("chatresp", "Or visit DDNet.org");
32}
33
34void CGameContext::ConList(IConsole::IResult *pResult, void *pUserData)
35{
36 CGameContext *pSelf = (CGameContext *)pUserData;
37 int ClientId = pResult->m_ClientId;
38 if(!CheckClientId(ClientId))
39 return;
40
41 if(pResult->NumArguments() > 0)
42 pSelf->List(ClientId, pFilter: pResult->GetString(Index: 0));
43 else
44 pSelf->List(ClientId, pFilter: "");
45}
46
47void CGameContext::ConHelp(IConsole::IResult *pResult, void *pUserData)
48{
49 CGameContext *pSelf = (CGameContext *)pUserData;
50
51 if(pResult->NumArguments() == 0)
52 {
53 log_info("chatresp", "/cmdlist will show a list of all chat commands");
54 log_info("chatresp", "/help + any command will show you the help for this command");
55 log_info("chatresp", "Example /help settings will display the help about /settings");
56 }
57 else
58 {
59 const char *pArg = pResult->GetString(Index: 0);
60 const IConsole::ICommandInfo *pCmdInfo =
61 pSelf->Console()->GetCommandInfo(pName: pArg, FlagMask: CFGFLAG_SERVER | CFGFLAG_CHAT, Temp: false);
62 if(pCmdInfo)
63 {
64 if(pCmdInfo->Params())
65 {
66 char aBuf[256];
67 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Usage: %s %s", pCmdInfo->Name(), pCmdInfo->Params());
68 log_info("chatresp", "%s", aBuf);
69 }
70
71 if(pCmdInfo->Help())
72 log_info("chatresp", "%s", pCmdInfo->Help());
73 }
74 else
75 {
76 char aBuf[256];
77 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Unknown command %s", pArg);
78 log_info("chatresp", "%s", aBuf);
79 }
80 }
81}
82
83void CGameContext::ConSettings(IConsole::IResult *pResult, void *pUserData)
84{
85 CGameContext *pSelf = (CGameContext *)pUserData;
86
87 if(pResult->NumArguments() == 0)
88 {
89 log_info("chatresp", "to check a server setting say /settings and setting's name, setting names are:");
90 log_info("chatresp", "teams, cheats, collision, hooking, endlesshooking,");
91 log_info("chatresp", "hitting, oldlaser, timeout, votes, pause and scores");
92 }
93 else
94 {
95 const char *pArg = pResult->GetString(Index: 0);
96 char aBuf[256];
97 float ColTemp;
98 float HookTemp;
99 pSelf->GlobalTuning()->Get(pName: "player_collision", pValue: &ColTemp);
100 pSelf->GlobalTuning()->Get(pName: "player_hooking", pValue: &HookTemp);
101 if(str_comp_nocase(a: pArg, b: "teams") == 0)
102 {
103 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s %s",
104 g_Config.m_SvTeam == SV_TEAM_ALLOWED ?
105 "Teams are available on this server" :
106 (g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO) ?
107 "Teams are not available on this server" :
108 "You have to be in a team to play on this server", /*g_Config.m_SvTeamStrict ? "and if you die in a team all of you die" : */
109 "and all of your team will die if the team is locked");
110 log_info("chatresp", "%s", aBuf);
111 }
112 else if(str_comp_nocase(a: pArg, b: "cheats") == 0)
113 {
114 log_info("chatresp", g_Config.m_SvTestingCommands ?
115 "Cheats are enabled on this server" :
116 "Cheats are disabled on this server");
117 }
118 else if(str_comp_nocase(a: pArg, b: "collision") == 0)
119 {
120 log_info("chatresp", ColTemp ?
121 "Players can collide on this server" :
122 "Players can't collide on this server");
123 }
124 else if(str_comp_nocase(a: pArg, b: "hooking") == 0)
125 {
126 log_info("chatresp", HookTemp ?
127 "Players can hook each other on this server" :
128 "Players can't hook each other on this server");
129 }
130 else if(str_comp_nocase(a: pArg, b: "endlesshooking") == 0)
131 {
132 log_info("chatresp", g_Config.m_SvEndlessDrag ?
133 "Players hook time is unlimited" :
134 "Players hook time is limited");
135 }
136 else if(str_comp_nocase(a: pArg, b: "hitting") == 0)
137 {
138 log_info("chatresp", g_Config.m_SvHit ?
139 "Players weapons affect others" :
140 "Players weapons has no affect on others");
141 }
142 else if(str_comp_nocase(a: pArg, b: "oldlaser") == 0)
143 {
144 log_info("chatresp", g_Config.m_SvOldLaser ?
145 "Lasers can hit you if you shot them and they pull you towards the bounce origin (Like DDRace Beta)" :
146 "Lasers can't hit you if you shot them, and they pull others towards the shooter");
147 }
148 else if(str_comp_nocase(a: pArg, b: "timeout") == 0)
149 {
150 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "The Server Timeout is currently set to %d seconds", g_Config.m_ConnTimeout);
151 log_info("chatresp", "%s", aBuf);
152 }
153 else if(str_comp_nocase(a: pArg, b: "votes") == 0)
154 {
155 log_info("chatresp", g_Config.m_SvVoteKick ?
156 "Players can use Callvote menu tab to kick offenders" :
157 "Players can't use the Callvote menu tab to kick offenders");
158 if(g_Config.m_SvVoteKick)
159 {
160 str_format(buffer: aBuf, buffer_size: sizeof(aBuf),
161 format: "Players are banned for %d minute(s) if they get voted off", g_Config.m_SvVoteKickBantime);
162
163 log_info("chatresp", "%s", g_Config.m_SvVoteKickBantime ? aBuf : "Players are just kicked and not banned if they get voted off");
164 }
165 }
166 else if(str_comp_nocase(a: pArg, b: "pause") == 0)
167 {
168 log_info("chatresp", g_Config.m_SvPauseable ?
169 "/spec will pause you and your tee will vanish" :
170 "/spec will pause you but your tee will not vanish");
171 }
172 else if(str_comp_nocase(a: pArg, b: "scores") == 0)
173 {
174 log_info("chatresp", g_Config.m_SvHideScore ?
175 "Scores are private on this server" :
176 "Scores are public on this server");
177 }
178 else
179 {
180 log_info("chatresp", "no matching settings found, type /settings to view them");
181 }
182 }
183}
184
185void CGameContext::ConRules(IConsole::IResult *pResult, void *pUserData)
186{
187 bool Printed = false;
188 if(g_Config.m_SvDDRaceRules)
189 {
190 log_info("chatresp", "Be nice.");
191 Printed = true;
192 }
193 char *apRuleLines[] = {
194 g_Config.m_SvRulesLine1,
195 g_Config.m_SvRulesLine2,
196 g_Config.m_SvRulesLine3,
197 g_Config.m_SvRulesLine4,
198 g_Config.m_SvRulesLine5,
199 g_Config.m_SvRulesLine6,
200 g_Config.m_SvRulesLine7,
201 g_Config.m_SvRulesLine8,
202 g_Config.m_SvRulesLine9,
203 g_Config.m_SvRulesLine10,
204 };
205 for(auto &pRuleLine : apRuleLines)
206 {
207 if(pRuleLine[0])
208 {
209 log_info("chatresp", "%s", pRuleLine);
210 Printed = true;
211 }
212 }
213 if(!Printed)
214 {
215 log_info("chatresp", "No Rules Defined, Kill em all!!");
216 }
217}
218
219static void ToggleSpecPause(IConsole::IResult *pResult, void *pUserData, int PauseType)
220{
221 if(!CheckClientId(ClientId: pResult->m_ClientId))
222 return;
223
224 CGameContext *pSelf = (CGameContext *)pUserData;
225 IServer *pServ = pSelf->Server();
226 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
227 if(!pPlayer)
228 return;
229
230 int PauseState = pPlayer->IsPaused();
231 if(PauseState > 0)
232 {
233 char aBuf[128];
234 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "You are force-paused for %d seconds.", (PauseState - pServ->Tick()) / pServ->TickSpeed());
235 log_info("chatresp", "%s", aBuf);
236 }
237 else if(pResult->NumArguments() > 0)
238 {
239 if(-PauseState == PauseType && pPlayer->SpectatorId() != pResult->m_ClientId && pServ->ClientIngame(ClientId: pPlayer->SpectatorId()) && !str_comp(a: pServ->ClientName(ClientId: pPlayer->SpectatorId()), b: pResult->GetString(Index: 0)))
240 {
241 pPlayer->Pause(State: CPlayer::PAUSE_NONE, Force: false);
242 }
243 else
244 {
245 pPlayer->Pause(State: PauseType, Force: false);
246 pPlayer->SpectatePlayerName(pName: pResult->GetString(Index: 0));
247 }
248 }
249 else if(-PauseState != CPlayer::PAUSE_NONE && PauseType != CPlayer::PAUSE_NONE)
250 {
251 pPlayer->Pause(State: CPlayer::PAUSE_NONE, Force: false);
252 }
253 else if(-PauseState != PauseType)
254 {
255 pPlayer->Pause(State: PauseType, Force: false);
256 }
257}
258
259static void ToggleSpecPauseVoted(IConsole::IResult *pResult, void *pUserData, int PauseType)
260{
261 if(!CheckClientId(ClientId: pResult->m_ClientId))
262 return;
263
264 CGameContext *pSelf = (CGameContext *)pUserData;
265 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
266 if(!pPlayer)
267 return;
268
269 int PauseState = pPlayer->IsPaused();
270 if(PauseState > 0)
271 {
272 IServer *pServ = pSelf->Server();
273 char aBuf[128];
274 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "You are force-paused for %d seconds.", (PauseState - pServ->Tick()) / pServ->TickSpeed());
275 log_info("chatresp", "%s", aBuf);
276 return;
277 }
278
279 bool IsPlayerBeingVoted = pSelf->m_VoteCloseTime &&
280 (pSelf->IsKickVote() || pSelf->IsSpecVote()) &&
281 pResult->m_ClientId != pSelf->m_VoteVictim;
282 if((!IsPlayerBeingVoted && -PauseState == PauseType) ||
283 (IsPlayerBeingVoted && PauseState && pPlayer->SpectatorId() == pSelf->m_VoteVictim))
284 {
285 pPlayer->Pause(State: CPlayer::PAUSE_NONE, Force: false);
286 }
287 else
288 {
289 pPlayer->Pause(State: PauseType, Force: false);
290 if(IsPlayerBeingVoted)
291 pPlayer->SetSpectatorId(pSelf->m_VoteVictim);
292 }
293}
294
295void CGameContext::ConToggleSpec(IConsole::IResult *pResult, void *pUserData)
296{
297 if(!CheckClientId(ClientId: pResult->m_ClientId))
298 return;
299
300 CGameContext *pSelf = (CGameContext *)pUserData;
301 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
302 if(!pPlayer)
303 return;
304
305 int PauseType = g_Config.m_SvPauseable ? CPlayer::PAUSE_SPEC : CPlayer::PAUSE_PAUSED;
306
307 if(pPlayer->GetCharacter())
308 {
309 CGameTeams &Teams = pSelf->m_pController->Teams();
310 if(Teams.IsPractice(Team: Teams.m_Core.Team(ClientId: pResult->m_ClientId)))
311 PauseType = CPlayer::PAUSE_SPEC;
312 }
313
314 ToggleSpecPause(pResult, pUserData, PauseType);
315}
316
317void CGameContext::ConToggleSpecVoted(IConsole::IResult *pResult, void *pUserData)
318{
319 ToggleSpecPauseVoted(pResult, pUserData, PauseType: g_Config.m_SvPauseable ? CPlayer::PAUSE_SPEC : CPlayer::PAUSE_PAUSED);
320}
321
322void CGameContext::ConTogglePause(IConsole::IResult *pResult, void *pUserData)
323{
324 ToggleSpecPause(pResult, pUserData, PauseType: CPlayer::PAUSE_PAUSED);
325}
326
327void CGameContext::ConTogglePauseVoted(IConsole::IResult *pResult, void *pUserData)
328{
329 ToggleSpecPauseVoted(pResult, pUserData, PauseType: CPlayer::PAUSE_PAUSED);
330}
331
332void CGameContext::ConTeamTop5(IConsole::IResult *pResult, void *pUserData)
333{
334 CGameContext *pSelf = (CGameContext *)pUserData;
335 if(!CheckClientId(ClientId: pResult->m_ClientId))
336 return;
337
338 if(g_Config.m_SvHideScore)
339 {
340 log_info("chatresp", "Showing the team top 5 is not allowed on this server.");
341 return;
342 }
343
344 if(pResult->NumArguments() == 0)
345 {
346 pSelf->Score()->ShowTeamTop5(ClientId: pResult->m_ClientId, Offset: 1);
347 }
348 else if(pResult->NumArguments() == 1)
349 {
350 if(pResult->GetInteger(Index: 0) != 0)
351 {
352 pSelf->Score()->ShowTeamTop5(ClientId: pResult->m_ClientId, Offset: pResult->GetInteger(Index: 0));
353 }
354 else
355 {
356 const char *pRequestedName = (str_comp_nocase(a: pResult->GetString(Index: 0), b: "me") == 0) ?
357 pSelf->Server()->ClientName(ClientId: pResult->m_ClientId) :
358 pResult->GetString(Index: 0);
359 pSelf->Score()->ShowPlayerTeamTop5(ClientId: pResult->m_ClientId, pName: pRequestedName, Offset: 0);
360 }
361 }
362 else if(pResult->NumArguments() == 2 && pResult->GetInteger(Index: 1) != 0)
363 {
364 const char *pRequestedName = (str_comp_nocase(a: pResult->GetString(Index: 0), b: "me") == 0) ?
365 pSelf->Server()->ClientName(ClientId: pResult->m_ClientId) :
366 pResult->GetString(Index: 0);
367 pSelf->Score()->ShowPlayerTeamTop5(ClientId: pResult->m_ClientId, pName: pRequestedName, Offset: pResult->GetInteger(Index: 1));
368 }
369 else
370 {
371 log_info("chatresp", "/top5team needs 0, 1 or 2 parameter. 1. = name, 2. = start number");
372 log_info("chatresp", "Example: /top5team, /top5team me, /top5team Hans, /top5team \"Papa Smurf\" 5");
373 log_info("chatresp", "Bad: /top5team Papa Smurf 5 # Good: /top5team \"Papa Smurf\" 5 ");
374 }
375}
376
377void CGameContext::ConTop(IConsole::IResult *pResult, void *pUserData)
378{
379 CGameContext *pSelf = (CGameContext *)pUserData;
380 if(!CheckClientId(ClientId: pResult->m_ClientId))
381 return;
382
383 if(g_Config.m_SvHideScore)
384 {
385 log_info("chatresp", "Showing the top is not allowed on this server.");
386 return;
387 }
388
389 if(pResult->NumArguments() > 0)
390 pSelf->Score()->ShowTop(ClientId: pResult->m_ClientId, Offset: pResult->GetInteger(Index: 0));
391 else
392 pSelf->Score()->ShowTop(ClientId: pResult->m_ClientId);
393}
394
395void CGameContext::ConTimes(IConsole::IResult *pResult, void *pUserData)
396{
397 CGameContext *pSelf = (CGameContext *)pUserData;
398 if(!CheckClientId(ClientId: pResult->m_ClientId))
399 return;
400
401 int Offset = 1;
402 const char *pRequestedName = nullptr;
403
404 // input validation
405 if(pResult->NumArguments() == 1)
406 {
407 if(pResult->GetInteger(Index: 0) != 0)
408 {
409 Offset = pResult->GetInteger(Index: 0);
410 }
411 else
412 {
413 pRequestedName = pResult->GetString(Index: 0);
414 }
415 }
416 else if(pResult->NumArguments() == 2)
417 {
418 pRequestedName = pResult->GetString(Index: 0);
419 Offset = pResult->GetInteger(Index: 1);
420 }
421 else if(pResult->NumArguments() > 2)
422 {
423 log_info("chatresp", "/times needs 0, 1 or 2 parameter. 1. = name, 2. = start number");
424 log_info("chatresp", "Example: /times, /times me, /times Hans, /times \"Papa Smurf\" 5");
425 log_info("chatresp", "Bad: /times Papa Smurf 5 # Good: /times \"Papa Smurf\" 5 ");
426 return;
427 }
428
429 // execution
430 if(g_Config.m_SvHideScore)
431 {
432 if(pRequestedName && str_comp_nocase(a: pRequestedName, b: "me") != 0 && str_comp_nocase(a: pRequestedName, b: pSelf->Server()->ClientName(ClientId: pResult->m_ClientId)) != 0)
433 {
434 log_info("chatresp", "Showing the times of others is not allowed on this server.");
435 return;
436 }
437 pRequestedName = pSelf->Server()->ClientName(ClientId: pResult->m_ClientId);
438 pSelf->Score()->ShowTimes(ClientId: pResult->m_ClientId, pName: pRequestedName, Offset);
439 }
440 else if(!pRequestedName)
441 {
442 pSelf->Score()->ShowTimes(ClientId: pResult->m_ClientId, Offset);
443 }
444 else
445 {
446 if(str_comp_nocase(a: pRequestedName, b: "me") == 0)
447 pRequestedName = pSelf->Server()->ClientName(ClientId: pResult->m_ClientId);
448 pSelf->Score()->ShowTimes(ClientId: pResult->m_ClientId, pName: pRequestedName, Offset);
449 }
450}
451
452void CGameContext::ConDND(IConsole::IResult *pResult, void *pUserData)
453{
454 CGameContext *pSelf = (CGameContext *)pUserData;
455 if(!CheckClientId(ClientId: pResult->m_ClientId))
456 return;
457
458 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
459 if(!pPlayer)
460 return;
461
462 pPlayer->m_DND = pResult->NumArguments() == 0 ? !pPlayer->m_DND : pResult->GetInteger(Index: 0);
463 log_info("chatresp", pPlayer->m_DND ? "You will not receive any further global chat and server messages" : "You will receive global chat and server messages");
464}
465
466void CGameContext::ConWhispers(IConsole::IResult *pResult, void *pUserData)
467{
468 CGameContext *pSelf = (CGameContext *)pUserData;
469 if(!CheckClientId(ClientId: pResult->m_ClientId))
470 return;
471
472 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
473 if(!pPlayer)
474 return;
475
476 pPlayer->m_Whispers = pResult->NumArguments() == 0 ? !pPlayer->m_Whispers : pResult->GetInteger(Index: 0);
477 log_info("chatresp", pPlayer->m_Whispers ? "You will receive whispers" : "You will not receive any further whispers");
478}
479
480void CGameContext::ConMap(IConsole::IResult *pResult, void *pUserData)
481{
482 CGameContext *pSelf = (CGameContext *)pUserData;
483 if(!CheckClientId(ClientId: pResult->m_ClientId))
484 return;
485
486 if(g_Config.m_SvMapVote == 0)
487 {
488 log_info("chatresp", "/map is disabled");
489 return;
490 }
491
492 if(pResult->NumArguments() <= 0)
493 {
494 log_info("chatresp", "Example: /map adr3 to call vote for Adrenaline 3. This means that the map name must start with 'a' and contain the characters 'd', 'r' and '3' in that order");
495 return;
496 }
497
498 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
499 if(!pPlayer)
500 return;
501
502 if(pSelf->RateLimitPlayerVote(ClientId: pResult->m_ClientId) || pSelf->RateLimitPlayerMapVote(ClientId: pResult->m_ClientId))
503 return;
504
505 pSelf->Score()->MapVote(ClientId: pResult->m_ClientId, pMapName: pResult->GetString(Index: 0));
506}
507
508void CGameContext::ConMapInfo(IConsole::IResult *pResult, void *pUserData)
509{
510 CGameContext *pSelf = (CGameContext *)pUserData;
511 if(!CheckClientId(ClientId: pResult->m_ClientId))
512 return;
513
514 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
515 if(!pPlayer)
516 return;
517
518 // use cached map info for current map
519 const bool IsCurrentMap = pResult->NumArguments() == 0 || str_comp_nocase(a: pResult->GetString(Index: 0), b: pSelf->Map()->BaseName()) == 0;
520 if(IsCurrentMap && pSelf->m_aMapInfoMessage[0] != '\0')
521 {
522 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: pSelf->m_aMapInfoMessage);
523 return;
524 }
525
526 if(pResult->NumArguments() > 0)
527 pSelf->Score()->MapInfo(ClientId: pResult->m_ClientId, pMapName: pResult->GetString(Index: 0));
528 else
529 pSelf->Score()->MapInfo(ClientId: pResult->m_ClientId, pMapName: pSelf->Map()->BaseName());
530}
531
532void CGameContext::ConTimeout(IConsole::IResult *pResult, void *pUserData)
533{
534 CGameContext *pSelf = (CGameContext *)pUserData;
535 if(!CheckClientId(ClientId: pResult->m_ClientId))
536 return;
537
538 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
539 if(!pPlayer)
540 return;
541
542 const char *pTimeout = pResult->NumArguments() > 0 ? pResult->GetString(Index: 0) : pPlayer->m_aTimeoutCode;
543
544 for(int i = 0; i < pSelf->Server()->MaxClients(); i++)
545 {
546 if(i == pResult->m_ClientId)
547 continue;
548 if(!pSelf->m_apPlayers[i])
549 continue;
550 if(str_comp(a: pSelf->m_apPlayers[i]->m_aTimeoutCode, b: pTimeout))
551 continue;
552 if(pSelf->Server()->SetTimedOut(ClientId: i, OrigId: pResult->m_ClientId))
553 {
554 if(pSelf->m_apPlayers[i]->GetCharacter())
555 pSelf->SendTuningParams(ClientId: i, Zone: pSelf->m_apPlayers[i]->GetCharacter()->m_TuneZone);
556 return;
557 }
558 }
559
560 pSelf->Server()->SetTimeoutProtected(pResult->m_ClientId);
561 str_copy(dst&: pPlayer->m_aTimeoutCode, src: pResult->GetString(Index: 0));
562}
563
564void CGameContext::ConPractice(IConsole::IResult *pResult, void *pUserData)
565{
566 CGameContext *pSelf = (CGameContext *)pUserData;
567 if(!CheckClientId(ClientId: pResult->m_ClientId))
568 return;
569
570 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
571 if(!pPlayer)
572 return;
573
574 if(pSelf->ProcessSpamProtection(ClientId: pResult->m_ClientId, RespectChatInitialDelay: false))
575 return;
576
577 if(!g_Config.m_SvPractice)
578 {
579 log_info("chatresp", "Practice mode is disabled");
580 return;
581 }
582
583 CGameTeams &Teams = pSelf->m_pController->Teams();
584
585 int Team = Teams.m_Core.Team(ClientId: pResult->m_ClientId);
586
587 if(!Teams.IsValidTeamNumber(Team) || (Team == TEAM_FLOCK && g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO))
588 {
589 log_info("chatresp", "Join a team to enable practice mode, which means you can use /r, but can't earn a rank.");
590 return;
591 }
592
593 if(Teams.TeamFlock(Team))
594 {
595 log_info("chatresp", "Practice mode can't be enabled in team 0 mode.");
596 return;
597 }
598
599 if(Teams.GetSaving(TeamId: Team))
600 {
601 log_info("chatresp", "Practice mode can't be enabled while team save or load is in progress");
602 return;
603 }
604
605 if(Teams.IsPractice(Team))
606 {
607 log_info("chatresp", "Team is already in practice mode");
608 return;
609 }
610
611 bool VotedForPractice = pResult->NumArguments() == 0 || pResult->GetInteger(Index: 0);
612
613 if(VotedForPractice == pPlayer->m_VotedForPractice)
614 return;
615
616 pPlayer->m_VotedForPractice = VotedForPractice;
617
618 int NumCurrentVotes = 0;
619 int TeamSize = 0;
620
621 for(int i = 0; i < MAX_CLIENTS; i++)
622 {
623 if(Teams.m_Core.Team(ClientId: i) == Team)
624 {
625 CPlayer *pPlayer2 = pSelf->m_apPlayers[i];
626 if(pPlayer2 && pPlayer2->m_VotedForPractice)
627 NumCurrentVotes++;
628 TeamSize++;
629 }
630 }
631
632 int NumRequiredVotes = TeamSize / 2 + 1;
633
634 char aBuf[512];
635 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' voted to %s /practice mode for your team, which means you can use practice commands, but you can't earn a rank. Type /practice to vote (%d/%d required votes)", pSelf->Server()->ClientName(ClientId: pResult->m_ClientId), VotedForPractice ? "enable" : "disable", NumCurrentVotes, NumRequiredVotes);
636 pSelf->SendChatTeam(Team, pText: aBuf);
637
638 if(NumCurrentVotes >= NumRequiredVotes)
639 {
640 Teams.SetPractice(Team, Enabled: true);
641 pSelf->SendChatTeam(Team, pText: "Practice mode enabled for your team, happy practicing!");
642 pSelf->SendChatTeam(Team, pText: "See /practicecmdlist for a list of all available practice commands. Most commonly used ones are /telecursor, /lasttp and /rescue");
643 }
644}
645
646void CGameContext::ConUnPractice(IConsole::IResult *pResult, void *pUserData)
647{
648 CGameContext *pSelf = (CGameContext *)pUserData;
649 if(!CheckClientId(ClientId: pResult->m_ClientId))
650 return;
651
652 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
653 if(!pPlayer)
654 return;
655
656 if(pSelf->ProcessSpamProtection(ClientId: pResult->m_ClientId, RespectChatInitialDelay: false))
657 return;
658
659 CGameTeams &Teams = pSelf->m_pController->Teams();
660
661 int Team = Teams.m_Core.Team(ClientId: pResult->m_ClientId);
662
663 if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && Team == TEAM_FLOCK)
664 {
665 log_info("chatresp", "Practice mode can't be disabled for team 0");
666 return;
667 }
668
669 if(!Teams.IsPractice(Team))
670 {
671 log_info("chatresp", "Team isn't in practice mode");
672 return;
673 }
674
675 if(Teams.GetSaving(TeamId: Team))
676 {
677 log_info("chatresp", "Practice mode can't be disabled while team save or load is in progress");
678 return;
679 }
680
681 if(Teams.TeamSize(Team) > g_Config.m_SvMaxTeamSize && pSelf->m_pController->Teams().TeamLocked(Team))
682 {
683 log_info("chatresp", "Can't disable practice. This team exceeds the maximum allowed size of %d players for regular team", g_Config.m_SvMaxTeamSize);
684 return;
685 }
686
687 for(int i = 0; i < MAX_CLIENTS; i++)
688 {
689 if(Teams.m_Core.Team(ClientId: i) == Team)
690 {
691 CPlayer *pPlayer2 = pSelf->m_apPlayers[i];
692 if(pPlayer2)
693 {
694 if(pPlayer2->m_VotedForPractice)
695 pPlayer2->m_VotedForPractice = false;
696
697 if(!g_Config.m_SvPauseable && pPlayer2->IsPaused() == -1 * CPlayer::PAUSE_SPEC)
698 pPlayer2->Pause(State: CPlayer::PAUSE_PAUSED, Force: true);
699 }
700 }
701 }
702
703 // send before kill, in case team isn't locked
704 char aBuf[256];
705 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' disabled practice mode for your team", pSelf->Server()->ClientName(ClientId: pResult->m_ClientId));
706 pSelf->SendChatTeam(Team, pText: aBuf);
707
708 Teams.KillCharacterOrTeam(ClientId: pResult->m_ClientId, Team);
709 Teams.SetPractice(Team, Enabled: false);
710 pPlayer->Respawn(); // set spawn as strong hook
711}
712
713void CGameContext::ConPracticeCmdList(IConsole::IResult *pResult, void *pUserData)
714{
715 CGameContext *pSelf = (CGameContext *)pUserData;
716
717 char aPracticeCommands[256] = "Available practice commands: ";
718 for(const IConsole::ICommandInfo *pCmd = pSelf->Console()->FirstCommandInfo(ClientId: pResult->m_ClientId, FlagMask: CMDFLAG_PRACTICE);
719 pCmd; pCmd = pSelf->Console()->NextCommandInfo(pInfo: pCmd, ClientId: pResult->m_ClientId, FlagMask: CMDFLAG_PRACTICE))
720 {
721 char aCommand[64];
722
723 str_format(buffer: aCommand, buffer_size: sizeof(aCommand), format: "/%s%s", pCmd->Name(), pSelf->Console()->NextCommandInfo(pInfo: pCmd, ClientId: pResult->m_ClientId, FlagMask: CMDFLAG_PRACTICE) ? ", " : "");
724
725 if(str_length(str: aCommand) + str_length(str: aPracticeCommands) > 255)
726 {
727 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: aPracticeCommands);
728 aPracticeCommands[0] = '\0';
729 }
730 str_append(dst&: aPracticeCommands, src: aCommand);
731 }
732 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: aPracticeCommands);
733}
734
735void CGameContext::ConSwap(IConsole::IResult *pResult, void *pUserData)
736{
737 CGameContext *pSelf = (CGameContext *)pUserData;
738 const char *pName = pResult->GetString(Index: 0);
739
740 if(!CheckClientId(ClientId: pResult->m_ClientId))
741 return;
742
743 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
744 if(!pPlayer)
745 return;
746
747 if(!g_Config.m_SvSwap)
748 {
749 log_info("chatresp", "Swap is disabled on this server.");
750 return;
751 }
752
753 if(g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
754 {
755 log_info("chatresp", "Swap is not available on forced solo servers.");
756 return;
757 }
758
759 CGameTeams &Teams = pSelf->m_pController->Teams();
760
761 int Team = Teams.m_Core.Team(ClientId: pResult->m_ClientId);
762
763 if(Team == TEAM_SUPER)
764 {
765 log_info(
766 "chatresp",
767 "Turn off super to use swap feature, which means you can swap positions with each other.");
768 return;
769 }
770
771 int TargetClientId = -1;
772 if(pResult->NumArguments() == 1)
773 {
774 TargetClientId = pSelf->FindClientIdByName(pName).value_or(u: -1);
775 }
776 else
777 {
778 int TeamSize = 1;
779 for(int i = 0; i < MAX_CLIENTS; i++)
780 {
781 if(pSelf->m_apPlayers[i] && Teams.m_Core.Team(ClientId: i) == Team && i != pResult->m_ClientId)
782 {
783 TargetClientId = i;
784 TeamSize++;
785 }
786 }
787 if(TeamSize != 2)
788 TargetClientId = -1;
789 }
790
791 if(TargetClientId < 0)
792 {
793 log_info("chatresp", "Player not found");
794 return;
795 }
796
797 if(TargetClientId == pResult->m_ClientId)
798 {
799 log_info("chatresp", "Can't swap with yourself");
800 return;
801 }
802
803 int TargetTeam = Teams.m_Core.Team(ClientId: TargetClientId);
804 if(TargetTeam != Team)
805 {
806 log_info("chatresp", "Player is on a different team");
807 return;
808 }
809
810 CPlayer *pSwapPlayer = pSelf->m_apPlayers[TargetClientId];
811 if(Team == TEAM_FLOCK || Teams.TeamFlock(Team))
812 {
813 CCharacter *pChr = pPlayer->GetCharacter();
814 CCharacter *pSwapChr = pSwapPlayer->GetCharacter();
815 if(!pChr || !pSwapChr || pChr->m_DDRaceState != ERaceState::STARTED || pSwapChr->m_DDRaceState != ERaceState::STARTED)
816 {
817 log_info("chatresp", "You and other player need to have started the map");
818 return;
819 }
820 }
821 else if(!Teams.IsStarted(Team) && !Teams.TeamFlock(Team))
822 {
823 log_info("chatresp", "Need to have started the map to swap with a player.");
824 return;
825 }
826 if(pSelf->m_World.m_Core.m_apCharacters[pResult->m_ClientId] == nullptr || pSelf->m_World.m_Core.m_apCharacters[TargetClientId] == nullptr)
827 {
828 log_info("chatresp", "You and the other player must not be paused.");
829 return;
830 }
831
832 bool SwapPending = pSwapPlayer->m_SwapTargetsClientId != pResult->m_ClientId;
833 if(SwapPending)
834 {
835 if(pSelf->ProcessSpamProtection(ClientId: pResult->m_ClientId))
836 return;
837
838 Teams.RequestTeamSwap(pPlayer, pTargetPlayer: pSwapPlayer, Team);
839 return;
840 }
841
842 Teams.SwapTeamCharacters(pPrimaryPlayer: pPlayer, pTargetPlayer: pSwapPlayer, Team);
843}
844
845void CGameContext::ConCancelSwap(IConsole::IResult *pResult, void *pUserData)
846{
847 CGameContext *pSelf = (CGameContext *)pUserData;
848
849 if(!CheckClientId(ClientId: pResult->m_ClientId))
850 return;
851
852 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
853 if(!pPlayer)
854 return;
855
856 if(!g_Config.m_SvSwap)
857 {
858 log_info("chatresp", "Swap is disabled on this server.");
859 return;
860 }
861
862 if(g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
863 {
864 log_info("chatresp", "Swap is not available on forced solo servers.");
865 return;
866 }
867
868 CGameTeams &Teams = pSelf->m_pController->Teams();
869
870 int Team = Teams.m_Core.Team(ClientId: pResult->m_ClientId);
871
872 bool SwapPending = pPlayer->m_SwapTargetsClientId != -1 && !pSelf->Server()->ClientSlotEmpty(ClientId: pPlayer->m_SwapTargetsClientId);
873
874 if(!SwapPending)
875 {
876 log_info("chatresp", "You do not have a pending swap request.");
877 return;
878 }
879
880 Teams.CancelTeamSwap(pPlayer, Team);
881}
882
883void CGameContext::ConSave(IConsole::IResult *pResult, void *pUserData)
884{
885 CGameContext *pSelf = (CGameContext *)pUserData;
886 if(!CheckClientId(ClientId: pResult->m_ClientId))
887 return;
888
889 if(!g_Config.m_SvSaveGames)
890 {
891 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: "Save-function is disabled on this server");
892 return;
893 }
894
895 const char *pCode = "";
896 if(pResult->NumArguments() > 0)
897 pCode = pResult->GetString(Index: 0);
898
899 pSelf->Score()->SaveTeam(ClientId: pResult->m_ClientId, pCode, pServer: g_Config.m_SvSqlServerName);
900}
901
902void CGameContext::ConLoad(IConsole::IResult *pResult, void *pUserData)
903{
904 CGameContext *pSelf = (CGameContext *)pUserData;
905 if(!CheckClientId(ClientId: pResult->m_ClientId))
906 return;
907
908 if(!g_Config.m_SvSaveGames)
909 {
910 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: "Save-function is disabled on this server");
911 return;
912 }
913
914 if(pResult->NumArguments() > 0)
915 pSelf->Score()->LoadTeam(pCode: pResult->GetString(Index: 0), ClientId: pResult->m_ClientId);
916 else
917 pSelf->Score()->GetSaves(ClientId: pResult->m_ClientId);
918}
919
920void CGameContext::ConTeamRank(IConsole::IResult *pResult, void *pUserData)
921{
922 CGameContext *pSelf = (CGameContext *)pUserData;
923 if(!CheckClientId(ClientId: pResult->m_ClientId))
924 return;
925
926 if(pResult->NumArguments() > 0)
927 {
928 if(!g_Config.m_SvHideScore)
929 pSelf->Score()->ShowTeamRank(ClientId: pResult->m_ClientId, pName: pResult->GetString(Index: 0));
930 else
931 log_info("chatresp", "Showing the team rank of other players is not allowed on this server.");
932 }
933 else
934 pSelf->Score()->ShowTeamRank(ClientId: pResult->m_ClientId,
935 pName: pSelf->Server()->ClientName(ClientId: pResult->m_ClientId));
936}
937
938void CGameContext::ConRank(IConsole::IResult *pResult, void *pUserData)
939{
940 CGameContext *pSelf = (CGameContext *)pUserData;
941 if(!CheckClientId(ClientId: pResult->m_ClientId))
942 return;
943
944 if(pResult->NumArguments() > 0)
945 {
946 if(!g_Config.m_SvHideScore)
947 pSelf->Score()->ShowRank(ClientId: pResult->m_ClientId, pName: pResult->GetString(Index: 0));
948 else
949 log_info("chatresp", "Showing the rank of other players is not allowed on this server.");
950 }
951 else
952 pSelf->Score()->ShowRank(ClientId: pResult->m_ClientId,
953 pName: pSelf->Server()->ClientName(ClientId: pResult->m_ClientId));
954}
955
956void CGameContext::ConLock(IConsole::IResult *pResult, void *pUserData)
957{
958 CGameContext *pSelf = (CGameContext *)pUserData;
959 if(!CheckClientId(ClientId: pResult->m_ClientId))
960 return;
961
962 if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
963 {
964 log_info("chatresp", "Teams are disabled");
965 return;
966 }
967
968 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
969
970 bool Lock = pSelf->m_pController->Teams().TeamLocked(Team);
971
972 if(pResult->NumArguments() > 0)
973 Lock = !pResult->GetInteger(Index: 0);
974
975 if(Team == TEAM_FLOCK || !pSelf->m_pController->Teams().IsValidTeamNumber(Team))
976 {
977 log_info("chatresp", "This team can't be locked");
978 return;
979 }
980
981 if(pSelf->ProcessSpamProtection(ClientId: pResult->m_ClientId, RespectChatInitialDelay: false))
982 return;
983
984 char aBuf[512];
985 if(Lock)
986 {
987 pSelf->UnlockTeam(ClientId: pResult->m_ClientId, Team);
988 }
989 else
990 {
991 pSelf->m_pController->Teams().SetTeamLock(Team, Lock: true);
992
993 if(pSelf->m_pController->Teams().TeamFlock(Team))
994 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' locked your team.", pSelf->Server()->ClientName(ClientId: pResult->m_ClientId));
995 else
996 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' locked your team. After the race starts, killing will kill everyone in your team.", pSelf->Server()->ClientName(ClientId: pResult->m_ClientId));
997 pSelf->SendChatTeam(Team, pText: aBuf);
998 }
999}
1000
1001void CGameContext::ConUnlock(IConsole::IResult *pResult, void *pUserData)
1002{
1003 CGameContext *pSelf = (CGameContext *)pUserData;
1004 if(!CheckClientId(ClientId: pResult->m_ClientId))
1005 return;
1006
1007 if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
1008 {
1009 log_info("chatresp", "Teams are disabled");
1010 return;
1011 }
1012
1013 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1014
1015 if(Team == TEAM_FLOCK || !pSelf->m_pController->Teams().IsValidTeamNumber(Team))
1016 return;
1017
1018 if(pSelf->ProcessSpamProtection(ClientId: pResult->m_ClientId, RespectChatInitialDelay: false))
1019 return;
1020
1021 pSelf->UnlockTeam(ClientId: pResult->m_ClientId, Team);
1022}
1023
1024void CGameContext::UnlockTeam(int ClientId, int Team) const
1025{
1026 m_pController->Teams().SetTeamLock(Team, Lock: false);
1027
1028 char aBuf[512];
1029 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' unlocked your team.", Server()->ClientName(ClientId));
1030 SendChatTeam(Team, pText: aBuf);
1031}
1032
1033void CGameContext::AttemptJoinTeam(int ClientId, int Team)
1034{
1035 CPlayer *pPlayer = m_apPlayers[ClientId];
1036 if(!pPlayer)
1037 return;
1038
1039 if(IsRunningKickOrSpecVote(ClientId))
1040 {
1041 log_info("chatresp", "You are running a vote, please try again after the vote is done!");
1042 return;
1043 }
1044 else if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
1045 {
1046 log_info("chatresp", "Teams are disabled");
1047 return;
1048 }
1049 else if(g_Config.m_SvTeam == SV_TEAM_MANDATORY && Team == 0 && pPlayer->GetCharacter() && pPlayer->GetCharacter()->m_LastStartWarning < Server()->Tick() - 3 * Server()->TickSpeed())
1050 {
1051 log_info("chatresp", "You must join a team and play with somebody or else you can't play");
1052 pPlayer->GetCharacter()->m_LastStartWarning = Server()->Tick();
1053 }
1054
1055 if(!m_pController->Teams().IsValidTeamNumber(Team))
1056 {
1057 auto EmptyTeam = m_pController->Teams().GetFirstEmptyTeam();
1058 if(!EmptyTeam.has_value())
1059 {
1060 log_info("chatresp", "No empty team left.");
1061 return;
1062 }
1063 Team = EmptyTeam.value();
1064 }
1065
1066 char aError[512];
1067 if(pPlayer->m_LastDDRaceTeamChange.has_value() && pPlayer->m_LastDDRaceTeamChange.value() + (int64_t)Server()->TickSpeed() * g_Config.m_SvTeamChangeDelay > Server()->Tick())
1068 {
1069 log_info("chatresp", "You can't change teams that fast!");
1070 }
1071 else if(Team != TEAM_FLOCK && m_pController->Teams().TeamLocked(Team) && !m_pController->Teams().IsInvited(Team, ClientId))
1072 {
1073 log_info("chatresp", g_Config.m_SvInvite ?
1074 "This team is locked using /lock. Only members of the team can unlock it using /lock." :
1075 "This team is locked using /lock. Only members of the team can invite you or unlock it using /lock.");
1076 }
1077 else if(Team != TEAM_FLOCK && m_pController->Teams().TeamSize(Team) >= g_Config.m_SvMaxTeamSize && !m_pController->Teams().TeamFlock(Team) && !m_pController->Teams().IsPractice(Team))
1078 {
1079 char aBuf[512];
1080 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "This team already has the maximum allowed size of %d players", g_Config.m_SvMaxTeamSize);
1081 log_info("chatresp", "%s", aBuf);
1082 }
1083 else if(!m_pController->Teams().SetCharacterTeam(ClientId: pPlayer->GetCid(), Team, pError: aError, ErrorSize: sizeof(aError)))
1084 {
1085 log_info("chatresp", "%s", aError);
1086 }
1087 else
1088 {
1089 if(PracticeByDefault())
1090 {
1091 // joined an empty team
1092 if(m_pController->Teams().TeamSize(Team) == 1)
1093 m_pController->Teams().SetPractice(Team, Enabled: true);
1094 }
1095
1096 char aBuf[512];
1097 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' joined team %d",
1098 Server()->ClientName(ClientId: pPlayer->GetCid()),
1099 Team);
1100 SendChat(ClientId: -1, Team: TEAM_ALL, pText: aBuf);
1101 pPlayer->m_LastDDRaceTeamChange = Server()->Tick();
1102
1103 if(m_pController->Teams().IsPractice(Team))
1104 SendChatTarget(To: pPlayer->GetCid(), pText: "Practice mode enabled for your team, happy practicing!");
1105
1106 if(m_pController->Teams().TeamFlock(Team))
1107 SendChatTarget(To: pPlayer->GetCid(), pText: "Team 0 mode enabled for your team. This will make your team behave like team 0.");
1108 }
1109}
1110
1111void CGameContext::ConInvite(IConsole::IResult *pResult, void *pUserData)
1112{
1113 CGameContext *pSelf = (CGameContext *)pUserData;
1114 auto *pController = pSelf->m_pController;
1115 const char *pName = pResult->GetString(Index: 0);
1116
1117 if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
1118 {
1119 log_info("chatresp", "Teams are disabled");
1120 return;
1121 }
1122
1123 if(!g_Config.m_SvInvite)
1124 {
1125 log_info("chatresp", "Invites are disabled");
1126 return;
1127 }
1128
1129 int Team = pController->Teams().m_Core.Team(ClientId: pResult->m_ClientId);
1130 if(Team != TEAM_FLOCK && pController->Teams().IsValidTeamNumber(Team))
1131 {
1132 int Target = pSelf->FindClientIdByName(pName).value_or(u: -1);
1133 if(Target == -1)
1134 {
1135 log_info("chatresp", "Player not found");
1136 return;
1137 }
1138
1139 if(pController->Teams().IsInvited(Team, ClientId: Target))
1140 {
1141 log_info("chatresp", "Player already invited");
1142 return;
1143 }
1144
1145 if(pSelf->m_apPlayers[pResult->m_ClientId] && pSelf->m_apPlayers[pResult->m_ClientId]->m_LastInvited + g_Config.m_SvInviteFrequency * pSelf->Server()->TickSpeed() > pSelf->Server()->Tick())
1146 {
1147 log_info("chatresp", "Can't invite this quickly");
1148 return;
1149 }
1150
1151 pController->Teams().SetClientInvited(Team, ClientId: Target, Invited: true);
1152 pSelf->m_apPlayers[pResult->m_ClientId]->m_LastInvited = pSelf->Server()->Tick();
1153
1154 char aBuf[512];
1155 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' invited you to team %d. Use /team %d to join.", pSelf->Server()->ClientName(ClientId: pResult->m_ClientId), Team, Team);
1156 pSelf->SendChatTarget(To: Target, pText: aBuf);
1157
1158 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' invited '%s' to your team.", pSelf->Server()->ClientName(ClientId: pResult->m_ClientId), pSelf->Server()->ClientName(ClientId: Target));
1159 pSelf->SendChatTeam(Team, pText: aBuf);
1160 }
1161 else
1162 log_info("chatresp", "Can't invite players to this team");
1163}
1164
1165void CGameContext::ConTeam0Mode(IConsole::IResult *pResult, void *pUserData)
1166{
1167 CGameContext *pSelf = (CGameContext *)pUserData;
1168 auto *pController = pSelf->m_pController;
1169
1170 if(!CheckClientId(ClientId: pResult->m_ClientId))
1171 return;
1172
1173 if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO || g_Config.m_SvTeam == SV_TEAM_MANDATORY)
1174 {
1175 log_info("chatresp", "Team mode change disabled");
1176 return;
1177 }
1178
1179 if(!g_Config.m_SvTeam0Mode)
1180 {
1181 log_info("chatresp", "Team mode change is disabled on this server.");
1182 return;
1183 }
1184
1185 int Team = pController->Teams().m_Core.Team(ClientId: pResult->m_ClientId);
1186 bool Mode = pController->Teams().TeamFlock(Team);
1187
1188 if(Team == TEAM_FLOCK || !pController->Teams().IsValidTeamNumber(Team))
1189 {
1190 log_info("chatresp", "This team can't have the mode changed");
1191 return;
1192 }
1193
1194 if(pController->Teams().GetTeamState(Team) != ETeamState::OPEN)
1195 {
1196 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: "Team mode can't be changed while racing");
1197 return;
1198 }
1199
1200 if(pResult->NumArguments() > 0)
1201 Mode = !pResult->GetInteger(Index: 0);
1202
1203 if(pSelf->ProcessSpamProtection(ClientId: pResult->m_ClientId, RespectChatInitialDelay: false))
1204 return;
1205
1206 char aBuf[512];
1207 if(Mode)
1208 {
1209 if(pController->Teams().TeamSize(Team) > g_Config.m_SvMaxTeamSize)
1210 {
1211 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Can't disable team 0 mode. This team exceeds the maximum allowed size of %d players for regular team", g_Config.m_SvMaxTeamSize);
1212 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: aBuf);
1213 }
1214 else
1215 {
1216 pController->Teams().SetTeamFlock(Team, Mode: false);
1217
1218 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' disabled team 0 mode.", pSelf->Server()->ClientName(ClientId: pResult->m_ClientId));
1219 pSelf->SendChatTeam(Team, pText: aBuf);
1220 }
1221 }
1222 else
1223 {
1224 if(pController->Teams().IsPractice(Team))
1225 {
1226 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: "Can't enable team 0 mode with practice mode on.");
1227 }
1228 else
1229 {
1230 pController->Teams().SetTeamFlock(Team, Mode: true);
1231
1232 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' enabled team 0 mode. This will make your team behave like team 0.", pSelf->Server()->ClientName(ClientId: pResult->m_ClientId));
1233 pSelf->SendChatTeam(Team, pText: aBuf);
1234 }
1235 }
1236}
1237
1238void CGameContext::ConTeam(IConsole::IResult *pResult, void *pUserData)
1239{
1240 CGameContext *pSelf = (CGameContext *)pUserData;
1241 if(!CheckClientId(ClientId: pResult->m_ClientId))
1242 return;
1243
1244 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1245 if(!pPlayer)
1246 return;
1247
1248 if(pResult->NumArguments() > 0)
1249 {
1250 pSelf->AttemptJoinTeam(ClientId: pResult->m_ClientId, Team: pResult->GetInteger(Index: 0));
1251 }
1252 else
1253 {
1254 char aBuf[512];
1255 if(!pPlayer->IsPlaying())
1256 {
1257 log_info("chatresp", "You can't check your team while you are dead/a spectator.");
1258 }
1259 else
1260 {
1261 int TeamSize = 0;
1262 const int PlayerTeam = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1263
1264 // Count players in team
1265 for(int ClientId = 0; ClientId < MAX_CLIENTS; ClientId++)
1266 {
1267 const CPlayer *pOtherPlayer = pSelf->m_apPlayers[ClientId];
1268 if(!pOtherPlayer || !pOtherPlayer->IsPlaying())
1269 continue;
1270
1271 if(pSelf->GetDDRaceTeam(ClientId) == PlayerTeam)
1272 TeamSize++;
1273 }
1274
1275 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "You are in team %d having %d %s", PlayerTeam, TeamSize, TeamSize > 1 ? "players" : "player");
1276 log_info("chatresp", "%s", aBuf);
1277 }
1278 }
1279}
1280
1281void CGameContext::ConJoin(IConsole::IResult *pResult, void *pUserData)
1282{
1283 CGameContext *pSelf = (CGameContext *)pUserData;
1284 if(!CheckClientId(ClientId: pResult->m_ClientId))
1285 return;
1286
1287 const char *pName = pResult->GetString(Index: 0);
1288 int Target = pSelf->FindClientIdByName(pName).value_or(u: -1);
1289 if(Target == -1)
1290 {
1291 log_info("chatresp", "Player not found");
1292 return;
1293 }
1294
1295 int Team = pSelf->GetDDRaceTeam(ClientId: Target);
1296 if(pSelf->ProcessSpamProtection(ClientId: pResult->m_ClientId, RespectChatInitialDelay: false))
1297 return;
1298
1299 pSelf->AttemptJoinTeam(ClientId: pResult->m_ClientId, Team);
1300}
1301
1302void CGameContext::ConConverse(IConsole::IResult *pResult, void *pUserData)
1303{
1304 // This will never be called
1305}
1306
1307void CGameContext::ConWhisper(IConsole::IResult *pResult, void *pUserData)
1308{
1309 // This will never be called
1310}
1311
1312void CGameContext::ConSetEyeEmote(IConsole::IResult *pResult,
1313 void *pUserData)
1314{
1315 CGameContext *pSelf = (CGameContext *)pUserData;
1316 if(!CheckClientId(ClientId: pResult->m_ClientId))
1317 return;
1318
1319 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1320 if(!pPlayer)
1321 return;
1322 if(pResult->NumArguments() == 0)
1323 {
1324 log_info("chatresp", pPlayer->m_EyeEmoteEnabled ?
1325 "You can now use the preset eye emotes." :
1326 "You don't have any eye emotes, remember to bind some.");
1327 return;
1328 }
1329 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "on") == 0)
1330 pPlayer->m_EyeEmoteEnabled = true;
1331 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "off") == 0)
1332 pPlayer->m_EyeEmoteEnabled = false;
1333 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "toggle") == 0)
1334 pPlayer->m_EyeEmoteEnabled = !pPlayer->m_EyeEmoteEnabled;
1335 log_info("chatresp", pPlayer->m_EyeEmoteEnabled ?
1336 "You can now use the preset eye emotes." :
1337 "You don't have any eye emotes, remember to bind some.");
1338}
1339
1340void CGameContext::ConEyeEmote(IConsole::IResult *pResult, void *pUserData)
1341{
1342 CGameContext *pSelf = (CGameContext *)pUserData;
1343 if(g_Config.m_SvEmotionalTees == -1)
1344 {
1345 log_info("chatresp", "Emotes are disabled.");
1346 return;
1347 }
1348
1349 if(!CheckClientId(ClientId: pResult->m_ClientId))
1350 return;
1351
1352 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1353 if(!pPlayer)
1354 return;
1355
1356 if(pResult->NumArguments() == 0)
1357 {
1358 log_info("chatresp", "Emote commands are: /emote surprise /emote blink /emote close /emote angry /emote happy /emote pain /emote normal");
1359 log_info("chatresp", "Example: /emote surprise 10 for 10 seconds or /emote surprise (default 1 second)");
1360 }
1361 else
1362 {
1363 if(!pPlayer->CanOverrideDefaultEmote())
1364 return;
1365
1366 int EmoteType = 0;
1367 if(!str_comp_nocase(a: pResult->GetString(Index: 0), b: "angry"))
1368 EmoteType = EMOTE_ANGRY;
1369 else if(!str_comp_nocase(a: pResult->GetString(Index: 0), b: "blink"))
1370 EmoteType = EMOTE_BLINK;
1371 else if(!str_comp_nocase(a: pResult->GetString(Index: 0), b: "close"))
1372 EmoteType = EMOTE_BLINK;
1373 else if(!str_comp_nocase(a: pResult->GetString(Index: 0), b: "happy"))
1374 EmoteType = EMOTE_HAPPY;
1375 else if(!str_comp_nocase(a: pResult->GetString(Index: 0), b: "pain"))
1376 EmoteType = EMOTE_PAIN;
1377 else if(!str_comp_nocase(a: pResult->GetString(Index: 0), b: "surprise"))
1378 EmoteType = EMOTE_SURPRISE;
1379 else if(!str_comp_nocase(a: pResult->GetString(Index: 0), b: "normal"))
1380 EmoteType = EMOTE_NORMAL;
1381 else
1382 {
1383 log_info("chatresp", "Unknown emote... Say /emote");
1384 return;
1385 }
1386
1387 int Duration = 1;
1388 if(pResult->NumArguments() > 1)
1389 Duration = std::clamp(val: pResult->GetInteger(Index: 1), lo: 1, hi: 86400);
1390
1391 pPlayer->OverrideDefaultEmote(Emote: EmoteType, Tick: pSelf->Server()->Tick() + Duration * pSelf->Server()->TickSpeed());
1392 }
1393}
1394
1395void CGameContext::ConNinjaJetpack(IConsole::IResult *pResult, void *pUserData)
1396{
1397 CGameContext *pSelf = (CGameContext *)pUserData;
1398 if(!CheckClientId(ClientId: pResult->m_ClientId))
1399 return;
1400
1401 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1402 if(!pPlayer)
1403 return;
1404 if(pResult->NumArguments())
1405 pPlayer->m_NinjaJetpack = pResult->GetInteger(Index: 0);
1406 else
1407 pPlayer->m_NinjaJetpack = !pPlayer->m_NinjaJetpack;
1408}
1409
1410void CGameContext::ConShowOthers(IConsole::IResult *pResult, void *pUserData)
1411{
1412 CGameContext *pSelf = (CGameContext *)pUserData;
1413 if(!CheckClientId(ClientId: pResult->m_ClientId))
1414 return;
1415
1416 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1417 if(!pPlayer)
1418 return;
1419 if(g_Config.m_SvShowOthers)
1420 {
1421 if(pResult->NumArguments())
1422 pPlayer->m_ShowOthers = pResult->GetInteger(Index: 0);
1423 else
1424 pPlayer->m_ShowOthers = !pPlayer->m_ShowOthers;
1425 }
1426 else
1427 log_info("chatresp", "Showing players from other teams is disabled");
1428}
1429
1430void CGameContext::ConShowAll(IConsole::IResult *pResult, void *pUserData)
1431{
1432 CGameContext *pSelf = (CGameContext *)pUserData;
1433 if(!CheckClientId(ClientId: pResult->m_ClientId))
1434 return;
1435
1436 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1437 if(!pPlayer)
1438 return;
1439
1440 if(pResult->NumArguments())
1441 {
1442 if(pPlayer->m_ShowAll == (bool)pResult->GetInteger(Index: 0))
1443 return;
1444
1445 pPlayer->m_ShowAll = pResult->GetInteger(Index: 0);
1446 }
1447 else
1448 {
1449 pPlayer->m_ShowAll = !pPlayer->m_ShowAll;
1450 }
1451
1452 if(pPlayer->m_ShowAll)
1453 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: "You will now see all tees on this server, no matter the distance");
1454 else
1455 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: "You will no longer see all tees on this server");
1456}
1457
1458void CGameContext::ConSpecTeam(IConsole::IResult *pResult, void *pUserData)
1459{
1460 CGameContext *pSelf = (CGameContext *)pUserData;
1461 if(!CheckClientId(ClientId: pResult->m_ClientId))
1462 return;
1463
1464 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1465 if(!pPlayer)
1466 return;
1467
1468 if(pResult->NumArguments())
1469 pPlayer->m_SpecTeam = pResult->GetInteger(Index: 0);
1470 else
1471 pPlayer->m_SpecTeam = !pPlayer->m_SpecTeam;
1472}
1473
1474void CGameContext::ConSayTime(IConsole::IResult *pResult, void *pUserData)
1475{
1476 CGameContext *pSelf = (CGameContext *)pUserData;
1477 if(!CheckClientId(ClientId: pResult->m_ClientId))
1478 return;
1479
1480 int ClientId;
1481 char aBufName[MAX_NAME_LENGTH];
1482
1483 if(pResult->NumArguments() > 0)
1484 {
1485 ClientId = pSelf->FindClientIdByName(pName: pResult->GetString(Index: 0)).value_or(u: -1);
1486 if(ClientId == -1)
1487 return;
1488
1489 str_format(buffer: aBufName, buffer_size: sizeof(aBufName), format: "%s's", pSelf->Server()->ClientName(ClientId));
1490 }
1491 else
1492 {
1493 str_copy(dst&: aBufName, src: "Your");
1494 ClientId = pResult->m_ClientId;
1495 }
1496
1497 CPlayer *pPlayer = pSelf->m_apPlayers[ClientId];
1498 if(!pPlayer)
1499 return;
1500 CCharacter *pChr = pPlayer->GetCharacter();
1501 if(!pChr)
1502 return;
1503 if(pChr->m_DDRaceState != ERaceState::STARTED)
1504 return;
1505
1506 char aBufTime[32];
1507 char aBuf[64];
1508 int64_t Time = (int64_t)100 * (float)(pSelf->Server()->Tick() - pChr->m_StartTime) / ((float)pSelf->Server()->TickSpeed());
1509 str_time(centisecs: Time, format: ETimeFormat::HOURS, buffer: aBufTime, buffer_size: sizeof(aBufTime));
1510 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s current race time is %s", aBufName, aBufTime);
1511 log_info("chatresp", "%s", aBuf);
1512}
1513
1514void CGameContext::ConSayTimeAll(IConsole::IResult *pResult, void *pUserData)
1515{
1516 CGameContext *pSelf = (CGameContext *)pUserData;
1517 if(!CheckClientId(ClientId: pResult->m_ClientId))
1518 return;
1519
1520 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1521 if(!pPlayer)
1522 return;
1523 CCharacter *pChr = pPlayer->GetCharacter();
1524 if(!pChr)
1525 return;
1526 if(pChr->m_DDRaceState != ERaceState::STARTED)
1527 return;
1528
1529 char aBufTime[32];
1530 char aBuf[64];
1531 int64_t Time = (int64_t)100 * (float)(pSelf->Server()->Tick() - pChr->m_StartTime) / ((float)pSelf->Server()->TickSpeed());
1532 const char *pName = pSelf->Server()->ClientName(ClientId: pResult->m_ClientId);
1533 str_time(centisecs: Time, format: ETimeFormat::HOURS, buffer: aBufTime, buffer_size: sizeof(aBufTime));
1534 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s's current race time is %s", pName, aBufTime);
1535 pSelf->SendChat(ClientId: -1, Team: TEAM_ALL, pText: aBuf, SpamProtectionClientId: pResult->m_ClientId);
1536}
1537
1538void CGameContext::ConTime(IConsole::IResult *pResult, void *pUserData)
1539{
1540 CGameContext *pSelf = (CGameContext *)pUserData;
1541 if(!CheckClientId(ClientId: pResult->m_ClientId))
1542 return;
1543
1544 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1545 if(!pPlayer)
1546 return;
1547 CCharacter *pChr = pPlayer->GetCharacter();
1548 if(!pChr)
1549 return;
1550
1551 char aBufTime[32];
1552 char aBuf[64];
1553 int64_t Time = (int64_t)100 * (float)(pSelf->Server()->Tick() - pChr->m_StartTime) / ((float)pSelf->Server()->TickSpeed());
1554 str_time(centisecs: Time, format: ETimeFormat::HOURS, buffer: aBufTime, buffer_size: sizeof(aBufTime));
1555 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Your time is %s", aBufTime);
1556 pSelf->SendBroadcast(pText: aBuf, ClientId: pResult->m_ClientId);
1557}
1558
1559static const char s_aaMsg[4][128] = {"game/round timer.", "broadcast.", "both game/round timer and broadcast.", "racetime."};
1560
1561void CGameContext::ConSetTimerType(IConsole::IResult *pResult, void *pUserData)
1562{
1563 CGameContext *pSelf = (CGameContext *)pUserData;
1564
1565 if(!CheckClientId(ClientId: pResult->m_ClientId))
1566 return;
1567
1568 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1569 if(!pPlayer)
1570 return;
1571
1572 char aBuf[128];
1573
1574 if(pResult->NumArguments() > 0)
1575 {
1576 int OldType = pPlayer->m_TimerType;
1577 bool Result = false;
1578
1579 if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "default") == 0)
1580 Result = pPlayer->SetTimerType(CPlayer::TIMERTYPE_DEFAULT);
1581 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "gametimer") == 0)
1582 Result = pPlayer->SetTimerType(CPlayer::TIMERTYPE_GAMETIMER);
1583 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "broadcast") == 0)
1584 Result = pPlayer->SetTimerType(CPlayer::TIMERTYPE_BROADCAST);
1585 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "both") == 0)
1586 Result = pPlayer->SetTimerType(CPlayer::TIMERTYPE_GAMETIMER_AND_BROADCAST);
1587 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "none") == 0)
1588 Result = pPlayer->SetTimerType(CPlayer::TIMERTYPE_NONE);
1589 else
1590 {
1591 log_info("chatresp", "Unknown parameter. Accepted values: default, gametimer, broadcast, both, none");
1592 return;
1593 }
1594
1595 if(!Result)
1596 {
1597 log_info("chatresp", "Selected timertype is not supported by your client");
1598 return;
1599 }
1600
1601 if((OldType == CPlayer::TIMERTYPE_BROADCAST || OldType == CPlayer::TIMERTYPE_GAMETIMER_AND_BROADCAST) && (pPlayer->m_TimerType == CPlayer::TIMERTYPE_GAMETIMER || pPlayer->m_TimerType == CPlayer::TIMERTYPE_NONE))
1602 pSelf->SendBroadcast(pText: "", ClientId: pResult->m_ClientId);
1603 }
1604
1605 if(pPlayer->m_TimerType <= CPlayer::TIMERTYPE_SIXUP && pPlayer->m_TimerType >= CPlayer::TIMERTYPE_GAMETIMER)
1606 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Timer is displayed in %s", s_aaMsg[pPlayer->m_TimerType]);
1607 else if(pPlayer->m_TimerType == CPlayer::TIMERTYPE_NONE)
1608 str_copy(dst&: aBuf, src: "Timer isn't displayed.");
1609
1610 log_info("chatresp", "%s", aBuf);
1611}
1612
1613void CGameContext::ConRescue(IConsole::IResult *pResult, void *pUserData)
1614{
1615 CGameContext *pSelf = (CGameContext *)pUserData;
1616 if(!CheckClientId(ClientId: pResult->m_ClientId))
1617 return;
1618 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1619 if(!pPlayer)
1620 return;
1621 CCharacter *pChr = pPlayer->GetCharacter();
1622 if(!pChr)
1623 return;
1624
1625 CGameTeams &Teams = pSelf->m_pController->Teams();
1626 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1627 if(!g_Config.m_SvRescue && !Teams.IsPractice(Team))
1628 {
1629 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "Rescue is not enabled on this server and you're not in a team with /practice turned on. Note that you can't earn a rank with practice enabled.");
1630 return;
1631 }
1632
1633 bool GoRescue = true;
1634
1635 if(pPlayer->m_RescueMode == RESCUEMODE_MANUAL)
1636 {
1637 // if character can't set their rescue state then we should rescue them instead
1638 GoRescue = !pChr->TrySetRescue(RescueMode: RESCUEMODE_MANUAL);
1639 }
1640
1641 if(GoRescue)
1642 {
1643 if(pChr->Rescue())
1644 pChr->Unfreeze();
1645 }
1646}
1647
1648void CGameContext::ConRescueMode(IConsole::IResult *pResult, void *pUserData)
1649{
1650 CGameContext *pSelf = (CGameContext *)pUserData;
1651 if(!CheckClientId(ClientId: pResult->m_ClientId))
1652 return;
1653 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1654 if(!pPlayer)
1655 return;
1656
1657 CGameTeams &Teams = pSelf->m_pController->Teams();
1658 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1659 if(!g_Config.m_SvRescue && !Teams.IsPractice(Team))
1660 {
1661 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "Rescue is not enabled on this server and you're not in a team with /practice turned on. Note that you can't earn a rank with practice enabled.");
1662 return;
1663 }
1664
1665 if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "auto") == 0)
1666 {
1667 if(pPlayer->m_RescueMode != RESCUEMODE_AUTO)
1668 {
1669 pPlayer->m_RescueMode = RESCUEMODE_AUTO;
1670
1671 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "Rescue mode changed to auto.");
1672 }
1673
1674 return;
1675 }
1676
1677 if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "manual") == 0)
1678 {
1679 if(pPlayer->m_RescueMode != RESCUEMODE_MANUAL)
1680 {
1681 pPlayer->m_RescueMode = RESCUEMODE_MANUAL;
1682
1683 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "Rescue mode changed to manual.");
1684 }
1685
1686 return;
1687 }
1688
1689 if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "list") == 0)
1690 {
1691 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "Available rescue modes: auto, manual");
1692 }
1693 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "") == 0)
1694 {
1695 char aBuf[64];
1696 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Current rescue mode: %s.", pPlayer->m_RescueMode == RESCUEMODE_MANUAL ? "manual" : "auto");
1697 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: aBuf);
1698 }
1699 else
1700 {
1701 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "Unknown argument. Check '/rescuemode list'");
1702 }
1703}
1704
1705void CGameContext::ConBack(IConsole::IResult *pResult, void *pUserData)
1706{
1707 auto *pSelf = static_cast<CGameContext *>(pUserData);
1708 if(auto *pChr = pSelf->GetPracticeCharacter(pResult))
1709 {
1710 auto *pPlayer = pChr->GetPlayer();
1711 if(!pPlayer->m_LastDeath.has_value())
1712 {
1713 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "There is nowhere to go back to.");
1714 return;
1715 }
1716 pChr->GetLastRescueTeeRef(Mode: pPlayer->m_RescueMode) = pPlayer->m_LastDeath.value();
1717 if(pChr->Rescue())
1718 pChr->Unfreeze();
1719 }
1720}
1721
1722void CGameContext::ConTeleTo(IConsole::IResult *pResult, void *pUserData)
1723{
1724 CGameContext *pSelf = (CGameContext *)pUserData;
1725 if(!CheckClientId(ClientId: pResult->m_ClientId))
1726 return;
1727 CPlayer *pCallingPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1728 if(!pCallingPlayer)
1729 return;
1730 CCharacter *pCallingCharacter = pCallingPlayer->GetCharacter();
1731 if(!pCallingCharacter)
1732 return;
1733
1734 CGameTeams &Teams = pSelf->m_pController->Teams();
1735 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1736 if(!Teams.IsPractice(Team))
1737 {
1738 pSelf->SendChatTarget(To: pCallingPlayer->GetCid(), pText: "You're not in a team with /practice turned on. Note that you can't earn a rank with practice enabled.");
1739 return;
1740 }
1741
1742 vec2 Pos = {};
1743
1744 if(pResult->NumArguments() == 0)
1745 {
1746 // Set calling tee's position to the origin of its spectating viewport
1747 Pos = pCallingPlayer->m_ViewPos;
1748 }
1749 else
1750 {
1751 const CPlayer *pDestPlayer = pSelf->FindPlayerByName(pName: pResult->GetString(Index: 0));
1752 if(!pDestPlayer)
1753 {
1754 pSelf->SendChatTarget(To: pCallingPlayer->GetCid(), pText: "No player with this name found.");
1755 return;
1756 }
1757 const CCharacter *pDestCharacter = pDestPlayer->GetCharacter();
1758 if(!pDestCharacter)
1759 return;
1760
1761 // Set calling tee's position to that of the destination tee
1762 Pos = pDestCharacter->m_Pos;
1763 }
1764
1765 // Teleport tee
1766 pSelf->Teleport(pChr: pCallingCharacter, Pos);
1767 pCallingCharacter->ResetJumps();
1768 pCallingCharacter->Unfreeze();
1769 pCallingCharacter->ResetVelocity();
1770 pCallingPlayer->m_LastTeleTee.Save(pChr: pCallingCharacter);
1771}
1772
1773void CGameContext::ConTeleXY(IConsole::IResult *pResult, void *pUserData)
1774{
1775 CGameContext *pSelf = (CGameContext *)pUserData;
1776 if(!CheckClientId(ClientId: pResult->m_ClientId))
1777 return;
1778 CPlayer *pCallingPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1779 if(!pCallingPlayer)
1780 return;
1781 CCharacter *pCallingCharacter = pCallingPlayer->GetCharacter();
1782 if(!pCallingCharacter)
1783 return;
1784
1785 CGameTeams &Teams = pSelf->m_pController->Teams();
1786 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1787 if(!Teams.IsPractice(Team))
1788 {
1789 pSelf->SendChatTarget(To: pCallingPlayer->GetCid(), pText: "You're not in a team with /practice turned on. Note that you can't earn a rank with practice enabled.");
1790 return;
1791 }
1792
1793 vec2 Pos = {};
1794
1795 if(pResult->NumArguments() != 2)
1796 {
1797 pSelf->SendChatTarget(To: pCallingPlayer->GetCid(), pText: "Can't recognize specified arguments. Usage: /tpxy x y, e.g. /tpxy 9 3.");
1798 return;
1799 }
1800 else
1801 {
1802 float BaseX = 0.f, BaseY = 0.f;
1803
1804 CMapItemLayerTilemap *pGameLayer = pSelf->m_Layers.GameLayer();
1805 constexpr float OuterKillTileBoundaryDistance = 201 * 32.f;
1806 float MapWidth = (pGameLayer->m_Width * 32) + (OuterKillTileBoundaryDistance * 2.f), MapHeight = (pGameLayer->m_Height * 32) + (OuterKillTileBoundaryDistance * 2.f);
1807
1808 const auto DetermineCoordinateRelativity = [](const char *pInString, const float AbsoluteDefaultValue, float &OutFloat) -> bool {
1809 // mode 0 = abs, 1 = sub, 2 = add
1810
1811 // Relative?
1812 const char *pStrDelta = str_startswith(str: pInString, prefix: "~");
1813
1814 float d;
1815 if(!str_tofloat(str: pStrDelta ? pStrDelta : pInString, out: &d))
1816 return false;
1817
1818 // Is the number valid?
1819 if(std::isnan(x: d) || std::isinf(x: d))
1820 return false;
1821
1822 // Convert our gleaned 'display' coordinate to an actual map coordinate
1823 d *= 32.f;
1824
1825 OutFloat = (pStrDelta ? AbsoluteDefaultValue : 0) + d;
1826 return true;
1827 };
1828
1829 if(!DetermineCoordinateRelativity(pResult->GetString(Index: 0), pCallingPlayer->m_ViewPos.x, BaseX))
1830 {
1831 pSelf->SendChatTarget(To: pCallingPlayer->GetCid(), pText: "Invalid X coordinate.");
1832 return;
1833 }
1834 if(!DetermineCoordinateRelativity(pResult->GetString(Index: 1), pCallingPlayer->m_ViewPos.y, BaseY))
1835 {
1836 pSelf->SendChatTarget(To: pCallingPlayer->GetCid(), pText: "Invalid Y coordinate.");
1837 return;
1838 }
1839
1840 Pos = {std::clamp(val: BaseX, lo: (-OuterKillTileBoundaryDistance) + 1.f, hi: (-OuterKillTileBoundaryDistance) + MapWidth - 1.f), std::clamp(val: BaseY, lo: (-OuterKillTileBoundaryDistance) + 1.f, hi: (-OuterKillTileBoundaryDistance) + MapHeight - 1.f)};
1841 }
1842
1843 // Teleport tee
1844 pSelf->Teleport(pChr: pCallingCharacter, Pos);
1845 pCallingCharacter->ResetJumps();
1846 pCallingCharacter->Unfreeze();
1847 pCallingCharacter->ResetVelocity();
1848 pCallingPlayer->m_LastTeleTee.Save(pChr: pCallingCharacter);
1849}
1850
1851void CGameContext::ConTeleCursor(IConsole::IResult *pResult, void *pUserData)
1852{
1853 CGameContext *pSelf = (CGameContext *)pUserData;
1854 if(!CheckClientId(ClientId: pResult->m_ClientId))
1855 return;
1856 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1857 if(!pPlayer)
1858 return;
1859 CCharacter *pChr = pPlayer->GetCharacter();
1860 if(!pChr)
1861 return;
1862
1863 CGameTeams &Teams = pSelf->m_pController->Teams();
1864 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1865 if(!Teams.IsPractice(Team))
1866 {
1867 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "You're not in a team with /practice turned on. Note that you can't earn a rank with practice enabled.");
1868 return;
1869 }
1870
1871 // default to view pos when character is not available
1872 vec2 Pos = pPlayer->m_ViewPos;
1873 if(pResult->NumArguments() == 0 && !pPlayer->IsPaused() && pPlayer->GetCharacter() && pPlayer->GetCharacter()->IsAlive())
1874 {
1875 vec2 Target = vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY);
1876 Pos = pPlayer->m_CameraInfo.ConvertTargetToWorld(Position: pPlayer->GetCharacter()->GetPos(), Target);
1877 }
1878 else if(pResult->NumArguments() > 0)
1879 {
1880 const CPlayer *pPlayerTo = pSelf->FindPlayerByName(pName: pResult->GetString(Index: 0));
1881 if(!pPlayerTo)
1882 {
1883 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "No player with this name found.");
1884 return;
1885 }
1886 const CCharacter *pChrTo = pPlayerTo->GetCharacter();
1887 if(!pChrTo)
1888 return;
1889 Pos = pChrTo->m_Pos;
1890 }
1891 pSelf->Teleport(pChr, Pos);
1892 pChr->ResetJumps();
1893 pChr->Unfreeze();
1894 pChr->ResetVelocity();
1895 pPlayer->m_LastTeleTee.Save(pChr);
1896}
1897
1898void CGameContext::ConLastTele(IConsole::IResult *pResult, void *pUserData)
1899{
1900 CGameContext *pSelf = (CGameContext *)pUserData;
1901 if(!CheckClientId(ClientId: pResult->m_ClientId))
1902 return;
1903 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1904 if(!pPlayer)
1905 return;
1906 CCharacter *pChr = pPlayer->GetCharacter();
1907 if(!pChr)
1908 return;
1909
1910 CGameTeams &Teams = pSelf->m_pController->Teams();
1911 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1912 if(!Teams.IsPractice(Team))
1913 {
1914 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "You're not in a team with /practice turned on. Note that you can't earn a rank with practice enabled.");
1915 return;
1916 }
1917 if(!pPlayer->m_LastTeleTee.GetPos().x)
1918 {
1919 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "You haven't previously teleported. Use /tp before using this command.");
1920 return;
1921 }
1922 pPlayer->m_LastTeleTee.Load(pChr);
1923 pPlayer->Pause(State: CPlayer::PAUSE_NONE, Force: true);
1924}
1925
1926CCharacter *CGameContext::GetPracticeCharacter(IConsole::IResult *pResult)
1927{
1928 if(!CheckClientId(ClientId: pResult->m_ClientId))
1929 return nullptr;
1930 CPlayer *pPlayer = m_apPlayers[pResult->m_ClientId];
1931 if(!pPlayer)
1932 return nullptr;
1933 CCharacter *pChr = pPlayer->GetCharacter();
1934 if(!pChr)
1935 return nullptr;
1936
1937 CGameTeams &Teams = m_pController->Teams();
1938 int Team = GetDDRaceTeam(ClientId: pResult->m_ClientId);
1939 if(!Teams.IsPractice(Team))
1940 {
1941 SendChatTarget(To: pPlayer->GetCid(), pText: "You're not in a team with /practice turned on. Note that you can't earn a rank with practice enabled.");
1942 return nullptr;
1943 }
1944 return pChr;
1945}
1946
1947void CGameContext::ConPracticeToTeleporter(IConsole::IResult *pResult, void *pUserData)
1948{
1949 CGameContext *pSelf = (CGameContext *)pUserData;
1950 CCharacter *pChr = pSelf->GetPracticeCharacter(pResult);
1951 if(pChr)
1952 {
1953 if(pSelf->Collision()->TeleOuts(Number: pResult->GetInteger(Index: 0) - 1).empty())
1954 {
1955 pSelf->SendChatTarget(To: pChr->GetPlayer()->GetCid(), pText: "There is no teleporter with that index on the map.");
1956 return;
1957 }
1958
1959 ConToTeleporter(pResult, pUserData);
1960 pChr->ResetJumps();
1961 pChr->Unfreeze();
1962 pChr->ResetVelocity();
1963 pChr->GetPlayer()->m_LastTeleTee.Save(pChr);
1964 }
1965}
1966
1967void CGameContext::ConPracticeToCheckTeleporter(IConsole::IResult *pResult, void *pUserData)
1968{
1969 CGameContext *pSelf = (CGameContext *)pUserData;
1970 CCharacter *pChr = pSelf->GetPracticeCharacter(pResult);
1971 if(pChr)
1972 {
1973 if(pSelf->Collision()->TeleCheckOuts(Number: pResult->GetInteger(Index: 0) - 1).empty())
1974 {
1975 pSelf->SendChatTarget(To: pChr->GetPlayer()->GetCid(), pText: "There is no checkpoint teleporter with that index on the map.");
1976 return;
1977 }
1978
1979 ConToCheckTeleporter(pResult, pUserData);
1980 pChr->ResetJumps();
1981 pChr->Unfreeze();
1982 pChr->ResetVelocity();
1983 pChr->GetPlayer()->m_LastTeleTee.Save(pChr);
1984 }
1985}
1986
1987void CGameContext::ConPracticeUnSolo(IConsole::IResult *pResult, void *pUserData)
1988{
1989 CGameContext *pSelf = (CGameContext *)pUserData;
1990 if(!CheckClientId(ClientId: pResult->m_ClientId))
1991 return;
1992 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1993 if(!pPlayer)
1994 return;
1995 CCharacter *pChr = pPlayer->GetCharacter();
1996 if(!pChr)
1997 return;
1998
1999 if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
2000 {
2001 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "Command is not available on solo servers");
2002 return;
2003 }
2004
2005 CGameTeams &Teams = pSelf->m_pController->Teams();
2006 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
2007 if(!Teams.IsPractice(Team))
2008 {
2009 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "You're not in a team with /practice turned on. Note that you can't earn a rank with practice enabled.");
2010 return;
2011 }
2012 pChr->SetSolo(false);
2013}
2014
2015void CGameContext::ConPracticeSolo(IConsole::IResult *pResult, void *pUserData)
2016{
2017 CGameContext *pSelf = (CGameContext *)pUserData;
2018 if(!CheckClientId(ClientId: pResult->m_ClientId))
2019 return;
2020 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
2021 if(!pPlayer)
2022 return;
2023 CCharacter *pChr = pPlayer->GetCharacter();
2024 if(!pChr)
2025 return;
2026
2027 if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
2028 {
2029 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "Command is not available on solo servers");
2030 return;
2031 }
2032
2033 CGameTeams &Teams = pSelf->m_pController->Teams();
2034 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
2035 if(!Teams.IsPractice(Team))
2036 {
2037 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "You're not in a team with /practice turned on. Note that you can't earn a rank with practice enabled.");
2038 return;
2039 }
2040 pChr->SetSolo(true);
2041}
2042
2043void CGameContext::ConPracticeUnDeep(IConsole::IResult *pResult, void *pUserData)
2044{
2045 CGameContext *pSelf = (CGameContext *)pUserData;
2046 auto *pChr = pSelf->GetPracticeCharacter(pResult);
2047 if(!pChr)
2048 return;
2049
2050 pChr->SetDeepFrozen(false);
2051 pChr->Unfreeze();
2052}
2053
2054void CGameContext::ConPracticeDeep(IConsole::IResult *pResult, void *pUserData)
2055{
2056 CGameContext *pSelf = (CGameContext *)pUserData;
2057 auto *pChr = pSelf->GetPracticeCharacter(pResult);
2058 if(!pChr)
2059 return;
2060
2061 pChr->SetDeepFrozen(true);
2062}
2063
2064void CGameContext::ConPracticeUnLiveFreeze(IConsole::IResult *pResult, void *pUserData)
2065{
2066 CGameContext *pSelf = (CGameContext *)pUserData;
2067 auto *pChr = pSelf->GetPracticeCharacter(pResult);
2068 if(!pChr)
2069 return;
2070
2071 pChr->SetLiveFrozen(false);
2072}
2073
2074void CGameContext::ConPracticeLiveFreeze(IConsole::IResult *pResult, void *pUserData)
2075{
2076 CGameContext *pSelf = (CGameContext *)pUserData;
2077 auto *pChr = pSelf->GetPracticeCharacter(pResult);
2078 if(!pChr)
2079 return;
2080
2081 pChr->SetLiveFrozen(true);
2082}
2083
2084void CGameContext::ConPracticeShotgun(IConsole::IResult *pResult, void *pUserData)
2085{
2086 CGameContext *pSelf = (CGameContext *)pUserData;
2087 if(pSelf->GetPracticeCharacter(pResult))
2088 ConShotgun(pResult, pUserData);
2089}
2090
2091void CGameContext::ConPracticeGrenade(IConsole::IResult *pResult, void *pUserData)
2092{
2093 CGameContext *pSelf = (CGameContext *)pUserData;
2094 if(pSelf->GetPracticeCharacter(pResult))
2095 ConGrenade(pResult, pUserData);
2096}
2097
2098void CGameContext::ConPracticeLaser(IConsole::IResult *pResult, void *pUserData)
2099{
2100 CGameContext *pSelf = (CGameContext *)pUserData;
2101 if(pSelf->GetPracticeCharacter(pResult))
2102 ConLaser(pResult, pUserData);
2103}
2104
2105void CGameContext::ConPracticeJetpack(IConsole::IResult *pResult, void *pUserData)
2106{
2107 CGameContext *pSelf = (CGameContext *)pUserData;
2108 if(pSelf->GetPracticeCharacter(pResult))
2109 ConJetpack(pResult, pUserData);
2110}
2111
2112void CGameContext::ConPracticeEndlessJump(IConsole::IResult *pResult, void *pUserData)
2113{
2114 CGameContext *pSelf = (CGameContext *)pUserData;
2115 if(pSelf->GetPracticeCharacter(pResult))
2116 ConEndlessJump(pResult, pUserData);
2117}
2118
2119void CGameContext::ConPracticeSetJumps(IConsole::IResult *pResult, void *pUserData)
2120{
2121 CGameContext *pSelf = (CGameContext *)pUserData;
2122 if(pSelf->GetPracticeCharacter(pResult))
2123 ConSetJumps(pResult, pUserData);
2124}
2125
2126void CGameContext::ConPracticeWeapons(IConsole::IResult *pResult, void *pUserData)
2127{
2128 CGameContext *pSelf = (CGameContext *)pUserData;
2129 if(pSelf->GetPracticeCharacter(pResult))
2130 ConWeapons(pResult, pUserData);
2131}
2132
2133void CGameContext::ConPracticeUnShotgun(IConsole::IResult *pResult, void *pUserData)
2134{
2135 CGameContext *pSelf = (CGameContext *)pUserData;
2136 if(pSelf->GetPracticeCharacter(pResult))
2137 ConUnShotgun(pResult, pUserData);
2138}
2139
2140void CGameContext::ConPracticeUnGrenade(IConsole::IResult *pResult, void *pUserData)
2141{
2142 CGameContext *pSelf = (CGameContext *)pUserData;
2143 if(pSelf->GetPracticeCharacter(pResult))
2144 ConUnGrenade(pResult, pUserData);
2145}
2146
2147void CGameContext::ConPracticeUnLaser(IConsole::IResult *pResult, void *pUserData)
2148{
2149 CGameContext *pSelf = (CGameContext *)pUserData;
2150 if(pSelf->GetPracticeCharacter(pResult))
2151 ConUnLaser(pResult, pUserData);
2152}
2153
2154void CGameContext::ConPracticeUnJetpack(IConsole::IResult *pResult, void *pUserData)
2155{
2156 CGameContext *pSelf = (CGameContext *)pUserData;
2157 if(pSelf->GetPracticeCharacter(pResult))
2158 ConUnJetpack(pResult, pUserData);
2159}
2160
2161void CGameContext::ConPracticeUnEndlessJump(IConsole::IResult *pResult, void *pUserData)
2162{
2163 CGameContext *pSelf = (CGameContext *)pUserData;
2164 if(pSelf->GetPracticeCharacter(pResult))
2165 ConUnEndlessJump(pResult, pUserData);
2166}
2167
2168void CGameContext::ConPracticeUnWeapons(IConsole::IResult *pResult, void *pUserData)
2169{
2170 CGameContext *pSelf = (CGameContext *)pUserData;
2171 if(pSelf->GetPracticeCharacter(pResult))
2172 ConUnWeapons(pResult, pUserData);
2173}
2174
2175void CGameContext::ConPracticeNinja(IConsole::IResult *pResult, void *pUserData)
2176{
2177 CGameContext *pSelf = (CGameContext *)pUserData;
2178 if(pSelf->GetPracticeCharacter(pResult))
2179 ConNinja(pResult, pUserData);
2180}
2181
2182void CGameContext::ConPracticeUnNinja(IConsole::IResult *pResult, void *pUserData)
2183{
2184 CGameContext *pSelf = (CGameContext *)pUserData;
2185 if(pSelf->GetPracticeCharacter(pResult))
2186 ConUnNinja(pResult, pUserData);
2187}
2188
2189void CGameContext::ConPracticeEndlessHook(IConsole::IResult *pResult, void *pUserData)
2190{
2191 CGameContext *pSelf = (CGameContext *)pUserData;
2192 if(pSelf->GetPracticeCharacter(pResult))
2193 ConEndlessHook(pResult, pUserData);
2194}
2195
2196void CGameContext::ConPracticeUnEndlessHook(IConsole::IResult *pResult, void *pUserData)
2197{
2198 CGameContext *pSelf = (CGameContext *)pUserData;
2199 if(pSelf->GetPracticeCharacter(pResult))
2200 ConUnEndlessHook(pResult, pUserData);
2201}
2202
2203void CGameContext::ConPracticeSetSwitch(IConsole::IResult *pResult, void *pUserData)
2204{
2205 CGameContext *pSelf = (CGameContext *)pUserData;
2206 if(pSelf->GetPracticeCharacter(pResult))
2207 ConSetSwitch(pResult, pUserData);
2208}
2209
2210void CGameContext::ConPracticeToggleInvincible(IConsole::IResult *pResult, void *pUserData)
2211{
2212 CGameContext *pSelf = (CGameContext *)pUserData;
2213 if(pSelf->GetPracticeCharacter(pResult))
2214 ConToggleInvincible(pResult, pUserData);
2215}
2216
2217void CGameContext::ConPracticeToggleCollision(IConsole::IResult *pResult, void *pUserData)
2218{
2219 CGameContext *pSelf = (CGameContext *)pUserData;
2220 auto *pChr = pSelf->GetPracticeCharacter(pResult);
2221 if(!pChr)
2222 return;
2223
2224 pChr->SetCollisionDisabled(!pChr->Core()->m_CollisionDisabled);
2225}
2226
2227void CGameContext::ConPracticeToggleHookCollision(IConsole::IResult *pResult, void *pUserData)
2228{
2229 CGameContext *pSelf = (CGameContext *)pUserData;
2230 auto *pChr = pSelf->GetPracticeCharacter(pResult);
2231 if(!pChr)
2232 return;
2233
2234 pChr->SetHookHitDisabled(!pChr->Core()->m_HookHitDisabled);
2235}
2236
2237void CGameContext::ConPracticeToggleHitOthers(IConsole::IResult *pResult, void *pUserData)
2238{
2239 CGameContext *pSelf = (CGameContext *)pUserData;
2240 auto *pChr = pSelf->GetPracticeCharacter(pResult);
2241 if(!pChr)
2242 return;
2243
2244 if(pResult->NumArguments() == 0 || str_comp(a: pResult->GetString(Index: 0), b: "all") == 0)
2245 {
2246 bool IsEnabled = (pChr->HammerHitDisabled() && pChr->ShotgunHitDisabled() &&
2247 pChr->GrenadeHitDisabled() && pChr->LaserHitDisabled());
2248 pChr->SetHammerHitDisabled(!IsEnabled);
2249 pChr->SetShotgunHitDisabled(!IsEnabled);
2250 pChr->SetGrenadeHitDisabled(!IsEnabled);
2251 pChr->SetLaserHitDisabled(!IsEnabled);
2252 return;
2253 }
2254
2255 if(str_comp(a: pResult->GetString(Index: 0), b: "hammer") == 0)
2256 pChr->SetHammerHitDisabled(!pChr->HammerHitDisabled());
2257 else if(str_comp(a: pResult->GetString(Index: 0), b: "shotgun") == 0)
2258 pChr->SetShotgunHitDisabled(!pChr->ShotgunHitDisabled());
2259 else if(str_comp(a: pResult->GetString(Index: 0), b: "grenade") == 0)
2260 pChr->SetGrenadeHitDisabled(!pChr->GrenadeHitDisabled());
2261 else if(str_comp(a: pResult->GetString(Index: 0), b: "laser") == 0)
2262 pChr->SetLaserHitDisabled(!pChr->LaserHitDisabled());
2263}
2264
2265void CGameContext::ConPracticeAddWeapon(IConsole::IResult *pResult, void *pUserData)
2266{
2267 CGameContext *pSelf = (CGameContext *)pUserData;
2268 if(pSelf->GetPracticeCharacter(pResult))
2269 ConAddWeapon(pResult, pUserData);
2270}
2271
2272void CGameContext::ConPracticeRemoveWeapon(IConsole::IResult *pResult, void *pUserData)
2273{
2274 CGameContext *pSelf = (CGameContext *)pUserData;
2275 if(pSelf->GetPracticeCharacter(pResult))
2276 ConRemoveWeapon(pResult, pUserData);
2277}
2278
2279void CGameContext::ConProtectedKill(IConsole::IResult *pResult, void *pUserData)
2280{
2281 CGameContext *pSelf = (CGameContext *)pUserData;
2282 if(!CheckClientId(ClientId: pResult->m_ClientId))
2283 return;
2284 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
2285 if(!pPlayer)
2286 return;
2287 CCharacter *pChr = pPlayer->GetCharacter();
2288 if(!pChr)
2289 return;
2290
2291 int CurrTime = (pSelf->Server()->Tick() - pChr->m_StartTime) / pSelf->Server()->TickSpeed();
2292 if(g_Config.m_SvKillProtection != 0 && CurrTime >= (60 * g_Config.m_SvKillProtection) && pChr->m_DDRaceState == ERaceState::STARTED)
2293 {
2294 pPlayer->KillCharacter(Weapon: WEAPON_SELF);
2295 pPlayer->Respawn();
2296 }
2297}
2298
2299void CGameContext::ConPoints(IConsole::IResult *pResult, void *pUserData)
2300{
2301 CGameContext *pSelf = (CGameContext *)pUserData;
2302 if(!CheckClientId(ClientId: pResult->m_ClientId))
2303 return;
2304
2305 if(pResult->NumArguments() > 0)
2306 {
2307 if(!g_Config.m_SvHideScore)
2308 pSelf->Score()->ShowPoints(ClientId: pResult->m_ClientId, pName: pResult->GetString(Index: 0));
2309 else
2310 log_info("chatresp", "Showing the global points of other players is not allowed on this server.");
2311 }
2312 else
2313 pSelf->Score()->ShowPoints(ClientId: pResult->m_ClientId,
2314 pName: pSelf->Server()->ClientName(ClientId: pResult->m_ClientId));
2315}
2316
2317void CGameContext::ConTopPoints(IConsole::IResult *pResult, void *pUserData)
2318{
2319 CGameContext *pSelf = (CGameContext *)pUserData;
2320 if(!CheckClientId(ClientId: pResult->m_ClientId))
2321 return;
2322
2323 if(g_Config.m_SvHideScore)
2324 {
2325 log_info("chatresp", "Showing the global top points is not allowed on this server.");
2326 return;
2327 }
2328
2329 if(pResult->NumArguments() > 0)
2330 pSelf->Score()->ShowTopPoints(ClientId: pResult->m_ClientId, Offset: pResult->GetInteger(Index: 0));
2331 else
2332 pSelf->Score()->ShowTopPoints(ClientId: pResult->m_ClientId);
2333}
2334
2335void CGameContext::ConTimeCP(IConsole::IResult *pResult, void *pUserData)
2336{
2337 CGameContext *pSelf = (CGameContext *)pUserData;
2338 if(!CheckClientId(ClientId: pResult->m_ClientId))
2339 return;
2340
2341 if(g_Config.m_SvHideScore)
2342 {
2343 log_info("chatresp", "Showing the checkpoint times is not allowed on this server.");
2344 return;
2345 }
2346
2347 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
2348 if(!pPlayer)
2349 return;
2350
2351 const char *pName = pResult->GetString(Index: 0);
2352 pSelf->Score()->LoadPlayerTimeCp(ClientId: pResult->m_ClientId, pName);
2353}
2354