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