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 #include <game/mapitems.h>
6 #include <game/generated/protocol.h>
8 #include "entities/pickup.h"
9 #include "gamecontroller.h"
10 #include "gamecontext.h"
13 IGameController::IGameController(class CGameContext
*pGameServer
)
15 m_pGameServer
= pGameServer
;
16 m_pServer
= m_pGameServer
->Server();
17 m_pGameType
= "unknown";
20 DoWarmup(g_Config
.m_SvWarmup
);
23 m_RoundStartTick
= Server()->Tick();
26 m_aTeamscore
[TEAM_RED
] = 0;
27 m_aTeamscore
[TEAM_BLUE
] = 0;
30 m_UnbalancedTick
= -1;
31 m_ForceBalanced
= false;
33 m_aNumSpawnPoints
[0] = 0;
34 m_aNumSpawnPoints
[1] = 0;
35 m_aNumSpawnPoints
[2] = 0;
38 IGameController::~IGameController()
42 float IGameController::EvaluateSpawnPos(CSpawnEval
*pEval
, vec2 Pos
)
45 CCharacter
*pC
= static_cast<CCharacter
*>(GameServer()->m_World
.FindFirst(CGameWorld::ENTTYPE_CHARACTER
));
46 for(; pC
; pC
= (CCharacter
*)pC
->TypeNext())
48 // team mates are not as dangerous as enemies
49 float Scoremod
= 1.0f
;
50 if(pEval
->m_FriendlyTeam
!= -1 && pC
->GetPlayer()->GetTeam() == pEval
->m_FriendlyTeam
)
53 float d
= distance(Pos
, pC
->m_Pos
);
54 Score
+= Scoremod
* (d
== 0 ? 1000000000.0f
: 1.0f
/d
);
60 void IGameController::EvaluateSpawnType(CSpawnEval
*pEval
, int Type
)
63 for(int i
= 0; i
< m_aNumSpawnPoints
[Type
]; i
++)
65 // check if the position is occupado
66 CCharacter
*aEnts
[MAX_CLIENTS
];
67 int Num
= GameServer()->m_World
.FindEntities(m_aaSpawnPoints
[Type
][i
], 64, (CEntity
**)aEnts
, MAX_CLIENTS
, CGameWorld::ENTTYPE_CHARACTER
);
68 vec2 Positions
[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
70 for(int Index
= 0; Index
< 5 && Result
== -1; ++Index
)
73 for(int c
= 0; c
< Num
; ++c
)
74 if(GameServer()->Collision()->CheckPoint(m_aaSpawnPoints
[Type
][i
]+Positions
[Index
]) ||
75 distance(aEnts
[c
]->m_Pos
, m_aaSpawnPoints
[Type
][i
]+Positions
[Index
]) <= aEnts
[c
]->m_ProximityRadius
)
82 continue; // try next spawn point
84 vec2 P
= m_aaSpawnPoints
[Type
][i
]+Positions
[Result
];
85 float S
= EvaluateSpawnPos(pEval
, P
);
86 if(!pEval
->m_Got
|| pEval
->m_Score
> S
)
95 bool IGameController::CanSpawn(int Team
, vec2
*pOutPos
)
99 // spectators can't spawn
100 if(Team
== TEAM_SPECTATORS
)
105 Eval
.m_FriendlyTeam
= Team
;
107 // first try own team spawn, then normal spawn and then enemy
108 EvaluateSpawnType(&Eval
, 1+(Team
&1));
111 EvaluateSpawnType(&Eval
, 0);
113 EvaluateSpawnType(&Eval
, 1+((Team
+1)&1));
118 EvaluateSpawnType(&Eval
, 0);
119 EvaluateSpawnType(&Eval
, 1);
120 EvaluateSpawnType(&Eval
, 2);
123 *pOutPos
= Eval
.m_Pos
;
128 bool IGameController::OnEntity(int Index
, vec2 Pos
)
133 if(Index
== ENTITY_SPAWN
)
134 m_aaSpawnPoints
[0][m_aNumSpawnPoints
[0]++] = Pos
;
135 else if(Index
== ENTITY_SPAWN_RED
)
136 m_aaSpawnPoints
[1][m_aNumSpawnPoints
[1]++] = Pos
;
137 else if(Index
== ENTITY_SPAWN_BLUE
)
138 m_aaSpawnPoints
[2][m_aNumSpawnPoints
[2]++] = Pos
;
139 else if(Index
== ENTITY_ARMOR_1
)
140 Type
= POWERUP_ARMOR
;
141 else if(Index
== ENTITY_HEALTH_1
)
142 Type
= POWERUP_HEALTH
;
143 else if(Index
== ENTITY_WEAPON_SHOTGUN
)
145 Type
= POWERUP_WEAPON
;
146 SubType
= WEAPON_SHOTGUN
;
148 else if(Index
== ENTITY_WEAPON_GRENADE
)
150 Type
= POWERUP_WEAPON
;
151 SubType
= WEAPON_GRENADE
;
153 else if(Index
== ENTITY_WEAPON_RIFLE
)
155 Type
= POWERUP_WEAPON
;
156 SubType
= WEAPON_RIFLE
;
158 else if(Index
== ENTITY_POWERUP_NINJA
&& g_Config
.m_SvPowerups
)
160 Type
= POWERUP_NINJA
;
161 SubType
= WEAPON_NINJA
;
166 CPickup
*pPickup
= new CPickup(&GameServer()->m_World
, Type
, SubType
);
167 pPickup
->m_Pos
= Pos
;
174 void IGameController::EndRound()
176 if(m_Warmup
) // game can't end when we are running warmup
179 GameServer()->m_World
.m_Paused
= true;
180 m_GameOverTick
= Server()->Tick();
184 void IGameController::ResetGame()
186 GameServer()->m_World
.m_ResetRequested
= true;
189 const char *IGameController::GetTeamName(int Team
)
195 else if(Team
== TEAM_BLUE
)
207 static bool IsSeparator(char c
) { return c
== ';' || c
== ' ' || c
== ',' || c
== '\t'; }
209 void IGameController::StartRound()
213 m_RoundStartTick
= Server()->Tick();
216 GameServer()->m_World
.m_Paused
= false;
217 m_aTeamscore
[TEAM_RED
] = 0;
218 m_aTeamscore
[TEAM_BLUE
] = 0;
219 m_ForceBalanced
= false;
220 Server()->DemoRecorder_HandleAutoStart();
222 str_format(aBuf
, sizeof(aBuf
), "start round type='%s' teamplay='%d'", m_pGameType
, m_GameFlags
&GAMEFLAG_TEAMS
);
223 GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "game", aBuf
);
226 void IGameController::ChangeMap(const char *pToMap
)
228 str_copy(m_aMapWish
, pToMap
, sizeof(m_aMapWish
));
232 void IGameController::CycleMap()
234 if(m_aMapWish
[0] != 0)
237 str_format(aBuf
, sizeof(aBuf
), "rotating map to %s", m_aMapWish
);
238 GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "game", aBuf
);
239 str_copy(g_Config
.m_SvMap
, m_aMapWish
, sizeof(g_Config
.m_SvMap
));
244 if(!str_length(g_Config
.m_SvMaprotation
))
247 if(m_RoundCount
< g_Config
.m_SvRoundsPerMap
-1)
250 // handle maprotation
251 const char *pMapRotation
= g_Config
.m_SvMaprotation
;
252 const char *pCurrentMap
= g_Config
.m_SvMap
;
254 int CurrentMapLen
= str_length(pCurrentMap
);
255 const char *pNextMap
= pMapRotation
;
259 while(pNextMap
[WordLen
] && !IsSeparator(pNextMap
[WordLen
]))
262 if(WordLen
== CurrentMapLen
&& str_comp_num(pNextMap
, pCurrentMap
, CurrentMapLen
) == 0)
265 pNextMap
+= CurrentMapLen
;
266 while(*pNextMap
&& IsSeparator(*pNextMap
))
277 pNextMap
= pMapRotation
;
279 // cut out the next map
281 for(int i
= 0; i
< 512; i
++)
283 aBuf
[i
] = pNextMap
[i
];
284 if(IsSeparator(pNextMap
[i
]) || pNextMap
[i
] == 0)
293 while(IsSeparator(aBuf
[i
]))
299 str_format(aBufMsg
, sizeof(aBufMsg
), "rotating map to %s", &aBuf
[i
]);
300 GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "game", aBuf
);
301 str_copy(g_Config
.m_SvMap
, &aBuf
[i
], sizeof(g_Config
.m_SvMap
));
304 void IGameController::PostReset()
306 for(int i
= 0; i
< MAX_CLIENTS
; i
++)
308 if(GameServer()->m_apPlayers
[i
])
310 GameServer()->m_apPlayers
[i
]->Respawn();
311 GameServer()->m_apPlayers
[i
]->m_Score
= 0;
312 GameServer()->m_apPlayers
[i
]->m_ScoreStartTick
= Server()->Tick();
313 GameServer()->m_apPlayers
[i
]->m_RespawnTick
= Server()->Tick()+Server()->TickSpeed()/2;
318 void IGameController::OnPlayerInfoChange(class CPlayer
*pP
)
320 const int aTeamColors
[2] = {65387, 10223467};
323 pP
->m_TeeInfos
.m_UseCustomColor
= 1;
324 if(pP
->GetTeam() >= TEAM_RED
&& pP
->GetTeam() <= TEAM_BLUE
)
326 pP
->m_TeeInfos
.m_ColorBody
= aTeamColors
[pP
->GetTeam()];
327 pP
->m_TeeInfos
.m_ColorFeet
= aTeamColors
[pP
->GetTeam()];
331 pP
->m_TeeInfos
.m_ColorBody
= 12895054;
332 pP
->m_TeeInfos
.m_ColorFeet
= 12895054;
338 int IGameController::OnCharacterDeath(class CCharacter
*pVictim
, class CPlayer
*pKiller
, int Weapon
)
341 if(!pKiller
|| Weapon
== WEAPON_GAME
)
343 if(pKiller
== pVictim
->GetPlayer())
344 pVictim
->GetPlayer()->m_Score
--; // suicide
347 if(IsTeamplay() && pVictim
->GetPlayer()->GetTeam() == pKiller
->GetTeam())
348 pKiller
->m_Score
--; // teamkill
350 pKiller
->m_Score
++; // normal kill
352 if(Weapon
== WEAPON_SELF
)
353 pVictim
->GetPlayer()->m_RespawnTick
= Server()->Tick()+Server()->TickSpeed()*3.0f
;
357 void IGameController::OnCharacterSpawn(class CCharacter
*pChr
)
360 pChr
->IncreaseHealth(10);
362 // give default weapons
363 pChr
->GiveWeapon(WEAPON_HAMMER
, -1);
364 pChr
->GiveWeapon(WEAPON_GUN
, 10);
367 void IGameController::DoWarmup(int Seconds
)
372 m_Warmup
= Seconds
*Server()->TickSpeed();
375 bool IGameController::IsFriendlyFire(int ClientID1
, int ClientID2
)
377 if(ClientID1
== ClientID2
)
382 if(!GameServer()->m_apPlayers
[ClientID1
] || !GameServer()->m_apPlayers
[ClientID2
])
385 if(GameServer()->m_apPlayers
[ClientID1
]->GetTeam() == GameServer()->m_apPlayers
[ClientID2
]->GetTeam())
392 bool IGameController::IsForceBalanced()
396 m_ForceBalanced
= false;
403 bool IGameController::CanBeMovedOnBalance(int ClientID
)
408 void IGameController::Tick()
418 if(m_GameOverTick
!= -1)
420 // game over.. wait for restart
421 if(Server()->Tick() > m_GameOverTick
+Server()->TickSpeed()*10)
430 if (IsTeamplay() && m_UnbalancedTick
!= -1 && Server()->Tick() > m_UnbalancedTick
+g_Config
.m_SvTeambalanceTime
*Server()->TickSpeed()*60)
432 GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "game", "Balancing teams");
435 float aTScore
[2] = {0,0};
436 float aPScore
[MAX_CLIENTS
] = {0.0f
};
437 for(int i
= 0; i
< MAX_CLIENTS
; i
++)
439 if(GameServer()->m_apPlayers
[i
] && GameServer()->m_apPlayers
[i
]->GetTeam() != TEAM_SPECTATORS
)
441 aT
[GameServer()->m_apPlayers
[i
]->GetTeam()]++;
442 aPScore
[i
] = GameServer()->m_apPlayers
[i
]->m_Score
*Server()->TickSpeed()*60.0f
/
443 (Server()->Tick()-GameServer()->m_apPlayers
[i
]->m_ScoreStartTick
);
444 aTScore
[GameServer()->m_apPlayers
[i
]->GetTeam()] += aPScore
[i
];
448 // are teams unbalanced?
449 if(absolute(aT
[0]-aT
[1]) >= 2)
451 int M
= (aT
[0] > aT
[1]) ? 0 : 1;
452 int NumBalance
= absolute(aT
[0]-aT
[1]) / 2;
457 float PD
= aTScore
[M
];
458 for(int i
= 0; i
< MAX_CLIENTS
; i
++)
460 if(!GameServer()->m_apPlayers
[i
] || !CanBeMovedOnBalance(i
))
462 // remember the player who would cause lowest score-difference
463 if(GameServer()->m_apPlayers
[i
]->GetTeam() == M
&& (!pP
|| absolute((aTScore
[M
^1]+aPScore
[i
]) - (aTScore
[M
]-aPScore
[i
])) < PD
))
465 pP
= GameServer()->m_apPlayers
[i
];
466 PD
= absolute((aTScore
[M
^1]+aPScore
[i
]) - (aTScore
[M
]-aPScore
[i
]));
470 // move the player to the other team
471 int Temp
= pP
->m_LastActionTick
;
473 pP
->m_LastActionTick
= Temp
;
476 pP
->m_ForceBalanced
= true;
477 } while (--NumBalance
);
479 m_ForceBalanced
= true;
481 m_UnbalancedTick
= -1;
484 // check for inactive players
485 if(g_Config
.m_SvInactiveKickTime
> 0)
487 for(int i
= 0; i
< MAX_CLIENTS
; ++i
)
489 if(GameServer()->m_apPlayers
[i
] && GameServer()->m_apPlayers
[i
]->GetTeam() != TEAM_SPECTATORS
&& !Server()->IsAuthed(i
))
491 if(Server()->Tick() > GameServer()->m_apPlayers
[i
]->m_LastActionTick
+g_Config
.m_SvInactiveKickTime
*Server()->TickSpeed()*60)
493 switch(g_Config
.m_SvInactiveKick
)
497 // move player to spectator
498 GameServer()->m_apPlayers
[i
]->SetTeam(TEAM_SPECTATORS
);
503 // move player to spectator if the reserved slots aren't filled yet, kick him otherwise
505 for(int j
= 0; j
< MAX_CLIENTS
; ++j
)
506 if(GameServer()->m_apPlayers
[j
] && GameServer()->m_apPlayers
[j
]->GetTeam() == TEAM_SPECTATORS
)
508 if(Spectators
>= g_Config
.m_SvSpectatorSlots
)
509 Server()->Kick(i
, "Kicked for inactivity");
511 GameServer()->m_apPlayers
[i
]->SetTeam(TEAM_SPECTATORS
);
517 Server()->Kick(i
, "Kicked for inactivity");
529 bool IGameController::IsTeamplay() const
531 return m_GameFlags
&GAMEFLAG_TEAMS
;
534 void IGameController::Snap(int SnappingClient
)
536 CNetObj_GameInfo
*pGameInfoObj
= (CNetObj_GameInfo
*)Server()->SnapNewItem(NETOBJTYPE_GAMEINFO
, 0, sizeof(CNetObj_GameInfo
));
540 pGameInfoObj
->m_GameFlags
= m_GameFlags
;
541 pGameInfoObj
->m_GameStateFlags
= 0;
542 if(m_GameOverTick
!= -1)
543 pGameInfoObj
->m_GameStateFlags
|= GAMESTATEFLAG_GAMEOVER
;
545 pGameInfoObj
->m_GameStateFlags
|= GAMESTATEFLAG_SUDDENDEATH
;
546 if(GameServer()->m_World
.m_Paused
)
547 pGameInfoObj
->m_GameStateFlags
|= GAMESTATEFLAG_PAUSED
;
548 pGameInfoObj
->m_RoundStartTick
= m_RoundStartTick
;
549 pGameInfoObj
->m_WarmupTimer
= m_Warmup
;
551 pGameInfoObj
->m_ScoreLimit
= g_Config
.m_SvScorelimit
;
552 pGameInfoObj
->m_TimeLimit
= g_Config
.m_SvTimelimit
;
554 pGameInfoObj
->m_RoundNum
= (str_length(g_Config
.m_SvMaprotation
) && g_Config
.m_SvRoundsPerMap
) ? g_Config
.m_SvRoundsPerMap
: 0;
555 pGameInfoObj
->m_RoundCurrent
= m_RoundCount
+1;
558 int IGameController::GetAutoTeam(int NotThisID
)
560 // this will force the auto balancer to work overtime aswell
561 if(g_Config
.m_DbgStress
)
564 int aNumplayers
[2] = {0,0};
565 for(int i
= 0; i
< MAX_CLIENTS
; i
++)
567 if(GameServer()->m_apPlayers
[i
] && i
!= NotThisID
)
569 if(GameServer()->m_apPlayers
[i
]->GetTeam() >= TEAM_RED
&& GameServer()->m_apPlayers
[i
]->GetTeam() <= TEAM_BLUE
)
570 aNumplayers
[GameServer()->m_apPlayers
[i
]->GetTeam()]++;
576 Team
= aNumplayers
[TEAM_RED
] > aNumplayers
[TEAM_BLUE
] ? TEAM_BLUE
: TEAM_RED
;
578 if(CanJoinTeam(Team
, NotThisID
))
583 bool IGameController::CanJoinTeam(int Team
, int NotThisID
)
585 if(Team
== TEAM_SPECTATORS
|| (GameServer()->m_apPlayers
[NotThisID
] && GameServer()->m_apPlayers
[NotThisID
]->GetTeam() != TEAM_SPECTATORS
))
588 int aNumplayers
[2] = {0,0};
589 for(int i
= 0; i
< MAX_CLIENTS
; i
++)
591 if(GameServer()->m_apPlayers
[i
] && i
!= NotThisID
)
593 if(GameServer()->m_apPlayers
[i
]->GetTeam() >= TEAM_RED
&& GameServer()->m_apPlayers
[i
]->GetTeam() <= TEAM_BLUE
)
594 aNumplayers
[GameServer()->m_apPlayers
[i
]->GetTeam()]++;
598 return (aNumplayers
[0] + aNumplayers
[1]) < g_Config
.m_SvMaxClients
-g_Config
.m_SvSpectatorSlots
;
601 bool IGameController::CheckTeamBalance()
603 if(!IsTeamplay() || !g_Config
.m_SvTeambalanceTime
)
607 for(int i
= 0; i
< MAX_CLIENTS
; i
++)
609 CPlayer
*pP
= GameServer()->m_apPlayers
[i
];
610 if(pP
&& pP
->GetTeam() != TEAM_SPECTATORS
)
615 if(absolute(aT
[0]-aT
[1]) >= 2)
617 str_format(aBuf
, sizeof(aBuf
), "Teams are NOT balanced (red=%d blue=%d)", aT
[0], aT
[1]);
618 GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "game", aBuf
);
619 if(GameServer()->m_pController
->m_UnbalancedTick
== -1)
620 GameServer()->m_pController
->m_UnbalancedTick
= Server()->Tick();
625 str_format(aBuf
, sizeof(aBuf
), "Teams are balanced (red=%d blue=%d)", aT
[0], aT
[1]);
626 GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "game", aBuf
);
627 GameServer()->m_pController
->m_UnbalancedTick
= -1;
632 bool IGameController::CanChangeTeam(CPlayer
*pPlayer
, int JoinTeam
)
636 if (!IsTeamplay() || JoinTeam
== TEAM_SPECTATORS
|| !g_Config
.m_SvTeambalanceTime
)
639 for(int i
= 0; i
< MAX_CLIENTS
; i
++)
641 CPlayer
*pP
= GameServer()->m_apPlayers
[i
];
642 if(pP
&& pP
->GetTeam() != TEAM_SPECTATORS
)
646 // simulate what would happen if changed team
648 if (pPlayer
->GetTeam() != TEAM_SPECTATORS
)
651 // there is a player-difference of at least 2
652 if(absolute(aT
[0]-aT
[1]) >= 2)
654 // player wants to join team with less players
655 if ((aT
[0] < aT
[1] && JoinTeam
== TEAM_RED
) || (aT
[0] > aT
[1] && JoinTeam
== TEAM_BLUE
))
664 void IGameController::DoWincheck()
666 if(m_GameOverTick
== -1 && !m_Warmup
&& !GameServer()->m_World
.m_ResetRequested
)
670 // check score win condition
671 if((g_Config
.m_SvScorelimit
> 0 && (m_aTeamscore
[TEAM_RED
] >= g_Config
.m_SvScorelimit
|| m_aTeamscore
[TEAM_BLUE
] >= g_Config
.m_SvScorelimit
)) ||
672 (g_Config
.m_SvTimelimit
> 0 && (Server()->Tick()-m_RoundStartTick
) >= g_Config
.m_SvTimelimit
*Server()->TickSpeed()*60))
674 if(m_aTeamscore
[TEAM_RED
] != m_aTeamscore
[TEAM_BLUE
])
684 int TopscoreCount
= 0;
685 for(int i
= 0; i
< MAX_CLIENTS
; i
++)
687 if(GameServer()->m_apPlayers
[i
])
689 if(GameServer()->m_apPlayers
[i
]->m_Score
> Topscore
)
691 Topscore
= GameServer()->m_apPlayers
[i
]->m_Score
;
694 else if(GameServer()->m_apPlayers
[i
]->m_Score
== Topscore
)
699 // check score win condition
700 if((g_Config
.m_SvScorelimit
> 0 && Topscore
>= g_Config
.m_SvScorelimit
) ||
701 (g_Config
.m_SvTimelimit
> 0 && (Server()->Tick()-m_RoundStartTick
) >= g_Config
.m_SvTimelimit
*Server()->TickSpeed()*60))
703 if(TopscoreCount
== 1)
712 int IGameController::ClampTeam(int Team
)
715 return TEAM_SPECTATORS
;