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