1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include <engine/shared/config.h>
4
5#include <game/generated/protocol.h>
6#include <game/mapitems.h>
7#include <game/server/score.h>
8#include <game/teamscore.h>
9
10#include "gamecontext.h"
11#include "gamecontroller.h"
12#include "player.h"
13
14#include "entities/character.h"
15#include "entities/door.h"
16#include "entities/dragger.h"
17#include "entities/gun.h"
18#include "entities/light.h"
19#include "entities/pickup.h"
20#include "entities/projectile.h"
21
22IGameController::IGameController(class CGameContext *pGameServer) :
23 m_Teams(pGameServer), m_pLoadBestTimeResult(nullptr)
24{
25 m_pGameServer = pGameServer;
26 m_pConfig = m_pGameServer->Config();
27 m_pServer = m_pGameServer->Server();
28 m_pGameType = "unknown";
29
30 //
31 DoWarmup(Seconds: g_Config.m_SvWarmup);
32 m_GameOverTick = -1;
33 m_SuddenDeath = 0;
34 m_RoundStartTick = Server()->Tick();
35 m_RoundCount = 0;
36 m_GameFlags = 0;
37 m_aMapWish[0] = 0;
38
39 m_UnbalancedTick = -1;
40 m_ForceBalanced = false;
41
42 m_CurrentRecord = 0;
43}
44
45IGameController::~IGameController() = default;
46
47void IGameController::DoActivityCheck()
48{
49 if(g_Config.m_SvInactiveKickTime == 0)
50 return;
51
52 for(int i = 0; i < MAX_CLIENTS; ++i)
53 {
54 if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS && Server()->GetAuthedState(ClientId: i) == AUTHED_NO)
55 {
56 if(Server()->Tick() > GameServer()->m_apPlayers[i]->m_LastActionTick + g_Config.m_SvInactiveKickTime * Server()->TickSpeed() * 60)
57 {
58 switch(g_Config.m_SvInactiveKick)
59 {
60 case 0:
61 {
62 // move player to spectator
63 DoTeamChange(pPlayer: GameServer()->m_apPlayers[i], Team: TEAM_SPECTATORS);
64 }
65 break;
66 case 1:
67 {
68 // move player to spectator if the reserved slots aren't filled yet, kick him otherwise
69 int Spectators = 0;
70 for(auto &pPlayer : GameServer()->m_apPlayers)
71 if(pPlayer && pPlayer->GetTeam() == TEAM_SPECTATORS)
72 ++Spectators;
73 if(Spectators >= g_Config.m_SvSpectatorSlots)
74 Server()->Kick(ClientId: i, pReason: "Kicked for inactivity");
75 else
76 DoTeamChange(pPlayer: GameServer()->m_apPlayers[i], Team: TEAM_SPECTATORS);
77 }
78 break;
79 case 2:
80 {
81 // kick the player
82 Server()->Kick(ClientId: i, pReason: "Kicked for inactivity");
83 }
84 }
85 }
86 }
87 }
88}
89
90float IGameController::EvaluateSpawnPos(CSpawnEval *pEval, vec2 Pos, int DDTeam)
91{
92 float Score = 0.0f;
93 CCharacter *pC = static_cast<CCharacter *>(GameServer()->m_World.FindFirst(Type: CGameWorld::ENTTYPE_CHARACTER));
94 for(; pC; pC = (CCharacter *)pC->TypeNext())
95 {
96 // ignore players in other teams
97 if(GameServer()->GetDDRaceTeam(ClientId: pC->GetPlayer()->GetCid()) != DDTeam)
98 continue;
99
100 float d = distance(a: Pos, b: pC->m_Pos);
101 Score += d == 0 ? 1000000000.0f : 1.0f / d;
102 }
103
104 return Score;
105}
106
107void IGameController::EvaluateSpawnType(CSpawnEval *pEval, int Type, int DDTeam)
108{
109 // j == 0: Find an empty slot, j == 1: Take any slot if no empty one found
110 for(int j = 0; j < 2 && !pEval->m_Got; j++)
111 {
112 // get spawn point
113 for(const vec2 &SpawnPoint : m_avSpawnPoints[Type])
114 {
115 vec2 P = SpawnPoint;
116 if(j == 0)
117 {
118 // check if the position is occupado
119 CEntity *apEnts[MAX_CLIENTS];
120 int Num = GameServer()->m_World.FindEntities(Pos: SpawnPoint, Radius: 64, ppEnts: apEnts, Max: MAX_CLIENTS, Type: CGameWorld::ENTTYPE_CHARACTER);
121 vec2 aPositions[5] = {vec2(0.0f, 0.0f), vec2(-32.0f, 0.0f), vec2(0.0f, -32.0f), vec2(32.0f, 0.0f), vec2(0.0f, 32.0f)}; // start, left, up, right, down
122 int Result = -1;
123 for(int Index = 0; Index < 5 && Result == -1; ++Index)
124 {
125 Result = Index;
126 if(!GameServer()->m_World.m_Core.m_aTuning[0].m_PlayerCollision)
127 break;
128 for(int c = 0; c < Num; ++c)
129 {
130 CCharacter *pChr = static_cast<CCharacter *>(apEnts[c]);
131 if(GameServer()->Collision()->CheckPoint(Pos: SpawnPoint + aPositions[Index]) ||
132 distance(a: pChr->m_Pos, b: SpawnPoint + aPositions[Index]) <= pChr->GetProximityRadius())
133 {
134 Result = -1;
135 break;
136 }
137 }
138 }
139 if(Result == -1)
140 continue; // try next spawn point
141
142 P += aPositions[Result];
143 }
144
145 float S = EvaluateSpawnPos(pEval, Pos: P, DDTeam);
146 if(!pEval->m_Got || (j == 0 && pEval->m_Score > S))
147 {
148 pEval->m_Got = true;
149 pEval->m_Score = S;
150 pEval->m_Pos = P;
151 }
152 }
153 }
154}
155
156bool IGameController::CanSpawn(int Team, vec2 *pOutPos, int DDTeam)
157{
158 // spectators can't spawn
159 if(Team == TEAM_SPECTATORS)
160 return false;
161
162 CSpawnEval Eval;
163 EvaluateSpawnType(pEval: &Eval, Type: 0, DDTeam);
164 EvaluateSpawnType(pEval: &Eval, Type: 1, DDTeam);
165 EvaluateSpawnType(pEval: &Eval, Type: 2, DDTeam);
166
167 *pOutPos = Eval.m_Pos;
168 return Eval.m_Got;
169}
170
171bool IGameController::OnEntity(int Index, int x, int y, int Layer, int Flags, bool Initial, int Number)
172{
173 dbg_assert(Index >= 0, "Invalid entity index");
174
175 const vec2 Pos(x * 32.0f + 16.0f, y * 32.0f + 16.0f);
176
177 int aSides[8];
178 aSides[0] = GameServer()->Collision()->Entity(x, y: y + 1, Layer);
179 aSides[1] = GameServer()->Collision()->Entity(x: x + 1, y: y + 1, Layer);
180 aSides[2] = GameServer()->Collision()->Entity(x: x + 1, y, Layer);
181 aSides[3] = GameServer()->Collision()->Entity(x: x + 1, y: y - 1, Layer);
182 aSides[4] = GameServer()->Collision()->Entity(x, y: y - 1, Layer);
183 aSides[5] = GameServer()->Collision()->Entity(x: x - 1, y: y - 1, Layer);
184 aSides[6] = GameServer()->Collision()->Entity(x: x - 1, y, Layer);
185 aSides[7] = GameServer()->Collision()->Entity(x: x - 1, y: y + 1, Layer);
186
187 if(Index >= ENTITY_SPAWN && Index <= ENTITY_SPAWN_BLUE && Initial)
188 {
189 const int Type = Index - ENTITY_SPAWN;
190 m_avSpawnPoints[Type].push_back(x: Pos);
191 }
192 else if(Index == ENTITY_DOOR)
193 {
194 for(int i = 0; i < 8; i++)
195 {
196 if(aSides[i] >= ENTITY_LASER_SHORT && aSides[i] <= ENTITY_LASER_LONG)
197 {
198 new CDoor(
199 &GameServer()->m_World, //GameWorld
200 Pos, //Pos
201 pi / 4 * i, //Rotation
202 32 * 3 + 32 * (aSides[i] - ENTITY_LASER_SHORT) * 3, //Length
203 Number //Number
204 );
205 }
206 }
207 }
208 else if(Index == ENTITY_CRAZY_SHOTGUN_EX)
209 {
210 int Dir;
211 if(!Flags)
212 Dir = 0;
213 else if(Flags == ROTATION_90)
214 Dir = 1;
215 else if(Flags == ROTATION_180)
216 Dir = 2;
217 else
218 Dir = 3;
219 float Deg = Dir * (pi / 2);
220 CProjectile *pBullet = new CProjectile(
221 &GameServer()->m_World,
222 WEAPON_SHOTGUN, //Type
223 -1, //Owner
224 Pos, //Pos
225 vec2(std::sin(x: Deg), std::cos(x: Deg)), //Dir
226 -2, //Span
227 true, //Freeze
228 true, //Explosive
229 (g_Config.m_SvShotgunBulletSound) ? SOUND_GRENADE_EXPLODE : -1, //SoundImpact
230 vec2(std::sin(x: Deg), std::cos(x: Deg)), // InitDir
231 Layer,
232 Number);
233 pBullet->SetBouncing(2 - (Dir % 2));
234 }
235 else if(Index == ENTITY_CRAZY_SHOTGUN)
236 {
237 int Dir;
238 if(!Flags)
239 Dir = 0;
240 else if(Flags == (TILEFLAG_ROTATE))
241 Dir = 1;
242 else if(Flags == (TILEFLAG_XFLIP | TILEFLAG_YFLIP))
243 Dir = 2;
244 else
245 Dir = 3;
246 float Deg = Dir * (pi / 2);
247 CProjectile *pBullet = new CProjectile(
248 &GameServer()->m_World,
249 WEAPON_SHOTGUN, //Type
250 -1, //Owner
251 Pos, //Pos
252 vec2(std::sin(x: Deg), std::cos(x: Deg)), //Dir
253 -2, //Span
254 true, //Freeze
255 false, //Explosive
256 SOUND_GRENADE_EXPLODE,
257 vec2(std::sin(x: Deg), std::cos(x: Deg)), // InitDir
258 Layer,
259 Number);
260 pBullet->SetBouncing(2 - (Dir % 2));
261 }
262
263 int Type = -1;
264 int SubType = 0;
265
266 if(Index == ENTITY_ARMOR_1)
267 Type = POWERUP_ARMOR;
268 else if(Index == ENTITY_ARMOR_SHOTGUN)
269 Type = POWERUP_ARMOR_SHOTGUN;
270 else if(Index == ENTITY_ARMOR_GRENADE)
271 Type = POWERUP_ARMOR_GRENADE;
272 else if(Index == ENTITY_ARMOR_NINJA)
273 Type = POWERUP_ARMOR_NINJA;
274 else if(Index == ENTITY_ARMOR_LASER)
275 Type = POWERUP_ARMOR_LASER;
276 else if(Index == ENTITY_HEALTH_1)
277 Type = POWERUP_HEALTH;
278 else if(Index == ENTITY_WEAPON_SHOTGUN)
279 {
280 Type = POWERUP_WEAPON;
281 SubType = WEAPON_SHOTGUN;
282 }
283 else if(Index == ENTITY_WEAPON_GRENADE)
284 {
285 Type = POWERUP_WEAPON;
286 SubType = WEAPON_GRENADE;
287 }
288 else if(Index == ENTITY_WEAPON_LASER)
289 {
290 Type = POWERUP_WEAPON;
291 SubType = WEAPON_LASER;
292 }
293 else if(Index == ENTITY_POWERUP_NINJA)
294 {
295 Type = POWERUP_NINJA;
296 SubType = WEAPON_NINJA;
297 }
298 else if(Index >= ENTITY_LASER_FAST_CCW && Index <= ENTITY_LASER_FAST_CW)
299 {
300 int aSides2[8];
301 aSides2[0] = GameServer()->Collision()->Entity(x, y: y + 2, Layer);
302 aSides2[1] = GameServer()->Collision()->Entity(x: x + 2, y: y + 2, Layer);
303 aSides2[2] = GameServer()->Collision()->Entity(x: x + 2, y, Layer);
304 aSides2[3] = GameServer()->Collision()->Entity(x: x + 2, y: y - 2, Layer);
305 aSides2[4] = GameServer()->Collision()->Entity(x, y: y - 2, Layer);
306 aSides2[5] = GameServer()->Collision()->Entity(x: x - 2, y: y - 2, Layer);
307 aSides2[6] = GameServer()->Collision()->Entity(x: x - 2, y, Layer);
308 aSides2[7] = GameServer()->Collision()->Entity(x: x - 2, y: y + 2, Layer);
309
310 int Ind = Index - ENTITY_LASER_STOP;
311 int M;
312 if(Ind < 0)
313 {
314 Ind = -Ind;
315 M = 1;
316 }
317 else if(Ind == 0)
318 M = 0;
319 else
320 M = -1;
321
322 float AngularSpeed = 0.0f;
323 if(Ind == 0)
324 AngularSpeed = 0.0f;
325 else if(Ind == 1)
326 AngularSpeed = pi / 360;
327 else if(Ind == 2)
328 AngularSpeed = pi / 180;
329 else if(Ind == 3)
330 AngularSpeed = pi / 90;
331 AngularSpeed *= M;
332
333 for(int i = 0; i < 8; i++)
334 {
335 if(aSides[i] >= ENTITY_LASER_SHORT && aSides[i] <= ENTITY_LASER_LONG)
336 {
337 CLight *pLight = new CLight(&GameServer()->m_World, Pos, pi / 4 * i, 32 * 3 + 32 * (aSides[i] - ENTITY_LASER_SHORT) * 3, Layer, Number);
338 pLight->m_AngularSpeed = AngularSpeed;
339 if(aSides2[i] >= ENTITY_LASER_C_SLOW && aSides2[i] <= ENTITY_LASER_C_FAST)
340 {
341 pLight->m_Speed = 1 + (aSides2[i] - ENTITY_LASER_C_SLOW) * 2;
342 pLight->m_CurveLength = pLight->m_Length;
343 }
344 else if(aSides2[i] >= ENTITY_LASER_O_SLOW && aSides2[i] <= ENTITY_LASER_O_FAST)
345 {
346 pLight->m_Speed = 1 + (aSides2[i] - ENTITY_LASER_O_SLOW) * 2;
347 pLight->m_CurveLength = 0;
348 }
349 else
350 pLight->m_CurveLength = pLight->m_Length;
351 }
352 }
353 }
354 else if(Index >= ENTITY_DRAGGER_WEAK && Index <= ENTITY_DRAGGER_STRONG)
355 {
356 new CDragger(&GameServer()->m_World, Pos, Index - ENTITY_DRAGGER_WEAK + 1, false, Layer, Number);
357 }
358 else if(Index >= ENTITY_DRAGGER_WEAK_NW && Index <= ENTITY_DRAGGER_STRONG_NW)
359 {
360 new CDragger(&GameServer()->m_World, Pos, Index - ENTITY_DRAGGER_WEAK_NW + 1, true, Layer, Number);
361 }
362 else if(Index == ENTITY_PLASMAE)
363 {
364 new CGun(&GameServer()->m_World, Pos, false, true, Layer, Number);
365 }
366 else if(Index == ENTITY_PLASMAF)
367 {
368 new CGun(&GameServer()->m_World, Pos, true, false, Layer, Number);
369 }
370 else if(Index == ENTITY_PLASMA)
371 {
372 new CGun(&GameServer()->m_World, Pos, true, true, Layer, Number);
373 }
374 else if(Index == ENTITY_PLASMAU)
375 {
376 new CGun(&GameServer()->m_World, Pos, false, false, Layer, Number);
377 }
378
379 if(Type != -1) // NOLINT(clang-analyzer-unix.Malloc)
380 {
381 CPickup *pPickup = new CPickup(&GameServer()->m_World, Type, SubType, Layer, Number);
382 pPickup->m_Pos = Pos;
383 return true; // NOLINT(clang-analyzer-unix.Malloc)
384 }
385
386 return false;
387}
388
389void IGameController::OnPlayerConnect(CPlayer *pPlayer)
390{
391 int ClientId = pPlayer->GetCid();
392 pPlayer->Respawn();
393
394 if(!Server()->ClientPrevIngame(ClientId))
395 {
396 char aBuf[128];
397 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "team_join player='%d:%s' team=%d", ClientId, Server()->ClientName(ClientId), pPlayer->GetTeam());
398 GameServer()->Console()->Print(Level: IConsole::OUTPUT_LEVEL_DEBUG, pFrom: "game", pStr: aBuf);
399 }
400}
401
402void IGameController::OnPlayerDisconnect(class CPlayer *pPlayer, const char *pReason)
403{
404 pPlayer->OnDisconnect();
405 int ClientId = pPlayer->GetCid();
406 if(Server()->ClientIngame(ClientId))
407 {
408 char aBuf[512];
409 if(pReason && *pReason)
410 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' has left the game (%s)", Server()->ClientName(ClientId), pReason);
411 else
412 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' has left the game", Server()->ClientName(ClientId));
413 GameServer()->SendChat(ClientId: -1, Team: CGameContext::CHAT_ALL, pText: aBuf, SpamProtectionClientId: -1, Flags: CGameContext::CHAT_SIX);
414
415 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "leave player='%d:%s'", ClientId, Server()->ClientName(ClientId));
416 GameServer()->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "game", pStr: aBuf);
417 }
418}
419
420void IGameController::EndRound()
421{
422 if(m_Warmup) // game can't end when we are running warmup
423 return;
424
425 GameServer()->m_World.m_Paused = true;
426 m_GameOverTick = Server()->Tick();
427 m_SuddenDeath = 0;
428}
429
430void IGameController::ResetGame()
431{
432 GameServer()->m_World.m_ResetRequested = true;
433}
434
435const char *IGameController::GetTeamName(int Team)
436{
437 if(Team == 0)
438 return "game";
439 return "spectators";
440}
441
442void IGameController::StartRound()
443{
444 ResetGame();
445
446 m_RoundStartTick = Server()->Tick();
447 m_SuddenDeath = 0;
448 m_GameOverTick = -1;
449 GameServer()->m_World.m_Paused = false;
450 m_ForceBalanced = false;
451 Server()->DemoRecorder_HandleAutoStart();
452 char aBuf[256];
453 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "start round type='%s' teamplay='%d'", m_pGameType, m_GameFlags & GAMEFLAG_TEAMS);
454 GameServer()->Console()->Print(Level: IConsole::OUTPUT_LEVEL_DEBUG, pFrom: "game", pStr: aBuf);
455}
456
457void IGameController::ChangeMap(const char *pToMap)
458{
459 Server()->ChangeMap(pMap: pToMap);
460}
461
462void IGameController::OnReset()
463{
464 for(auto &pPlayer : GameServer()->m_apPlayers)
465 if(pPlayer)
466 pPlayer->Respawn();
467}
468
469int IGameController::OnCharacterDeath(class CCharacter *pVictim, class CPlayer *pKiller, int Weapon)
470{
471 return 0;
472}
473
474void IGameController::OnCharacterSpawn(class CCharacter *pChr)
475{
476 pChr->SetTeams(&Teams());
477 Teams().OnCharacterSpawn(ClientId: pChr->GetPlayer()->GetCid());
478
479 // default health
480 pChr->IncreaseHealth(Amount: 10);
481
482 // give default weapons
483 pChr->GiveWeapon(Weapon: WEAPON_HAMMER);
484 pChr->GiveWeapon(Weapon: WEAPON_GUN);
485}
486
487void IGameController::HandleCharacterTiles(CCharacter *pChr, int MapIndex)
488{
489 // Do nothing by default
490}
491
492void IGameController::DoWarmup(int Seconds)
493{
494 if(Seconds < 0)
495 m_Warmup = 0;
496 else
497 m_Warmup = Seconds * Server()->TickSpeed();
498}
499
500bool IGameController::IsForceBalanced()
501{
502 return false;
503}
504
505bool IGameController::CanBeMovedOnBalance(int ClientId)
506{
507 return true;
508}
509
510void IGameController::Tick()
511{
512 // do warmup
513 if(m_Warmup)
514 {
515 m_Warmup--;
516 if(!m_Warmup)
517 StartRound();
518 }
519
520 if(m_GameOverTick != -1)
521 {
522 // game over.. wait for restart
523 if(Server()->Tick() > m_GameOverTick + Server()->TickSpeed() * 10)
524 {
525 StartRound();
526 m_RoundCount++;
527 }
528 }
529
530 if(m_pLoadBestTimeResult != nullptr && m_pLoadBestTimeResult->m_Completed)
531 {
532 if(m_pLoadBestTimeResult->m_Success)
533 {
534 m_CurrentRecord = m_pLoadBestTimeResult->m_CurrentRecord;
535
536 for(int i = 0; i < MAX_CLIENTS; i++)
537 {
538 if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->GetClientVersion() >= VERSION_DDRACE)
539 {
540 GameServer()->SendRecord(ClientId: i);
541 }
542 }
543 }
544 m_pLoadBestTimeResult = nullptr;
545 }
546
547 DoActivityCheck();
548}
549
550void IGameController::Snap(int SnappingClient)
551{
552 CNetObj_GameInfo *pGameInfoObj = Server()->SnapNewItem<CNetObj_GameInfo>(Id: 0);
553 if(!pGameInfoObj)
554 return;
555
556 pGameInfoObj->m_GameFlags = m_GameFlags;
557 pGameInfoObj->m_GameStateFlags = 0;
558 if(m_GameOverTick != -1)
559 pGameInfoObj->m_GameStateFlags |= GAMESTATEFLAG_GAMEOVER;
560 if(m_SuddenDeath)
561 pGameInfoObj->m_GameStateFlags |= GAMESTATEFLAG_SUDDENDEATH;
562 if(GameServer()->m_World.m_Paused)
563 pGameInfoObj->m_GameStateFlags |= GAMESTATEFLAG_PAUSED;
564 pGameInfoObj->m_RoundStartTick = m_RoundStartTick;
565 pGameInfoObj->m_WarmupTimer = m_Warmup;
566
567 pGameInfoObj->m_RoundNum = 0;
568 pGameInfoObj->m_RoundCurrent = m_RoundCount + 1;
569
570 CCharacter *pChr;
571 CPlayer *pPlayer = SnappingClient != SERVER_DEMO_CLIENT ? GameServer()->m_apPlayers[SnappingClient] : 0;
572 CPlayer *pPlayer2;
573
574 if(pPlayer && (pPlayer->m_TimerType == CPlayer::TIMERTYPE_GAMETIMER || pPlayer->m_TimerType == CPlayer::TIMERTYPE_GAMETIMER_AND_BROADCAST) && pPlayer->GetClientVersion() >= VERSION_DDNET_GAMETICK)
575 {
576 if((pPlayer->GetTeam() == TEAM_SPECTATORS || pPlayer->IsPaused()) && pPlayer->m_SpectatorId != SPEC_FREEVIEW && (pPlayer2 = GameServer()->m_apPlayers[pPlayer->m_SpectatorId]))
577 {
578 if((pChr = pPlayer2->GetCharacter()) && pChr->m_DDRaceState == DDRACE_STARTED)
579 {
580 pGameInfoObj->m_WarmupTimer = -pChr->m_StartTime;
581 pGameInfoObj->m_GameStateFlags |= GAMESTATEFLAG_RACETIME;
582 }
583 }
584 else if((pChr = pPlayer->GetCharacter()) && pChr->m_DDRaceState == DDRACE_STARTED)
585 {
586 pGameInfoObj->m_WarmupTimer = -pChr->m_StartTime;
587 pGameInfoObj->m_GameStateFlags |= GAMESTATEFLAG_RACETIME;
588 }
589 }
590
591 CNetObj_GameInfoEx *pGameInfoEx = Server()->SnapNewItem<CNetObj_GameInfoEx>(Id: 0);
592 if(!pGameInfoEx)
593 return;
594
595 pGameInfoEx->m_Flags =
596 GAMEINFOFLAG_TIMESCORE |
597 GAMEINFOFLAG_GAMETYPE_RACE |
598 GAMEINFOFLAG_GAMETYPE_DDRACE |
599 GAMEINFOFLAG_GAMETYPE_DDNET |
600 GAMEINFOFLAG_UNLIMITED_AMMO |
601 GAMEINFOFLAG_RACE_RECORD_MESSAGE |
602 GAMEINFOFLAG_ALLOW_EYE_WHEEL |
603 GAMEINFOFLAG_ALLOW_HOOK_COLL |
604 GAMEINFOFLAG_ALLOW_ZOOM |
605 GAMEINFOFLAG_BUG_DDRACE_GHOST |
606 GAMEINFOFLAG_BUG_DDRACE_INPUT |
607 GAMEINFOFLAG_PREDICT_DDRACE |
608 GAMEINFOFLAG_PREDICT_DDRACE_TILES |
609 GAMEINFOFLAG_ENTITIES_DDNET |
610 GAMEINFOFLAG_ENTITIES_DDRACE |
611 GAMEINFOFLAG_ENTITIES_RACE |
612 GAMEINFOFLAG_RACE;
613 pGameInfoEx->m_Flags2 = GAMEINFOFLAG2_HUD_DDRACE;
614 if(g_Config.m_SvNoWeakHook)
615 pGameInfoEx->m_Flags2 |= GAMEINFOFLAG2_NO_WEAK_HOOK;
616 pGameInfoEx->m_Version = GAMEINFO_CURVERSION;
617
618 if(Server()->IsSixup(ClientId: SnappingClient))
619 {
620 protocol7::CNetObj_GameData *pGameData = Server()->SnapNewItem<protocol7::CNetObj_GameData>(Id: 0);
621 if(!pGameData)
622 return;
623
624 pGameData->m_GameStartTick = m_RoundStartTick;
625 pGameData->m_GameStateFlags = 0;
626 if(m_GameOverTick != -1)
627 pGameData->m_GameStateFlags |= protocol7::GAMESTATEFLAG_GAMEOVER;
628 if(m_SuddenDeath)
629 pGameData->m_GameStateFlags |= protocol7::GAMESTATEFLAG_SUDDENDEATH;
630 if(GameServer()->m_World.m_Paused)
631 pGameData->m_GameStateFlags |= protocol7::GAMESTATEFLAG_PAUSED;
632
633 pGameData->m_GameStateEndTick = 0;
634
635 protocol7::CNetObj_GameDataRace *pRaceData = Server()->SnapNewItem<protocol7::CNetObj_GameDataRace>(Id: 0);
636 if(!pRaceData)
637 return;
638
639 pRaceData->m_BestTime = round_to_int(f: m_CurrentRecord * 1000);
640 pRaceData->m_Precision = 2;
641 pRaceData->m_RaceFlags = protocol7::RACEFLAG_KEEP_WANTED_WEAPON;
642 }
643
644 GameServer()->SnapSwitchers(SnappingClient);
645}
646
647int IGameController::GetAutoTeam(int NotThisId)
648{
649 int aNumplayers[2] = {0, 0};
650 for(int i = 0; i < MAX_CLIENTS; i++)
651 {
652 if(GameServer()->m_apPlayers[i] && i != NotThisId)
653 {
654 if(GameServer()->m_apPlayers[i]->GetTeam() >= TEAM_RED && GameServer()->m_apPlayers[i]->GetTeam() <= TEAM_BLUE)
655 aNumplayers[GameServer()->m_apPlayers[i]->GetTeam()]++;
656 }
657 }
658
659 int Team = 0;
660
661 if(CanJoinTeam(Team, NotThisId, pErrorReason: nullptr, ErrorReasonSize: 0))
662 return Team;
663 return -1;
664}
665
666bool IGameController::CanJoinTeam(int Team, int NotThisId, char *pErrorReason, int ErrorReasonSize)
667{
668 const CPlayer *pPlayer = GameServer()->m_apPlayers[NotThisId];
669 if(pPlayer && pPlayer->IsPaused())
670 {
671 if(pErrorReason)
672 str_copy(dst: pErrorReason, src: "Use /pause first then you can kill", dst_size: ErrorReasonSize);
673 return false;
674 }
675 if(Team == TEAM_SPECTATORS || (pPlayer && pPlayer->GetTeam() != TEAM_SPECTATORS))
676 return true;
677
678 int aNumplayers[2] = {0, 0};
679 for(int i = 0; i < MAX_CLIENTS; i++)
680 {
681 if(GameServer()->m_apPlayers[i] && i != NotThisId)
682 {
683 if(GameServer()->m_apPlayers[i]->GetTeam() >= TEAM_RED && GameServer()->m_apPlayers[i]->GetTeam() <= TEAM_BLUE)
684 aNumplayers[GameServer()->m_apPlayers[i]->GetTeam()]++;
685 }
686 }
687
688 if((aNumplayers[0] + aNumplayers[1]) < Server()->MaxClients() - g_Config.m_SvSpectatorSlots)
689 return true;
690
691 if(pErrorReason)
692 str_format(buffer: pErrorReason, buffer_size: ErrorReasonSize, format: "Only %d active players are allowed", Server()->MaxClients() - g_Config.m_SvSpectatorSlots);
693 return false;
694}
695
696int IGameController::ClampTeam(int Team)
697{
698 if(Team < 0)
699 return TEAM_SPECTATORS;
700 return 0;
701}
702
703CClientMask IGameController::GetMaskForPlayerWorldEvent(int Asker, int ExceptId)
704{
705 if(Asker == -1)
706 return CClientMask().set().reset(position: ExceptId);
707
708 return Teams().TeamMask(Team: GameServer()->GetDDRaceTeam(ClientId: Asker), ExceptId, Asker);
709}
710
711void IGameController::DoTeamChange(CPlayer *pPlayer, int Team, bool DoChatMsg)
712{
713 Team = ClampTeam(Team);
714 if(Team == pPlayer->GetTeam())
715 return;
716
717 pPlayer->SetTeam(Team);
718 int ClientId = pPlayer->GetCid();
719
720 char aBuf[128];
721 DoChatMsg = false;
722 if(DoChatMsg)
723 {
724 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' joined the %s", Server()->ClientName(ClientId), GameServer()->m_pController->GetTeamName(Team));
725 GameServer()->SendChat(ClientId: -1, Team: CGameContext::CHAT_ALL, pText: aBuf);
726 }
727
728 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "team_join player='%d:%s' m_Team=%d", ClientId, Server()->ClientName(ClientId), Team);
729 GameServer()->Console()->Print(Level: IConsole::OUTPUT_LEVEL_DEBUG, pFrom: "game", pStr: aBuf);
730
731 // OnPlayerInfoChange(pPlayer);
732}
733