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_DDRACE_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 | int TimeTicks = Server()->Tick() - GetStartTime(Player: pPlayer); |
197 | if(TimeTicks <= 0) |
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, TimeTicks, 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 | int TimeTicks = Server()->Tick() - GetStartTime(Player: apTeamPlayers[0]); |
339 | float Time = (float)TimeTicks / (float)Server()->TickSpeed(); |
340 | if(TimeTicks <= 0) |
341 | { |
342 | return; |
343 | } |
344 | |
345 | if(m_aPractice[Team]) |
346 | { |
347 | ChangeTeamState(Team, State: TEAMSTATE_FINISHED); |
348 | |
349 | char aBuf[256]; |
350 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
351 | 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." , |
352 | (int)Time / 50 / 60, Time - ((int)Time / 60 * 60)); |
353 | GameServer()->SendChatTeam(Team, pText: aBuf); |
354 | |
355 | for(unsigned int i = 0; i < PlayersCount; ++i) |
356 | { |
357 | SetDDRaceState(Player: apTeamPlayers[i], DDRaceState: DDRACE_FINISHED); |
358 | } |
359 | |
360 | return; |
361 | } |
362 | |
363 | char aTimestamp[TIMESTAMP_STR_LENGTH]; |
364 | str_timestamp_format(buffer: aTimestamp, buffer_size: sizeof(aTimestamp), FORMAT_SPACE); // 2019-04-02 19:41:58 |
365 | |
366 | for(unsigned int i = 0; i < PlayersCount; ++i) |
367 | OnFinish(Player: apTeamPlayers[i], TimeTicks, pTimestamp: aTimestamp); |
368 | ChangeTeamState(Team, State: TEAMSTATE_FINISHED); // TODO: Make it better |
369 | OnTeamFinish(Team, Players: apTeamPlayers, Size: PlayersCount, TimeTicks, pTimestamp: aTimestamp); |
370 | } |
371 | } |
372 | } |
373 | |
374 | const char *CGameTeams::SetCharacterTeam(int ClientId, int Team) |
375 | { |
376 | if(ClientId < 0 || ClientId >= MAX_CLIENTS) |
377 | return "Invalid client ID" ; |
378 | if(Team < 0 || Team >= MAX_CLIENTS + 1) |
379 | return "Invalid team number" ; |
380 | if(Team != TEAM_SUPER && m_aTeamState[Team] > TEAMSTATE_OPEN && !m_aPractice[Team] && !m_aTeamFlock[Team]) |
381 | return "This team started already" ; |
382 | if(m_Core.Team(ClientId) == Team) |
383 | return "You are in this team already" ; |
384 | if(!Character(ClientId)) |
385 | return "Your character is not valid" ; |
386 | if(Team == TEAM_SUPER && !Character(ClientId)->IsSuper()) |
387 | return "You can't join super team if you don't have super rights" ; |
388 | if(Team != TEAM_SUPER && Character(ClientId)->m_DDRaceState != DDRACE_NONE) |
389 | return "You have started racing already" ; |
390 | // No cheating through noob filter with practice and then leaving team |
391 | if(m_aPractice[m_Core.Team(ClientId)]) |
392 | return "You have used practice mode already" ; |
393 | |
394 | // you can not join a team which is currently in the process of saving, |
395 | // because the save-process can fail and then the team is reset into the game |
396 | if(Team != TEAM_SUPER && GetSaving(TeamId: Team)) |
397 | return "Your team is currently saving" ; |
398 | if(m_Core.Team(ClientId) != TEAM_SUPER && GetSaving(TeamId: m_Core.Team(ClientId))) |
399 | return "This team is currently saving" ; |
400 | |
401 | SetForceCharacterTeam(ClientId, Team); |
402 | return nullptr; |
403 | } |
404 | |
405 | void CGameTeams::SetForceCharacterTeam(int ClientId, int Team) |
406 | { |
407 | m_aTeeStarted[ClientId] = false; |
408 | m_aTeeFinished[ClientId] = false; |
409 | int OldTeam = m_Core.Team(ClientId); |
410 | |
411 | if(Team != OldTeam && (OldTeam != TEAM_FLOCK || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO) && OldTeam != TEAM_SUPER && m_aTeamState[OldTeam] != TEAMSTATE_EMPTY) |
412 | { |
413 | bool NoElseInOldTeam = Count(Team: OldTeam) <= 1; |
414 | if(NoElseInOldTeam) |
415 | { |
416 | m_aTeamState[OldTeam] = TEAMSTATE_EMPTY; |
417 | |
418 | // unlock team when last player leaves |
419 | SetTeamLock(Team: OldTeam, Lock: false); |
420 | SetTeamFlock(Team: OldTeam, Mode: false); |
421 | ResetRoundState(Team: OldTeam); |
422 | // do not reset SaveTeamResult, because it should be logged into teehistorian even if the team leaves |
423 | } |
424 | } |
425 | |
426 | m_Core.Team(ClientId, Team); |
427 | |
428 | if(OldTeam != Team) |
429 | { |
430 | for(int LoopClientId = 0; LoopClientId < MAX_CLIENTS; ++LoopClientId) |
431 | if(GetPlayer(ClientId: LoopClientId)) |
432 | SendTeamsState(ClientId: LoopClientId); |
433 | |
434 | if(GetPlayer(ClientId)) |
435 | { |
436 | GetPlayer(ClientId)->m_VotedForPractice = false; |
437 | GetPlayer(ClientId)->m_SwapTargetsClientId = -1; |
438 | } |
439 | m_pGameContext->m_World.RemoveEntitiesFromPlayer(PlayerId: ClientId); |
440 | } |
441 | |
442 | if(Team != TEAM_SUPER && (m_aTeamState[Team] == TEAMSTATE_EMPTY || (m_aTeamLocked[Team] && !m_aTeamFlock[Team]))) |
443 | { |
444 | if(!m_aTeamLocked[Team]) |
445 | ChangeTeamState(Team, State: TEAMSTATE_OPEN); |
446 | |
447 | ResetSwitchers(Team); |
448 | } |
449 | } |
450 | |
451 | int CGameTeams::Count(int Team) const |
452 | { |
453 | if(Team == TEAM_SUPER) |
454 | return -1; |
455 | |
456 | int Count = 0; |
457 | |
458 | for(int i = 0; i < MAX_CLIENTS; ++i) |
459 | if(m_Core.Team(ClientId: i) == Team) |
460 | Count++; |
461 | |
462 | return Count; |
463 | } |
464 | |
465 | void CGameTeams::ChangeTeamState(int Team, int State) |
466 | { |
467 | m_aTeamState[Team] = State; |
468 | } |
469 | |
470 | void CGameTeams::KillTeam(int Team, int NewStrongId, int ExceptId) |
471 | { |
472 | for(int i = 0; i < MAX_CLIENTS; i++) |
473 | { |
474 | if(m_Core.Team(ClientId: i) == Team && GameServer()->m_apPlayers[i]) |
475 | { |
476 | GameServer()->m_apPlayers[i]->m_VotedForPractice = false; |
477 | if(i != ExceptId) |
478 | { |
479 | GameServer()->m_apPlayers[i]->KillCharacter(Weapon: WEAPON_SELF, SendKillMsg: false); |
480 | if(NewStrongId != -1 && i != NewStrongId) |
481 | { |
482 | GameServer()->m_apPlayers[i]->Respawn(WeakHook: true); // spawn the rest of team with weak hook on the killer |
483 | } |
484 | } |
485 | } |
486 | } |
487 | |
488 | // send the team kill message |
489 | CNetMsg_Sv_KillMsgTeam Msg; |
490 | Msg.m_Team = Team; |
491 | Msg.m_First = NewStrongId; |
492 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId: -1); |
493 | } |
494 | |
495 | bool CGameTeams::TeamFinished(int Team) |
496 | { |
497 | if(m_aTeamState[Team] != TEAMSTATE_STARTED) |
498 | { |
499 | return false; |
500 | } |
501 | for(int i = 0; i < MAX_CLIENTS; ++i) |
502 | if(m_Core.Team(ClientId: i) == Team && !m_aTeeFinished[i]) |
503 | return false; |
504 | return true; |
505 | } |
506 | |
507 | CClientMask CGameTeams::TeamMask(int Team, int ExceptId, int Asker) |
508 | { |
509 | if(Team == TEAM_SUPER) |
510 | { |
511 | if(ExceptId == -1) |
512 | return CClientMask().set(); |
513 | return CClientMask().set().reset(position: ExceptId); |
514 | } |
515 | |
516 | CClientMask Mask; |
517 | for(int i = 0; i < MAX_CLIENTS; ++i) |
518 | { |
519 | if(i == ExceptId) |
520 | continue; // Explicitly excluded |
521 | if(!GetPlayer(ClientId: i)) |
522 | continue; // Player doesn't exist |
523 | |
524 | if(!(GetPlayer(ClientId: i)->GetTeam() == TEAM_SPECTATORS || GetPlayer(ClientId: i)->IsPaused())) |
525 | { // Not spectator |
526 | if(i != Asker) |
527 | { // Actions of other players |
528 | if(!Character(ClientId: i)) |
529 | continue; // Player is currently dead |
530 | if(GetPlayer(ClientId: i)->m_ShowOthers == SHOW_OTHERS_ONLY_TEAM) |
531 | { |
532 | if(m_Core.Team(ClientId: i) != Team && m_Core.Team(ClientId: i) != TEAM_SUPER) |
533 | continue; // In different teams |
534 | } |
535 | else if(GetPlayer(ClientId: i)->m_ShowOthers == SHOW_OTHERS_OFF) |
536 | { |
537 | if(m_Core.GetSolo(ClientId: Asker)) |
538 | continue; // When in solo part don't show others |
539 | if(m_Core.GetSolo(ClientId: i)) |
540 | continue; // When in solo part don't show others |
541 | if(m_Core.Team(ClientId: i) != Team && m_Core.Team(ClientId: i) != TEAM_SUPER) |
542 | continue; // In different teams |
543 | } |
544 | } // See everything of yourself |
545 | } |
546 | else if(GetPlayer(ClientId: i)->m_SpectatorId != SPEC_FREEVIEW) |
547 | { // Spectating specific player |
548 | if(GetPlayer(ClientId: i)->m_SpectatorId != Asker) |
549 | { // Actions of other players |
550 | if(!Character(ClientId: GetPlayer(ClientId: i)->m_SpectatorId)) |
551 | continue; // Player is currently dead |
552 | if(GetPlayer(ClientId: i)->m_ShowOthers == SHOW_OTHERS_ONLY_TEAM) |
553 | { |
554 | if(m_Core.Team(ClientId: GetPlayer(ClientId: i)->m_SpectatorId) != Team && m_Core.Team(ClientId: GetPlayer(ClientId: i)->m_SpectatorId) != TEAM_SUPER) |
555 | continue; // In different teams |
556 | } |
557 | else if(GetPlayer(ClientId: i)->m_ShowOthers == SHOW_OTHERS_OFF) |
558 | { |
559 | if(m_Core.GetSolo(ClientId: Asker)) |
560 | continue; // When in solo part don't show others |
561 | if(m_Core.GetSolo(ClientId: GetPlayer(ClientId: i)->m_SpectatorId)) |
562 | continue; // When in solo part don't show others |
563 | if(m_Core.Team(ClientId: GetPlayer(ClientId: i)->m_SpectatorId) != Team && m_Core.Team(ClientId: GetPlayer(ClientId: i)->m_SpectatorId) != TEAM_SUPER) |
564 | continue; // In different teams |
565 | } |
566 | } // See everything of player you're spectating |
567 | } |
568 | else |
569 | { // Freeview |
570 | if(GetPlayer(ClientId: i)->m_SpecTeam) |
571 | { // Show only players in own team when spectating |
572 | if(m_Core.Team(ClientId: i) != Team && m_Core.Team(ClientId: i) != TEAM_SUPER) |
573 | continue; // in different teams |
574 | } |
575 | } |
576 | |
577 | Mask.set(position: i); |
578 | } |
579 | return Mask; |
580 | } |
581 | |
582 | void CGameTeams::SendTeamsState(int ClientId) |
583 | { |
584 | if(g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO) |
585 | return; |
586 | |
587 | if(!m_pGameContext->m_apPlayers[ClientId]) |
588 | return; |
589 | |
590 | CMsgPacker Msg(NETMSGTYPE_SV_TEAMSSTATE); |
591 | CMsgPacker MsgLegacy(NETMSGTYPE_SV_TEAMSSTATELEGACY); |
592 | |
593 | for(unsigned i = 0; i < MAX_CLIENTS; i++) |
594 | { |
595 | Msg.AddInt(i: m_Core.Team(ClientId: i)); |
596 | MsgLegacy.AddInt(i: m_Core.Team(ClientId: i)); |
597 | } |
598 | |
599 | Server()->SendMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId); |
600 | int ClientVersion = m_pGameContext->m_apPlayers[ClientId]->GetClientVersion(); |
601 | if(!Server()->IsSixup(ClientId) && VERSION_DDRACE < ClientVersion && ClientVersion < VERSION_DDNET_MSG_LEGACY) |
602 | { |
603 | Server()->SendMsg(pMsg: &MsgLegacy, Flags: MSGFLAG_VITAL, ClientId); |
604 | } |
605 | } |
606 | |
607 | int CGameTeams::GetDDRaceState(CPlayer *Player) |
608 | { |
609 | if(!Player) |
610 | return DDRACE_NONE; |
611 | |
612 | CCharacter *pChar = Player->GetCharacter(); |
613 | if(pChar) |
614 | return pChar->m_DDRaceState; |
615 | return DDRACE_NONE; |
616 | } |
617 | |
618 | void CGameTeams::SetDDRaceState(CPlayer *Player, int DDRaceState) |
619 | { |
620 | if(!Player) |
621 | return; |
622 | |
623 | CCharacter *pChar = Player->GetCharacter(); |
624 | if(pChar) |
625 | pChar->m_DDRaceState = DDRaceState; |
626 | } |
627 | |
628 | int CGameTeams::GetStartTime(CPlayer *Player) |
629 | { |
630 | if(!Player) |
631 | return 0; |
632 | |
633 | CCharacter *pChar = Player->GetCharacter(); |
634 | if(pChar) |
635 | return pChar->m_StartTime; |
636 | return 0; |
637 | } |
638 | |
639 | void CGameTeams::SetStartTime(CPlayer *Player, int StartTime) |
640 | { |
641 | if(!Player) |
642 | return; |
643 | |
644 | CCharacter *pChar = Player->GetCharacter(); |
645 | if(pChar) |
646 | pChar->m_StartTime = StartTime; |
647 | } |
648 | |
649 | void CGameTeams::SetLastTimeCp(CPlayer *Player, int LastTimeCp) |
650 | { |
651 | if(!Player) |
652 | return; |
653 | |
654 | CCharacter *pChar = Player->GetCharacter(); |
655 | if(pChar) |
656 | pChar->m_LastTimeCp = LastTimeCp; |
657 | } |
658 | |
659 | float *CGameTeams::GetCurrentTimeCp(CPlayer *Player) |
660 | { |
661 | if(!Player) |
662 | return NULL; |
663 | |
664 | CCharacter *pChar = Player->GetCharacter(); |
665 | if(pChar) |
666 | return pChar->m_aCurrentTimeCp; |
667 | return NULL; |
668 | } |
669 | |
670 | void CGameTeams::OnTeamFinish(int Team, CPlayer **Players, unsigned int Size, int TimeTicks, const char *pTimestamp) |
671 | { |
672 | int aPlayerCids[MAX_CLIENTS]; |
673 | |
674 | for(unsigned int i = 0; i < Size; i++) |
675 | { |
676 | aPlayerCids[i] = Players[i]->GetCid(); |
677 | |
678 | 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())])) |
679 | { |
680 | SetForceCharacterTeam(ClientId: Players[i]->GetCid(), Team: TEAM_FLOCK); |
681 | char aBuf[512]; |
682 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "'%s' joined team 0" , |
683 | GameServer()->Server()->ClientName(ClientId: Players[i]->GetCid())); |
684 | GameServer()->SendChat(ClientId: -1, Team: TEAM_ALL, pText: aBuf); |
685 | } |
686 | } |
687 | |
688 | if(Size >= (unsigned int)g_Config.m_SvMinTeamSize) |
689 | GameServer()->Score()->SaveTeamScore(Team, pClientIds: aPlayerCids, Size, TimeTicks, pTimestamp); |
690 | } |
691 | |
692 | void CGameTeams::OnFinish(CPlayer *Player, int TimeTicks, const char *pTimestamp) |
693 | { |
694 | if(!Player || !Player->IsPlaying()) |
695 | return; |
696 | |
697 | float Time = TimeTicks / (float)Server()->TickSpeed(); |
698 | |
699 | // TODO:DDRace:btd: this ugly |
700 | const int ClientId = Player->GetCid(); |
701 | CPlayerData *pData = GameServer()->Score()->PlayerData(Id: ClientId); |
702 | |
703 | char aBuf[128]; |
704 | SetLastTimeCp(Player, LastTimeCp: -1); |
705 | // Note that the "finished in" message is parsed by the client |
706 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
707 | format: "%s finished in: %d minute(s) %5.2f second(s)" , |
708 | Server()->ClientName(ClientId), (int)Time / 60, |
709 | Time - ((int)Time / 60 * 60)); |
710 | if(g_Config.m_SvHideScore || !g_Config.m_SvSaveWorseScores) |
711 | GameServer()->SendChatTarget(To: ClientId, pText: aBuf, Flags: CGameContext::CHAT_SIX); |
712 | else |
713 | GameServer()->SendChat(ClientId: -1, Team: TEAM_ALL, pText: aBuf, SpamProtectionClientId: -1., Flags: CGameContext::CHAT_SIX); |
714 | |
715 | float Diff = absolute(a: Time - pData->m_BestTime); |
716 | |
717 | if(Time - pData->m_BestTime < 0) |
718 | { |
719 | // new record \o/ |
720 | pData->m_RecordStopTick = Server()->Tick() + Server()->TickSpeed(); |
721 | pData->m_RecordFinishTime = Time; |
722 | |
723 | if(Diff >= 60) |
724 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "New record: %d minute(s) %5.2f second(s) better." , |
725 | (int)Diff / 60, Diff - ((int)Diff / 60 * 60)); |
726 | else |
727 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "New record: %5.2f second(s) better." , |
728 | Diff); |
729 | if(g_Config.m_SvHideScore || !g_Config.m_SvSaveWorseScores) |
730 | GameServer()->SendChatTarget(To: ClientId, pText: aBuf, Flags: CGameContext::CHAT_SIX); |
731 | else |
732 | GameServer()->SendChat(ClientId: -1, Team: TEAM_ALL, pText: aBuf, SpamProtectionClientId: -1, Flags: CGameContext::CHAT_SIX); |
733 | } |
734 | else if(pData->m_BestTime != 0) // tee has already finished? |
735 | { |
736 | Server()->StopRecord(ClientId); |
737 | |
738 | if(Diff <= 0.005f) |
739 | { |
740 | GameServer()->SendChatTarget(To: ClientId, |
741 | pText: "You finished with your best time." ); |
742 | } |
743 | else |
744 | { |
745 | if(Diff >= 60) |
746 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d minute(s) %5.2f second(s) worse, better luck next time." , |
747 | (int)Diff / 60, Diff - ((int)Diff / 60 * 60)); |
748 | else |
749 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
750 | format: "%5.2f second(s) worse, better luck next time." , |
751 | Diff); |
752 | GameServer()->SendChatTarget(To: ClientId, pText: aBuf, Flags: CGameContext::CHAT_SIX); // this is private, sent only to the tee |
753 | } |
754 | } |
755 | else |
756 | { |
757 | pData->m_RecordStopTick = Server()->Tick() + Server()->TickSpeed(); |
758 | pData->m_RecordFinishTime = Time; |
759 | } |
760 | |
761 | if(!Server()->IsSixup(ClientId)) |
762 | { |
763 | CNetMsg_Sv_DDRaceTime Msg; |
764 | CNetMsg_Sv_DDRaceTimeLegacy MsgLegacy; |
765 | MsgLegacy.m_Time = Msg.m_Time = (int)(Time * 100.0f); |
766 | MsgLegacy.m_Check = Msg.m_Check = 0; |
767 | MsgLegacy.m_Finish = Msg.m_Finish = 1; |
768 | |
769 | if(pData->m_BestTime) |
770 | { |
771 | float Diff100 = (Time - pData->m_BestTime) * 100; |
772 | MsgLegacy.m_Check = Msg.m_Check = (int)Diff100; |
773 | } |
774 | if(VERSION_DDRACE <= Player->GetClientVersion()) |
775 | { |
776 | if(Player->GetClientVersion() < VERSION_DDNET_MSG_LEGACY) |
777 | { |
778 | Server()->SendPackMsg(pMsg: &Msg, Flags: MSGFLAG_VITAL, ClientId); |
779 | } |
780 | else |
781 | { |
782 | Server()->SendPackMsg(pMsg: &MsgLegacy, Flags: MSGFLAG_VITAL, ClientId); |
783 | } |
784 | } |
785 | } |
786 | |
787 | CNetMsg_Sv_RaceFinish RaceFinishMsg; |
788 | RaceFinishMsg.m_ClientId = ClientId; |
789 | RaceFinishMsg.m_Time = Time * 1000; |
790 | RaceFinishMsg.m_Diff = 0; |
791 | if(pData->m_BestTime) |
792 | { |
793 | RaceFinishMsg.m_Diff = Diff * 1000 * (Time < pData->m_BestTime ? -1 : 1); |
794 | } |
795 | RaceFinishMsg.m_RecordPersonal = (Time < pData->m_BestTime || !pData->m_BestTime); |
796 | RaceFinishMsg.m_RecordServer = Time < GameServer()->m_pController->m_CurrentRecord; |
797 | Server()->SendPackMsg(pMsg: &RaceFinishMsg, Flags: MSGFLAG_VITAL | MSGFLAG_NORECORD, ClientId: -1); |
798 | |
799 | bool CallSaveScore = g_Config.m_SvSaveWorseScores; |
800 | bool NeedToSendNewPersonalRecord = false; |
801 | if(!pData->m_BestTime || Time < pData->m_BestTime) |
802 | { |
803 | // update the score |
804 | pData->Set(Time, aTimeCp: GetCurrentTimeCp(Player)); |
805 | CallSaveScore = true; |
806 | NeedToSendNewPersonalRecord = true; |
807 | } |
808 | |
809 | if(CallSaveScore) |
810 | if(g_Config.m_SvNamelessScore || !str_startswith(str: Server()->ClientName(ClientId), prefix: "nameless tee" )) |
811 | GameServer()->Score()->SaveScore(ClientId, TimeTicks, pTimestamp, |
812 | aTimeCp: GetCurrentTimeCp(Player), NotEligible: Player->m_NotEligibleForFinish); |
813 | |
814 | bool NeedToSendNewServerRecord = false; |
815 | // update server best time |
816 | if(GameServer()->m_pController->m_CurrentRecord == 0) |
817 | { |
818 | GameServer()->Score()->LoadBestTime(); |
819 | } |
820 | else if(Time < GameServer()->m_pController->m_CurrentRecord) |
821 | { |
822 | // check for nameless |
823 | if(g_Config.m_SvNamelessScore || !str_startswith(str: Server()->ClientName(ClientId), prefix: "nameless tee" )) |
824 | { |
825 | GameServer()->m_pController->m_CurrentRecord = Time; |
826 | NeedToSendNewServerRecord = true; |
827 | } |
828 | } |
829 | |
830 | SetDDRaceState(Player, DDRaceState: DDRACE_FINISHED); |
831 | if(NeedToSendNewServerRecord) |
832 | { |
833 | for(int i = 0; i < MAX_CLIENTS; i++) |
834 | { |
835 | if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->GetClientVersion() >= VERSION_DDRACE) |
836 | { |
837 | GameServer()->SendRecord(ClientId: i); |
838 | } |
839 | } |
840 | } |
841 | if(!NeedToSendNewServerRecord && NeedToSendNewPersonalRecord && Player->GetClientVersion() >= VERSION_DDRACE) |
842 | { |
843 | GameServer()->SendRecord(ClientId); |
844 | } |
845 | |
846 | int TTime = (int)Time; |
847 | if(!Player->m_Score.has_value() || TTime < Player->m_Score.value()) |
848 | { |
849 | Player->m_Score = TTime; |
850 | } |
851 | |
852 | // Confetti |
853 | CCharacter *pChar = Player->GetCharacter(); |
854 | m_pGameContext->CreateFinishConfetti(Pos: pChar->m_Pos, Mask: pChar->TeamMask()); |
855 | } |
856 | |
857 | void CGameTeams::RequestTeamSwap(CPlayer *pPlayer, CPlayer *pTargetPlayer, int Team) |
858 | { |
859 | if(!pPlayer || !pTargetPlayer) |
860 | return; |
861 | |
862 | char aBuf[512]; |
863 | if(pPlayer->m_SwapTargetsClientId == pTargetPlayer->GetCid()) |
864 | { |
865 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
866 | format: "You have already requested to swap with %s." , Server()->ClientName(ClientId: pTargetPlayer->GetCid())); |
867 | |
868 | GameServer()->SendChatTarget(To: pPlayer->GetCid(), pText: aBuf); |
869 | return; |
870 | } |
871 | |
872 | // Notification for the swap initiator |
873 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
874 | format: "You have requested to swap with %s." , |
875 | Server()->ClientName(ClientId: pTargetPlayer->GetCid())); |
876 | GameServer()->SendChatTarget(To: pPlayer->GetCid(), pText: aBuf); |
877 | |
878 | // Notification to the target swap player |
879 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
880 | format: "%s has requested to swap with you. To complete the swap process please wait %d seconds and then type /swap %s." , |
881 | Server()->ClientName(ClientId: pPlayer->GetCid()), g_Config.m_SvSaveSwapGamesDelay, Server()->ClientName(ClientId: pPlayer->GetCid())); |
882 | GameServer()->SendChatTarget(To: pTargetPlayer->GetCid(), pText: aBuf); |
883 | |
884 | // Notification for the remaining team |
885 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
886 | format: "%s has requested to swap with %s." , |
887 | Server()->ClientName(ClientId: pPlayer->GetCid()), Server()->ClientName(ClientId: pTargetPlayer->GetCid())); |
888 | // Do not send the team notification for team 0 |
889 | if(Team != 0) |
890 | { |
891 | for(int i = 0; i < MAX_CLIENTS; i++) |
892 | { |
893 | if(m_Core.Team(ClientId: i) == Team && i != pTargetPlayer->GetCid() && i != pPlayer->GetCid()) |
894 | { |
895 | GameServer()->SendChatTarget(To: i, pText: aBuf); |
896 | } |
897 | } |
898 | } |
899 | |
900 | pPlayer->m_SwapTargetsClientId = pTargetPlayer->GetCid(); |
901 | m_aLastSwap[pPlayer->GetCid()] = Server()->Tick(); |
902 | } |
903 | |
904 | void CGameTeams::SwapTeamCharacters(CPlayer *pPrimaryPlayer, CPlayer *pTargetPlayer, int Team) |
905 | { |
906 | if(!pPrimaryPlayer || !pTargetPlayer) |
907 | return; |
908 | |
909 | char aBuf[128]; |
910 | |
911 | int Since = (Server()->Tick() - m_aLastSwap[pTargetPlayer->GetCid()]) / Server()->TickSpeed(); |
912 | if(Since < g_Config.m_SvSaveSwapGamesDelay) |
913 | { |
914 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
915 | format: "You have to wait %d seconds until you can swap." , |
916 | g_Config.m_SvSaveSwapGamesDelay - Since); |
917 | |
918 | GameServer()->SendChatTarget(To: pPrimaryPlayer->GetCid(), pText: aBuf); |
919 | |
920 | return; |
921 | } |
922 | |
923 | pPrimaryPlayer->m_SwapTargetsClientId = -1; |
924 | pTargetPlayer->m_SwapTargetsClientId = -1; |
925 | |
926 | int TimeoutAfterDelay = g_Config.m_SvSaveSwapGamesDelay + g_Config.m_SvSwapTimeout; |
927 | if(Since >= TimeoutAfterDelay) |
928 | { |
929 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
930 | format: "Your swap request timed out %d seconds ago. Use /swap again to re-initiate it." , |
931 | Since - g_Config.m_SvSwapTimeout); |
932 | |
933 | GameServer()->SendChatTarget(To: pPrimaryPlayer->GetCid(), pText: aBuf); |
934 | |
935 | return; |
936 | } |
937 | |
938 | CSaveTee PrimarySavedTee; |
939 | PrimarySavedTee.Save(pchr: pPrimaryPlayer->GetCharacter()); |
940 | |
941 | CSaveTee SecondarySavedTee; |
942 | SecondarySavedTee.Save(pchr: pTargetPlayer->GetCharacter()); |
943 | |
944 | PrimarySavedTee.Load(pchr: pTargetPlayer->GetCharacter(), Team, IsSwap: true); |
945 | SecondarySavedTee.Load(pchr: pPrimaryPlayer->GetCharacter(), Team, IsSwap: true); |
946 | |
947 | if(Team >= 1 && !m_aTeamFlock[Team]) |
948 | { |
949 | for(const auto &pPlayer : GameServer()->m_apPlayers) |
950 | { |
951 | CCharacter *pChar = pPlayer ? pPlayer->GetCharacter() : nullptr; |
952 | if(pChar && pChar->Team() == Team && pChar != pPrimaryPlayer->GetCharacter() && pChar != pTargetPlayer->GetCharacter()) |
953 | pChar->m_StartTime = pPrimaryPlayer->GetCharacter()->m_StartTime; |
954 | } |
955 | } |
956 | std::swap(a&: m_aTeeStarted[pPrimaryPlayer->GetCid()], b&: m_aTeeStarted[pTargetPlayer->GetCid()]); |
957 | std::swap(a&: m_aTeeFinished[pPrimaryPlayer->GetCid()], b&: m_aTeeFinished[pTargetPlayer->GetCid()]); |
958 | std::swap(a&: pPrimaryPlayer->GetCharacter()->GetLastRescueTeeRef(Mode: RESCUEMODE_AUTO), b&: pTargetPlayer->GetCharacter()->GetLastRescueTeeRef(Mode: RESCUEMODE_AUTO)); |
959 | std::swap(a&: pPrimaryPlayer->GetCharacter()->GetLastRescueTeeRef(Mode: RESCUEMODE_MANUAL), b&: pTargetPlayer->GetCharacter()->GetLastRescueTeeRef(Mode: RESCUEMODE_MANUAL)); |
960 | |
961 | GameServer()->m_World.SwapClients(Client1: pPrimaryPlayer->GetCid(), Client2: pTargetPlayer->GetCid()); |
962 | |
963 | if(GameServer()->TeeHistorianActive()) |
964 | { |
965 | GameServer()->TeeHistorian()->RecordPlayerSwap(ClientId1: pPrimaryPlayer->GetCid(), ClientId2: pTargetPlayer->GetCid()); |
966 | } |
967 | |
968 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), |
969 | format: "%s has swapped with %s." , |
970 | Server()->ClientName(ClientId: pPrimaryPlayer->GetCid()), Server()->ClientName(ClientId: pTargetPlayer->GetCid())); |
971 | |
972 | GameServer()->SendChatTeam(Team, pText: aBuf); |
973 | } |
974 | |
975 | void CGameTeams::ProcessSaveTeam() |
976 | { |
977 | for(int Team = 0; Team < NUM_DDRACE_TEAMS; Team++) |
978 | { |
979 | if(m_apSaveTeamResult[Team] == nullptr || !m_apSaveTeamResult[Team]->m_Completed) |
980 | continue; |
981 | if(m_apSaveTeamResult[Team]->m_aBroadcast[0] != '\0') |
982 | GameServer()->SendBroadcast(pText: m_apSaveTeamResult[Team]->m_aBroadcast, ClientId: -1); |
983 | if(m_apSaveTeamResult[Team]->m_aMessage[0] != '\0' && m_apSaveTeamResult[Team]->m_Status != CScoreSaveResult::LOAD_FAILED) |
984 | GameServer()->SendChatTeam(Team, pText: m_apSaveTeamResult[Team]->m_aMessage); |
985 | switch(m_apSaveTeamResult[Team]->m_Status) |
986 | { |
987 | case CScoreSaveResult::SAVE_SUCCESS: |
988 | { |
989 | if(GameServer()->TeeHistorianActive()) |
990 | { |
991 | GameServer()->TeeHistorian()->RecordTeamSaveSuccess( |
992 | Team, |
993 | SaveId: m_apSaveTeamResult[Team]->m_SaveId, |
994 | pTeamSave: m_apSaveTeamResult[Team]->m_SavedTeam.GetString()); |
995 | } |
996 | for(int i = 0; i < m_apSaveTeamResult[Team]->m_SavedTeam.GetMembersCount(); i++) |
997 | { |
998 | if(m_apSaveTeamResult[Team]->m_SavedTeam.m_pSavedTees->IsHooking()) |
999 | { |
1000 | int ClientId = m_apSaveTeamResult[Team]->m_SavedTeam.m_pSavedTees->GetClientId(); |
1001 | if(GameServer()->m_apPlayers[ClientId] != nullptr) |
1002 | GameServer()->SendChatTarget(To: ClientId, pText: "Start holding the hook before loading the savegame to keep the hook" ); |
1003 | } |
1004 | } |
1005 | ResetSavedTeam(ClientId: m_apSaveTeamResult[Team]->m_RequestingPlayer, Team); |
1006 | char aSaveId[UUID_MAXSTRSIZE]; |
1007 | FormatUuid(Uuid: m_apSaveTeamResult[Team]->m_SaveId, pBuffer: aSaveId, BufferLength: UUID_MAXSTRSIZE); |
1008 | dbg_msg(sys: "save" , fmt: "Save successful: %s" , aSaveId); |
1009 | break; |
1010 | } |
1011 | case CScoreSaveResult::SAVE_FAILED: |
1012 | if(GameServer()->TeeHistorianActive()) |
1013 | GameServer()->TeeHistorian()->RecordTeamSaveFailure(Team); |
1014 | if(Count(Team) > 0) |
1015 | { |
1016 | // load weak/strong order to prevent switching weak/strong while saving |
1017 | m_apSaveTeamResult[Team]->m_SavedTeam.Load(pGameServer: GameServer(), Team, KeepCurrentWeakStrong: false); |
1018 | } |
1019 | break; |
1020 | case CScoreSaveResult::LOAD_SUCCESS: |
1021 | { |
1022 | if(GameServer()->TeeHistorianActive()) |
1023 | { |
1024 | GameServer()->TeeHistorian()->RecordTeamLoadSuccess( |
1025 | Team, |
1026 | SaveId: m_apSaveTeamResult[Team]->m_SaveId, |
1027 | pTeamSave: m_apSaveTeamResult[Team]->m_SavedTeam.GetString()); |
1028 | } |
1029 | if(Count(Team) > 0) |
1030 | { |
1031 | // keep current weak/strong order as on some maps there is no other way of switching |
1032 | m_apSaveTeamResult[Team]->m_SavedTeam.Load(pGameServer: GameServer(), Team, KeepCurrentWeakStrong: true); |
1033 | } |
1034 | char aSaveId[UUID_MAXSTRSIZE]; |
1035 | FormatUuid(Uuid: m_apSaveTeamResult[Team]->m_SaveId, pBuffer: aSaveId, BufferLength: UUID_MAXSTRSIZE); |
1036 | dbg_msg(sys: "save" , fmt: "Load successful: %s" , aSaveId); |
1037 | break; |
1038 | } |
1039 | case CScoreSaveResult::LOAD_FAILED: |
1040 | if(GameServer()->TeeHistorianActive()) |
1041 | GameServer()->TeeHistorian()->RecordTeamLoadFailure(Team); |
1042 | if(m_apSaveTeamResult[Team]->m_aMessage[0] != '\0') |
1043 | GameServer()->SendChatTarget(To: m_apSaveTeamResult[Team]->m_RequestingPlayer, pText: m_apSaveTeamResult[Team]->m_aMessage); |
1044 | break; |
1045 | } |
1046 | m_apSaveTeamResult[Team] = nullptr; |
1047 | } |
1048 | } |
1049 | |
1050 | void CGameTeams::OnCharacterSpawn(int ClientId) |
1051 | { |
1052 | m_Core.SetSolo(ClientId, Value: false); |
1053 | int Team = m_Core.Team(ClientId); |
1054 | |
1055 | if(GetSaving(TeamId: Team)) |
1056 | return; |
1057 | |
1058 | if(m_Core.Team(ClientId) >= TEAM_SUPER || !m_aTeamLocked[Team]) |
1059 | { |
1060 | if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO) |
1061 | SetForceCharacterTeam(ClientId, Team: TEAM_FLOCK); |
1062 | else |
1063 | SetForceCharacterTeam(ClientId, Team: ClientId); // initialize team |
1064 | if(!m_aTeamFlock[Team]) |
1065 | CheckTeamFinished(Team); |
1066 | } |
1067 | } |
1068 | |
1069 | void CGameTeams::OnCharacterDeath(int ClientId, int Weapon) |
1070 | { |
1071 | m_Core.SetSolo(ClientId, Value: false); |
1072 | |
1073 | int Team = m_Core.Team(ClientId); |
1074 | if(GetSaving(TeamId: Team)) |
1075 | return; |
1076 | bool Locked = TeamLocked(Team) && Weapon != WEAPON_GAME; |
1077 | |
1078 | if(g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO && Team != TEAM_SUPER) |
1079 | { |
1080 | ChangeTeamState(Team, State: CGameTeams::TEAMSTATE_OPEN); |
1081 | if(m_aPractice[Team]) |
1082 | { |
1083 | if(Weapon != WEAPON_WORLD) |
1084 | { |
1085 | ResetRoundState(Team); |
1086 | } |
1087 | else |
1088 | { |
1089 | GameServer()->SendChatTeam(Team, pText: "You died, but will stay in practice until you use kill." ); |
1090 | } |
1091 | } |
1092 | else |
1093 | { |
1094 | ResetRoundState(Team); |
1095 | } |
1096 | } |
1097 | else if(Locked) |
1098 | { |
1099 | SetForceCharacterTeam(ClientId, Team); |
1100 | |
1101 | if(GetTeamState(Team) != TEAMSTATE_OPEN && !m_aTeamFlock[m_Core.Team(ClientId)]) |
1102 | { |
1103 | ChangeTeamState(Team, State: CGameTeams::TEAMSTATE_OPEN); |
1104 | |
1105 | m_aPractice[Team] = false; |
1106 | |
1107 | if(Count(Team) > 1) |
1108 | { |
1109 | // Disband team if the team has more players than allowed. |
1110 | if(Count(Team) > g_Config.m_SvMaxTeamSize) |
1111 | { |
1112 | GameServer()->SendChatTeam(Team, pText: "This team was disbanded because there are more players than allowed in the team." ); |
1113 | SetTeamLock(Team, Lock: false); |
1114 | KillTeam(Team, NewStrongId: Weapon == WEAPON_SELF ? ClientId : -1, ExceptId: ClientId); |
1115 | return; |
1116 | } |
1117 | |
1118 | KillTeam(Team, NewStrongId: Weapon == WEAPON_SELF ? ClientId : -1, ExceptId: ClientId); |
1119 | |
1120 | char aBuf[512]; |
1121 | 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" ); |
1122 | |
1123 | GameServer()->SendChatTeam(Team, pText: aBuf); |
1124 | } |
1125 | } |
1126 | } |
1127 | else |
1128 | { |
1129 | if(m_aTeamState[m_Core.Team(ClientId)] == CGameTeams::TEAMSTATE_STARTED && !m_aTeeStarted[ClientId] && !m_aTeamFlock[m_Core.Team(ClientId)]) |
1130 | { |
1131 | char aBuf[128]; |
1132 | 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)); |
1133 | GameServer()->SendChatTeam(Team, pText: aBuf); |
1134 | GameServer()->SendChatTeam(Team, pText: "Enter /practice mode or restart to avoid the entire team being killed in 60 seconds" ); |
1135 | |
1136 | m_aTeamUnfinishableKillTick[Team] = Server()->Tick() + 60 * Server()->TickSpeed(); |
1137 | ChangeTeamState(Team, State: CGameTeams::TEAMSTATE_STARTED_UNFINISHABLE); |
1138 | } |
1139 | SetForceCharacterTeam(ClientId, Team: TEAM_FLOCK); |
1140 | if(!m_aTeamFlock[m_Core.Team(ClientId)]) |
1141 | CheckTeamFinished(Team); |
1142 | } |
1143 | } |
1144 | |
1145 | void CGameTeams::SetTeamLock(int Team, bool Lock) |
1146 | { |
1147 | if(Team > TEAM_FLOCK && Team < TEAM_SUPER) |
1148 | m_aTeamLocked[Team] = Lock; |
1149 | } |
1150 | |
1151 | void CGameTeams::SetTeamFlock(int Team, bool Mode) |
1152 | { |
1153 | if(Team > TEAM_FLOCK && Team < TEAM_SUPER) |
1154 | m_aTeamFlock[Team] = Mode; |
1155 | } |
1156 | |
1157 | void CGameTeams::ResetInvited(int Team) |
1158 | { |
1159 | m_aInvited[Team].reset(); |
1160 | } |
1161 | |
1162 | void CGameTeams::SetClientInvited(int Team, int ClientId, bool Invited) |
1163 | { |
1164 | if(Team > TEAM_FLOCK && Team < TEAM_SUPER) |
1165 | { |
1166 | if(Invited) |
1167 | m_aInvited[Team].set(position: ClientId); |
1168 | else |
1169 | m_aInvited[Team].reset(position: ClientId); |
1170 | } |
1171 | } |
1172 | |
1173 | void CGameTeams::KillSavedTeam(int ClientId, int Team) |
1174 | { |
1175 | if(g_Config.m_SvSoloServer || !g_Config.m_SvTeam) |
1176 | { |
1177 | GameServer()->m_apPlayers[ClientId]->KillCharacter(Weapon: WEAPON_SELF, SendKillMsg: true); |
1178 | } |
1179 | else |
1180 | { |
1181 | KillTeam(Team, NewStrongId: -1); |
1182 | } |
1183 | } |
1184 | |
1185 | void CGameTeams::ResetSavedTeam(int ClientId, int Team) |
1186 | { |
1187 | if(g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO) |
1188 | { |
1189 | ChangeTeamState(Team, State: CGameTeams::TEAMSTATE_OPEN); |
1190 | ResetRoundState(Team); |
1191 | } |
1192 | else |
1193 | { |
1194 | for(int i = 0; i < MAX_CLIENTS; i++) |
1195 | { |
1196 | if(m_Core.Team(ClientId: i) == Team && GameServer()->m_apPlayers[i]) |
1197 | { |
1198 | SetForceCharacterTeam(ClientId: i, Team: TEAM_FLOCK); |
1199 | } |
1200 | } |
1201 | } |
1202 | } |
1203 | |
1204 | int CGameTeams::GetFirstEmptyTeam() const |
1205 | { |
1206 | for(int i = 1; i < MAX_CLIENTS; i++) |
1207 | if(m_aTeamState[i] == TEAMSTATE_EMPTY) |
1208 | return i; |
1209 | return -1; |
1210 | } |
1211 | |