1/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */
2#ifndef GAME_SERVER_TEAMS_H
3#define GAME_SERVER_TEAMS_H
4
5#include <engine/shared/config.h>
6#include <game/server/gamecontext.h>
7#include <game/teamscore.h>
8
9class CCharacter;
10class CPlayer;
11struct CScoreSaveResult;
12
13class CGameTeams
14{
15 // `m_TeeStarted` is used to keep track whether a given tee has hit the
16 // start of the map yet. If a tee that leaves hasn't hit the start line
17 // yet, the team will be marked as "not allowed to finish"
18 // (`TEAMSTATE_STARTED_UNFINISHABLE`). If this were not the case, tees
19 // could go around the startline on a map, leave one tee behind at
20 // start, go to the finish line, let the tee start and kill, allowing
21 // the team to finish instantly.
22 bool m_aTeeStarted[MAX_CLIENTS];
23 bool m_aTeeFinished[MAX_CLIENTS];
24 int m_aLastChat[MAX_CLIENTS];
25
26 int m_aTeamState[NUM_DDRACE_TEAMS];
27 bool m_aTeamLocked[NUM_DDRACE_TEAMS];
28 bool m_aTeamFlock[NUM_DDRACE_TEAMS];
29 CClientMask m_aInvited[NUM_DDRACE_TEAMS];
30 bool m_aPractice[NUM_DDRACE_TEAMS];
31 std::shared_ptr<CScoreSaveResult> m_apSaveTeamResult[NUM_DDRACE_TEAMS];
32 uint64_t m_aLastSwap[MAX_CLIENTS]; // index is id of player who initiated swap
33 bool m_aTeamSentStartWarning[NUM_DDRACE_TEAMS];
34 // `m_aTeamUnfinishableKillTick` is -1 by default and gets set when a
35 // team becomes unfinishable. If the team hasn't entered practice mode
36 // by that time, it'll get killed to prevent people not understanding
37 // the message from playing for a long time in an unfinishable team.
38 int m_aTeamUnfinishableKillTick[NUM_DDRACE_TEAMS];
39
40 class CGameContext *m_pGameContext;
41
42 /**
43 * Kill the whole team.
44 * @param Team The team id to kill
45 * @param NewStrongId The player with that id will get strong hook on everyone else, -1 will set the normal spawning order
46 * @param ExceptId The player that should not get killed
47 */
48 void KillTeam(int Team, int NewStrongId, int ExceptId = -1);
49 bool TeamFinished(int Team);
50 void OnTeamFinish(int Team, CPlayer **Players, unsigned int Size, int TimeTicks, const char *pTimestamp);
51 void OnFinish(CPlayer *Player, int TimeTicks, const char *pTimestamp);
52
53public:
54 enum
55 {
56 TEAMSTATE_EMPTY,
57 TEAMSTATE_OPEN,
58 TEAMSTATE_STARTED,
59 // Happens when a tee that hasn't hit the start tiles leaves
60 // the team.
61 TEAMSTATE_STARTED_UNFINISHABLE,
62 TEAMSTATE_FINISHED
63 };
64
65 CTeamsCore m_Core;
66
67 CGameTeams(CGameContext *pGameContext);
68
69 // helper methods
70 CCharacter *Character(int ClientId)
71 {
72 return GameServer()->GetPlayerChar(ClientId);
73 }
74 CPlayer *GetPlayer(int ClientId)
75 {
76 return GameServer()->m_apPlayers[ClientId];
77 }
78
79 class CGameContext *GameServer()
80 {
81 return m_pGameContext;
82 }
83 class IServer *Server()
84 {
85 return m_pGameContext->Server();
86 }
87
88 void OnCharacterStart(int ClientId);
89 void OnCharacterFinish(int ClientId);
90 void OnCharacterSpawn(int ClientId);
91 void OnCharacterDeath(int ClientId, int Weapon);
92 void Tick();
93
94 // returns nullptr if successful, error string if failed
95 const char *SetCharacterTeam(int ClientId, int Team);
96 void CheckTeamFinished(int Team);
97
98 void ChangeTeamState(int Team, int State);
99
100 CClientMask TeamMask(int Team, int ExceptId = -1, int Asker = -1);
101
102 int Count(int Team) const;
103
104 // need to be very careful using this method. SERIOUSLY...
105 void SetForceCharacterTeam(int ClientId, int Team);
106
107 void Reset();
108 void ResetRoundState(int Team);
109 void ResetSwitchers(int Team);
110
111 void SendTeamsState(int ClientId);
112 void SetTeamLock(int Team, bool Lock);
113 void SetTeamFlock(int Team, bool Mode);
114 void ResetInvited(int Team);
115 void SetClientInvited(int Team, int ClientId, bool Invited);
116
117 int GetDDRaceState(CPlayer *Player);
118 int GetStartTime(CPlayer *Player);
119 float *GetCurrentTimeCp(CPlayer *Player);
120 void SetDDRaceState(CPlayer *Player, int DDRaceState);
121 void SetStartTime(CPlayer *Player, int StartTime);
122 void SetLastTimeCp(CPlayer *Player, int LastTimeCp);
123 void KillSavedTeam(int ClientId, int Team);
124 void ResetSavedTeam(int ClientId, int Team);
125 void RequestTeamSwap(CPlayer *pPlayer, CPlayer *pTargetPlayer, int Team);
126 void SwapTeamCharacters(CPlayer *pPrimaryPlayer, CPlayer *pTargetPlayer, int Team);
127 void ProcessSaveTeam();
128
129 int GetFirstEmptyTeam() const;
130
131 bool TeeStarted(int ClientId)
132 {
133 return m_aTeeStarted[ClientId];
134 }
135
136 bool TeeFinished(int ClientId)
137 {
138 return m_aTeeFinished[ClientId];
139 }
140
141 int GetTeamState(int Team)
142 {
143 return m_aTeamState[Team];
144 }
145
146 bool TeamLocked(int Team)
147 {
148 if(Team <= TEAM_FLOCK || Team >= TEAM_SUPER)
149 return false;
150
151 return m_aTeamLocked[Team];
152 }
153
154 bool TeamFlock(int Team)
155 {
156 if(Team <= TEAM_FLOCK || Team >= TEAM_SUPER)
157 return false;
158
159 return m_aTeamFlock[Team];
160 }
161
162 bool IsInvited(int Team, int ClientId)
163 {
164 return m_aInvited[Team].test(position: ClientId);
165 }
166
167 bool IsStarted(int Team)
168 {
169 return m_aTeamState[Team] == CGameTeams::TEAMSTATE_STARTED;
170 }
171
172 void SetStarted(int ClientId, bool Started)
173 {
174 m_aTeeStarted[ClientId] = Started;
175 }
176
177 void SetFinished(int ClientId, bool Finished)
178 {
179 m_aTeeFinished[ClientId] = Finished;
180 }
181
182 void SetSaving(int TeamId, std::shared_ptr<CScoreSaveResult> &SaveResult)
183 {
184 m_apSaveTeamResult[TeamId] = SaveResult;
185 }
186
187 bool GetSaving(int TeamId)
188 {
189 if(TeamId < TEAM_FLOCK || TeamId >= TEAM_SUPER)
190 return false;
191 if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && TeamId == TEAM_FLOCK)
192 return false;
193
194 return m_apSaveTeamResult[TeamId] != nullptr;
195 }
196
197 void SetPractice(int Team, bool Enabled)
198 {
199 if(Team < TEAM_FLOCK || Team >= TEAM_SUPER)
200 return;
201 if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && Team == TEAM_FLOCK)
202 return;
203
204 m_aPractice[Team] = Enabled;
205 }
206
207 bool IsPractice(int Team)
208 {
209 if(Team < TEAM_FLOCK || Team >= TEAM_SUPER)
210 return false;
211 if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && Team == TEAM_FLOCK)
212 return false;
213
214 return m_aPractice[Team];
215 }
216};
217
218#endif
219