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.TeamSize(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 pPlayer->Respawn(); // set spawn as strong hook
748}
749
750void CGameContext::ConPracticeCmdList(IConsole::IResult *pResult, void *pUserData)
751{
752 CGameContext *pSelf = (CGameContext *)pUserData;
753
754 char aPracticeCommands[256] = "Available practice commands: ";
755 for(const IConsole::ICommandInfo *pCmd = pSelf->Console()->FirstCommandInfo(ClientId: pResult->m_ClientId, FlagMask: CMDFLAG_PRACTICE);
756 pCmd; pCmd = pSelf->Console()->NextCommandInfo(pInfo: pCmd, ClientId: pResult->m_ClientId, FlagMask: CMDFLAG_PRACTICE))
757 {
758 char aCommand[64];
759
760 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) ? ", " : "");
761
762 if(str_length(str: aCommand) + str_length(str: aPracticeCommands) > 255)
763 {
764 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: aPracticeCommands);
765 aPracticeCommands[0] = '\0';
766 }
767 str_append(dst&: aPracticeCommands, src: aCommand);
768 }
769 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: aPracticeCommands);
770}
771
772void CGameContext::ConSwap(IConsole::IResult *pResult, void *pUserData)
773{
774 CGameContext *pSelf = (CGameContext *)pUserData;
775 const char *pName = pResult->GetString(Index: 0);
776
777 if(!CheckClientId(ClientId: pResult->m_ClientId))
778 return;
779
780 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
781 if(!pPlayer)
782 return;
783
784 if(!g_Config.m_SvSwap)
785 {
786 log_info("chatresp", "Swap is disabled on this server.");
787 return;
788 }
789
790 if(g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
791 {
792 log_info("chatresp", "Swap is not available on forced solo servers.");
793 return;
794 }
795
796 CGameTeams &Teams = pSelf->m_pController->Teams();
797
798 int Team = Teams.m_Core.Team(ClientId: pResult->m_ClientId);
799
800 if(!Teams.IsValidTeamNumber(Team))
801 {
802 log_info("chatresp", "You aren't in a valid team.");
803 return;
804 }
805
806 int TargetClientId = -1;
807 if(pResult->NumArguments() == 1)
808 {
809 TargetClientId = pSelf->FindClientIdByName(pName).value_or(u: -1);
810 }
811 else
812 {
813 int TeamSize = 1;
814 for(int i = 0; i < MAX_CLIENTS; i++)
815 {
816 if(pSelf->m_apPlayers[i] && Teams.m_Core.Team(ClientId: i) == Team && i != pResult->m_ClientId)
817 {
818 TargetClientId = i;
819 TeamSize++;
820 }
821 }
822 if(TeamSize != 2)
823 TargetClientId = -1;
824 }
825
826 if(TargetClientId < 0)
827 {
828 log_info("chatresp", "Player not found");
829 return;
830 }
831
832 if(TargetClientId == pResult->m_ClientId)
833 {
834 log_info("chatresp", "Can't swap with yourself");
835 return;
836 }
837
838 int TargetTeam = Teams.m_Core.Team(ClientId: TargetClientId);
839 if(TargetTeam != Team)
840 {
841 log_info("chatresp", "Player is on a different team");
842 return;
843 }
844
845 CPlayer *pSwapPlayer = pSelf->m_apPlayers[TargetClientId];
846 if(Team == TEAM_FLOCK || Teams.TeamFlock(Team))
847 {
848 CCharacter *pChr = pPlayer->GetCharacter();
849 CCharacter *pSwapChr = pSwapPlayer->GetCharacter();
850 if(!pChr || !pSwapChr || pChr->m_DDRaceState != ERaceState::STARTED || pSwapChr->m_DDRaceState != ERaceState::STARTED)
851 {
852 log_info("chatresp", "You and other player need to have started the map");
853 return;
854 }
855 }
856 else if(!Teams.IsStarted(Team) && !Teams.TeamFlock(Team))
857 {
858 log_info("chatresp", "Need to have started the map to swap with a player.");
859 return;
860 }
861 if(pSelf->m_World.m_Core.m_apCharacters[pResult->m_ClientId] == nullptr || pSelf->m_World.m_Core.m_apCharacters[TargetClientId] == nullptr)
862 {
863 log_info("chatresp", "You and the other player must not be paused.");
864 return;
865 }
866
867 bool SwapPending = pSwapPlayer->m_SwapTargetsClientId != pResult->m_ClientId;
868 if(SwapPending)
869 {
870 if(pSelf->ProcessSpamProtection(ClientId: pResult->m_ClientId))
871 return;
872
873 Teams.RequestTeamSwap(pPlayer, pTargetPlayer: pSwapPlayer, Team);
874 return;
875 }
876
877 Teams.SwapTeamCharacters(pPrimaryPlayer: pPlayer, pTargetPlayer: pSwapPlayer, Team);
878}
879
880void CGameContext::ConCancelSwap(IConsole::IResult *pResult, void *pUserData)
881{
882 CGameContext *pSelf = (CGameContext *)pUserData;
883
884 if(!CheckClientId(ClientId: pResult->m_ClientId))
885 return;
886
887 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
888 if(!pPlayer)
889 return;
890
891 if(!g_Config.m_SvSwap)
892 {
893 log_info("chatresp", "Swap is disabled on this server.");
894 return;
895 }
896
897 if(g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
898 {
899 log_info("chatresp", "Swap is not available on forced solo servers.");
900 return;
901 }
902
903 CGameTeams &Teams = pSelf->m_pController->Teams();
904
905 int Team = Teams.m_Core.Team(ClientId: pResult->m_ClientId);
906
907 if(!pSelf->m_pController->Teams().IsValidTeamNumber(Team))
908 {
909 log_info("chatresp", "You aren't in a valid team.");
910 return;
911 }
912
913 bool SwapPending = pPlayer->m_SwapTargetsClientId != -1 && !pSelf->Server()->ClientSlotEmpty(ClientId: pPlayer->m_SwapTargetsClientId);
914
915 if(!SwapPending)
916 {
917 log_info("chatresp", "You do not have a pending swap request.");
918 return;
919 }
920
921 Teams.CancelTeamSwap(pPlayer, Team);
922}
923
924void CGameContext::ConSave(IConsole::IResult *pResult, void *pUserData)
925{
926 CGameContext *pSelf = (CGameContext *)pUserData;
927 if(!CheckClientId(ClientId: pResult->m_ClientId))
928 return;
929
930 if(!g_Config.m_SvSaveGames)
931 {
932 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: "Save-function is disabled on this server");
933 return;
934 }
935
936 const char *pCode = "";
937 if(pResult->NumArguments() > 0)
938 pCode = pResult->GetString(Index: 0);
939
940 pSelf->Score()->SaveTeam(ClientId: pResult->m_ClientId, pCode, pServer: g_Config.m_SvSqlServerName);
941}
942
943void CGameContext::ConLoad(IConsole::IResult *pResult, void *pUserData)
944{
945 CGameContext *pSelf = (CGameContext *)pUserData;
946 if(!CheckClientId(ClientId: pResult->m_ClientId))
947 return;
948
949 if(!g_Config.m_SvSaveGames)
950 {
951 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: "Save-function is disabled on this server");
952 return;
953 }
954
955 if(pResult->NumArguments() > 0)
956 pSelf->Score()->LoadTeam(pCode: pResult->GetString(Index: 0), ClientId: pResult->m_ClientId);
957 else
958 pSelf->Score()->GetSaves(ClientId: pResult->m_ClientId);
959}
960
961void CGameContext::ConTeamRank(IConsole::IResult *pResult, void *pUserData)
962{
963 CGameContext *pSelf = (CGameContext *)pUserData;
964 if(!CheckClientId(ClientId: pResult->m_ClientId))
965 return;
966
967 if(pResult->NumArguments() > 0)
968 {
969 if(!g_Config.m_SvHideScore)
970 pSelf->Score()->ShowTeamRank(ClientId: pResult->m_ClientId, pName: pResult->GetString(Index: 0));
971 else
972 log_info("chatresp", "Showing the team rank of other players is not allowed on this server.");
973 }
974 else
975 pSelf->Score()->ShowTeamRank(ClientId: pResult->m_ClientId,
976 pName: pSelf->Server()->ClientName(ClientId: pResult->m_ClientId));
977}
978
979void CGameContext::ConRank(IConsole::IResult *pResult, void *pUserData)
980{
981 CGameContext *pSelf = (CGameContext *)pUserData;
982 if(!CheckClientId(ClientId: pResult->m_ClientId))
983 return;
984
985 if(pResult->NumArguments() > 0)
986 {
987 if(!g_Config.m_SvHideScore)
988 pSelf->Score()->ShowRank(ClientId: pResult->m_ClientId, pName: pResult->GetString(Index: 0));
989 else
990 log_info("chatresp", "Showing the rank of other players is not allowed on this server.");
991 }
992 else
993 pSelf->Score()->ShowRank(ClientId: pResult->m_ClientId,
994 pName: pSelf->Server()->ClientName(ClientId: pResult->m_ClientId));
995}
996
997void CGameContext::ConLock(IConsole::IResult *pResult, void *pUserData)
998{
999 CGameContext *pSelf = (CGameContext *)pUserData;
1000 if(!CheckClientId(ClientId: pResult->m_ClientId))
1001 return;
1002
1003 if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
1004 {
1005 log_info("chatresp", "Teams are disabled");
1006 return;
1007 }
1008
1009 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1010
1011 bool Lock = pSelf->m_pController->Teams().TeamLocked(Team);
1012
1013 if(pResult->NumArguments() > 0)
1014 Lock = !pResult->GetInteger(Index: 0);
1015
1016 if(Team == TEAM_FLOCK || !pSelf->m_pController->Teams().IsValidTeamNumber(Team))
1017 {
1018 log_info("chatresp", "This team can't be locked");
1019 return;
1020 }
1021
1022 if(pSelf->ProcessSpamProtection(ClientId: pResult->m_ClientId, RespectChatInitialDelay: false))
1023 return;
1024
1025 char aBuf[512];
1026 if(Lock)
1027 {
1028 pSelf->UnlockTeam(ClientId: pResult->m_ClientId, Team);
1029 }
1030 else
1031 {
1032 pSelf->m_pController->Teams().SetTeamLock(Team, Lock: true);
1033
1034 if(pSelf->m_pController->Teams().TeamFlock(Team))
1035 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' locked your team.", pSelf->Server()->ClientName(ClientId: pResult->m_ClientId));
1036 else
1037 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));
1038 pSelf->SendChatTeam(Team, pText: aBuf);
1039 }
1040}
1041
1042void CGameContext::ConUnlock(IConsole::IResult *pResult, void *pUserData)
1043{
1044 CGameContext *pSelf = (CGameContext *)pUserData;
1045 if(!CheckClientId(ClientId: pResult->m_ClientId))
1046 return;
1047
1048 if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
1049 {
1050 log_info("chatresp", "Teams are disabled");
1051 return;
1052 }
1053
1054 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1055
1056 if(Team == TEAM_FLOCK || !pSelf->m_pController->Teams().IsValidTeamNumber(Team))
1057 return;
1058
1059 if(pSelf->ProcessSpamProtection(ClientId: pResult->m_ClientId, RespectChatInitialDelay: false))
1060 return;
1061
1062 pSelf->UnlockTeam(ClientId: pResult->m_ClientId, Team);
1063}
1064
1065void CGameContext::UnlockTeam(int ClientId, int Team) const
1066{
1067 m_pController->Teams().SetTeamLock(Team, Lock: false);
1068
1069 char aBuf[512];
1070 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' unlocked your team.", Server()->ClientName(ClientId));
1071 SendChatTeam(Team, pText: aBuf);
1072}
1073
1074void CGameContext::AttemptJoinTeam(int ClientId, int Team)
1075{
1076 CPlayer *pPlayer = m_apPlayers[ClientId];
1077 if(!pPlayer)
1078 return;
1079
1080 if(IsRunningKickOrSpecVote(ClientId))
1081 {
1082 log_info("chatresp", "You are running a vote, please try again after the vote is done!");
1083 return;
1084 }
1085 else if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
1086 {
1087 log_info("chatresp", "Teams are disabled");
1088 return;
1089 }
1090 else if(g_Config.m_SvTeam == SV_TEAM_MANDATORY && Team == 0 && pPlayer->GetCharacter() && pPlayer->GetCharacter()->m_LastStartWarning < Server()->Tick() - 3 * Server()->TickSpeed())
1091 {
1092 log_info("chatresp", "You must join a team and play with somebody or else you can't play");
1093 pPlayer->GetCharacter()->m_LastStartWarning = Server()->Tick();
1094 }
1095
1096 if(!m_pController->Teams().IsValidTeamNumber(Team))
1097 {
1098 auto EmptyTeam = m_pController->Teams().GetFirstEmptyTeam();
1099 if(!EmptyTeam.has_value())
1100 {
1101 log_info("chatresp", "No empty team left.");
1102 return;
1103 }
1104 Team = EmptyTeam.value();
1105 }
1106
1107 char aError[512];
1108 if(pPlayer->m_LastDDRaceTeamChange + (int64_t)Server()->TickSpeed() * g_Config.m_SvTeamChangeDelay > Server()->Tick())
1109 {
1110 log_info("chatresp", "You can't change teams that fast!");
1111 }
1112 else if(Team != TEAM_FLOCK && m_pController->Teams().TeamLocked(Team) && !m_pController->Teams().IsInvited(Team, ClientId))
1113 {
1114 log_info("chatresp", g_Config.m_SvInvite ?
1115 "This team is locked using /lock. Only members of the team can unlock it using /lock." :
1116 "This team is locked using /lock. Only members of the team can invite you or unlock it using /lock.");
1117 }
1118 else if(Team != TEAM_FLOCK && m_pController->Teams().TeamSize(Team) >= g_Config.m_SvMaxTeamSize && !m_pController->Teams().TeamFlock(Team) && !m_pController->Teams().IsPractice(Team))
1119 {
1120 char aBuf[512];
1121 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "This team already has the maximum allowed size of %d players", g_Config.m_SvMaxTeamSize);
1122 log_info("chatresp", "%s", aBuf);
1123 }
1124 else if(!m_pController->Teams().SetCharacterTeam(ClientId: pPlayer->GetCid(), Team, pError: aError, ErrorSize: sizeof(aError)))
1125 {
1126 log_info("chatresp", "%s", aError);
1127 }
1128 else
1129 {
1130 if(PracticeByDefault())
1131 {
1132 // joined an empty team
1133 if(m_pController->Teams().TeamSize(Team) == 1)
1134 m_pController->Teams().SetPractice(Team, Enabled: true);
1135 }
1136
1137 char aBuf[512];
1138 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' joined team %d",
1139 Server()->ClientName(ClientId: pPlayer->GetCid()),
1140 Team);
1141 SendChat(ClientId: -1, Team: TEAM_ALL, pText: aBuf);
1142 pPlayer->m_LastDDRaceTeamChange = Server()->Tick();
1143
1144 if(m_pController->Teams().IsPractice(Team))
1145 SendChatTarget(To: pPlayer->GetCid(), pText: "Practice mode enabled for your team, happy practicing!");
1146
1147 if(m_pController->Teams().TeamFlock(Team))
1148 SendChatTarget(To: pPlayer->GetCid(), pText: "Team 0 mode enabled for your team. This will make your team behave like team 0.");
1149 }
1150}
1151
1152void CGameContext::ConInvite(IConsole::IResult *pResult, void *pUserData)
1153{
1154 CGameContext *pSelf = (CGameContext *)pUserData;
1155 auto *pController = pSelf->m_pController;
1156 const char *pName = pResult->GetString(Index: 0);
1157
1158 if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
1159 {
1160 log_info("chatresp", "Teams are disabled");
1161 return;
1162 }
1163
1164 if(!g_Config.m_SvInvite)
1165 {
1166 log_info("chatresp", "Invites are disabled");
1167 return;
1168 }
1169
1170 int Team = pController->Teams().m_Core.Team(ClientId: pResult->m_ClientId);
1171 if(Team != TEAM_FLOCK && pController->Teams().IsValidTeamNumber(Team))
1172 {
1173 int Target = pSelf->FindClientIdByName(pName).value_or(u: -1);
1174 if(Target == -1)
1175 {
1176 log_info("chatresp", "Player not found");
1177 return;
1178 }
1179
1180 if(pController->Teams().IsInvited(Team, ClientId: Target))
1181 {
1182 log_info("chatresp", "Player already invited");
1183 return;
1184 }
1185
1186 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())
1187 {
1188 log_info("chatresp", "Can't invite this quickly");
1189 return;
1190 }
1191
1192 pController->Teams().SetClientInvited(Team, ClientId: Target, Invited: true);
1193 pSelf->m_apPlayers[pResult->m_ClientId]->m_LastInvited = pSelf->Server()->Tick();
1194
1195 char aBuf[512];
1196 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);
1197 pSelf->SendChatTarget(To: Target, pText: aBuf);
1198
1199 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));
1200 pSelf->SendChatTeam(Team, pText: aBuf);
1201 }
1202 else
1203 log_info("chatresp", "Can't invite players to this team");
1204}
1205
1206void CGameContext::ConTeam0Mode(IConsole::IResult *pResult, void *pUserData)
1207{
1208 CGameContext *pSelf = (CGameContext *)pUserData;
1209 auto *pController = pSelf->m_pController;
1210
1211 if(!CheckClientId(ClientId: pResult->m_ClientId))
1212 return;
1213
1214 if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO || g_Config.m_SvTeam == SV_TEAM_MANDATORY)
1215 {
1216 log_info("chatresp", "Team mode change disabled");
1217 return;
1218 }
1219
1220 if(!g_Config.m_SvTeam0Mode)
1221 {
1222 log_info("chatresp", "Team mode change is disabled on this server.");
1223 return;
1224 }
1225
1226 int Team = pController->Teams().m_Core.Team(ClientId: pResult->m_ClientId);
1227 bool Mode = pController->Teams().TeamFlock(Team);
1228
1229 if(Team == TEAM_FLOCK || !pController->Teams().IsValidTeamNumber(Team))
1230 {
1231 log_info("chatresp", "This team can't have the mode changed");
1232 return;
1233 }
1234
1235 if(pController->Teams().GetTeamState(Team) != ETeamState::OPEN)
1236 {
1237 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: "Team mode can't be changed while racing");
1238 return;
1239 }
1240
1241 if(pResult->NumArguments() > 0)
1242 Mode = !pResult->GetInteger(Index: 0);
1243
1244 if(pSelf->ProcessSpamProtection(ClientId: pResult->m_ClientId, RespectChatInitialDelay: false))
1245 return;
1246
1247 char aBuf[512];
1248 if(Mode)
1249 {
1250 if(pController->Teams().TeamSize(Team) > g_Config.m_SvMaxTeamSize)
1251 {
1252 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);
1253 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: aBuf);
1254 }
1255 else
1256 {
1257 pController->Teams().SetTeamFlock(Team, Mode: false);
1258
1259 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' disabled team 0 mode.", pSelf->Server()->ClientName(ClientId: pResult->m_ClientId));
1260 pSelf->SendChatTeam(Team, pText: aBuf);
1261 }
1262 }
1263 else
1264 {
1265 if(pController->Teams().IsPractice(Team))
1266 {
1267 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: "Can't enable team 0 mode with practice mode on.");
1268 }
1269 else
1270 {
1271 pController->Teams().SetTeamFlock(Team, Mode: true);
1272
1273 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));
1274 pSelf->SendChatTeam(Team, pText: aBuf);
1275 }
1276 }
1277}
1278
1279void CGameContext::ConTeam(IConsole::IResult *pResult, void *pUserData)
1280{
1281 CGameContext *pSelf = (CGameContext *)pUserData;
1282 if(!CheckClientId(ClientId: pResult->m_ClientId))
1283 return;
1284
1285 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1286 if(!pPlayer)
1287 return;
1288
1289 if(pResult->NumArguments() > 0)
1290 {
1291 pSelf->AttemptJoinTeam(ClientId: pResult->m_ClientId, Team: pResult->GetInteger(Index: 0));
1292 }
1293 else
1294 {
1295 char aBuf[512];
1296 if(!pPlayer->IsPlaying())
1297 {
1298 log_info("chatresp", "You can't check your team while you are dead/a spectator.");
1299 }
1300 else
1301 {
1302 int TeamSize = 0;
1303 const int PlayerTeam = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1304
1305 // Count players in team
1306 for(int ClientId = 0; ClientId < MAX_CLIENTS; ClientId++)
1307 {
1308 const CPlayer *pOtherPlayer = pSelf->m_apPlayers[ClientId];
1309 if(!pOtherPlayer || !pOtherPlayer->IsPlaying())
1310 continue;
1311
1312 if(pSelf->GetDDRaceTeam(ClientId) == PlayerTeam)
1313 TeamSize++;
1314 }
1315
1316 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "You are in team %d having %d %s", PlayerTeam, TeamSize, TeamSize > 1 ? "players" : "player");
1317 log_info("chatresp", "%s", aBuf);
1318 }
1319 }
1320}
1321
1322void CGameContext::ConJoin(IConsole::IResult *pResult, void *pUserData)
1323{
1324 CGameContext *pSelf = (CGameContext *)pUserData;
1325 if(!CheckClientId(ClientId: pResult->m_ClientId))
1326 return;
1327
1328 const char *pName = pResult->GetString(Index: 0);
1329 int Target = pSelf->FindClientIdByName(pName).value_or(u: -1);
1330 if(Target == -1)
1331 {
1332 log_info("chatresp", "Player not found");
1333 return;
1334 }
1335
1336 int Team = pSelf->GetDDRaceTeam(ClientId: Target);
1337 if(pSelf->ProcessSpamProtection(ClientId: pResult->m_ClientId, RespectChatInitialDelay: false))
1338 return;
1339
1340 pSelf->AttemptJoinTeam(ClientId: pResult->m_ClientId, Team);
1341}
1342
1343void CGameContext::ConConverse(IConsole::IResult *pResult, void *pUserData)
1344{
1345 // This will never be called
1346}
1347
1348void CGameContext::ConWhisper(IConsole::IResult *pResult, void *pUserData)
1349{
1350 // This will never be called
1351}
1352
1353void CGameContext::ConSetEyeEmote(IConsole::IResult *pResult,
1354 void *pUserData)
1355{
1356 CGameContext *pSelf = (CGameContext *)pUserData;
1357 if(!CheckClientId(ClientId: pResult->m_ClientId))
1358 return;
1359
1360 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1361 if(!pPlayer)
1362 return;
1363 if(pResult->NumArguments() == 0)
1364 {
1365 log_info("chatresp", pPlayer->m_EyeEmoteEnabled ?
1366 "You can now use the preset eye emotes." :
1367 "You don't have any eye emotes, remember to bind some.");
1368 return;
1369 }
1370 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "on") == 0)
1371 pPlayer->m_EyeEmoteEnabled = true;
1372 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "off") == 0)
1373 pPlayer->m_EyeEmoteEnabled = false;
1374 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "toggle") == 0)
1375 pPlayer->m_EyeEmoteEnabled = !pPlayer->m_EyeEmoteEnabled;
1376 log_info("chatresp", pPlayer->m_EyeEmoteEnabled ?
1377 "You can now use the preset eye emotes." :
1378 "You don't have any eye emotes, remember to bind some.");
1379}
1380
1381void CGameContext::ConEyeEmote(IConsole::IResult *pResult, void *pUserData)
1382{
1383 CGameContext *pSelf = (CGameContext *)pUserData;
1384 if(g_Config.m_SvEmotionalTees == -1)
1385 {
1386 log_info("chatresp", "Emotes are disabled.");
1387 return;
1388 }
1389
1390 if(!CheckClientId(ClientId: pResult->m_ClientId))
1391 return;
1392
1393 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1394 if(!pPlayer)
1395 return;
1396
1397 if(pResult->NumArguments() == 0)
1398 {
1399 log_info("chatresp", "Emote commands are: /emote surprise /emote blink /emote close /emote angry /emote happy /emote pain /emote normal");
1400 log_info("chatresp", "Example: /emote surprise 10 for 10 seconds or /emote surprise (default 1 second)");
1401 }
1402 else
1403 {
1404 if(!pPlayer->CanOverrideDefaultEmote())
1405 return;
1406
1407 int EmoteType = 0;
1408 if(!str_comp_nocase(a: pResult->GetString(Index: 0), b: "angry"))
1409 EmoteType = EMOTE_ANGRY;
1410 else if(!str_comp_nocase(a: pResult->GetString(Index: 0), b: "blink"))
1411 EmoteType = EMOTE_BLINK;
1412 else if(!str_comp_nocase(a: pResult->GetString(Index: 0), b: "close"))
1413 EmoteType = EMOTE_BLINK;
1414 else if(!str_comp_nocase(a: pResult->GetString(Index: 0), b: "happy"))
1415 EmoteType = EMOTE_HAPPY;
1416 else if(!str_comp_nocase(a: pResult->GetString(Index: 0), b: "pain"))
1417 EmoteType = EMOTE_PAIN;
1418 else if(!str_comp_nocase(a: pResult->GetString(Index: 0), b: "surprise"))
1419 EmoteType = EMOTE_SURPRISE;
1420 else if(!str_comp_nocase(a: pResult->GetString(Index: 0), b: "normal"))
1421 EmoteType = EMOTE_NORMAL;
1422 else
1423 {
1424 log_info("chatresp", "Unknown emote... Say /emote");
1425 return;
1426 }
1427
1428 int Duration = 1;
1429 if(pResult->NumArguments() > 1)
1430 Duration = std::clamp(val: pResult->GetInteger(Index: 1), lo: 1, hi: 86400);
1431
1432 pPlayer->OverrideDefaultEmote(Emote: EmoteType, Tick: pSelf->Server()->Tick() + Duration * pSelf->Server()->TickSpeed());
1433 }
1434}
1435
1436void CGameContext::ConNinjaJetpack(IConsole::IResult *pResult, void *pUserData)
1437{
1438 CGameContext *pSelf = (CGameContext *)pUserData;
1439 if(!CheckClientId(ClientId: pResult->m_ClientId))
1440 return;
1441
1442 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1443 if(!pPlayer)
1444 return;
1445 if(pResult->NumArguments())
1446 pPlayer->m_NinjaJetpack = pResult->GetInteger(Index: 0);
1447 else
1448 pPlayer->m_NinjaJetpack = !pPlayer->m_NinjaJetpack;
1449}
1450
1451void CGameContext::ConShowOthers(IConsole::IResult *pResult, void *pUserData)
1452{
1453 CGameContext *pSelf = (CGameContext *)pUserData;
1454 if(!CheckClientId(ClientId: pResult->m_ClientId))
1455 return;
1456
1457 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1458 if(!pPlayer)
1459 return;
1460 if(g_Config.m_SvShowOthers)
1461 {
1462 if(pResult->NumArguments())
1463 pPlayer->m_ShowOthers = pResult->GetInteger(Index: 0);
1464 else
1465 pPlayer->m_ShowOthers = !pPlayer->m_ShowOthers;
1466 }
1467 else
1468 log_info("chatresp", "Showing players from other teams is disabled");
1469}
1470
1471void CGameContext::ConShowAll(IConsole::IResult *pResult, void *pUserData)
1472{
1473 CGameContext *pSelf = (CGameContext *)pUserData;
1474 if(!CheckClientId(ClientId: pResult->m_ClientId))
1475 return;
1476
1477 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1478 if(!pPlayer)
1479 return;
1480
1481 if(pResult->NumArguments())
1482 {
1483 if(pPlayer->m_ShowAll == (bool)pResult->GetInteger(Index: 0))
1484 return;
1485
1486 pPlayer->m_ShowAll = pResult->GetInteger(Index: 0);
1487 }
1488 else
1489 {
1490 pPlayer->m_ShowAll = !pPlayer->m_ShowAll;
1491 }
1492
1493 if(pPlayer->m_ShowAll)
1494 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: "You will now see all tees on this server, no matter the distance");
1495 else
1496 pSelf->SendChatTarget(To: pResult->m_ClientId, pText: "You will no longer see all tees on this server");
1497}
1498
1499void CGameContext::ConSpecTeam(IConsole::IResult *pResult, void *pUserData)
1500{
1501 CGameContext *pSelf = (CGameContext *)pUserData;
1502 if(!CheckClientId(ClientId: pResult->m_ClientId))
1503 return;
1504
1505 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1506 if(!pPlayer)
1507 return;
1508
1509 if(pResult->NumArguments())
1510 pPlayer->m_SpecTeam = pResult->GetInteger(Index: 0);
1511 else
1512 pPlayer->m_SpecTeam = !pPlayer->m_SpecTeam;
1513}
1514
1515void CGameContext::ConSayTime(IConsole::IResult *pResult, void *pUserData)
1516{
1517 CGameContext *pSelf = (CGameContext *)pUserData;
1518 if(!CheckClientId(ClientId: pResult->m_ClientId))
1519 return;
1520
1521 int ClientId;
1522 char aBufName[MAX_NAME_LENGTH];
1523
1524 if(pResult->NumArguments() > 0)
1525 {
1526 ClientId = pSelf->FindClientIdByName(pName: pResult->GetString(Index: 0)).value_or(u: -1);
1527 if(ClientId == -1)
1528 return;
1529
1530 str_format(buffer: aBufName, buffer_size: sizeof(aBufName), format: "%s's", pSelf->Server()->ClientName(ClientId));
1531 }
1532 else
1533 {
1534 str_copy(dst: aBufName, src: "Your", dst_size: sizeof(aBufName));
1535 ClientId = pResult->m_ClientId;
1536 }
1537
1538 CPlayer *pPlayer = pSelf->m_apPlayers[ClientId];
1539 if(!pPlayer)
1540 return;
1541 CCharacter *pChr = pPlayer->GetCharacter();
1542 if(!pChr)
1543 return;
1544 if(pChr->m_DDRaceState != ERaceState::STARTED)
1545 return;
1546
1547 char aBufTime[32];
1548 char aBuf[64];
1549 int64_t Time = (int64_t)100 * (float)(pSelf->Server()->Tick() - pChr->m_StartTime) / ((float)pSelf->Server()->TickSpeed());
1550 str_time(centisecs: Time, format: ETimeFormat::HOURS, buffer: aBufTime, buffer_size: sizeof(aBufTime));
1551 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s current race time is %s", aBufName, aBufTime);
1552 log_info("chatresp", "%s", aBuf);
1553}
1554
1555void CGameContext::ConSayTimeAll(IConsole::IResult *pResult, void *pUserData)
1556{
1557 CGameContext *pSelf = (CGameContext *)pUserData;
1558 if(!CheckClientId(ClientId: pResult->m_ClientId))
1559 return;
1560
1561 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1562 if(!pPlayer)
1563 return;
1564 CCharacter *pChr = pPlayer->GetCharacter();
1565 if(!pChr)
1566 return;
1567 if(pChr->m_DDRaceState != ERaceState::STARTED)
1568 return;
1569
1570 char aBufTime[32];
1571 char aBuf[64];
1572 int64_t Time = (int64_t)100 * (float)(pSelf->Server()->Tick() - pChr->m_StartTime) / ((float)pSelf->Server()->TickSpeed());
1573 const char *pName = pSelf->Server()->ClientName(ClientId: pResult->m_ClientId);
1574 str_time(centisecs: Time, format: ETimeFormat::HOURS, buffer: aBufTime, buffer_size: sizeof(aBufTime));
1575 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s's current race time is %s", pName, aBufTime);
1576 pSelf->SendChat(ClientId: -1, Team: TEAM_ALL, pText: aBuf, SpamProtectionClientId: pResult->m_ClientId);
1577}
1578
1579void CGameContext::ConTime(IConsole::IResult *pResult, void *pUserData)
1580{
1581 CGameContext *pSelf = (CGameContext *)pUserData;
1582 if(!CheckClientId(ClientId: pResult->m_ClientId))
1583 return;
1584
1585 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1586 if(!pPlayer)
1587 return;
1588 CCharacter *pChr = pPlayer->GetCharacter();
1589 if(!pChr)
1590 return;
1591
1592 char aBufTime[32];
1593 char aBuf[64];
1594 int64_t Time = (int64_t)100 * (float)(pSelf->Server()->Tick() - pChr->m_StartTime) / ((float)pSelf->Server()->TickSpeed());
1595 str_time(centisecs: Time, format: ETimeFormat::HOURS, buffer: aBufTime, buffer_size: sizeof(aBufTime));
1596 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Your time is %s", aBufTime);
1597 pSelf->SendBroadcast(pText: aBuf, ClientId: pResult->m_ClientId);
1598}
1599
1600static const char s_aaMsg[4][128] = {"game/round timer.", "broadcast.", "both game/round timer and broadcast.", "racetime."};
1601
1602void CGameContext::ConSetTimerType(IConsole::IResult *pResult, void *pUserData)
1603{
1604 CGameContext *pSelf = (CGameContext *)pUserData;
1605
1606 if(!CheckClientId(ClientId: pResult->m_ClientId))
1607 return;
1608
1609 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1610 if(!pPlayer)
1611 return;
1612
1613 char aBuf[128];
1614
1615 if(pResult->NumArguments() > 0)
1616 {
1617 int OldType = pPlayer->m_TimerType;
1618 bool Result = false;
1619
1620 if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "default") == 0)
1621 Result = pPlayer->SetTimerType(CPlayer::TIMERTYPE_DEFAULT);
1622 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "gametimer") == 0)
1623 Result = pPlayer->SetTimerType(CPlayer::TIMERTYPE_GAMETIMER);
1624 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "broadcast") == 0)
1625 Result = pPlayer->SetTimerType(CPlayer::TIMERTYPE_BROADCAST);
1626 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "both") == 0)
1627 Result = pPlayer->SetTimerType(CPlayer::TIMERTYPE_GAMETIMER_AND_BROADCAST);
1628 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "none") == 0)
1629 Result = pPlayer->SetTimerType(CPlayer::TIMERTYPE_NONE);
1630 else
1631 {
1632 log_info("chatresp", "Unknown parameter. Accepted values: default, gametimer, broadcast, both, none");
1633 return;
1634 }
1635
1636 if(!Result)
1637 {
1638 log_info("chatresp", "Selected timertype is not supported by your client");
1639 return;
1640 }
1641
1642 if((OldType == CPlayer::TIMERTYPE_BROADCAST || OldType == CPlayer::TIMERTYPE_GAMETIMER_AND_BROADCAST) && (pPlayer->m_TimerType == CPlayer::TIMERTYPE_GAMETIMER || pPlayer->m_TimerType == CPlayer::TIMERTYPE_NONE))
1643 pSelf->SendBroadcast(pText: "", ClientId: pResult->m_ClientId);
1644 }
1645
1646 if(pPlayer->m_TimerType <= CPlayer::TIMERTYPE_SIXUP && pPlayer->m_TimerType >= CPlayer::TIMERTYPE_GAMETIMER)
1647 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Timer is displayed in %s", s_aaMsg[pPlayer->m_TimerType]);
1648 else if(pPlayer->m_TimerType == CPlayer::TIMERTYPE_NONE)
1649 str_copy(dst&: aBuf, src: "Timer isn't displayed.");
1650
1651 log_info("chatresp", "%s", aBuf);
1652}
1653
1654void CGameContext::ConRescue(IConsole::IResult *pResult, void *pUserData)
1655{
1656 CGameContext *pSelf = (CGameContext *)pUserData;
1657 if(!CheckClientId(ClientId: pResult->m_ClientId))
1658 return;
1659 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1660 if(!pPlayer)
1661 return;
1662 CCharacter *pChr = pPlayer->GetCharacter();
1663 if(!pChr)
1664 return;
1665
1666 CGameTeams &Teams = pSelf->m_pController->Teams();
1667 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1668 if(!g_Config.m_SvRescue && !Teams.IsPractice(Team))
1669 {
1670 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.");
1671 return;
1672 }
1673
1674 bool GoRescue = true;
1675
1676 if(pPlayer->m_RescueMode == RESCUEMODE_MANUAL)
1677 {
1678 // if character can't set their rescue state then we should rescue them instead
1679 GoRescue = !pChr->TrySetRescue(RescueMode: RESCUEMODE_MANUAL);
1680 }
1681
1682 if(GoRescue)
1683 {
1684 pChr->Rescue();
1685 pChr->Unfreeze();
1686 }
1687}
1688
1689void CGameContext::ConRescueMode(IConsole::IResult *pResult, void *pUserData)
1690{
1691 CGameContext *pSelf = (CGameContext *)pUserData;
1692 if(!CheckClientId(ClientId: pResult->m_ClientId))
1693 return;
1694 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1695 if(!pPlayer)
1696 return;
1697
1698 CGameTeams &Teams = pSelf->m_pController->Teams();
1699 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1700 if(!g_Config.m_SvRescue && !Teams.IsPractice(Team))
1701 {
1702 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.");
1703 return;
1704 }
1705
1706 if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "auto") == 0)
1707 {
1708 if(pPlayer->m_RescueMode != RESCUEMODE_AUTO)
1709 {
1710 pPlayer->m_RescueMode = RESCUEMODE_AUTO;
1711
1712 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "Rescue mode changed to auto.");
1713 }
1714
1715 return;
1716 }
1717
1718 if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "manual") == 0)
1719 {
1720 if(pPlayer->m_RescueMode != RESCUEMODE_MANUAL)
1721 {
1722 pPlayer->m_RescueMode = RESCUEMODE_MANUAL;
1723
1724 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "Rescue mode changed to manual.");
1725 }
1726
1727 return;
1728 }
1729
1730 if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "list") == 0)
1731 {
1732 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "Available rescue modes: auto, manual");
1733 }
1734 else if(str_comp_nocase(a: pResult->GetString(Index: 0), b: "") == 0)
1735 {
1736 char aBuf[64];
1737 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Current rescue mode: %s.", pPlayer->m_RescueMode == RESCUEMODE_MANUAL ? "manual" : "auto");
1738 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: aBuf);
1739 }
1740 else
1741 {
1742 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "Unknown argument. Check '/rescuemode list'");
1743 }
1744}
1745
1746void CGameContext::ConBack(IConsole::IResult *pResult, void *pUserData)
1747{
1748 auto *pSelf = static_cast<CGameContext *>(pUserData);
1749 if(auto *pChr = pSelf->GetPracticeCharacter(pResult))
1750 {
1751 auto *pPlayer = pChr->GetPlayer();
1752 if(!pPlayer->m_LastDeath.has_value())
1753 {
1754 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "There is nowhere to go back to.");
1755 return;
1756 }
1757 pChr->GetLastRescueTeeRef(Mode: pPlayer->m_RescueMode) = pPlayer->m_LastDeath.value();
1758 pChr->Rescue();
1759 pChr->Unfreeze();
1760 }
1761}
1762
1763void CGameContext::ConTeleTo(IConsole::IResult *pResult, void *pUserData)
1764{
1765 CGameContext *pSelf = (CGameContext *)pUserData;
1766 if(!CheckClientId(ClientId: pResult->m_ClientId))
1767 return;
1768 CPlayer *pCallingPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1769 if(!pCallingPlayer)
1770 return;
1771 CCharacter *pCallingCharacter = pCallingPlayer->GetCharacter();
1772 if(!pCallingCharacter)
1773 return;
1774
1775 CGameTeams &Teams = pSelf->m_pController->Teams();
1776 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1777 if(!Teams.IsPractice(Team))
1778 {
1779 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.");
1780 return;
1781 }
1782
1783 vec2 Pos = {};
1784
1785 if(pResult->NumArguments() == 0)
1786 {
1787 // Set calling tee's position to the origin of its spectating viewport
1788 Pos = pCallingPlayer->m_ViewPos;
1789 }
1790 else
1791 {
1792 const CPlayer *pDestPlayer = pSelf->FindPlayerByName(pName: pResult->GetString(Index: 0));
1793 if(!pDestPlayer)
1794 {
1795 pSelf->SendChatTarget(To: pCallingPlayer->GetCid(), pText: "No player with this name found.");
1796 return;
1797 }
1798 const CCharacter *pDestCharacter = pDestPlayer->GetCharacter();
1799 if(!pDestCharacter)
1800 return;
1801
1802 // Set calling tee's position to that of the destination tee
1803 Pos = pDestCharacter->m_Pos;
1804 }
1805
1806 // Teleport tee
1807 pSelf->Teleport(pChr: pCallingCharacter, Pos);
1808 pCallingCharacter->ResetJumps();
1809 pCallingCharacter->Unfreeze();
1810 pCallingCharacter->ResetVelocity();
1811 pCallingPlayer->m_LastTeleTee.Save(pChr: pCallingCharacter);
1812}
1813
1814void CGameContext::ConTeleXY(IConsole::IResult *pResult, void *pUserData)
1815{
1816 CGameContext *pSelf = (CGameContext *)pUserData;
1817 if(!CheckClientId(ClientId: pResult->m_ClientId))
1818 return;
1819 CPlayer *pCallingPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1820 if(!pCallingPlayer)
1821 return;
1822 CCharacter *pCallingCharacter = pCallingPlayer->GetCharacter();
1823 if(!pCallingCharacter)
1824 return;
1825
1826 CGameTeams &Teams = pSelf->m_pController->Teams();
1827 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1828 if(!Teams.IsPractice(Team))
1829 {
1830 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.");
1831 return;
1832 }
1833
1834 vec2 Pos = {};
1835
1836 if(pResult->NumArguments() != 2)
1837 {
1838 pSelf->SendChatTarget(To: pCallingPlayer->GetCid(), pText: "Can't recognize specified arguments. Usage: /tpxy x y, e.g. /tpxy 9 3.");
1839 return;
1840 }
1841 else
1842 {
1843 float BaseX = 0.f, BaseY = 0.f;
1844
1845 CMapItemLayerTilemap *pGameLayer = pSelf->m_Layers.GameLayer();
1846 constexpr float OuterKillTileBoundaryDistance = 201 * 32.f;
1847 float MapWidth = (pGameLayer->m_Width * 32) + (OuterKillTileBoundaryDistance * 2.f), MapHeight = (pGameLayer->m_Height * 32) + (OuterKillTileBoundaryDistance * 2.f);
1848
1849 const auto DetermineCoordinateRelativity = [](const char *pInString, const float AbsoluteDefaultValue, float &OutFloat) -> bool {
1850 // mode 0 = abs, 1 = sub, 2 = add
1851
1852 // Relative?
1853 const char *pStrDelta = str_startswith(str: pInString, prefix: "~");
1854
1855 float d;
1856 if(!str_tofloat(str: pStrDelta ? pStrDelta : pInString, out: &d))
1857 return false;
1858
1859 // Is the number valid?
1860 if(std::isnan(x: d) || std::isinf(x: d))
1861 return false;
1862
1863 // Convert our gleaned 'display' coordinate to an actual map coordinate
1864 d *= 32.f;
1865
1866 OutFloat = (pStrDelta ? AbsoluteDefaultValue : 0) + d;
1867 return true;
1868 };
1869
1870 if(!DetermineCoordinateRelativity(pResult->GetString(Index: 0), pCallingPlayer->m_ViewPos.x, BaseX))
1871 {
1872 pSelf->SendChatTarget(To: pCallingPlayer->GetCid(), pText: "Invalid X coordinate.");
1873 return;
1874 }
1875 if(!DetermineCoordinateRelativity(pResult->GetString(Index: 1), pCallingPlayer->m_ViewPos.y, BaseY))
1876 {
1877 pSelf->SendChatTarget(To: pCallingPlayer->GetCid(), pText: "Invalid Y coordinate.");
1878 return;
1879 }
1880
1881 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)};
1882 }
1883
1884 // Teleport tee
1885 pSelf->Teleport(pChr: pCallingCharacter, Pos);
1886 pCallingCharacter->ResetJumps();
1887 pCallingCharacter->Unfreeze();
1888 pCallingCharacter->ResetVelocity();
1889 pCallingPlayer->m_LastTeleTee.Save(pChr: pCallingCharacter);
1890}
1891
1892void CGameContext::ConTeleCursor(IConsole::IResult *pResult, void *pUserData)
1893{
1894 CGameContext *pSelf = (CGameContext *)pUserData;
1895 if(!CheckClientId(ClientId: pResult->m_ClientId))
1896 return;
1897 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1898 if(!pPlayer)
1899 return;
1900 CCharacter *pChr = pPlayer->GetCharacter();
1901 if(!pChr)
1902 return;
1903
1904 CGameTeams &Teams = pSelf->m_pController->Teams();
1905 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1906 if(!Teams.IsPractice(Team))
1907 {
1908 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.");
1909 return;
1910 }
1911
1912 // default to view pos when character is not available
1913 vec2 Pos = pPlayer->m_ViewPos;
1914 if(pResult->NumArguments() == 0 && !pPlayer->IsPaused() && pPlayer->GetCharacter() && pPlayer->GetCharacter()->IsAlive())
1915 {
1916 vec2 Target = vec2(pChr->Core()->m_Input.m_TargetX, pChr->Core()->m_Input.m_TargetY);
1917 Pos = pPlayer->m_CameraInfo.ConvertTargetToWorld(Position: pPlayer->GetCharacter()->GetPos(), Target);
1918 }
1919 else if(pResult->NumArguments() > 0)
1920 {
1921 const CPlayer *pPlayerTo = pSelf->FindPlayerByName(pName: pResult->GetString(Index: 0));
1922 if(!pPlayerTo)
1923 {
1924 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "No player with this name found.");
1925 return;
1926 }
1927 const CCharacter *pChrTo = pPlayerTo->GetCharacter();
1928 if(!pChrTo)
1929 return;
1930 Pos = pChrTo->m_Pos;
1931 }
1932 pSelf->Teleport(pChr, Pos);
1933 pChr->ResetJumps();
1934 pChr->Unfreeze();
1935 pChr->ResetVelocity();
1936 pPlayer->m_LastTeleTee.Save(pChr);
1937}
1938
1939void CGameContext::ConLastTele(IConsole::IResult *pResult, void *pUserData)
1940{
1941 CGameContext *pSelf = (CGameContext *)pUserData;
1942 if(!CheckClientId(ClientId: pResult->m_ClientId))
1943 return;
1944 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
1945 if(!pPlayer)
1946 return;
1947 CCharacter *pChr = pPlayer->GetCharacter();
1948 if(!pChr)
1949 return;
1950
1951 CGameTeams &Teams = pSelf->m_pController->Teams();
1952 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
1953 if(!Teams.IsPractice(Team))
1954 {
1955 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.");
1956 return;
1957 }
1958 if(!pPlayer->m_LastTeleTee.GetPos().x)
1959 {
1960 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "You haven't previously teleported. Use /tp before using this command.");
1961 return;
1962 }
1963 pPlayer->m_LastTeleTee.Load(pChr);
1964 pPlayer->Pause(State: CPlayer::PAUSE_NONE, Force: true);
1965}
1966
1967CCharacter *CGameContext::GetPracticeCharacter(IConsole::IResult *pResult)
1968{
1969 if(!CheckClientId(ClientId: pResult->m_ClientId))
1970 return nullptr;
1971 CPlayer *pPlayer = m_apPlayers[pResult->m_ClientId];
1972 if(!pPlayer)
1973 return nullptr;
1974 CCharacter *pChr = pPlayer->GetCharacter();
1975 if(!pChr)
1976 return nullptr;
1977
1978 CGameTeams &Teams = m_pController->Teams();
1979 int Team = GetDDRaceTeam(ClientId: pResult->m_ClientId);
1980 if(!Teams.IsPractice(Team))
1981 {
1982 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.");
1983 return nullptr;
1984 }
1985 return pChr;
1986}
1987
1988void CGameContext::ConPracticeToTeleporter(IConsole::IResult *pResult, void *pUserData)
1989{
1990 CGameContext *pSelf = (CGameContext *)pUserData;
1991 CCharacter *pChr = pSelf->GetPracticeCharacter(pResult);
1992 if(pChr)
1993 {
1994 if(pSelf->Collision()->TeleOuts(Number: pResult->GetInteger(Index: 0) - 1).empty())
1995 {
1996 pSelf->SendChatTarget(To: pChr->GetPlayer()->GetCid(), pText: "There is no teleporter with that index on the map.");
1997 return;
1998 }
1999
2000 ConToTeleporter(pResult, pUserData);
2001 pChr->ResetJumps();
2002 pChr->Unfreeze();
2003 pChr->ResetVelocity();
2004 pChr->GetPlayer()->m_LastTeleTee.Save(pChr);
2005 }
2006}
2007
2008void CGameContext::ConPracticeToCheckTeleporter(IConsole::IResult *pResult, void *pUserData)
2009{
2010 CGameContext *pSelf = (CGameContext *)pUserData;
2011 CCharacter *pChr = pSelf->GetPracticeCharacter(pResult);
2012 if(pChr)
2013 {
2014 if(pSelf->Collision()->TeleCheckOuts(Number: pResult->GetInteger(Index: 0) - 1).empty())
2015 {
2016 pSelf->SendChatTarget(To: pChr->GetPlayer()->GetCid(), pText: "There is no checkpoint teleporter with that index on the map.");
2017 return;
2018 }
2019
2020 ConToCheckTeleporter(pResult, pUserData);
2021 pChr->ResetJumps();
2022 pChr->Unfreeze();
2023 pChr->ResetVelocity();
2024 pChr->GetPlayer()->m_LastTeleTee.Save(pChr);
2025 }
2026}
2027
2028void CGameContext::ConPracticeUnSolo(IConsole::IResult *pResult, void *pUserData)
2029{
2030 CGameContext *pSelf = (CGameContext *)pUserData;
2031 if(!CheckClientId(ClientId: pResult->m_ClientId))
2032 return;
2033 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
2034 if(!pPlayer)
2035 return;
2036 CCharacter *pChr = pPlayer->GetCharacter();
2037 if(!pChr)
2038 return;
2039
2040 if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
2041 {
2042 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "Command is not available on solo servers");
2043 return;
2044 }
2045
2046 CGameTeams &Teams = pSelf->m_pController->Teams();
2047 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
2048 if(!Teams.IsPractice(Team))
2049 {
2050 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.");
2051 return;
2052 }
2053 pChr->SetSolo(false);
2054}
2055
2056void CGameContext::ConPracticeSolo(IConsole::IResult *pResult, void *pUserData)
2057{
2058 CGameContext *pSelf = (CGameContext *)pUserData;
2059 if(!CheckClientId(ClientId: pResult->m_ClientId))
2060 return;
2061 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
2062 if(!pPlayer)
2063 return;
2064 CCharacter *pChr = pPlayer->GetCharacter();
2065 if(!pChr)
2066 return;
2067
2068 if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO)
2069 {
2070 pSelf->SendChatTarget(To: pPlayer->GetCid(), pText: "Command is not available on solo servers");
2071 return;
2072 }
2073
2074 CGameTeams &Teams = pSelf->m_pController->Teams();
2075 int Team = pSelf->GetDDRaceTeam(ClientId: pResult->m_ClientId);
2076 if(!Teams.IsPractice(Team))
2077 {
2078 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.");
2079 return;
2080 }
2081 pChr->SetSolo(true);
2082}
2083
2084void CGameContext::ConPracticeUnDeep(IConsole::IResult *pResult, void *pUserData)
2085{
2086 CGameContext *pSelf = (CGameContext *)pUserData;
2087 auto *pChr = pSelf->GetPracticeCharacter(pResult);
2088 if(!pChr)
2089 return;
2090
2091 pChr->SetDeepFrozen(false);
2092 pChr->Unfreeze();
2093}
2094
2095void CGameContext::ConPracticeDeep(IConsole::IResult *pResult, void *pUserData)
2096{
2097 CGameContext *pSelf = (CGameContext *)pUserData;
2098 auto *pChr = pSelf->GetPracticeCharacter(pResult);
2099 if(!pChr)
2100 return;
2101
2102 pChr->SetDeepFrozen(true);
2103}
2104
2105void CGameContext::ConPracticeUnLiveFreeze(IConsole::IResult *pResult, void *pUserData)
2106{
2107 CGameContext *pSelf = (CGameContext *)pUserData;
2108 auto *pChr = pSelf->GetPracticeCharacter(pResult);
2109 if(!pChr)
2110 return;
2111
2112 pChr->SetLiveFrozen(false);
2113}
2114
2115void CGameContext::ConPracticeLiveFreeze(IConsole::IResult *pResult, void *pUserData)
2116{
2117 CGameContext *pSelf = (CGameContext *)pUserData;
2118 auto *pChr = pSelf->GetPracticeCharacter(pResult);
2119 if(!pChr)
2120 return;
2121
2122 pChr->SetLiveFrozen(true);
2123}
2124
2125void CGameContext::ConPracticeShotgun(IConsole::IResult *pResult, void *pUserData)
2126{
2127 CGameContext *pSelf = (CGameContext *)pUserData;
2128 if(pSelf->GetPracticeCharacter(pResult))
2129 ConShotgun(pResult, pUserData);
2130}
2131
2132void CGameContext::ConPracticeGrenade(IConsole::IResult *pResult, void *pUserData)
2133{
2134 CGameContext *pSelf = (CGameContext *)pUserData;
2135 if(pSelf->GetPracticeCharacter(pResult))
2136 ConGrenade(pResult, pUserData);
2137}
2138
2139void CGameContext::ConPracticeLaser(IConsole::IResult *pResult, void *pUserData)
2140{
2141 CGameContext *pSelf = (CGameContext *)pUserData;
2142 if(pSelf->GetPracticeCharacter(pResult))
2143 ConLaser(pResult, pUserData);
2144}
2145
2146void CGameContext::ConPracticeJetpack(IConsole::IResult *pResult, void *pUserData)
2147{
2148 CGameContext *pSelf = (CGameContext *)pUserData;
2149 if(pSelf->GetPracticeCharacter(pResult))
2150 ConJetpack(pResult, pUserData);
2151}
2152
2153void CGameContext::ConPracticeEndlessJump(IConsole::IResult *pResult, void *pUserData)
2154{
2155 CGameContext *pSelf = (CGameContext *)pUserData;
2156 if(pSelf->GetPracticeCharacter(pResult))
2157 ConEndlessJump(pResult, pUserData);
2158}
2159
2160void CGameContext::ConPracticeSetJumps(IConsole::IResult *pResult, void *pUserData)
2161{
2162 CGameContext *pSelf = (CGameContext *)pUserData;
2163 if(pSelf->GetPracticeCharacter(pResult))
2164 ConSetJumps(pResult, pUserData);
2165}
2166
2167void CGameContext::ConPracticeWeapons(IConsole::IResult *pResult, void *pUserData)
2168{
2169 CGameContext *pSelf = (CGameContext *)pUserData;
2170 if(pSelf->GetPracticeCharacter(pResult))
2171 ConWeapons(pResult, pUserData);
2172}
2173
2174void CGameContext::ConPracticeUnShotgun(IConsole::IResult *pResult, void *pUserData)
2175{
2176 CGameContext *pSelf = (CGameContext *)pUserData;
2177 if(pSelf->GetPracticeCharacter(pResult))
2178 ConUnShotgun(pResult, pUserData);
2179}
2180
2181void CGameContext::ConPracticeUnGrenade(IConsole::IResult *pResult, void *pUserData)
2182{
2183 CGameContext *pSelf = (CGameContext *)pUserData;
2184 if(pSelf->GetPracticeCharacter(pResult))
2185 ConUnGrenade(pResult, pUserData);
2186}
2187
2188void CGameContext::ConPracticeUnLaser(IConsole::IResult *pResult, void *pUserData)
2189{
2190 CGameContext *pSelf = (CGameContext *)pUserData;
2191 if(pSelf->GetPracticeCharacter(pResult))
2192 ConUnLaser(pResult, pUserData);
2193}
2194
2195void CGameContext::ConPracticeUnJetpack(IConsole::IResult *pResult, void *pUserData)
2196{
2197 CGameContext *pSelf = (CGameContext *)pUserData;
2198 if(pSelf->GetPracticeCharacter(pResult))
2199 ConUnJetpack(pResult, pUserData);
2200}
2201
2202void CGameContext::ConPracticeUnEndlessJump(IConsole::IResult *pResult, void *pUserData)
2203{
2204 CGameContext *pSelf = (CGameContext *)pUserData;
2205 if(pSelf->GetPracticeCharacter(pResult))
2206 ConUnEndlessJump(pResult, pUserData);
2207}
2208
2209void CGameContext::ConPracticeUnWeapons(IConsole::IResult *pResult, void *pUserData)
2210{
2211 CGameContext *pSelf = (CGameContext *)pUserData;
2212 if(pSelf->GetPracticeCharacter(pResult))
2213 ConUnWeapons(pResult, pUserData);
2214}
2215
2216void CGameContext::ConPracticeNinja(IConsole::IResult *pResult, void *pUserData)
2217{
2218 CGameContext *pSelf = (CGameContext *)pUserData;
2219 if(pSelf->GetPracticeCharacter(pResult))
2220 ConNinja(pResult, pUserData);
2221}
2222
2223void CGameContext::ConPracticeUnNinja(IConsole::IResult *pResult, void *pUserData)
2224{
2225 CGameContext *pSelf = (CGameContext *)pUserData;
2226 if(pSelf->GetPracticeCharacter(pResult))
2227 ConUnNinja(pResult, pUserData);
2228}
2229
2230void CGameContext::ConPracticeEndlessHook(IConsole::IResult *pResult, void *pUserData)
2231{
2232 CGameContext *pSelf = (CGameContext *)pUserData;
2233 if(pSelf->GetPracticeCharacter(pResult))
2234 ConEndlessHook(pResult, pUserData);
2235}
2236
2237void CGameContext::ConPracticeUnEndlessHook(IConsole::IResult *pResult, void *pUserData)
2238{
2239 CGameContext *pSelf = (CGameContext *)pUserData;
2240 if(pSelf->GetPracticeCharacter(pResult))
2241 ConUnEndlessHook(pResult, pUserData);
2242}
2243
2244void CGameContext::ConPracticeSetSwitch(IConsole::IResult *pResult, void *pUserData)
2245{
2246 CGameContext *pSelf = (CGameContext *)pUserData;
2247 if(pSelf->GetPracticeCharacter(pResult))
2248 ConSetSwitch(pResult, pUserData);
2249}
2250
2251void CGameContext::ConPracticeToggleInvincible(IConsole::IResult *pResult, void *pUserData)
2252{
2253 CGameContext *pSelf = (CGameContext *)pUserData;
2254 if(pSelf->GetPracticeCharacter(pResult))
2255 ConToggleInvincible(pResult, pUserData);
2256}
2257
2258void CGameContext::ConPracticeToggleCollision(IConsole::IResult *pResult, void *pUserData)
2259{
2260 CGameContext *pSelf = (CGameContext *)pUserData;
2261 auto *pChr = pSelf->GetPracticeCharacter(pResult);
2262 if(!pChr)
2263 return;
2264
2265 pChr->SetCollisionDisabled(!pChr->Core()->m_CollisionDisabled);
2266}
2267
2268void CGameContext::ConPracticeToggleHookCollision(IConsole::IResult *pResult, void *pUserData)
2269{
2270 CGameContext *pSelf = (CGameContext *)pUserData;
2271 auto *pChr = pSelf->GetPracticeCharacter(pResult);
2272 if(!pChr)
2273 return;
2274
2275 pChr->SetHookHitDisabled(!pChr->Core()->m_HookHitDisabled);
2276}
2277
2278void CGameContext::ConPracticeToggleHitOthers(IConsole::IResult *pResult, void *pUserData)
2279{
2280 CGameContext *pSelf = (CGameContext *)pUserData;
2281 auto *pChr = pSelf->GetPracticeCharacter(pResult);
2282 if(!pChr)
2283 return;
2284
2285 if(pResult->NumArguments() == 0 || str_comp(a: pResult->GetString(Index: 0), b: "all") == 0)
2286 {
2287 bool IsEnabled = (pChr->HammerHitDisabled() && pChr->ShotgunHitDisabled() &&
2288 pChr->GrenadeHitDisabled() && pChr->LaserHitDisabled());
2289 pChr->SetHammerHitDisabled(!IsEnabled);
2290 pChr->SetShotgunHitDisabled(!IsEnabled);
2291 pChr->SetGrenadeHitDisabled(!IsEnabled);
2292 pChr->SetLaserHitDisabled(!IsEnabled);
2293 return;
2294 }
2295
2296 if(str_comp(a: pResult->GetString(Index: 0), b: "hammer") == 0)
2297 pChr->SetHammerHitDisabled(!pChr->HammerHitDisabled());
2298 else if(str_comp(a: pResult->GetString(Index: 0), b: "shotgun") == 0)
2299 pChr->SetShotgunHitDisabled(!pChr->ShotgunHitDisabled());
2300 else if(str_comp(a: pResult->GetString(Index: 0), b: "grenade") == 0)
2301 pChr->SetGrenadeHitDisabled(!pChr->GrenadeHitDisabled());
2302 else if(str_comp(a: pResult->GetString(Index: 0), b: "laser") == 0)
2303 pChr->SetLaserHitDisabled(!pChr->LaserHitDisabled());
2304}
2305
2306void CGameContext::ConPracticeAddWeapon(IConsole::IResult *pResult, void *pUserData)
2307{
2308 CGameContext *pSelf = (CGameContext *)pUserData;
2309 if(pSelf->GetPracticeCharacter(pResult))
2310 ConAddWeapon(pResult, pUserData);
2311}
2312
2313void CGameContext::ConPracticeRemoveWeapon(IConsole::IResult *pResult, void *pUserData)
2314{
2315 CGameContext *pSelf = (CGameContext *)pUserData;
2316 if(pSelf->GetPracticeCharacter(pResult))
2317 ConRemoveWeapon(pResult, pUserData);
2318}
2319
2320void CGameContext::ConProtectedKill(IConsole::IResult *pResult, void *pUserData)
2321{
2322 CGameContext *pSelf = (CGameContext *)pUserData;
2323 if(!CheckClientId(ClientId: pResult->m_ClientId))
2324 return;
2325 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
2326 if(!pPlayer)
2327 return;
2328 CCharacter *pChr = pPlayer->GetCharacter();
2329 if(!pChr)
2330 return;
2331
2332 int CurrTime = (pSelf->Server()->Tick() - pChr->m_StartTime) / pSelf->Server()->TickSpeed();
2333 if(g_Config.m_SvKillProtection != 0 && CurrTime >= (60 * g_Config.m_SvKillProtection) && pChr->m_DDRaceState == ERaceState::STARTED)
2334 {
2335 pPlayer->KillCharacter(Weapon: WEAPON_SELF);
2336 pPlayer->Respawn();
2337 }
2338}
2339
2340void CGameContext::ConPoints(IConsole::IResult *pResult, void *pUserData)
2341{
2342 CGameContext *pSelf = (CGameContext *)pUserData;
2343 if(!CheckClientId(ClientId: pResult->m_ClientId))
2344 return;
2345
2346 if(pResult->NumArguments() > 0)
2347 {
2348 if(!g_Config.m_SvHideScore)
2349 pSelf->Score()->ShowPoints(ClientId: pResult->m_ClientId, pName: pResult->GetString(Index: 0));
2350 else
2351 log_info("chatresp", "Showing the global points of other players is not allowed on this server.");
2352 }
2353 else
2354 pSelf->Score()->ShowPoints(ClientId: pResult->m_ClientId,
2355 pName: pSelf->Server()->ClientName(ClientId: pResult->m_ClientId));
2356}
2357
2358void CGameContext::ConTopPoints(IConsole::IResult *pResult, void *pUserData)
2359{
2360 CGameContext *pSelf = (CGameContext *)pUserData;
2361 if(!CheckClientId(ClientId: pResult->m_ClientId))
2362 return;
2363
2364 if(g_Config.m_SvHideScore)
2365 {
2366 log_info("chatresp", "Showing the global top points is not allowed on this server.");
2367 return;
2368 }
2369
2370 if(pResult->NumArguments() > 0)
2371 pSelf->Score()->ShowTopPoints(ClientId: pResult->m_ClientId, Offset: pResult->GetInteger(Index: 0));
2372 else
2373 pSelf->Score()->ShowTopPoints(ClientId: pResult->m_ClientId);
2374}
2375
2376void CGameContext::ConTimeCP(IConsole::IResult *pResult, void *pUserData)
2377{
2378 CGameContext *pSelf = (CGameContext *)pUserData;
2379 if(!CheckClientId(ClientId: pResult->m_ClientId))
2380 return;
2381
2382 if(g_Config.m_SvHideScore)
2383 {
2384 log_info("chatresp", "Showing the checkpoint times is not allowed on this server.");
2385 return;
2386 }
2387
2388 CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientId];
2389 if(!pPlayer)
2390 return;
2391
2392 const char *pName = pResult->GetString(Index: 0);
2393 pSelf->Score()->LoadPlayerTimeCp(ClientId: pResult->m_ClientId, pName);
2394}
2395