1 | /* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */ |
2 | #include "teams.h" |
3 | #include "entities/character.h" |
4 | #include "gamecontroller.h" |
5 | #include "player.h" |
6 | #include "score.h" |
7 | #include "teehistorian.h" |
8 | #include <base/system.h> |
9 | |
10 | #include <engine/shared/config.h> |
11 | |
12 | #include <game/mapitems.h> |
13 | |
14 | CGameTeams::CGameTeams(CGameContext *pGameContext) : |
15 | m_pGameContext(pGameContext) |
16 | { |
17 | Reset(); |
18 | } |
19 | |
20 | void CGameTeams::Reset() |
21 | { |
22 | m_Core.Reset(); |
23 | for(int i = 0; i < MAX_CLIENTS; ++i) |
24 | { |
25 | m_aTeeStarted[i] = false; |
26 | m_aTeeFinished[i] = false; |
27 | m_aLastChat[i] = 0; |
28 | SendTeamsState(ClientId: i); |
29 | } |
30 | |
31 | for(int i = 0; i < NUM_TEAMS; ++i) |
32 | { |
33 | m_aTeamState[i] = TEAMSTATE_EMPTY; |
34 | m_aTeamLocked[i] = false; |
35 | m_aTeamFlock[i] = false; |
36 | m_apSaveTeamResult[i] = nullptr; |
37 | m_aTeamSentStartWarning[i] = false; |
38 | ResetRoundState(Team: i); |
39 | } |
40 | } |
41 | |
42 | void CGameTeams::ResetRoundState(int Team) |
43 | { |
44 | ResetInvited(Team); |
45 | if(Team != TEAM_SUPER) |
46 | ResetSwitchers(Team); |
47 | |
48 | m_aPractice[Team] = false; |
49 | m_aTeamUnfinishableKillTick[Team] = -1; |
50 | for(int i = 0; i < MAX_CLIENTS; i++) |
51 | { |
52 | if(m_Core.Team(ClientId: i) == Team && GameServer()->m_apPlayers[i]) |
53 | { |
54 | GameServer()->m_apPlayers[i]->m_VotedForPractice = false; |
55 | GameServer()->m_apPlayers[i]->m_SwapTargetsClientId = -1; |
56 | m_aLastSwap[i] = 0; |
57 | } |
58 | } |
59 | } |
60 | |
61 | void CGameTeams::ResetSwitchers(int Team) |
62 | { |
63 | for(auto &Switcher : GameServer()->Switchers()) |
64 | { |
65 | Switcher.m_aStatus[Team] = Switcher.m_Initial; |
66 | Switcher.m_aEndTick[Team] = 0; |
67 | Switcher.m_aType[Team] = TILE_SWITCHOPEN; |
68 | } |
69 | } |
70 | |
71 | void CGameTeams::OnCharacterStart(int ClientId) |
72 | { |
73 | int Tick = Server()->Tick(); |
74 | CCharacter *pStartingChar = Character(ClientId); |
75 | if(!pStartingChar) |
76 | return; |
77 | if(g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO && pStartingChar->m_DDRaceState == DDRACE_STARTED) |
78 | return; |
79 | if((g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO || (m_Core.Team(ClientId) != TEAM_FLOCK && !m_aTeamFlock[m_Core.Team(ClientId)])) && pStartingChar->m_DDRaceState == DDRACE_FINISHED) |
80 | return; |
81 | if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && |
82 | (m_Core.Team(ClientId) == TEAM_FLOCK || TeamFlock(Team: m_Core.Team(ClientId)) || m_Core.Team(ClientId) == TEAM_SUPER)) |
83 | { |
84 | if(TeamFlock(Team: m_Core.Team(ClientId)) && (m_aTeamState[m_Core.Team(ClientId)] < TEAMSTATE_STARTED)) |
85 | ChangeTeamState(Team: m_Core.Team(ClientId), State: TEAMSTATE_STARTED); |
86 | |
87 | m_aTeeStarted[ClientId] = true; |
88 | pStartingChar->m_DDRaceState = DDRACE_STARTED; |
89 | pStartingChar->m_StartTime = Tick; |
90 | return; |
91 | } |
92 | bool Waiting = false; |
93 | for(int i = 0; i < MAX_CLIENTS; ++i) |
94 | { |
95 | if(m_Core.Team(ClientId) != m_Core.Team(ClientId: i)) |
96 | continue; |
97 | CPlayer *pPlayer = GetPlayer(ClientId: i); |
98 | if(!pPlayer || !pPlayer->IsPlaying()) |
99 | continue; |
100 | if(GetDDRaceState(Player: pPlayer) != DDRACE_FINISHED) |
101 | continue; |
102 | |
103 | Waiting = true; |
104 | pStartingChar->m_DDRaceState = DDRACE_NONE; |
105 | |
106 | if(m_aLastChat[ClientId] + Server()->TickSpeed() + g_Config.m_SvChatDelay < Tick) |
107 | { |
108 | char aBuf[128]; |
109 | str_format( |
110 | buffer: aBuf, |
111 | buffer_size: sizeof(aBuf), |
112 | format: "%s has finished and didn't go through start yet, wait for him or join another team." , |
113 | Server()->ClientName(ClientId: i)); |
114 | GameServer()->SendChatTarget(To: ClientId, pText: aBuf); |
115 | m_aLastChat[ClientId] = Tick; |
116 | } |
117 | if(m_aLastChat[i] + Server()->TickSpeed() + g_Config.m_SvChatDelay < Tick) |
118 | { |
119 | char aBuf[128]; |
120 | str_format( |
121 | buffer: aBuf, |
122 | buffer_size: sizeof(aBuf), |
123 | format: "%s wants to start a new round, kill or walk to start." , |
124 | Server()->ClientName(ClientId)); |
125 | GameServer()->SendChatTarget(To: i, pText: aBuf); |
126 | m_aLastChat[i] = Tick; |
127 | } |
128 | } |
129 | |
130 | if(!Waiting) |
131 | { |
132 | m_aTeeStarted[ClientId] = true; |
133 | } |
134 | |
135 | if(m_aTeamState[m_Core.Team(ClientId)] < TEAMSTATE_STARTED && !Waiting) |
136 | { |
137 | ChangeTeamState(Team: m_Core.Team(ClientId), State: TEAMSTATE_STARTED); |
138 | m_aTeamSentStartWarning[m_Core.Team(ClientId)] = false; |
139 | m_aTeamUnfinishableKillTick[m_Core.Team(ClientId)] = -1; |
140 | |
141 | int NumPlayers = Count(Team: m_Core.Team(ClientId)); |
142 | |
143 | char aBuf[512]; |
144 | str_format( |
145 | buffer: aBuf, |
146 | buffer_size: sizeof(aBuf), |
147 | format: "Team %d started with %d player%s: " , |
148 | m_Core.Team(ClientId), |
149 | NumPlayers, |
150 | NumPlayers == 1 ? "" : "s" ); |
151 | |
152 | bool First = true; |
153 | |
154 | for(int i = 0; i < MAX_CLIENTS; ++i) |
155 | { |
156 | if(m_Core.Team(ClientId) == m_Core.Team(ClientId: i)) |
157 | { |
158 | CPlayer *pPlayer = GetPlayer(ClientId: i); |
159 | // TODO: THE PROBLEM IS THAT THERE IS NO CHARACTER SO START TIME CAN'T BE SET! |
160 | if(pPlayer && (pPlayer->IsPlaying() || TeamLocked(Team: m_Core.Team(ClientId)))) |
161 | { |
162 | SetDDRaceState(Player: pPlayer, DDRaceState: DDRACE_STARTED); |
163 | SetStartTime(Player: pPlayer, StartTime: Tick); |
164 | |
165 | if(First) |
166 | First = false; |
167 | else |
168 | str_append(dst&: aBuf, src: ", " ); |
169 | |
170 | str_append(dst&: aBuf, src: GameServer()->Server()->ClientName(ClientId: i)); |
171 | } |
172 | } |
173 | } |
174 | |
175 | if(g_Config.m_SvTeam < SV_TEAM_FORCED_SOLO && g_Config.m_SvMaxTeamSize != 2 && g_Config.m_SvPauseable) |
176 | { |
177 | for(int i = 0; i < MAX_CLIENTS; ++i) |
178 | { |
179 | CPlayer *pPlayer = GetPlayer(ClientId: i); |
180 | if(m_Core.Team(ClientId) == m_Core.Team(ClientId: i) && pPlayer && (pPlayer->IsPlaying() || TeamLocked(Team: m_Core.Team(ClientId)))) |
181 | { |
182 | GameServer()->SendChatTarget(To: i, pText: aBuf); |
183 | } |
184 | } |
185 | } |
186 | } |
187 | } |
188 | |
189 | void CGameTeams::OnCharacterFinish(int ClientId) |
190 | { |
191 | if(((m_Core.Team(ClientId) == TEAM_FLOCK || m_aTeamFlock[m_Core.Team(ClientId)]) && g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO) || m_Core.Team(ClientId) == TEAM_SUPER) |
192 | { |
193 | CPlayer *pPlayer = GetPlayer(ClientId); |
194 | if(pPlayer && pPlayer->IsPlaying()) |
195 | { |
196 | float Time = (float)(Server()->Tick() - GetStartTime(Player: pPlayer)) / ((float)Server()->TickSpeed()); |
197 | if(Time < 0.000001f) |
198 | return; |
199 | char aTimestamp[TIMESTAMP_STR_LENGTH]; |
200 | str_timestamp_format(buffer: aTimestamp, buffer_size: sizeof(aTimestamp), FORMAT_SPACE); // 2019-04-02 19:41:58 |
201 | |
202 | OnFinish(Player: pPlayer, Time, pTimestamp: aTimestamp); |
203 | } |
204 | } |
205 | else |
206 | { |
207 | if(m_aTeeStarted[ClientId]) |
208 | { |
209 | m_aTeeFinished[ClientId] = true; |
210 | } |
211 | CheckTeamFinished(Team: m_Core.Team(ClientId)); |
212 | } |
213 | } |
214 | |
215 | void CGameTeams::Tick() |
216 | { |
217 | int Now = Server()->Tick(); |
218 | |
219 | for(int i = 0; i < MAX_CLIENTS; i++) |
220 | { |
221 | CPlayerData *pData = GameServer()->Score()->PlayerData(Id: i); |
222 | if(!Server()->IsRecording(ClientId: i)) |
223 | continue; |
224 | |
225 | if(Now >= pData->m_RecordStopTick && pData->m_RecordStopTick != -1) |
226 | { |
227 | Server()->SaveDemo(ClientId: i, Time: pData->m_RecordFinishTime); |
228 | pData->m_RecordStopTick = -1; |
229 | } |
230 | } |
231 | |
232 | for(int i = 0; i < MAX_CLIENTS; i++) |
233 | { |
234 | if(m_aTeamUnfinishableKillTick[i] == -1 || m_aTeamState[i] != TEAMSTATE_STARTED_UNFINISHABLE) |
235 | { |
236 | continue; |
237 | } |
238 | if(Now >= m_aTeamUnfinishableKillTick[i]) |
239 | { |
240 | if(m_aPractice[i]) |
241 | { |
242 | m_aTeamUnfinishableKillTick[i] = -1; |
243 | continue; |
244 | } |
245 | GameServer()->SendChatTeam(Team: i, pText: "Your team was killed because it couldn't finish anymore and hasn't entered /practice mode" ); |
246 | KillTeam(Team: i, NewStrongId: -1); |
247 | } |
248 | } |
249 | |
250 | int Frequency = Server()->TickSpeed() * 60; |
251 | int Remainder = Server()->TickSpeed() * 30; |
252 | uint64_t TeamHasWantedStartTime = 0; |
253 | for(int i = 0; i < MAX_CLIENTS; i++) |
254 | { |
255 | CCharacter *pChar = GameServer()->m_apPlayers[i] ? GameServer()->m_apPlayers[i]->GetCharacter() : nullptr; |
256 | int Team = m_Core.Team(ClientId: i); |
257 | if(!pChar || m_aTeamState[Team] != TEAMSTATE_STARTED || m_aTeamFlock[Team] || m_aTeeStarted[i] || m_aPractice[m_Core.Team(ClientId: i)]) |
258 | { |
259 | continue; |
260 | } |
261 | if((Now - pChar->m_StartTime) % Frequency == Remainder) |
262 | { |
263 | TeamHasWantedStartTime |= ((uint64_t)1) << m_Core.Team(ClientId: i); |
264 | } |
265 | } |
266 | TeamHasWantedStartTime &= ~(uint64_t)1; |
267 | if(!TeamHasWantedStartTime) |
268 | { |
269 | return; |
270 | } |
271 | for(int i = 0; i < MAX_CLIENTS; i++) |
272 | { |
273 | if(((TeamHasWantedStartTime >> i) & 1) == 0) |
274 | { |
275 | continue; |
276 | } |
277 | if(Count(Team: i) <= 1) |
278 | { |
279 | continue; |
280 | } |
281 | bool TeamHasCheatCharacter = false; |
282 | int NumPlayersNotStarted = 0; |
283 | char aPlayerNames[256]; |
284 | aPlayerNames[0] = 0; |
285 | for(int j = 0; j < MAX_CLIENTS; j++) |
286 | { |
287 | if(Character(ClientId: j) && Character(ClientId: j)->m_DDRaceState == DDRACE_CHEAT) |
288 | TeamHasCheatCharacter = true; |
289 | if(m_Core.Team(ClientId: j) == i && !m_aTeeStarted[j]) |
290 | { |
291 | if(aPlayerNames[0]) |
292 | { |
293 | str_append(dst&: aPlayerNames, src: ", " ); |
294 | } |
295 | str_append(dst&: aPlayerNames, src: Server()->ClientName(ClientId: j)); |
296 | NumPlayersNotStarted += 1; |
297 | } |
298 | } |
299 | if(!aPlayerNames[0] || TeamHasCheatCharacter) |
300 | { |
301 | continue; |
302 | } |
303 | char aBuf[512]; |
304 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
305 | format: "Your team has %d %s not started yet, they need " |
306 | "to touch the start before this team can finish: %s" , |
307 | NumPlayersNotStarted, |
308 | NumPlayersNotStarted == 1 ? "player that has" : "players that have" , |
309 | aPlayerNames); |
310 | GameServer()->SendChatTeam(Team: i, pText: aBuf); |
311 | } |
312 | } |
313 | |
314 | void CGameTeams::CheckTeamFinished(int Team) |
315 | { |
316 | if(TeamFinished(Team)) |
317 | { |
318 | CPlayer *apTeamPlayers[MAX_CLIENTS]; |
319 | unsigned int PlayersCount = 0; |
320 | |
321 | for(int i = 0; i < MAX_CLIENTS; ++i) |
322 | { |
323 | if(Team == m_Core.Team(ClientId: i)) |
324 | { |
325 | CPlayer *pPlayer = GetPlayer(ClientId: i); |
326 | if(pPlayer && pPlayer->IsPlaying()) |
327 | { |
328 | m_aTeeStarted[i] = false; |
329 | m_aTeeFinished[i] = false; |
330 | |
331 | apTeamPlayers[PlayersCount++] = pPlayer; |
332 | } |
333 | } |
334 | } |
335 | |
336 | if(PlayersCount > 0) |
337 | { |
338 | float Time = (float)(Server()->Tick() - GetStartTime(Player: apTeamPlayers[0])) / ((float)Server()->TickSpeed()); |
339 | if(Time < 0.000001f) |
340 | { |
341 | return; |
342 | } |
343 | |
344 | if(m_aPractice[Team]) |
345 | { |
346 | ChangeTeamState(Team, State: TEAMSTATE_FINISHED); |
347 | |
348 | char aBuf[256]; |
349 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
350 | format: "Your team would've finished in: %d minute(s) %5.2f second(s). Since you had practice mode enabled your rank doesn't count." , |
351 | (int)Time / 60, Time - ((int)Time / 60 * 60)); |
352 | GameServer()->SendChatTeam(Team, pText: aBuf); |
353 | |
354 | for(unsigned int i = 0; i < PlayersCount; ++i) |
355 | { |
356 | SetDDRaceState(Player: apTeamPlayers[i], DDRaceState: DDRACE_FINISHED); |
357 | } |
358 | |
359 | return; |
360 | } |
361 | |
362 | char aTimestamp[TIMESTAMP_STR_LENGTH]; |
363 | str_timestamp_format(buffer: aTimestamp, buffer_size: sizeof(aTimestamp), FORMAT_SPACE); // 2019-04-02 19:41:58 |
364 | |
365 | for(unsigned int i = 0; i < PlayersCount; ++i) |
366 | OnFinish(Player: apTeamPlayers[i], Time, pTimestamp: aTimestamp); |
367 | ChangeTeamState(Team, State: TEAMSTATE_FINISHED); // TODO: Make it better |
368 | OnTeamFinish(Players: apTeamPlayers, Size: PlayersCount, Time, pTimestamp: aTimestamp); |
369 | } |
370 | } |
371 | } |
372 | |
373 | const char *CGameTeams::SetCharacterTeam(int ClientId, int Team) |
374 | { |
375 | if(ClientId < 0 || ClientId >= MAX_CLIENTS) |
376 | return "Invalid client ID" ; |
377 | if(Team < 0 || Team >= MAX_CLIENTS + 1) |
378 | return "Invalid team number" ; |
379 | if(Team != TEAM_SUPER && m_aTeamState[Team] > TEAMSTATE_OPEN && !m_aPractice[Team] && !m_aTeamFlock[Team]) |
380 | return "This team started already" ; |
381 | if(m_Core.Team(ClientId) == Team) |
382 | return "You are in this team already" ; |
383 | if(!Character(ClientId)) |
384 | return "Your character is not valid" ; |
385 | if(Team == TEAM_SUPER && !Character(ClientId)->IsSuper()) |
386 | return "You can't join super team if you don't have super rights" ; |
387 | if(Team != TEAM_SUPER && Character(ClientId)->m_DDRaceState != DDRACE_NONE) |
388 | return "You have started racing already" ; |
389 | // No cheating through noob filter with practice and then leaving team |
390 | if(m_aPractice[m_Core.Team(ClientId)]) |
391 | return "You have used practice mode already" ; |
392 | |
393 | // you can not join a team which is currently in the process of saving, |
394 | // because the save-process can fail and then the team is reset into the game |
395 | if(Team != TEAM_SUPER && GetSaving(TeamId: Team)) |
396 | return "Your team is currently saving" ; |
397 | if(m_Core.Team(ClientId) != TEAM_SUPER && GetSaving(TeamId: m_Core.Team(ClientId))) |
398 | return "This team is currently saving" ; |
399 | |
400 | SetForceCharacterTeam(ClientId, Team); |
401 | return nullptr; |
402 | } |
403 | |
404 | void CGameTeams::SetForceCharacterTeam(int ClientId, int Team) |
405 | { |
406 | m_aTeeStarted[ClientId] = false; |
407 | m_aTeeFinished[ClientId] = false; |
408 | int OldTeam = m_Core.Team(ClientId); |
409 | |
410 | if(Team != OldTeam && (OldTeam != TEAM_FLOCK || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO) && OldTeam != TEAM_SUPER && m_aTeamState[OldTeam] != TEAMSTATE_EMPTY) |
411 | { |
412 | bool NoElseInOldTeam = Count(Team: OldTeam) <= 1; |
413 | if(NoElseInOldTeam) |
414 | { |
415 | m_aTeamState[OldTeam] = TEAMSTATE_EMPTY; |
416 | |
417 | // unlock team when last player leaves |
418 | SetTeamLock(Team: OldTeam, Lock: false); |
419 | SetTeamFlock(Team: OldTeam, Mode: false); |
420 | ResetRoundState(Team: OldTeam); |
421 | // do not reset SaveTeamResult, because it should be logged into teehistorian even if the team leaves |
422 | } |
423 | } |
424 | |
425 | m_Core.Team(ClientId, Team); |
426 | |
427 | if(OldTeam != Team) |
428 | { |
429 | for(int LoopClientId = 0; LoopClientId < MAX_CLIENTS; ++LoopClientId) |
430 | if(GetPlayer(ClientId: LoopClientId)) |
431 | SendTeamsState(ClientId: LoopClientId); |
432 | |
433 | if(GetPlayer(ClientId)) |
434 | { |
435 | GetPlayer(ClientId)->m_VotedForPractice = false; |
436 | GetPlayer(ClientId)->m_SwapTargetsClientId = -1; |
437 | } |
438 | m_pGameContext->m_World.RemoveEntitiesFromPlayer(PlayerId: ClientId); |
439 | } |
440 | |
441 | if(Team != TEAM_SUPER && (m_aTeamState[Team] == TEAMSTATE_EMPTY || (m_aTeamLocked[Team] && !m_aTeamFlock[Team]))) |
442 | { |
443 | if(!m_aTeamLocked[Team]) |
444 | ChangeTeamState(Team, State: TEAMSTATE_OPEN); |
445 | |
446 | ResetSwitchers(Team); |
447 | } |
448 | } |
449 | |
450 | int CGameTeams::Count(int Team) const |
451 | { |
452 | if(Team == TEAM_SUPER) |
453 | return -1; |
454 | |
455 | int Count = 0; |
456 | |
457 | for(int i = 0; i < MAX_CLIENTS; ++i) |
458 | if(m_Core.Team(ClientId: i) == Team) |
459 | Count++; |
460 | |
461 | return Count; |
462 | } |
463 | |
464 | void CGameTeams::ChangeTeamState(int Team, int State) |
465 | { |
466 | m_aTeamState[Team] = State; |
467 | } |
468 | |
469 | void CGameTeams::KillTeam(int Team, int NewStrongId, int ExceptId) |
470 | { |
471 | for(int i = 0; i < MAX_CLIENTS; i++) |
472 | { |
473 | if(m_Core.Team(ClientId: i) == Team && GameServer()->m_apPlayers[i]) |
474 | { |
475 | GameServer()->m_apPlayers[i]->m_VotedForPractice = false; |
476 | if(i != ExceptId) |
477 | { |
478 | GameServer()->m_apPlayers[i]->KillCharacter(Weapon: WEAPON_SELF, SendKillMsg: false); |
479 | if(NewStrongId != -1 && i != NewStrongId) |
480 | { |
481 | GameServer()->m_apPlayers[i]->Respawn(WeakHook: true); // spawn the rest of team with weak hook on the killer |
482 | } |
483 | } |
484 | } |
485 | } |
486 | |
487 | // send the team kill message |
488 | CNetMsg_Sv_KillMsgTeam Msg; |
489 | Msg.m_Team = Team; |
490 | Msg.m_First = NewStrongId; |
491 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId: -1); |
492 | } |
493 | |
494 | bool CGameTeams::TeamFinished(int Team) |
495 | { |
496 | if(m_aTeamState[Team] != TEAMSTATE_STARTED) |
497 | { |
498 | return false; |
499 | } |
500 | for(int i = 0; i < MAX_CLIENTS; ++i) |
501 | if(m_Core.Team(ClientId: i) == Team && !m_aTeeFinished[i]) |
502 | return false; |
503 | return true; |
504 | } |
505 | |
506 | CClientMask CGameTeams::TeamMask(int Team, int ExceptId, int Asker) |
507 | { |
508 | if(Team == TEAM_SUPER) |
509 | { |
510 | if(ExceptId == -1) |
511 | return CClientMask().set(); |
512 | return CClientMask().set().reset(position: ExceptId); |
513 | } |
514 | |
515 | CClientMask Mask; |
516 | for(int i = 0; i < MAX_CLIENTS; ++i) |
517 | { |
518 | if(i == ExceptId) |
519 | continue; // Explicitly excluded |
520 | if(!GetPlayer(ClientId: i)) |
521 | continue; // Player doesn't exist |
522 | |
523 | if(!(GetPlayer(ClientId: i)->GetTeam() == TEAM_SPECTATORS || GetPlayer(ClientId: i)->IsPaused())) |
524 | { // Not spectator |
525 | if(i != Asker) |
526 | { // Actions of other players |
527 | if(!Character(ClientId: i)) |
528 | continue; // Player is currently dead |
529 | if(GetPlayer(ClientId: i)->m_ShowOthers == SHOW_OTHERS_ONLY_TEAM) |
530 | { |
531 | if(m_Core.Team(ClientId: i) != Team && m_Core.Team(ClientId: i) != TEAM_SUPER) |
532 | continue; // In different teams |
533 | } |
534 | else if(GetPlayer(ClientId: i)->m_ShowOthers == SHOW_OTHERS_OFF) |
535 | { |
536 | if(m_Core.GetSolo(ClientId: Asker)) |
537 | continue; // When in solo part don't show others |
538 | if(m_Core.GetSolo(ClientId: i)) |
539 | continue; // When in solo part don't show others |
540 | if(m_Core.Team(ClientId: i) != Team && m_Core.Team(ClientId: i) != TEAM_SUPER) |
541 | continue; // In different teams |
542 | } |
543 | } // See everything of yourself |
544 | } |
545 | else if(GetPlayer(ClientId: i)->m_SpectatorId != SPEC_FREEVIEW) |
546 | { // Spectating specific player |
547 | if(GetPlayer(ClientId: i)->m_SpectatorId != Asker) |
548 | { // Actions of other players |
549 | if(!Character(ClientId: GetPlayer(ClientId: i)->m_SpectatorId)) |
550 | continue; // Player is currently dead |
551 | if(GetPlayer(ClientId: i)->m_ShowOthers == SHOW_OTHERS_ONLY_TEAM) |
552 | { |
553 | if(m_Core.Team(ClientId: GetPlayer(ClientId: i)->m_SpectatorId) != Team && m_Core.Team(ClientId: GetPlayer(ClientId: i)->m_SpectatorId) != TEAM_SUPER) |
554 | continue; // In different teams |
555 | } |
556 | else if(GetPlayer(ClientId: i)->m_ShowOthers == SHOW_OTHERS_OFF) |
557 | { |
558 | if(m_Core.GetSolo(ClientId: Asker)) |
559 | continue; // When in solo part don't show others |
560 | if(m_Core.GetSolo(ClientId: GetPlayer(ClientId: i)->m_SpectatorId)) |
561 | continue; // When in solo part don't show others |
562 | if(m_Core.Team(ClientId: GetPlayer(ClientId: i)->m_SpectatorId) != Team && m_Core.Team(ClientId: GetPlayer(ClientId: i)->m_SpectatorId) != TEAM_SUPER) |
563 | continue; // In different teams |
564 | } |
565 | } // See everything of player you're spectating |
566 | } |
567 | else |
568 | { // Freeview |
569 | if(GetPlayer(ClientId: i)->m_SpecTeam) |
570 | { // Show only players in own team when spectating |
571 | if(m_Core.Team(ClientId: i) != Team && m_Core.Team(ClientId: i) != TEAM_SUPER) |
572 | continue; // in different teams |
573 | } |
574 | } |
575 | |
576 | Mask.set(position: i); |
577 | } |
578 | return Mask; |
579 | } |
580 | |
581 | void CGameTeams::SendTeamsState(int ClientId) |
582 | { |
583 | if(g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO) |
584 | return; |
585 | |
586 | if(!m_pGameContext->m_apPlayers[ClientId]) |
587 | return; |
588 | |
589 | CMsgPacker Msg(NETMSGTYPE_SV_TEAMSSTATE); |
590 | CMsgPacker MsgLegacy(NETMSGTYPE_SV_TEAMSSTATELEGACY); |
591 | |
592 | for(unsigned i = 0; i < MAX_CLIENTS; i++) |
593 | { |
594 | Msg.AddInt(i: m_Core.Team(ClientId: i)); |
595 | MsgLegacy.AddInt(i: m_Core.Team(ClientId: i)); |
596 | } |
597 | |
598 | Server()->SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId); |
599 | int ClientVersion = m_pGameContext->m_apPlayers[ClientId]->GetClientVersion(); |
600 | if(!Server()->IsSixup(ClientId) && VERSION_DDRACE < ClientVersion && ClientVersion < VERSION_DDNET_MSG_LEGACY) |
601 | { |
602 | Server()->SendMsg(pMsg: &MsgLegacy, Flags: MSGFLAG_VITAL, ClientId); |
603 | } |
604 | } |
605 | |
606 | int CGameTeams::GetDDRaceState(CPlayer *Player) |
607 | { |
608 | if(!Player) |
609 | return DDRACE_NONE; |
610 | |
611 | CCharacter *pChar = Player->GetCharacter(); |
612 | if(pChar) |
613 | return pChar->m_DDRaceState; |
614 | return DDRACE_NONE; |
615 | } |
616 | |
617 | void CGameTeams::SetDDRaceState(CPlayer *Player, int DDRaceState) |
618 | { |
619 | if(!Player) |
620 | return; |
621 | |
622 | CCharacter *pChar = Player->GetCharacter(); |
623 | if(pChar) |
624 | pChar->m_DDRaceState = DDRaceState; |
625 | } |
626 | |
627 | int CGameTeams::GetStartTime(CPlayer *Player) |
628 | { |
629 | if(!Player) |
630 | return 0; |
631 | |
632 | CCharacter *pChar = Player->GetCharacter(); |
633 | if(pChar) |
634 | return pChar->m_StartTime; |
635 | return 0; |
636 | } |
637 | |
638 | void CGameTeams::SetStartTime(CPlayer *Player, int StartTime) |
639 | { |
640 | if(!Player) |
641 | return; |
642 | |
643 | CCharacter *pChar = Player->GetCharacter(); |
644 | if(pChar) |
645 | pChar->m_StartTime = StartTime; |
646 | } |
647 | |
648 | void CGameTeams::SetLastTimeCp(CPlayer *Player, int LastTimeCp) |
649 | { |
650 | if(!Player) |
651 | return; |
652 | |
653 | CCharacter *pChar = Player->GetCharacter(); |
654 | if(pChar) |
655 | pChar->m_LastTimeCp = LastTimeCp; |
656 | } |
657 | |
658 | float *CGameTeams::GetCurrentTimeCp(CPlayer *Player) |
659 | { |
660 | if(!Player) |
661 | return NULL; |
662 | |
663 | CCharacter *pChar = Player->GetCharacter(); |
664 | if(pChar) |
665 | return pChar->m_aCurrentTimeCp; |
666 | return NULL; |
667 | } |
668 | |
669 | void CGameTeams::OnTeamFinish(CPlayer **Players, unsigned int Size, float Time, const char *pTimestamp) |
670 | { |
671 | int aPlayerCids[MAX_CLIENTS]; |
672 | |
673 | for(unsigned int i = 0; i < Size; i++) |
674 | { |
675 | aPlayerCids[i] = Players[i]->GetCid(); |
676 | |
677 | if(g_Config.m_SvRejoinTeam0 && g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && (m_Core.Team(ClientId: Players[i]->GetCid()) >= TEAM_SUPER || !m_aTeamLocked[m_Core.Team(ClientId: Players[i]->GetCid())])) |
678 | { |
679 | SetForceCharacterTeam(ClientId: Players[i]->GetCid(), Team: TEAM_FLOCK); |
680 | char aBuf[512]; |
681 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' joined team 0" , |
682 | GameServer()->Server()->ClientName(ClientId: Players[i]->GetCid())); |
683 | GameServer()->SendChat(ClientId: -1, Team: CGameContext::CHAT_ALL, pText: aBuf); |
684 | } |
685 | } |
686 | |
687 | if(Size >= (unsigned int)g_Config.m_SvMinTeamSize) |
688 | GameServer()->Score()->SaveTeamScore(pClientIds: aPlayerCids, Size, Time, pTimestamp); |
689 | } |
690 | |
691 | void CGameTeams::OnFinish(CPlayer *Player, float Time, const char *pTimestamp) |
692 | { |
693 | if(!Player || !Player->IsPlaying()) |
694 | return; |
695 | // TODO:DDRace:btd: this ugly |
696 | const int ClientId = Player->GetCid(); |
697 | CPlayerData *pData = GameServer()->Score()->PlayerData(Id: ClientId); |
698 | |
699 | char aBuf[128]; |
700 | SetLastTimeCp(Player, LastTimeCp: -1); |
701 | // Note that the "finished in" message is parsed by the client |
702 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
703 | format: "%s finished in: %d minute(s) %5.2f second(s)" , |
704 | Server()->ClientName(ClientId), (int)Time / 60, |
705 | Time - ((int)Time / 60 * 60)); |
706 | if(g_Config.m_SvHideScore || !g_Config.m_SvSaveWorseScores) |
707 | GameServer()->SendChatTarget(To: ClientId, pText: aBuf, Flags: CGameContext::CHAT_SIX); |
708 | else |
709 | GameServer()->SendChat(ClientId: -1, Team: CGameContext::CHAT_ALL, pText: aBuf, SpamProtectionClientId: -1., Flags: CGameContext::CHAT_SIX); |
710 | |
711 | float Diff = absolute(a: Time - pData->m_BestTime); |
712 | |
713 | if(Time - pData->m_BestTime < 0) |
714 | { |
715 | // new record \o/ |
716 | pData->m_RecordStopTick = Server()->Tick() + Server()->TickSpeed(); |
717 | pData->m_RecordFinishTime = Time; |
718 | |
719 | if(Diff >= 60) |
720 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "New record: %d minute(s) %5.2f second(s) better." , |
721 | (int)Diff / 60, Diff - ((int)Diff / 60 * 60)); |
722 | else |
723 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "New record: %5.2f second(s) better." , |
724 | Diff); |
725 | if(g_Config.m_SvHideScore || !g_Config.m_SvSaveWorseScores) |
726 | GameServer()->SendChatTarget(To: ClientId, pText: aBuf, Flags: CGameContext::CHAT_SIX); |
727 | else |
728 | GameServer()->SendChat(ClientId: -1, Team: CGameContext::CHAT_ALL, pText: aBuf, SpamProtectionClientId: -1, Flags: CGameContext::CHAT_SIX); |
729 | } |
730 | else if(pData->m_BestTime != 0) // tee has already finished? |
731 | { |
732 | Server()->StopRecord(ClientId); |
733 | |
734 | if(Diff <= 0.005f) |
735 | { |
736 | GameServer()->SendChatTarget(To: ClientId, |
737 | pText: "You finished with your best time." ); |
738 | } |
739 | else |
740 | { |
741 | if(Diff >= 60) |
742 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d minute(s) %5.2f second(s) worse, better luck next time." , |
743 | (int)Diff / 60, Diff - ((int)Diff / 60 * 60)); |
744 | else |
745 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
746 | format: "%5.2f second(s) worse, better luck next time." , |
747 | Diff); |
748 | GameServer()->SendChatTarget(To: ClientId, pText: aBuf, Flags: CGameContext::CHAT_SIX); // this is private, sent only to the tee |
749 | } |
750 | } |
751 | else |
752 | { |
753 | pData->m_RecordStopTick = Server()->Tick() + Server()->TickSpeed(); |
754 | pData->m_RecordFinishTime = Time; |
755 | } |
756 | |
757 | if(!Server()->IsSixup(ClientId)) |
758 | { |
759 | CNetMsg_Sv_DDRaceTime Msg; |
760 | CNetMsg_Sv_DDRaceTimeLegacy MsgLegacy; |
761 | MsgLegacy.m_Time = Msg.m_Time = (int)(Time * 100.0f); |
762 | MsgLegacy.m_Check = Msg.m_Check = 0; |
763 | MsgLegacy.m_Finish = Msg.m_Finish = 1; |
764 | |
765 | if(pData->m_BestTime) |
766 | { |
767 | float Diff100 = (Time - pData->m_BestTime) * 100; |
768 | MsgLegacy.m_Check = Msg.m_Check = (int)Diff100; |
769 | } |
770 | if(VERSION_DDRACE <= Player->GetClientVersion()) |
771 | { |
772 | if(Player->GetClientVersion() < VERSION_DDNET_MSG_LEGACY) |
773 | { |
774 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId); |
775 | } |
776 | else |
777 | { |
778 | Server()->SendPackMsg(pMsg: &MsgLegacy, Flags: MSGFLAG_VITAL, ClientId); |
779 | } |
780 | } |
781 | } |
782 | |
783 | CNetMsg_Sv_RaceFinish RaceFinishMsg; |
784 | RaceFinishMsg.m_ClientId = ClientId; |
785 | RaceFinishMsg.m_Time = Time * 1000; |
786 | RaceFinishMsg.m_Diff = 0; |
787 | if(pData->m_BestTime) |
788 | { |
789 | RaceFinishMsg.m_Diff = Diff * 1000 * (Time < pData->m_BestTime ? -1 : 1); |
790 | } |
791 | RaceFinishMsg.m_RecordPersonal = (Time < pData->m_BestTime || !pData->m_BestTime); |
792 | RaceFinishMsg.m_RecordServer = Time < GameServer()->m_pController->m_CurrentRecord; |
793 | Server()->SendPackMsg(pMsg: &RaceFinishMsg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId: -1); |
794 | |
795 | bool CallSaveScore = g_Config.m_SvSaveWorseScores; |
796 | bool NeedToSendNewPersonalRecord = false; |
797 | if(!pData->m_BestTime || Time < pData->m_BestTime) |
798 | { |
799 | // update the score |
800 | pData->Set(Time, aTimeCp: GetCurrentTimeCp(Player)); |
801 | CallSaveScore = true; |
802 | NeedToSendNewPersonalRecord = true; |
803 | } |
804 | |
805 | if(CallSaveScore) |
806 | if(g_Config.m_SvNamelessScore || !str_startswith(str: Server()->ClientName(ClientId), prefix: "nameless tee" )) |
807 | GameServer()->Score()->SaveScore(ClientId, Time, pTimestamp, |
808 | aTimeCp: GetCurrentTimeCp(Player), NotEligible: Player->m_NotEligibleForFinish); |
809 | |
810 | bool NeedToSendNewServerRecord = false; |
811 | // update server best time |
812 | if(GameServer()->m_pController->m_CurrentRecord == 0) |
813 | { |
814 | GameServer()->Score()->LoadBestTime(); |
815 | } |
816 | else if(Time < GameServer()->m_pController->m_CurrentRecord) |
817 | { |
818 | // check for nameless |
819 | if(g_Config.m_SvNamelessScore || !str_startswith(str: Server()->ClientName(ClientId), prefix: "nameless tee" )) |
820 | { |
821 | GameServer()->m_pController->m_CurrentRecord = Time; |
822 | NeedToSendNewServerRecord = true; |
823 | } |
824 | } |
825 | |
826 | SetDDRaceState(Player, DDRaceState: DDRACE_FINISHED); |
827 | if(NeedToSendNewServerRecord) |
828 | { |
829 | for(int i = 0; i < MAX_CLIENTS; i++) |
830 | { |
831 | if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->GetClientVersion() >= VERSION_DDRACE) |
832 | { |
833 | GameServer()->SendRecord(ClientId: i); |
834 | } |
835 | } |
836 | } |
837 | if(!NeedToSendNewServerRecord && NeedToSendNewPersonalRecord && Player->GetClientVersion() >= VERSION_DDRACE) |
838 | { |
839 | GameServer()->SendRecord(ClientId); |
840 | } |
841 | |
842 | int TTime = (int)Time; |
843 | if(!Player->m_Score.has_value() || TTime < Player->m_Score.value()) |
844 | { |
845 | Player->m_Score = TTime; |
846 | } |
847 | } |
848 | |
849 | void CGameTeams::RequestTeamSwap(CPlayer *pPlayer, CPlayer *pTargetPlayer, int Team) |
850 | { |
851 | if(!pPlayer || !pTargetPlayer) |
852 | return; |
853 | |
854 | char aBuf[512]; |
855 | if(pPlayer->m_SwapTargetsClientId == pTargetPlayer->GetCid()) |
856 | { |
857 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
858 | format: "You have already requested to swap with %s." , Server()->ClientName(ClientId: pTargetPlayer->GetCid())); |
859 | |
860 | GameServer()->SendChatTarget(To: pPlayer->GetCid(), pText: aBuf); |
861 | return; |
862 | } |
863 | |
864 | // Notification for the swap initiator |
865 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
866 | format: "You have requested to swap with %s." , |
867 | Server()->ClientName(ClientId: pTargetPlayer->GetCid())); |
868 | GameServer()->SendChatTarget(To: pPlayer->GetCid(), pText: aBuf); |
869 | |
870 | // Notification to the target swap player |
871 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
872 | format: "%s has requested to swap with you. To complete the swap process please wait %d seconds and then type /swap %s." , |
873 | Server()->ClientName(ClientId: pPlayer->GetCid()), g_Config.m_SvSaveSwapGamesDelay, Server()->ClientName(ClientId: pPlayer->GetCid())); |
874 | GameServer()->SendChatTarget(To: pTargetPlayer->GetCid(), pText: aBuf); |
875 | |
876 | // Notification for the remaining team |
877 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
878 | format: "%s has requested to swap with %s." , |
879 | Server()->ClientName(ClientId: pPlayer->GetCid()), Server()->ClientName(ClientId: pTargetPlayer->GetCid())); |
880 | // Do not send the team notification for team 0 |
881 | if(Team != 0) |
882 | { |
883 | for(int i = 0; i < MAX_CLIENTS; i++) |
884 | { |
885 | if(m_Core.Team(ClientId: i) == Team && i != pTargetPlayer->GetCid() && i != pPlayer->GetCid()) |
886 | { |
887 | GameServer()->SendChatTarget(To: i, pText: aBuf); |
888 | } |
889 | } |
890 | } |
891 | |
892 | pPlayer->m_SwapTargetsClientId = pTargetPlayer->GetCid(); |
893 | m_aLastSwap[pPlayer->GetCid()] = Server()->Tick(); |
894 | } |
895 | |
896 | void CGameTeams::SwapTeamCharacters(CPlayer *pPrimaryPlayer, CPlayer *pTargetPlayer, int Team) |
897 | { |
898 | if(!pPrimaryPlayer || !pTargetPlayer) |
899 | return; |
900 | |
901 | char aBuf[128]; |
902 | |
903 | int Since = (Server()->Tick() - m_aLastSwap[pTargetPlayer->GetCid()]) / Server()->TickSpeed(); |
904 | if(Since < g_Config.m_SvSaveSwapGamesDelay) |
905 | { |
906 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
907 | format: "You have to wait %d seconds until you can swap." , |
908 | g_Config.m_SvSaveSwapGamesDelay - Since); |
909 | |
910 | GameServer()->SendChatTarget(To: pPrimaryPlayer->GetCid(), pText: aBuf); |
911 | |
912 | return; |
913 | } |
914 | |
915 | pPrimaryPlayer->m_SwapTargetsClientId = -1; |
916 | pTargetPlayer->m_SwapTargetsClientId = -1; |
917 | |
918 | int TimeoutAfterDelay = g_Config.m_SvSaveSwapGamesDelay + g_Config.m_SvSwapTimeout; |
919 | if(Since >= TimeoutAfterDelay) |
920 | { |
921 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
922 | format: "Your swap request timed out %d seconds ago. Use /swap again to re-initiate it." , |
923 | Since - g_Config.m_SvSwapTimeout); |
924 | |
925 | GameServer()->SendChatTarget(To: pPrimaryPlayer->GetCid(), pText: aBuf); |
926 | |
927 | return; |
928 | } |
929 | |
930 | CSaveTee PrimarySavedTee; |
931 | PrimarySavedTee.Save(pchr: pPrimaryPlayer->GetCharacter()); |
932 | |
933 | CSaveTee SecondarySavedTee; |
934 | SecondarySavedTee.Save(pchr: pTargetPlayer->GetCharacter()); |
935 | |
936 | PrimarySavedTee.Load(pchr: pTargetPlayer->GetCharacter(), Team, IsSwap: true); |
937 | SecondarySavedTee.Load(pchr: pPrimaryPlayer->GetCharacter(), Team, IsSwap: true); |
938 | |
939 | if(Team >= 1 && !m_aTeamFlock[Team]) |
940 | { |
941 | for(const auto &pPlayer : GameServer()->m_apPlayers) |
942 | { |
943 | CCharacter *pChar = pPlayer ? pPlayer->GetCharacter() : nullptr; |
944 | if(pChar && pChar->Team() == Team && pChar != pPrimaryPlayer->GetCharacter() && pChar != pTargetPlayer->GetCharacter()) |
945 | pChar->m_StartTime = pPrimaryPlayer->GetCharacter()->m_StartTime; |
946 | } |
947 | } |
948 | std::swap(a&: m_aTeeStarted[pPrimaryPlayer->GetCid()], b&: m_aTeeStarted[pTargetPlayer->GetCid()]); |
949 | std::swap(a&: m_aTeeFinished[pPrimaryPlayer->GetCid()], b&: m_aTeeFinished[pTargetPlayer->GetCid()]); |
950 | std::swap(a&: pPrimaryPlayer->GetCharacter()->GetRescueTeeRef(), b&: pTargetPlayer->GetCharacter()->GetRescueTeeRef()); |
951 | |
952 | GameServer()->m_World.SwapClients(Client1: pPrimaryPlayer->GetCid(), Client2: pTargetPlayer->GetCid()); |
953 | |
954 | if(GameServer()->TeeHistorianActive()) |
955 | { |
956 | GameServer()->TeeHistorian()->RecordPlayerSwap(ClientId1: pPrimaryPlayer->GetCid(), ClientId2: pTargetPlayer->GetCid()); |
957 | } |
958 | |
959 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
960 | format: "%s has swapped with %s." , |
961 | Server()->ClientName(ClientId: pPrimaryPlayer->GetCid()), Server()->ClientName(ClientId: pTargetPlayer->GetCid())); |
962 | |
963 | GameServer()->SendChatTeam(Team, pText: aBuf); |
964 | } |
965 | |
966 | void CGameTeams::ProcessSaveTeam() |
967 | { |
968 | for(int Team = 0; Team < NUM_TEAMS; Team++) |
969 | { |
970 | if(m_apSaveTeamResult[Team] == nullptr || !m_apSaveTeamResult[Team]->m_Completed) |
971 | continue; |
972 | if(m_apSaveTeamResult[Team]->m_aBroadcast[0] != '\0') |
973 | GameServer()->SendBroadcast(pText: m_apSaveTeamResult[Team]->m_aBroadcast, ClientId: -1); |
974 | if(m_apSaveTeamResult[Team]->m_aMessage[0] != '\0' && m_apSaveTeamResult[Team]->m_Status != CScoreSaveResult::LOAD_FAILED) |
975 | GameServer()->SendChatTeam(Team, pText: m_apSaveTeamResult[Team]->m_aMessage); |
976 | switch(m_apSaveTeamResult[Team]->m_Status) |
977 | { |
978 | case CScoreSaveResult::SAVE_SUCCESS: |
979 | { |
980 | if(GameServer()->TeeHistorianActive()) |
981 | { |
982 | GameServer()->TeeHistorian()->RecordTeamSaveSuccess( |
983 | Team, |
984 | SaveId: m_apSaveTeamResult[Team]->m_SaveId, |
985 | pTeamSave: m_apSaveTeamResult[Team]->m_SavedTeam.GetString()); |
986 | } |
987 | for(int i = 0; i < m_apSaveTeamResult[Team]->m_SavedTeam.GetMembersCount(); i++) |
988 | { |
989 | if(m_apSaveTeamResult[Team]->m_SavedTeam.m_pSavedTees->IsHooking()) |
990 | { |
991 | int ClientId = m_apSaveTeamResult[Team]->m_SavedTeam.m_pSavedTees->GetClientId(); |
992 | if(GameServer()->m_apPlayers[ClientId] != nullptr) |
993 | GameServer()->SendChatTarget(To: ClientId, pText: "Start holding the hook before loading the savegame to keep the hook" ); |
994 | } |
995 | } |
996 | ResetSavedTeam(ClientId: m_apSaveTeamResult[Team]->m_RequestingPlayer, Team); |
997 | char aSaveId[UUID_MAXSTRSIZE]; |
998 | FormatUuid(Uuid: m_apSaveTeamResult[Team]->m_SaveId, pBuffer: aSaveId, BufferLength: UUID_MAXSTRSIZE); |
999 | dbg_msg(sys: "save" , fmt: "Save successful: %s" , aSaveId); |
1000 | break; |
1001 | } |
1002 | case CScoreSaveResult::SAVE_FAILED: |
1003 | if(GameServer()->TeeHistorianActive()) |
1004 | GameServer()->TeeHistorian()->RecordTeamSaveFailure(Team); |
1005 | if(Count(Team) > 0) |
1006 | { |
1007 | // load weak/strong order to prevent switching weak/strong while saving |
1008 | m_apSaveTeamResult[Team]->m_SavedTeam.Load(pGameServer: GameServer(), Team, KeepCurrentWeakStrong: false); |
1009 | } |
1010 | break; |
1011 | case CScoreSaveResult::LOAD_SUCCESS: |
1012 | { |
1013 | if(GameServer()->TeeHistorianActive()) |
1014 | { |
1015 | GameServer()->TeeHistorian()->RecordTeamLoadSuccess( |
1016 | Team, |
1017 | SaveId: m_apSaveTeamResult[Team]->m_SaveId, |
1018 | pTeamSave: m_apSaveTeamResult[Team]->m_SavedTeam.GetString()); |
1019 | } |
1020 | if(Count(Team) > 0) |
1021 | { |
1022 | // keep current weak/strong order as on some maps there is no other way of switching |
1023 | m_apSaveTeamResult[Team]->m_SavedTeam.Load(pGameServer: GameServer(), Team, KeepCurrentWeakStrong: true); |
1024 | } |
1025 | char aSaveId[UUID_MAXSTRSIZE]; |
1026 | FormatUuid(Uuid: m_apSaveTeamResult[Team]->m_SaveId, pBuffer: aSaveId, BufferLength: UUID_MAXSTRSIZE); |
1027 | dbg_msg(sys: "save" , fmt: "Load successful: %s" , aSaveId); |
1028 | break; |
1029 | } |
1030 | case CScoreSaveResult::LOAD_FAILED: |
1031 | if(GameServer()->TeeHistorianActive()) |
1032 | GameServer()->TeeHistorian()->RecordTeamLoadFailure(Team); |
1033 | if(m_apSaveTeamResult[Team]->m_aMessage[0] != '\0') |
1034 | GameServer()->SendChatTarget(To: m_apSaveTeamResult[Team]->m_RequestingPlayer, pText: m_apSaveTeamResult[Team]->m_aMessage); |
1035 | break; |
1036 | } |
1037 | m_apSaveTeamResult[Team] = nullptr; |
1038 | } |
1039 | } |
1040 | |
1041 | void CGameTeams::OnCharacterSpawn(int ClientId) |
1042 | { |
1043 | m_Core.SetSolo(ClientId, Value: false); |
1044 | int Team = m_Core.Team(ClientId); |
1045 | |
1046 | if(GetSaving(TeamId: Team)) |
1047 | return; |
1048 | |
1049 | if(m_Core.Team(ClientId) >= TEAM_SUPER || !m_aTeamLocked[Team]) |
1050 | { |
1051 | if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO) |
1052 | SetForceCharacterTeam(ClientId, Team: TEAM_FLOCK); |
1053 | else |
1054 | SetForceCharacterTeam(ClientId, Team: ClientId); // initialize team |
1055 | if(!m_aTeamFlock[Team]) |
1056 | CheckTeamFinished(Team); |
1057 | } |
1058 | } |
1059 | |
1060 | void CGameTeams::OnCharacterDeath(int ClientId, int Weapon) |
1061 | { |
1062 | m_Core.SetSolo(ClientId, Value: false); |
1063 | |
1064 | int Team = m_Core.Team(ClientId); |
1065 | if(GetSaving(TeamId: Team)) |
1066 | return; |
1067 | bool Locked = TeamLocked(Team) && Weapon != WEAPON_GAME; |
1068 | |
1069 | if(g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO && Team != TEAM_SUPER) |
1070 | { |
1071 | ChangeTeamState(Team, State: CGameTeams::TEAMSTATE_OPEN); |
1072 | if(m_aPractice[Team]) |
1073 | { |
1074 | if(Weapon != WEAPON_WORLD) |
1075 | { |
1076 | ResetRoundState(Team); |
1077 | } |
1078 | else |
1079 | { |
1080 | GameServer()->SendChatTeam(Team, pText: "You died, but will stay in practice until you use kill." ); |
1081 | } |
1082 | } |
1083 | else |
1084 | { |
1085 | ResetRoundState(Team); |
1086 | } |
1087 | } |
1088 | else if(Locked) |
1089 | { |
1090 | SetForceCharacterTeam(ClientId, Team); |
1091 | |
1092 | if(GetTeamState(Team) != TEAMSTATE_OPEN && !m_aTeamFlock[m_Core.Team(ClientId)]) |
1093 | { |
1094 | ChangeTeamState(Team, State: CGameTeams::TEAMSTATE_OPEN); |
1095 | |
1096 | m_aPractice[Team] = false; |
1097 | |
1098 | if(Count(Team) > 1) |
1099 | { |
1100 | KillTeam(Team, NewStrongId: Weapon == WEAPON_SELF ? ClientId : -1, ExceptId: ClientId); |
1101 | |
1102 | char aBuf[512]; |
1103 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Everyone in your locked team was killed because '%s' %s." , Server()->ClientName(ClientId), Weapon == WEAPON_SELF ? "killed" : "died" ); |
1104 | |
1105 | GameServer()->SendChatTeam(Team, pText: aBuf); |
1106 | } |
1107 | } |
1108 | } |
1109 | else |
1110 | { |
1111 | if(m_aTeamState[m_Core.Team(ClientId)] == CGameTeams::TEAMSTATE_STARTED && !m_aTeeStarted[ClientId] && !m_aTeamFlock[m_Core.Team(ClientId)]) |
1112 | { |
1113 | char aBuf[128]; |
1114 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "This team cannot finish anymore because '%s' left the team before hitting the start" , Server()->ClientName(ClientId)); |
1115 | GameServer()->SendChatTeam(Team, pText: aBuf); |
1116 | GameServer()->SendChatTeam(Team, pText: "Enter /practice mode or restart to avoid the entire team being killed in 60 seconds" ); |
1117 | |
1118 | m_aTeamUnfinishableKillTick[Team] = Server()->Tick() + 60 * Server()->TickSpeed(); |
1119 | ChangeTeamState(Team, State: CGameTeams::TEAMSTATE_STARTED_UNFINISHABLE); |
1120 | } |
1121 | SetForceCharacterTeam(ClientId, Team: TEAM_FLOCK); |
1122 | if(!m_aTeamFlock[m_Core.Team(ClientId)]) |
1123 | CheckTeamFinished(Team); |
1124 | } |
1125 | } |
1126 | |
1127 | void CGameTeams::SetTeamLock(int Team, bool Lock) |
1128 | { |
1129 | if(Team > TEAM_FLOCK && Team < TEAM_SUPER) |
1130 | m_aTeamLocked[Team] = Lock; |
1131 | } |
1132 | |
1133 | void CGameTeams::SetTeamFlock(int Team, bool Mode) |
1134 | { |
1135 | if(Team > TEAM_FLOCK && Team < TEAM_SUPER) |
1136 | m_aTeamFlock[Team] = Mode; |
1137 | } |
1138 | |
1139 | void CGameTeams::ResetInvited(int Team) |
1140 | { |
1141 | m_aInvited[Team].reset(); |
1142 | } |
1143 | |
1144 | void CGameTeams::SetClientInvited(int Team, int ClientId, bool Invited) |
1145 | { |
1146 | if(Team > TEAM_FLOCK && Team < TEAM_SUPER) |
1147 | { |
1148 | if(Invited) |
1149 | m_aInvited[Team].set(position: ClientId); |
1150 | else |
1151 | m_aInvited[Team].reset(position: ClientId); |
1152 | } |
1153 | } |
1154 | |
1155 | void CGameTeams::KillSavedTeam(int ClientId, int Team) |
1156 | { |
1157 | if(g_Config.m_SvSoloServer || !g_Config.m_SvTeam) |
1158 | { |
1159 | GameServer()->m_apPlayers[ClientId]->KillCharacter(Weapon: WEAPON_SELF, SendKillMsg: true); |
1160 | } |
1161 | else |
1162 | { |
1163 | KillTeam(Team, NewStrongId: -1); |
1164 | } |
1165 | } |
1166 | |
1167 | void CGameTeams::ResetSavedTeam(int ClientId, int Team) |
1168 | { |
1169 | if(g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO) |
1170 | { |
1171 | ChangeTeamState(Team, State: CGameTeams::TEAMSTATE_OPEN); |
1172 | ResetRoundState(Team); |
1173 | } |
1174 | else |
1175 | { |
1176 | for(int i = 0; i < MAX_CLIENTS; i++) |
1177 | { |
1178 | if(m_Core.Team(ClientId: i) == Team && GameServer()->m_apPlayers[i]) |
1179 | { |
1180 | SetForceCharacterTeam(ClientId: i, Team: TEAM_FLOCK); |
1181 | } |
1182 | } |
1183 | } |
1184 | } |
1185 | |
1186 | int CGameTeams::GetFirstEmptyTeam() const |
1187 | { |
1188 | for(int i = 1; i < MAX_CLIENTS; i++) |
1189 | if(m_aTeamState[i] == TEAMSTATE_EMPTY) |
1190 | return i; |
1191 | return -1; |
1192 | } |
1193 | |