1#include "save.h"
2
3#include <cstdio> // sscanf
4
5#include "entities/character.h"
6#include "gamemodes/DDRace.h"
7#include "player.h"
8#include "teams.h"
9#include <engine/shared/config.h>
10#include <engine/shared/protocol.h>
11
12CSaveTee::CSaveTee() = default;
13
14void CSaveTee::Save(CCharacter *pChr)
15{
16 m_ClientId = pChr->m_pPlayer->GetCid();
17 str_copy(dst: m_aName, src: pChr->Server()->ClientName(ClientId: m_ClientId), dst_size: sizeof(m_aName));
18
19 m_Alive = pChr->m_Alive;
20 m_Paused = absolute(a: pChr->m_pPlayer->IsPaused());
21 m_NeededFaketuning = pChr->m_NeededFaketuning;
22
23 m_TeeStarted = pChr->Teams()->TeeStarted(ClientId: m_ClientId);
24 m_TeeFinished = pChr->Teams()->TeeFinished(ClientId: m_ClientId);
25 m_IsSolo = pChr->m_Core.m_Solo;
26
27 for(int i = 0; i < NUM_WEAPONS; i++)
28 {
29 m_aWeapons[i].m_AmmoRegenStart = pChr->m_Core.m_aWeapons[i].m_AmmoRegenStart;
30 m_aWeapons[i].m_Ammo = pChr->m_Core.m_aWeapons[i].m_Ammo;
31 m_aWeapons[i].m_Ammocost = pChr->m_Core.m_aWeapons[i].m_Ammocost;
32 m_aWeapons[i].m_Got = pChr->m_Core.m_aWeapons[i].m_Got;
33 }
34
35 m_Ninja.m_ActivationDir = pChr->m_Core.m_Ninja.m_ActivationDir;
36 if(pChr->m_Core.m_Ninja.m_ActivationTick)
37 m_Ninja.m_ActivationTick = pChr->Server()->Tick() - pChr->m_Core.m_Ninja.m_ActivationTick;
38 else
39 m_Ninja.m_ActivationTick = 0;
40 m_Ninja.m_CurrentMoveTime = pChr->m_Core.m_Ninja.m_CurrentMoveTime;
41 m_Ninja.m_OldVelAmount = pChr->m_Core.m_Ninja.m_OldVelAmount;
42
43 m_LastWeapon = pChr->m_LastWeapon;
44 m_QueuedWeapon = pChr->m_QueuedWeapon;
45
46 m_EndlessJump = pChr->m_Core.m_EndlessJump;
47 m_Jetpack = pChr->m_Core.m_Jetpack;
48 m_NinjaJetpack = pChr->m_NinjaJetpack;
49 m_FreezeTime = pChr->m_FreezeTime;
50 m_FreezeStart = pChr->Server()->Tick() - pChr->m_Core.m_FreezeStart;
51
52 m_DeepFrozen = pChr->m_Core.m_DeepFrozen;
53 m_LiveFrozen = pChr->m_Core.m_LiveFrozen;
54 m_EndlessHook = pChr->m_Core.m_EndlessHook;
55 m_DDRaceState = pChr->m_DDRaceState;
56
57 m_HitDisabledFlags = 0;
58 if(pChr->m_Core.m_HammerHitDisabled)
59 m_HitDisabledFlags |= CSaveTee::HAMMER_HIT_DISABLED;
60 if(pChr->m_Core.m_ShotgunHitDisabled)
61 m_HitDisabledFlags |= CSaveTee::SHOTGUN_HIT_DISABLED;
62 if(pChr->m_Core.m_GrenadeHitDisabled)
63 m_HitDisabledFlags |= CSaveTee::GRENADE_HIT_DISABLED;
64 if(pChr->m_Core.m_LaserHitDisabled)
65 m_HitDisabledFlags |= CSaveTee::LASER_HIT_DISABLED;
66
67 m_TuneZone = pChr->m_TuneZone;
68 m_TuneZoneOld = pChr->m_TuneZoneOld;
69
70 if(pChr->m_StartTime)
71 m_Time = pChr->Server()->Tick() - pChr->m_StartTime + g_Config.m_SvSaveSwapGamesPenalty * pChr->Server()->TickSpeed();
72 else
73 m_Time = 0;
74
75 m_Pos = pChr->m_Pos;
76 m_PrevPos = pChr->m_PrevPos;
77 m_TeleCheckpoint = pChr->m_TeleCheckpoint;
78 m_LastPenalty = pChr->m_LastPenalty;
79
80 if(pChr->m_TimeCpBroadcastEndTick)
81 m_TimeCpBroadcastEndTime = pChr->Server()->Tick() - pChr->m_TimeCpBroadcastEndTick;
82
83 m_LastTimeCp = pChr->m_LastTimeCp;
84 m_LastTimeCpBroadcasted = pChr->m_LastTimeCpBroadcasted;
85
86 for(int i = 0; i < MAX_CHECKPOINTS; i++)
87 m_aCurrentTimeCp[i] = pChr->m_aCurrentTimeCp[i];
88
89 m_NotEligibleForFinish = pChr->m_pPlayer->m_NotEligibleForFinish;
90
91 m_HasTelegunGun = pChr->m_Core.m_HasTelegunGun;
92 m_HasTelegunGrenade = pChr->m_Core.m_HasTelegunGrenade;
93 m_HasTelegunLaser = pChr->m_Core.m_HasTelegunLaser;
94
95 // Core
96 m_CorePos = pChr->m_Core.m_Pos;
97 m_Vel = pChr->m_Core.m_Vel;
98 m_HookHitEnabled = !pChr->m_Core.m_HookHitDisabled;
99 m_CollisionEnabled = !pChr->m_Core.m_CollisionDisabled;
100 m_ActiveWeapon = pChr->m_Core.m_ActiveWeapon;
101 m_Jumped = pChr->m_Core.m_Jumped;
102 m_JumpedTotal = pChr->m_Core.m_JumpedTotal;
103 m_Jumps = pChr->m_Core.m_Jumps;
104 m_HookPos = pChr->m_Core.m_HookPos;
105 m_HookDir = pChr->m_Core.m_HookDir;
106 m_HookTeleBase = pChr->m_Core.m_HookTeleBase;
107
108 m_HookTick = pChr->m_Core.m_HookTick;
109
110 m_HookState = pChr->m_Core.m_HookState;
111 m_HookedPlayer = pChr->m_Core.HookedPlayer();
112 m_NewHook = pChr->m_Core.m_NewHook != 0;
113
114 m_InputDirection = pChr->m_SavedInput.m_Direction;
115 m_InputJump = pChr->m_SavedInput.m_Jump;
116 m_InputFire = pChr->m_SavedInput.m_Fire;
117 m_InputHook = pChr->m_SavedInput.m_Hook;
118
119 m_ReloadTimer = pChr->m_ReloadTimer;
120
121 FormatUuid(Uuid: pChr->GameServer()->GameUuid(), pBuffer: m_aGameUuid, BufferLength: sizeof(m_aGameUuid));
122}
123
124void CSaveTee::Load(CCharacter *pChr, int Team, bool IsSwap)
125{
126 pChr->m_pPlayer->Pause(State: m_Paused, Force: true);
127
128 pChr->m_Alive = m_Alive;
129 pChr->m_NeededFaketuning = m_NeededFaketuning;
130
131 if(!IsSwap)
132 {
133 pChr->Teams()->SetForceCharacterTeam(ClientId: pChr->m_pPlayer->GetCid(), Team);
134 pChr->Teams()->SetStarted(ClientId: pChr->m_pPlayer->GetCid(), Started: m_TeeStarted);
135 pChr->Teams()->SetFinished(ClientId: pChr->m_pPlayer->GetCid(), Finished: m_TeeFinished);
136 }
137
138 for(int i = 0; i < NUM_WEAPONS; i++)
139 {
140 pChr->m_Core.m_aWeapons[i].m_AmmoRegenStart = m_aWeapons[i].m_AmmoRegenStart;
141 // m_Ammo not used anymore for tracking freeze following https://github.com/ddnet/ddnet/pull/2086
142 pChr->m_Core.m_aWeapons[i].m_Ammo = -1;
143 pChr->m_Core.m_aWeapons[i].m_Ammocost = m_aWeapons[i].m_Ammocost;
144 pChr->m_Core.m_aWeapons[i].m_Got = m_aWeapons[i].m_Got;
145 }
146
147 pChr->m_Core.m_Ninja.m_ActivationDir = m_Ninja.m_ActivationDir;
148 if(m_Ninja.m_ActivationTick)
149 pChr->m_Core.m_Ninja.m_ActivationTick = pChr->Server()->Tick() - m_Ninja.m_ActivationTick;
150 else
151 pChr->m_Core.m_Ninja.m_ActivationTick = 0;
152 pChr->m_Core.m_Ninja.m_CurrentMoveTime = m_Ninja.m_CurrentMoveTime;
153 pChr->m_Core.m_Ninja.m_OldVelAmount = m_Ninja.m_OldVelAmount;
154
155 pChr->m_LastWeapon = m_LastWeapon;
156 pChr->m_QueuedWeapon = m_QueuedWeapon;
157
158 pChr->m_Core.m_EndlessJump = m_EndlessJump;
159 pChr->m_Core.m_Jetpack = m_Jetpack;
160 pChr->m_NinjaJetpack = m_NinjaJetpack;
161 pChr->m_FreezeTime = m_FreezeTime;
162 pChr->m_Core.m_FreezeStart = pChr->Server()->Tick() - m_FreezeStart;
163
164 pChr->m_Core.m_DeepFrozen = m_DeepFrozen;
165 pChr->m_Core.m_LiveFrozen = m_LiveFrozen;
166 pChr->m_Core.m_EndlessHook = m_EndlessHook;
167 pChr->m_DDRaceState = m_DDRaceState;
168
169 pChr->m_Core.m_HammerHitDisabled = m_HitDisabledFlags & CSaveTee::HAMMER_HIT_DISABLED;
170 pChr->m_Core.m_ShotgunHitDisabled = m_HitDisabledFlags & CSaveTee::SHOTGUN_HIT_DISABLED;
171 pChr->m_Core.m_GrenadeHitDisabled = m_HitDisabledFlags & CSaveTee::GRENADE_HIT_DISABLED;
172 pChr->m_Core.m_LaserHitDisabled = m_HitDisabledFlags & CSaveTee::LASER_HIT_DISABLED;
173
174 pChr->m_TuneZone = m_TuneZone;
175 pChr->m_TuneZoneOld = m_TuneZoneOld;
176
177 if(m_Time)
178 pChr->m_StartTime = pChr->Server()->Tick() - m_Time;
179
180 pChr->m_Pos = m_Pos;
181 pChr->m_PrevPos = m_PrevPos;
182 pChr->m_TeleCheckpoint = m_TeleCheckpoint;
183 pChr->m_LastPenalty = m_LastPenalty;
184
185 if(m_TimeCpBroadcastEndTime)
186 pChr->m_TimeCpBroadcastEndTick = pChr->Server()->Tick() - m_TimeCpBroadcastEndTime;
187
188 pChr->m_LastTimeCp = m_LastTimeCp;
189 pChr->m_LastTimeCpBroadcasted = m_LastTimeCpBroadcasted;
190
191 for(int i = 0; i < MAX_CHECKPOINTS; i++)
192 pChr->m_aCurrentTimeCp[i] = m_aCurrentTimeCp[i];
193
194 pChr->m_pPlayer->m_NotEligibleForFinish = pChr->m_pPlayer->m_NotEligibleForFinish || m_NotEligibleForFinish;
195
196 pChr->m_Core.m_HasTelegunGun = m_HasTelegunGun;
197 pChr->m_Core.m_HasTelegunLaser = m_HasTelegunLaser;
198 pChr->m_Core.m_HasTelegunGrenade = m_HasTelegunGrenade;
199
200 // Core
201 pChr->m_Core.m_Pos = m_CorePos;
202 pChr->m_Core.m_Vel = m_Vel;
203 pChr->m_Core.m_HookHitDisabled = !m_HookHitEnabled;
204 pChr->m_Core.m_CollisionDisabled = !m_CollisionEnabled;
205 pChr->m_Core.m_ActiveWeapon = m_ActiveWeapon;
206 pChr->m_Core.m_Jumped = m_Jumped;
207 pChr->m_Core.m_JumpedTotal = m_JumpedTotal;
208 pChr->m_Core.m_Jumps = m_Jumps;
209 pChr->m_Core.m_HookPos = m_HookPos;
210 pChr->m_Core.m_HookDir = m_HookDir;
211 pChr->m_Core.m_HookTeleBase = m_HookTeleBase;
212
213 pChr->m_Core.m_HookTick = m_HookTick;
214
215 pChr->m_Core.m_HookState = m_HookState;
216 if(m_HookedPlayer != -1 && pChr->Teams()->m_Core.Team(ClientId: m_HookedPlayer) != Team)
217 {
218 pChr->m_Core.SetHookedPlayer(-1);
219 pChr->m_Core.m_HookState = HOOK_FLYING;
220 }
221 else
222 {
223 pChr->m_Core.SetHookedPlayer(m_HookedPlayer);
224 }
225 pChr->m_Core.m_NewHook = m_NewHook;
226 pChr->m_SavedInput.m_Direction = m_InputDirection;
227 pChr->m_SavedInput.m_Jump = m_InputJump;
228 pChr->m_SavedInput.m_Fire = m_InputFire;
229 pChr->m_SavedInput.m_Hook = m_InputHook;
230
231 pChr->m_ReloadTimer = m_ReloadTimer;
232
233 pChr->SetSolo(m_IsSolo);
234
235 if(!IsSwap)
236 {
237 // Always create a rescue tee at the exact location we loaded from so that
238 // the old one gets overwritten.
239 pChr->SetRescue();
240 }
241}
242
243char *CSaveTee::GetString(const CSaveTeam *pTeam)
244{
245 int HookedPlayer = -1;
246 if(m_HookedPlayer != -1)
247 {
248 for(int n = 0; n < pTeam->GetMembersCount(); n++)
249 {
250 if(m_HookedPlayer == pTeam->m_pSavedTees[n].GetClientId())
251 {
252 HookedPlayer = n;
253 break;
254 }
255 }
256 }
257
258 str_format(buffer: m_aString, buffer_size: sizeof(m_aString),
259 format: "%s\t%d\t%d\t%d\t%d\t%d\t"
260 // weapons
261 "%d\t%d\t%d\t%d\t"
262 "%d\t%d\t%d\t%d\t"
263 "%d\t%d\t%d\t%d\t"
264 "%d\t%d\t%d\t%d\t"
265 "%d\t%d\t%d\t%d\t"
266 "%d\t%d\t%d\t%d\t"
267 "%d\t%d\t"
268 // tee stats
269 "%d\t%d\t%d\t%d\t%d\t%d\t%d\t" // m_EndlessJump
270 "%d\t%d\t%d\t%d\t%d\t%d\t%d\t" // m_DDRaceState
271 "%d\t%d\t%d\t%d\t" // m_Pos.x
272 "%d\t%d\t" // m_TeleCheckpoint
273 "%d\t%d\t%f\t%f\t" // m_CorePos.x
274 "%d\t%d\t%d\t%d\t" // m_ActiveWeapon
275 "%d\t%d\t%f\t%f\t" // m_HookPos.x
276 "%d\t%d\t%d\t%d\t" // m_HookTeleBase.x
277 // time checkpoints
278 "%d\t%d\t%d\t"
279 "%f\t%f\t%f\t%f\t%f\t"
280 "%f\t%f\t%f\t%f\t%f\t"
281 "%f\t%f\t%f\t%f\t%f\t"
282 "%f\t%f\t%f\t%f\t%f\t"
283 "%f\t%f\t%f\t%f\t%f\t"
284 "%d\t" // m_NotEligibleForFinish
285 "%d\t%d\t%d\t" // tele weapons
286 "%s\t" // m_aGameUuid
287 "%d\t%d\t" // m_HookedPlayer, m_NewHook
288 "%d\t%d\t%d\t%d\t" // input stuff
289 "%d\t" // m_ReloadTimer
290 "%d\t" // m_TeeStarted
291 "%d\t" //m_LiveFreeze
292 "%f\t%f\t%d\t%d\t%d", // m_Ninja
293 m_aName, m_Alive, m_Paused, m_NeededFaketuning, m_TeeFinished, m_IsSolo,
294 // weapons
295 m_aWeapons[0].m_AmmoRegenStart, m_aWeapons[0].m_Ammo, m_aWeapons[0].m_Ammocost, m_aWeapons[0].m_Got,
296 m_aWeapons[1].m_AmmoRegenStart, m_aWeapons[1].m_Ammo, m_aWeapons[1].m_Ammocost, m_aWeapons[1].m_Got,
297 m_aWeapons[2].m_AmmoRegenStart, m_aWeapons[2].m_Ammo, m_aWeapons[2].m_Ammocost, m_aWeapons[2].m_Got,
298 m_aWeapons[3].m_AmmoRegenStart, m_aWeapons[3].m_Ammo, m_aWeapons[3].m_Ammocost, m_aWeapons[3].m_Got,
299 m_aWeapons[4].m_AmmoRegenStart, m_aWeapons[4].m_Ammo, m_aWeapons[4].m_Ammocost, m_aWeapons[4].m_Got,
300 m_aWeapons[5].m_AmmoRegenStart, m_aWeapons[5].m_Ammo, m_aWeapons[5].m_Ammocost, m_aWeapons[5].m_Got,
301 m_LastWeapon, m_QueuedWeapon,
302 // tee states
303 m_EndlessJump, m_Jetpack, m_NinjaJetpack, m_FreezeTime, m_FreezeStart, m_DeepFrozen, m_EndlessHook,
304 m_DDRaceState, m_HitDisabledFlags, m_CollisionEnabled, m_TuneZone, m_TuneZoneOld, m_HookHitEnabled, m_Time,
305 (int)m_Pos.x, (int)m_Pos.y, (int)m_PrevPos.x, (int)m_PrevPos.y,
306 m_TeleCheckpoint, m_LastPenalty,
307 (int)m_CorePos.x, (int)m_CorePos.y, m_Vel.x, m_Vel.y,
308 m_ActiveWeapon, m_Jumped, m_JumpedTotal, m_Jumps,
309 (int)m_HookPos.x, (int)m_HookPos.y, m_HookDir.x, m_HookDir.y,
310 (int)m_HookTeleBase.x, (int)m_HookTeleBase.y, m_HookTick, m_HookState,
311 // time checkpoints
312 m_TimeCpBroadcastEndTime, m_LastTimeCp, m_LastTimeCpBroadcasted,
313 m_aCurrentTimeCp[0], m_aCurrentTimeCp[1], m_aCurrentTimeCp[2], m_aCurrentTimeCp[3], m_aCurrentTimeCp[4],
314 m_aCurrentTimeCp[5], m_aCurrentTimeCp[6], m_aCurrentTimeCp[7], m_aCurrentTimeCp[8], m_aCurrentTimeCp[9],
315 m_aCurrentTimeCp[10], m_aCurrentTimeCp[11], m_aCurrentTimeCp[12], m_aCurrentTimeCp[13], m_aCurrentTimeCp[14],
316 m_aCurrentTimeCp[15], m_aCurrentTimeCp[16], m_aCurrentTimeCp[17], m_aCurrentTimeCp[18], m_aCurrentTimeCp[19],
317 m_aCurrentTimeCp[20], m_aCurrentTimeCp[21], m_aCurrentTimeCp[22], m_aCurrentTimeCp[23], m_aCurrentTimeCp[24],
318 m_NotEligibleForFinish,
319 m_HasTelegunGun, m_HasTelegunLaser, m_HasTelegunGrenade,
320 m_aGameUuid,
321 HookedPlayer, m_NewHook,
322 m_InputDirection, m_InputJump, m_InputFire, m_InputHook,
323 m_ReloadTimer,
324 m_TeeStarted,
325 m_LiveFrozen,
326 m_Ninja.m_ActivationDir.x, m_Ninja.m_ActivationDir.y, m_Ninja.m_ActivationTick, m_Ninja.m_CurrentMoveTime, m_Ninja.m_OldVelAmount);
327 return m_aString;
328}
329
330int CSaveTee::FromString(const char *pString)
331{
332 int Num;
333 Num = sscanf(s: pString,
334 format: "%[^\t]\t%d\t%d\t%d\t%d\t%d\t"
335 // weapons
336 "%d\t%d\t%d\t%d\t"
337 "%d\t%d\t%d\t%d\t"
338 "%d\t%d\t%d\t%d\t"
339 "%d\t%d\t%d\t%d\t"
340 "%d\t%d\t%d\t%d\t"
341 "%d\t%d\t%d\t%d\t"
342 "%d\t%d\t"
343 // tee states
344 "%d\t%d\t%d\t%d\t%d\t%d\t%d\t" // m_EndlessJump
345 "%d\t%d\t%d\t%d\t%d\t%d\t%d\t" // m_DDRaceState
346 "%f\t%f\t%f\t%f\t" // m_Pos.x
347 "%d\t%d\t" // m_TeleCheckpoint
348 "%f\t%f\t%f\t%f\t" // m_CorePos.x
349 "%d\t%d\t%d\t%d\t" // m_ActiveWeapon
350 "%f\t%f\t%f\t%f\t" // m_HookPos.x
351 "%f\t%f\t%d\t%d\t" // m_HookTeleBase.x
352 // time checkpoints
353 "%d\t%d\t%d\t"
354 "%f\t%f\t%f\t%f\t%f\t"
355 "%f\t%f\t%f\t%f\t%f\t"
356 "%f\t%f\t%f\t%f\t%f\t"
357 "%f\t%f\t%f\t%f\t%f\t"
358 "%f\t%f\t%f\t%f\t%f\t"
359 "%d\t" // m_NotEligibleForFinish
360 "%d\t%d\t%d\t" // tele weapons
361 "%36s\t" // m_aGameUuid
362 "%d\t%d\t" // m_HookedPlayer, m_NewHook
363 "%d\t%d\t%d\t%d\t" // input stuff
364 "%d\t" // m_ReloadTimer
365 "%d\t" // m_TeeStarted
366 "%d\t" // m_LiveFreeze
367 "%f\t%f\t%d\t%d\t%d", // m_Ninja
368 m_aName, &m_Alive, &m_Paused, &m_NeededFaketuning, &m_TeeFinished, &m_IsSolo,
369 // weapons
370 &m_aWeapons[0].m_AmmoRegenStart, &m_aWeapons[0].m_Ammo, &m_aWeapons[0].m_Ammocost, &m_aWeapons[0].m_Got,
371 &m_aWeapons[1].m_AmmoRegenStart, &m_aWeapons[1].m_Ammo, &m_aWeapons[1].m_Ammocost, &m_aWeapons[1].m_Got,
372 &m_aWeapons[2].m_AmmoRegenStart, &m_aWeapons[2].m_Ammo, &m_aWeapons[2].m_Ammocost, &m_aWeapons[2].m_Got,
373 &m_aWeapons[3].m_AmmoRegenStart, &m_aWeapons[3].m_Ammo, &m_aWeapons[3].m_Ammocost, &m_aWeapons[3].m_Got,
374 &m_aWeapons[4].m_AmmoRegenStart, &m_aWeapons[4].m_Ammo, &m_aWeapons[4].m_Ammocost, &m_aWeapons[4].m_Got,
375 &m_aWeapons[5].m_AmmoRegenStart, &m_aWeapons[5].m_Ammo, &m_aWeapons[5].m_Ammocost, &m_aWeapons[5].m_Got,
376 &m_LastWeapon, &m_QueuedWeapon,
377 // tee states
378 &m_EndlessJump, &m_Jetpack, &m_NinjaJetpack, &m_FreezeTime, &m_FreezeStart, &m_DeepFrozen, &m_EndlessHook,
379 &m_DDRaceState, &m_HitDisabledFlags, &m_CollisionEnabled, &m_TuneZone, &m_TuneZoneOld, &m_HookHitEnabled, &m_Time,
380 &m_Pos.x, &m_Pos.y, &m_PrevPos.x, &m_PrevPos.y,
381 &m_TeleCheckpoint, &m_LastPenalty,
382 &m_CorePos.x, &m_CorePos.y, &m_Vel.x, &m_Vel.y,
383 &m_ActiveWeapon, &m_Jumped, &m_JumpedTotal, &m_Jumps,
384 &m_HookPos.x, &m_HookPos.y, &m_HookDir.x, &m_HookDir.y,
385 &m_HookTeleBase.x, &m_HookTeleBase.y, &m_HookTick, &m_HookState,
386 // time checkpoints
387 &m_TimeCpBroadcastEndTime, &m_LastTimeCp, &m_LastTimeCpBroadcasted,
388 &m_aCurrentTimeCp[0], &m_aCurrentTimeCp[1], &m_aCurrentTimeCp[2], &m_aCurrentTimeCp[3], &m_aCurrentTimeCp[4],
389 &m_aCurrentTimeCp[5], &m_aCurrentTimeCp[6], &m_aCurrentTimeCp[7], &m_aCurrentTimeCp[8], &m_aCurrentTimeCp[9],
390 &m_aCurrentTimeCp[10], &m_aCurrentTimeCp[11], &m_aCurrentTimeCp[12], &m_aCurrentTimeCp[13], &m_aCurrentTimeCp[14],
391 &m_aCurrentTimeCp[15], &m_aCurrentTimeCp[16], &m_aCurrentTimeCp[17], &m_aCurrentTimeCp[18], &m_aCurrentTimeCp[19],
392 &m_aCurrentTimeCp[20], &m_aCurrentTimeCp[21], &m_aCurrentTimeCp[22], &m_aCurrentTimeCp[23], &m_aCurrentTimeCp[24],
393 &m_NotEligibleForFinish,
394 &m_HasTelegunGun, &m_HasTelegunLaser, &m_HasTelegunGrenade,
395 m_aGameUuid,
396 &m_HookedPlayer, &m_NewHook,
397 &m_InputDirection, &m_InputJump, &m_InputFire, &m_InputHook,
398 &m_ReloadTimer,
399 &m_TeeStarted,
400 &m_LiveFrozen,
401 &m_Ninja.m_ActivationDir.x, &m_Ninja.m_ActivationDir.y, &m_Ninja.m_ActivationTick, &m_Ninja.m_CurrentMoveTime, &m_Ninja.m_OldVelAmount);
402 switch(Num) // Don't forget to update this when you save / load more / less.
403 {
404 case 96:
405 m_NotEligibleForFinish = false;
406 [[fallthrough]];
407 case 97:
408 m_HasTelegunGrenade = 0;
409 m_HasTelegunLaser = 0;
410 m_HasTelegunGun = 0;
411 FormatUuid(Uuid: CalculateUuid(pName: "game-uuid-nonexistent@ddnet.tw"), pBuffer: m_aGameUuid, BufferLength: sizeof(m_aGameUuid));
412 [[fallthrough]];
413 case 101:
414 m_HookedPlayer = -1;
415 m_NewHook = false;
416 if(m_HookState == HOOK_GRABBED)
417 m_HookState = HOOK_FLYING;
418 m_InputDirection = 0;
419 m_InputJump = 0;
420 m_InputFire = 0;
421 m_InputHook = 0;
422 m_ReloadTimer = 0;
423 [[fallthrough]];
424 case 108:
425 m_TeeStarted = true;
426 [[fallthrough]];
427 case 109:
428 m_LiveFrozen = false;
429 [[fallthrough]];
430 case 110:
431 if(m_aWeapons[WEAPON_NINJA].m_Got)
432 {
433 // remove ninja
434 m_aWeapons[WEAPON_NINJA].m_Got = false;
435 m_aWeapons[WEAPON_NINJA].m_Ammo = 0;
436 m_ActiveWeapon = m_LastWeapon;
437 }
438 m_Ninja.m_ActivationDir.x = 0.0;
439 m_Ninja.m_ActivationDir.y = 0.0;
440 m_Ninja.m_ActivationTick = 0;
441 m_Ninja.m_CurrentMoveTime = 0;
442 m_Ninja.m_OldVelAmount = 0;
443 [[fallthrough]];
444 case 115:
445 return 0;
446 default:
447 dbg_msg(sys: "load", fmt: "failed to load tee-string");
448 dbg_msg(sys: "load", fmt: "loaded %d vars", Num);
449 return Num + 1; // never 0 here
450 }
451}
452
453void CSaveTee::LoadHookedPlayer(const CSaveTeam *pTeam)
454{
455 if(m_HookedPlayer == -1)
456 return;
457 m_HookedPlayer = pTeam->m_pSavedTees[m_HookedPlayer].GetClientId();
458}
459
460bool CSaveTee::IsHooking() const
461{
462 return m_HookState == HOOK_GRABBED || m_HookState == HOOK_FLYING;
463}
464
465CSaveTeam::CSaveTeam()
466{
467 m_aString[0] = '\0';
468}
469
470CSaveTeam::~CSaveTeam()
471{
472 delete[] m_pSwitchers;
473 delete[] m_pSavedTees;
474}
475
476int CSaveTeam::Save(CGameContext *pGameServer, int Team, bool Dry)
477{
478 if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && (Team <= 0 || MAX_CLIENTS <= Team))
479 return 1;
480
481 IGameController *pController = pGameServer->m_pController;
482 CGameTeams *pTeams = &pController->Teams();
483
484 if(pTeams->TeamFlock(Team))
485 {
486 return 5;
487 }
488
489 m_MembersCount = pTeams->Count(Team);
490 if(m_MembersCount <= 0)
491 {
492 return 2;
493 }
494
495 m_TeamState = pTeams->GetTeamState(Team);
496
497 if(m_TeamState != CGameTeams::TEAMSTATE_STARTED)
498 {
499 return 4;
500 }
501
502 m_HighestSwitchNumber = pGameServer->Collision()->m_HighestSwitchNumber;
503 m_TeamLocked = pTeams->TeamLocked(Team);
504 m_Practice = pTeams->IsPractice(Team);
505
506 m_pSavedTees = new CSaveTee[m_MembersCount];
507 int aPlayerCids[MAX_CLIENTS];
508 int j = 0;
509 CCharacter *p = (CCharacter *)pGameServer->m_World.FindFirst(Type: CGameWorld::ENTTYPE_CHARACTER);
510 for(; p; p = (CCharacter *)p->TypeNext())
511 {
512 if(pTeams->m_Core.Team(ClientId: p->GetPlayer()->GetCid()) != Team)
513 continue;
514 if(m_MembersCount == j)
515 return 3;
516 m_pSavedTees[j].Save(pChr: p);
517 aPlayerCids[j] = p->GetPlayer()->GetCid();
518 j++;
519 }
520 if(m_MembersCount != j)
521 return 3;
522
523 if(pGameServer->Collision()->m_HighestSwitchNumber)
524 {
525 m_pSwitchers = new SSimpleSwitchers[pGameServer->Collision()->m_HighestSwitchNumber + 1];
526
527 for(int i = 1; i < pGameServer->Collision()->m_HighestSwitchNumber + 1; i++)
528 {
529 m_pSwitchers[i].m_Status = pGameServer->Switchers()[i].m_aStatus[Team];
530 if(pGameServer->Switchers()[i].m_aEndTick[Team])
531 m_pSwitchers[i].m_EndTime = pController->Server()->Tick() - pGameServer->Switchers()[i].m_aEndTick[Team];
532 else
533 m_pSwitchers[i].m_EndTime = 0;
534 m_pSwitchers[i].m_Type = pGameServer->Switchers()[i].m_aType[Team];
535 }
536 }
537 if(!Dry)
538 {
539 pGameServer->m_World.RemoveEntitiesFromPlayers(PlayerIds: aPlayerCids, NumPlayers: m_MembersCount);
540 }
541 return 0;
542}
543
544bool CSaveTeam::HandleSaveError(int Result, int ClientId, CGameContext *pGameContext)
545{
546 switch(Result)
547 {
548 case 0:
549 return false;
550 case 1:
551 pGameContext->SendChatTarget(To: ClientId, pText: "You have to be in a team (from 1-63)");
552 break;
553 case 2:
554 pGameContext->SendChatTarget(To: ClientId, pText: "Could not find your Team");
555 break;
556 case 3:
557 pGameContext->SendChatTarget(To: ClientId, pText: "To save all players in your team have to be alive and not in '/spec'");
558 break;
559 case 4:
560 pGameContext->SendChatTarget(To: ClientId, pText: "Your team has not started yet");
561 break;
562 case 5:
563 pGameContext->SendChatTarget(To: ClientId, pText: "Team can't be saved while in team 0 mode");
564 break;
565 default: // this state should never be reached
566 pGameContext->SendChatTarget(To: ClientId, pText: "Unknown error while saving");
567 break;
568 }
569 return true;
570}
571
572void CSaveTeam::Load(CGameContext *pGameServer, int Team, bool KeepCurrentWeakStrong)
573{
574 IGameController *pController = pGameServer->m_pController;
575 CGameTeams *pTeams = &pController->Teams();
576
577 pTeams->ChangeTeamState(Team, State: m_TeamState);
578 pTeams->SetTeamLock(Team, Lock: m_TeamLocked);
579 pTeams->SetPractice(Team, Enabled: m_Practice);
580
581 int aPlayerCids[MAX_CLIENTS];
582 for(int i = m_MembersCount; i-- > 0;)
583 {
584 int ClientId = m_pSavedTees[i].GetClientId();
585 aPlayerCids[i] = ClientId;
586 if(pGameServer->m_apPlayers[ClientId] && pTeams->m_Core.Team(ClientId) == Team)
587 {
588 CCharacter *pChr = MatchCharacter(pGameServer, ClientId: m_pSavedTees[i].GetClientId(), SaveId: i, KeepCurrentCharacter: KeepCurrentWeakStrong);
589 m_pSavedTees[i].Load(pChr, Team);
590 }
591 }
592
593 if(pGameServer->Collision()->m_HighestSwitchNumber)
594 {
595 for(int i = 1; i < minimum(a: m_HighestSwitchNumber, b: pGameServer->Collision()->m_HighestSwitchNumber) + 1; i++)
596 {
597 pGameServer->Switchers()[i].m_aStatus[Team] = m_pSwitchers[i].m_Status;
598 if(m_pSwitchers[i].m_EndTime)
599 pGameServer->Switchers()[i].m_aEndTick[Team] = pController->Server()->Tick() - m_pSwitchers[i].m_EndTime;
600 pGameServer->Switchers()[i].m_aType[Team] = m_pSwitchers[i].m_Type;
601 }
602 }
603 // remove projectiles and laser
604 pGameServer->m_World.RemoveEntitiesFromPlayers(PlayerIds: aPlayerCids, NumPlayers: m_MembersCount);
605}
606
607CCharacter *CSaveTeam::MatchCharacter(CGameContext *pGameServer, int ClientId, int SaveId, bool KeepCurrentCharacter) const
608{
609 if(KeepCurrentCharacter && pGameServer->m_apPlayers[ClientId]->GetCharacter())
610 {
611 // keep old character to retain current weak/strong order
612 return pGameServer->m_apPlayers[ClientId]->GetCharacter();
613 }
614 pGameServer->m_apPlayers[ClientId]->KillCharacter(Weapon: WEAPON_GAME);
615 return pGameServer->m_apPlayers[ClientId]->ForceSpawn(Pos: m_pSavedTees[SaveId].GetPos());
616}
617
618char *CSaveTeam::GetString()
619{
620 str_format(buffer: m_aString, buffer_size: sizeof(m_aString), format: "%d\t%d\t%d\t%d\t%d", m_TeamState, m_MembersCount, m_HighestSwitchNumber, m_TeamLocked, m_Practice);
621
622 for(int i = 0; i < m_MembersCount; i++)
623 {
624 char aBuf[1024];
625 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "\n%s", m_pSavedTees[i].GetString(pTeam: this));
626 str_append(dst&: m_aString, src: aBuf);
627 }
628
629 if(m_pSwitchers && m_HighestSwitchNumber)
630 {
631 for(int i = 1; i < m_HighestSwitchNumber + 1; i++)
632 {
633 char aBuf[64];
634 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "\n%d\t%d\t%d", m_pSwitchers[i].m_Status, m_pSwitchers[i].m_EndTime, m_pSwitchers[i].m_Type);
635 str_append(dst&: m_aString, src: aBuf);
636 }
637 }
638
639 return m_aString;
640}
641
642int CSaveTeam::FromString(const char *pString)
643{
644 char aTeamStats[MAX_CLIENTS];
645 char aSwitcher[64];
646 char aSaveTee[1024];
647
648 char *pCopyPos;
649 unsigned int Pos = 0;
650 unsigned int LastPos = 0;
651 unsigned int StrSize;
652
653 str_copy(dst: m_aString, src: pString, dst_size: sizeof(m_aString));
654
655 while(m_aString[Pos] != '\n' && Pos < sizeof(m_aString) && m_aString[Pos]) // find next \n or \0
656 Pos++;
657
658 pCopyPos = m_aString + LastPos;
659 StrSize = Pos - LastPos + 1;
660 if(m_aString[Pos] == '\n')
661 {
662 Pos++; // skip \n
663 LastPos = Pos;
664 }
665
666 if(StrSize <= 0)
667 {
668 dbg_msg(sys: "load", fmt: "savegame: wrong format (couldn't load teamstats)");
669 return 1;
670 }
671
672 if(StrSize < sizeof(aTeamStats))
673 {
674 str_copy(dst: aTeamStats, src: pCopyPos, dst_size: StrSize);
675 int Num = sscanf(s: aTeamStats, format: "%d\t%d\t%d\t%d\t%d", &m_TeamState, &m_MembersCount, &m_HighestSwitchNumber, &m_TeamLocked, &m_Practice);
676 switch(Num) // Don't forget to update this when you save / load more / less.
677 {
678 case 4:
679 m_Practice = false;
680 [[fallthrough]];
681 case 5:
682 break;
683 default:
684 dbg_msg(sys: "load", fmt: "failed to load teamstats");
685 dbg_msg(sys: "load", fmt: "loaded %d vars", Num);
686 return Num + 1; // never 0 here
687 }
688 }
689 else
690 {
691 dbg_msg(sys: "load", fmt: "savegame: wrong format (couldn't load teamstats, too big)");
692 return 1;
693 }
694
695 if(m_pSavedTees)
696 {
697 delete[] m_pSavedTees;
698 m_pSavedTees = 0;
699 }
700
701 if(m_MembersCount > 64)
702 {
703 dbg_msg(sys: "load", fmt: "savegame: team has too many players");
704 return 1;
705 }
706 else if(m_MembersCount)
707 {
708 m_pSavedTees = new CSaveTee[m_MembersCount];
709 }
710
711 for(int n = 0; n < m_MembersCount; n++)
712 {
713 while(m_aString[Pos] != '\n' && Pos < sizeof(m_aString) && m_aString[Pos]) // find next \n or \0
714 Pos++;
715
716 pCopyPos = m_aString + LastPos;
717 StrSize = Pos - LastPos + 1;
718 if(m_aString[Pos] == '\n')
719 {
720 Pos++; // skip \n
721 LastPos = Pos;
722 }
723
724 if(StrSize <= 0)
725 {
726 dbg_msg(sys: "load", fmt: "savegame: wrong format (couldn't load tee)");
727 return 1;
728 }
729
730 if(StrSize < sizeof(aSaveTee))
731 {
732 str_copy(dst: aSaveTee, src: pCopyPos, dst_size: StrSize);
733 int Num = m_pSavedTees[n].FromString(pString: aSaveTee);
734 if(Num)
735 {
736 dbg_msg(sys: "load", fmt: "failed to load tee");
737 dbg_msg(sys: "load", fmt: "loaded %d vars", Num - 1);
738 return 1;
739 }
740 }
741 else
742 {
743 dbg_msg(sys: "load", fmt: "savegame: wrong format (couldn't load tee, too big)");
744 return 1;
745 }
746 }
747
748 if(m_pSwitchers)
749 {
750 delete[] m_pSwitchers;
751 m_pSwitchers = 0;
752 }
753
754 if(m_HighestSwitchNumber)
755 m_pSwitchers = new SSimpleSwitchers[m_HighestSwitchNumber + 1];
756
757 for(int n = 1; n < m_HighestSwitchNumber + 1; n++)
758 {
759 while(m_aString[Pos] != '\n' && Pos < sizeof(m_aString) && m_aString[Pos]) // find next \n or \0
760 Pos++;
761
762 pCopyPos = m_aString + LastPos;
763 StrSize = Pos - LastPos + 1;
764 if(m_aString[Pos] == '\n')
765 {
766 Pos++; // skip \n
767 LastPos = Pos;
768 }
769
770 if(StrSize <= 0)
771 {
772 dbg_msg(sys: "load", fmt: "savegame: wrong format (couldn't load switcher)");
773 return 1;
774 }
775
776 if(StrSize < sizeof(aSwitcher))
777 {
778 str_copy(dst: aSwitcher, src: pCopyPos, dst_size: StrSize);
779 int Num = sscanf(s: aSwitcher, format: "%d\t%d\t%d", &(m_pSwitchers[n].m_Status), &(m_pSwitchers[n].m_EndTime), &(m_pSwitchers[n].m_Type));
780 if(Num != 3)
781 {
782 dbg_msg(sys: "load", fmt: "failed to load switcher");
783 dbg_msg(sys: "load", fmt: "loaded %d vars", Num - 1);
784 }
785 }
786 else
787 {
788 dbg_msg(sys: "load", fmt: "savegame: wrong format (couldn't load switcher, too big)");
789 return 1;
790 }
791 }
792
793 return 0;
794}
795
796bool CSaveTeam::MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientId, int NumPlayer, char *pMessage, int MessageLen) const
797{
798 if(NumPlayer > m_MembersCount)
799 {
800 str_format(buffer: pMessage, buffer_size: MessageLen, format: "Too many players in this team, should be %d", m_MembersCount);
801 return false;
802 }
803 // check for wrong players
804 for(int i = 0; i < NumPlayer; i++)
805 {
806 int Found = false;
807 for(int j = 0; j < m_MembersCount; j++)
808 {
809 if(str_comp(a: paNames[i], b: m_pSavedTees[j].GetName()) == 0)
810 {
811 Found = true;
812 }
813 }
814 if(!Found)
815 {
816 str_format(buffer: pMessage, buffer_size: MessageLen, format: "'%s' doesn't belong to this team", paNames[i]);
817 return false;
818 }
819 }
820 // check for missing players
821 for(int i = 0; i < m_MembersCount; i++)
822 {
823 int Found = false;
824 for(int j = 0; j < NumPlayer; j++)
825 {
826 if(str_comp(a: m_pSavedTees[i].GetName(), b: paNames[j]) == 0)
827 {
828 m_pSavedTees[i].SetClientId(pClientId[j]);
829 Found = true;
830 break;
831 }
832 }
833 if(!Found)
834 {
835 str_format(buffer: pMessage, buffer_size: MessageLen, format: "'%s' has to be in this team", m_pSavedTees[i].GetName());
836 return false;
837 }
838 }
839 // match hook to correct ClientId
840 for(int n = 0; n < m_MembersCount; n++)
841 m_pSavedTees[n].LoadHookedPlayer(pTeam: this);
842 return true;
843}
844