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