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