1/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */
2/* Based on Race mod stuff and tweaked by GreYFoX@GTi and others to fit our DDRace needs. */
3#include "DDRace.h"
4
5#include <engine/server.h>
6#include <engine/shared/config.h>
7#include <game/mapitems.h>
8#include <game/server/entities/character.h>
9#include <game/server/gamecontext.h>
10#include <game/server/player.h>
11#include <game/server/score.h>
12#include <game/version.h>
13
14#define GAME_TYPE_NAME "DDraceNetwork"
15#define TEST_TYPE_NAME "TestDDraceNetwork"
16
17CGameControllerDDRace::CGameControllerDDRace(class CGameContext *pGameServer) :
18 IGameController(pGameServer)
19{
20 m_pGameType = g_Config.m_SvTestingCommands ? TEST_TYPE_NAME : GAME_TYPE_NAME;
21}
22
23CGameControllerDDRace::~CGameControllerDDRace() = default;
24
25CScore *CGameControllerDDRace::Score()
26{
27 return GameServer()->Score();
28}
29
30void CGameControllerDDRace::HandleCharacterTiles(CCharacter *pChr, int MapIndex)
31{
32 CPlayer *pPlayer = pChr->GetPlayer();
33 const int ClientId = pPlayer->GetCid();
34
35 int m_TileIndex = GameServer()->Collision()->GetTileIndex(Index: MapIndex);
36 int m_TileFIndex = GameServer()->Collision()->GetFTileIndex(Index: MapIndex);
37
38 //Sensitivity
39 int S1 = GameServer()->Collision()->GetPureMapIndex(Pos: vec2(pChr->GetPos().x + pChr->GetProximityRadius() / 3.f, pChr->GetPos().y - pChr->GetProximityRadius() / 3.f));
40 int S2 = GameServer()->Collision()->GetPureMapIndex(Pos: vec2(pChr->GetPos().x + pChr->GetProximityRadius() / 3.f, pChr->GetPos().y + pChr->GetProximityRadius() / 3.f));
41 int S3 = GameServer()->Collision()->GetPureMapIndex(Pos: vec2(pChr->GetPos().x - pChr->GetProximityRadius() / 3.f, pChr->GetPos().y - pChr->GetProximityRadius() / 3.f));
42 int S4 = GameServer()->Collision()->GetPureMapIndex(Pos: vec2(pChr->GetPos().x - pChr->GetProximityRadius() / 3.f, pChr->GetPos().y + pChr->GetProximityRadius() / 3.f));
43 int Tile1 = GameServer()->Collision()->GetTileIndex(Index: S1);
44 int Tile2 = GameServer()->Collision()->GetTileIndex(Index: S2);
45 int Tile3 = GameServer()->Collision()->GetTileIndex(Index: S3);
46 int Tile4 = GameServer()->Collision()->GetTileIndex(Index: S4);
47 int FTile1 = GameServer()->Collision()->GetFTileIndex(Index: S1);
48 int FTile2 = GameServer()->Collision()->GetFTileIndex(Index: S2);
49 int FTile3 = GameServer()->Collision()->GetFTileIndex(Index: S3);
50 int FTile4 = GameServer()->Collision()->GetFTileIndex(Index: S4);
51
52 const int PlayerDDRaceState = pChr->m_DDRaceState;
53 bool IsOnStartTile = (m_TileIndex == TILE_START) || (m_TileFIndex == TILE_START) || FTile1 == TILE_START || FTile2 == TILE_START || FTile3 == TILE_START || FTile4 == TILE_START || Tile1 == TILE_START || Tile2 == TILE_START || Tile3 == TILE_START || Tile4 == TILE_START;
54 // start
55 if(IsOnStartTile && PlayerDDRaceState != DDRACE_CHEAT)
56 {
57 const int Team = GameServer()->GetDDRaceTeam(ClientId);
58 if(Teams().GetSaving(TeamId: Team))
59 {
60 GameServer()->SendStartWarning(ClientId, pMessage: "You can't start while loading/saving of team is in progress");
61 pChr->Die(Killer: ClientId, Weapon: WEAPON_WORLD);
62 return;
63 }
64 if(g_Config.m_SvTeam == SV_TEAM_MANDATORY && (Team == TEAM_FLOCK || Teams().Count(Team) <= 1))
65 {
66 GameServer()->SendStartWarning(ClientId, pMessage: "You have to be in a team with other tees to start");
67 pChr->Die(Killer: ClientId, Weapon: WEAPON_WORLD);
68 return;
69 }
70 if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && Team > TEAM_FLOCK && Team < TEAM_SUPER && Teams().Count(Team) < g_Config.m_SvMinTeamSize && !Teams().TeamFlock(Team))
71 {
72 char aBuf[128];
73 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Your team has fewer than %d players, so your team rank won't count", g_Config.m_SvMinTeamSize);
74 GameServer()->SendStartWarning(ClientId, pMessage: aBuf);
75 }
76 if(g_Config.m_SvResetPickups)
77 {
78 pChr->ResetPickups();
79 }
80
81 Teams().OnCharacterStart(ClientId);
82 pChr->m_LastTimeCp = -1;
83 pChr->m_LastTimeCpBroadcasted = -1;
84 for(float &CurrentTimeCp : pChr->m_aCurrentTimeCp)
85 {
86 CurrentTimeCp = 0.0f;
87 }
88 }
89
90 // finish
91 if(((m_TileIndex == TILE_FINISH) || (m_TileFIndex == TILE_FINISH) || FTile1 == TILE_FINISH || FTile2 == TILE_FINISH || FTile3 == TILE_FINISH || FTile4 == TILE_FINISH || Tile1 == TILE_FINISH || Tile2 == TILE_FINISH || Tile3 == TILE_FINISH || Tile4 == TILE_FINISH) && PlayerDDRaceState == DDRACE_STARTED)
92 Teams().OnCharacterFinish(ClientId);
93
94 // unlock team
95 else if(((m_TileIndex == TILE_UNLOCK_TEAM) || (m_TileFIndex == TILE_UNLOCK_TEAM)) && Teams().TeamLocked(Team: GameServer()->GetDDRaceTeam(ClientId)))
96 {
97 Teams().SetTeamLock(Team: GameServer()->GetDDRaceTeam(ClientId), Lock: false);
98 GameServer()->SendChatTeam(Team: GameServer()->GetDDRaceTeam(ClientId), pText: "Your team was unlocked by an unlock team tile");
99 }
100
101 // solo part
102 if(((m_TileIndex == TILE_SOLO_ENABLE) || (m_TileFIndex == TILE_SOLO_ENABLE)) && !Teams().m_Core.GetSolo(ClientId))
103 {
104 GameServer()->SendChatTarget(To: ClientId, pText: "You are now in a solo part");
105 pChr->SetSolo(true);
106 }
107 else if(((m_TileIndex == TILE_SOLO_DISABLE) || (m_TileFIndex == TILE_SOLO_DISABLE)) && Teams().m_Core.GetSolo(ClientId))
108 {
109 GameServer()->SendChatTarget(To: ClientId, pText: "You are now out of the solo part");
110 pChr->SetSolo(false);
111 }
112}
113
114void CGameControllerDDRace::OnPlayerConnect(CPlayer *pPlayer)
115{
116 IGameController::OnPlayerConnect(pPlayer);
117 int ClientId = pPlayer->GetCid();
118
119 // init the player
120 Score()->PlayerData(Id: ClientId)->Reset();
121
122 // Can't set score here as LoadScore() is threaded, run it in
123 // LoadScoreThreaded() instead
124 Score()->LoadPlayerData(ClientId);
125
126 if(!Server()->ClientPrevIngame(ClientId))
127 {
128 char aBuf[512];
129 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' entered and joined the %s", Server()->ClientName(ClientId), GetTeamName(Team: pPlayer->GetTeam()));
130 GameServer()->SendChat(ClientId: -1, Team: TEAM_ALL, pText: aBuf, SpamProtectionClientId: -1, Flags: CGameContext::CHAT_SIX);
131
132 GameServer()->SendChatTarget(To: ClientId, pText: "DDraceNetwork Mod. Version: " GAME_VERSION);
133 GameServer()->SendChatTarget(To: ClientId, pText: "please visit DDNet.org or say /info and make sure to read our /rules");
134 }
135}
136
137void CGameControllerDDRace::OnPlayerDisconnect(CPlayer *pPlayer, const char *pReason)
138{
139 int ClientId = pPlayer->GetCid();
140 bool WasModerator = pPlayer->m_Moderating && Server()->ClientIngame(ClientId);
141
142 IGameController::OnPlayerDisconnect(pPlayer, pReason);
143
144 if(!GameServer()->PlayerModerating() && WasModerator)
145 GameServer()->SendChat(ClientId: -1, Team: TEAM_ALL, pText: "Server kick/spec votes are no longer actively moderated.");
146
147 if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO)
148 Teams().SetForceCharacterTeam(ClientId, Team: TEAM_FLOCK);
149
150 for(int Team = TEAM_FLOCK + 1; Team < TEAM_SUPER; Team++)
151 if(Teams().IsInvited(Team, ClientId))
152 Teams().SetClientInvited(Team, ClientId, Invited: false);
153}
154
155void CGameControllerDDRace::OnReset()
156{
157 IGameController::OnReset();
158 Teams().Reset();
159}
160
161void CGameControllerDDRace::Tick()
162{
163 IGameController::Tick();
164 Teams().ProcessSaveTeam();
165 Teams().Tick();
166}
167
168void CGameControllerDDRace::DoTeamChange(class CPlayer *pPlayer, int Team, bool DoChatMsg)
169{
170 Team = ClampTeam(Team);
171 if(Team == pPlayer->GetTeam())
172 return;
173
174 CCharacter *pCharacter = pPlayer->GetCharacter();
175
176 if(Team == TEAM_SPECTATORS)
177 {
178 if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && pCharacter)
179 {
180 // Joining spectators should not kill a locked team, but should still
181 // check if the team finished by you leaving it.
182 int DDRTeam = pCharacter->Team();
183 Teams().SetForceCharacterTeam(ClientId: pPlayer->GetCid(), Team: TEAM_FLOCK);
184 Teams().CheckTeamFinished(Team: DDRTeam);
185 }
186 }
187
188 IGameController::DoTeamChange(pPlayer, Team, DoChatMsg);
189}
190