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