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->ForceSetRescue(RescueMode: RESCUEMODE_AUTO);
240 pChr->ForceSetRescue(RescueMode: RESCUEMODE_MANUAL);
241 }
242}
243
244char *CSaveTee::GetString(const CSaveTeam *pTeam)
245{
246 int HookedPlayer = -1;
247 if(m_HookedPlayer != -1)
248 {
249 for(int n = 0; n < pTeam->GetMembersCount(); n++)
250 {
251 if(m_HookedPlayer == pTeam->m_pSavedTees[n].GetClientId())
252 {
253 HookedPlayer = n;
254 break;
255 }
256 }
257 }
258
259 str_format(buffer: m_aString, buffer_size: sizeof(m_aString),
260 format: "%s\t%d\t%d\t%d\t%d\t%d\t"
261 // weapons
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%d\t%d\t"
268 "%d\t%d\t"
269 // tee stats
270 "%d\t%d\t%d\t%d\t%d\t%d\t%d\t" // m_EndlessJump
271 "%d\t%d\t%d\t%d\t%d\t%d\t%d\t" // m_DDRaceState
272 "%d\t%d\t%d\t%d\t" // m_Pos.x
273 "%d\t%d\t" // m_TeleCheckpoint
274 "%d\t%d\t%f\t%f\t" // m_CorePos.x
275 "%d\t%d\t%d\t%d\t" // m_ActiveWeapon
276 "%d\t%d\t%f\t%f\t" // m_HookPos.x
277 "%d\t%d\t%d\t%d\t" // m_HookTeleBase.x
278 // time checkpoints
279 "%d\t%d\t%d\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 "%f\t%f\t%f\t%f\t%f\t"
285 "%d\t" // m_NotEligibleForFinish
286 "%d\t%d\t%d\t" // tele weapons
287 "%s\t" // m_aGameUuid
288 "%d\t%d\t" // m_HookedPlayer, m_NewHook
289 "%d\t%d\t%d\t%d\t" // input stuff
290 "%d\t" // m_ReloadTimer
291 "%d\t" // m_TeeStarted
292 "%d\t" //m_LiveFreeze
293 "%f\t%f\t%d\t%d\t%d", // m_Ninja
294 m_aName, m_Alive, m_Paused, m_NeededFaketuning, m_TeeFinished, m_IsSolo,
295 // weapons
296 m_aWeapons[0].m_AmmoRegenStart, m_aWeapons[0].m_Ammo, m_aWeapons[0].m_Ammocost, m_aWeapons[0].m_Got,
297 m_aWeapons[1].m_AmmoRegenStart, m_aWeapons[1].m_Ammo, m_aWeapons[1].m_Ammocost, m_aWeapons[1].m_Got,
298 m_aWeapons[2].m_AmmoRegenStart, m_aWeapons[2].m_Ammo, m_aWeapons[2].m_Ammocost, m_aWeapons[2].m_Got,
299 m_aWeapons[3].m_AmmoRegenStart, m_aWeapons[3].m_Ammo, m_aWeapons[3].m_Ammocost, m_aWeapons[3].m_Got,
300 m_aWeapons[4].m_AmmoRegenStart, m_aWeapons[4].m_Ammo, m_aWeapons[4].m_Ammocost, m_aWeapons[4].m_Got,
301 m_aWeapons[5].m_AmmoRegenStart, m_aWeapons[5].m_Ammo, m_aWeapons[5].m_Ammocost, m_aWeapons[5].m_Got,
302 m_LastWeapon, m_QueuedWeapon,
303 // tee states
304 m_EndlessJump, m_Jetpack, m_NinjaJetpack, m_FreezeTime, m_FreezeStart, m_DeepFrozen, m_EndlessHook,
305 m_DDRaceState, m_HitDisabledFlags, m_CollisionEnabled, m_TuneZone, m_TuneZoneOld, m_HookHitEnabled, m_Time,
306 (int)m_Pos.x, (int)m_Pos.y, (int)m_PrevPos.x, (int)m_PrevPos.y,
307 m_TeleCheckpoint, m_LastPenalty,
308 (int)m_CorePos.x, (int)m_CorePos.y, m_Vel.x, m_Vel.y,
309 m_ActiveWeapon, m_Jumped, m_JumpedTotal, m_Jumps,
310 (int)m_HookPos.x, (int)m_HookPos.y, m_HookDir.x, m_HookDir.y,
311 (int)m_HookTeleBase.x, (int)m_HookTeleBase.y, m_HookTick, m_HookState,
312 // time checkpoints
313 m_TimeCpBroadcastEndTime, m_LastTimeCp, m_LastTimeCpBroadcasted,
314 m_aCurrentTimeCp[0], m_aCurrentTimeCp[1], m_aCurrentTimeCp[2], m_aCurrentTimeCp[3], m_aCurrentTimeCp[4],
315 m_aCurrentTimeCp[5], m_aCurrentTimeCp[6], m_aCurrentTimeCp[7], m_aCurrentTimeCp[8], m_aCurrentTimeCp[9],
316 m_aCurrentTimeCp[10], m_aCurrentTimeCp[11], m_aCurrentTimeCp[12], m_aCurrentTimeCp[13], m_aCurrentTimeCp[14],
317 m_aCurrentTimeCp[15], m_aCurrentTimeCp[16], m_aCurrentTimeCp[17], m_aCurrentTimeCp[18], m_aCurrentTimeCp[19],
318 m_aCurrentTimeCp[20], m_aCurrentTimeCp[21], m_aCurrentTimeCp[22], m_aCurrentTimeCp[23], m_aCurrentTimeCp[24],
319 m_NotEligibleForFinish,
320 m_HasTelegunGun, m_HasTelegunLaser, m_HasTelegunGrenade,
321 m_aGameUuid,
322 HookedPlayer, m_NewHook,
323 m_InputDirection, m_InputJump, m_InputFire, m_InputHook,
324 m_ReloadTimer,
325 m_TeeStarted,
326 m_LiveFrozen,
327 m_Ninja.m_ActivationDir.x, m_Ninja.m_ActivationDir.y, m_Ninja.m_ActivationTick, m_Ninja.m_CurrentMoveTime, m_Ninja.m_OldVelAmount);
328 return m_aString;
329}
330
331int CSaveTee::FromString(const char *pString)
332{
333 int Num;
334 Num = sscanf(s: pString,
335 format: "%[^\t]\t%d\t%d\t%d\t%d\t%d\t"
336 // weapons
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%d\t%d\t"
343 "%d\t%d\t"
344 // tee states
345 "%d\t%d\t%d\t%d\t%d\t%d\t%d\t" // m_EndlessJump
346 "%d\t%d\t%d\t%d\t%d\t%d\t%d\t" // m_DDRaceState
347 "%f\t%f\t%f\t%f\t" // m_Pos.x
348 "%d\t%d\t" // m_TeleCheckpoint
349 "%f\t%f\t%f\t%f\t" // m_CorePos.x
350 "%d\t%d\t%d\t%d\t" // m_ActiveWeapon
351 "%f\t%f\t%f\t%f\t" // m_HookPos.x
352 "%f\t%f\t%d\t%d\t" // m_HookTeleBase.x
353 // time checkpoints
354 "%d\t%d\t%d\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 "%f\t%f\t%f\t%f\t%f\t"
360 "%d\t" // m_NotEligibleForFinish
361 "%d\t%d\t%d\t" // tele weapons
362 "%36s\t" // m_aGameUuid
363 "%d\t%d\t" // m_HookedPlayer, m_NewHook
364 "%d\t%d\t%d\t%d\t" // input stuff
365 "%d\t" // m_ReloadTimer
366 "%d\t" // m_TeeStarted
367 "%d\t" // m_LiveFreeze
368 "%f\t%f\t%d\t%d\t%d", // m_Ninja
369 m_aName, &m_Alive, &m_Paused, &m_NeededFaketuning, &m_TeeFinished, &m_IsSolo,
370 // weapons
371 &m_aWeapons[0].m_AmmoRegenStart, &m_aWeapons[0].m_Ammo, &m_aWeapons[0].m_Ammocost, &m_aWeapons[0].m_Got,
372 &m_aWeapons[1].m_AmmoRegenStart, &m_aWeapons[1].m_Ammo, &m_aWeapons[1].m_Ammocost, &m_aWeapons[1].m_Got,
373 &m_aWeapons[2].m_AmmoRegenStart, &m_aWeapons[2].m_Ammo, &m_aWeapons[2].m_Ammocost, &m_aWeapons[2].m_Got,
374 &m_aWeapons[3].m_AmmoRegenStart, &m_aWeapons[3].m_Ammo, &m_aWeapons[3].m_Ammocost, &m_aWeapons[3].m_Got,
375 &m_aWeapons[4].m_AmmoRegenStart, &m_aWeapons[4].m_Ammo, &m_aWeapons[4].m_Ammocost, &m_aWeapons[4].m_Got,
376 &m_aWeapons[5].m_AmmoRegenStart, &m_aWeapons[5].m_Ammo, &m_aWeapons[5].m_Ammocost, &m_aWeapons[5].m_Got,
377 &m_LastWeapon, &m_QueuedWeapon,
378 // tee states
379 &m_EndlessJump, &m_Jetpack, &m_NinjaJetpack, &m_FreezeTime, &m_FreezeStart, &m_DeepFrozen, &m_EndlessHook,
380 &m_DDRaceState, &m_HitDisabledFlags, &m_CollisionEnabled, &m_TuneZone, &m_TuneZoneOld, &m_HookHitEnabled, &m_Time,
381 &m_Pos.x, &m_Pos.y, &m_PrevPos.x, &m_PrevPos.y,
382 &m_TeleCheckpoint, &m_LastPenalty,
383 &m_CorePos.x, &m_CorePos.y, &m_Vel.x, &m_Vel.y,
384 &m_ActiveWeapon, &m_Jumped, &m_JumpedTotal, &m_Jumps,
385 &m_HookPos.x, &m_HookPos.y, &m_HookDir.x, &m_HookDir.y,
386 &m_HookTeleBase.x, &m_HookTeleBase.y, &m_HookTick, &m_HookState,
387 // time checkpoints
388 &m_TimeCpBroadcastEndTime, &m_LastTimeCp, &m_LastTimeCpBroadcasted,
389 &m_aCurrentTimeCp[0], &m_aCurrentTimeCp[1], &m_aCurrentTimeCp[2], &m_aCurrentTimeCp[3], &m_aCurrentTimeCp[4],
390 &m_aCurrentTimeCp[5], &m_aCurrentTimeCp[6], &m_aCurrentTimeCp[7], &m_aCurrentTimeCp[8], &m_aCurrentTimeCp[9],
391 &m_aCurrentTimeCp[10], &m_aCurrentTimeCp[11], &m_aCurrentTimeCp[12], &m_aCurrentTimeCp[13], &m_aCurrentTimeCp[14],
392 &m_aCurrentTimeCp[15], &m_aCurrentTimeCp[16], &m_aCurrentTimeCp[17], &m_aCurrentTimeCp[18], &m_aCurrentTimeCp[19],
393 &m_aCurrentTimeCp[20], &m_aCurrentTimeCp[21], &m_aCurrentTimeCp[22], &m_aCurrentTimeCp[23], &m_aCurrentTimeCp[24],
394 &m_NotEligibleForFinish,
395 &m_HasTelegunGun, &m_HasTelegunLaser, &m_HasTelegunGrenade,
396 m_aGameUuid,
397 &m_HookedPlayer, &m_NewHook,
398 &m_InputDirection, &m_InputJump, &m_InputFire, &m_InputHook,
399 &m_ReloadTimer,
400 &m_TeeStarted,
401 &m_LiveFrozen,
402 &m_Ninja.m_ActivationDir.x, &m_Ninja.m_ActivationDir.y, &m_Ninja.m_ActivationTick, &m_Ninja.m_CurrentMoveTime, &m_Ninja.m_OldVelAmount);
403 switch(Num) // Don't forget to update this when you save / load more / less.
404 {
405 case 96:
406 m_NotEligibleForFinish = false;
407 [[fallthrough]];
408 case 97:
409 m_HasTelegunGrenade = 0;
410 m_HasTelegunLaser = 0;
411 m_HasTelegunGun = 0;
412 FormatUuid(Uuid: CalculateUuid(pName: "game-uuid-nonexistent@ddnet.tw"), pBuffer: m_aGameUuid, BufferLength: sizeof(m_aGameUuid));
413 [[fallthrough]];
414 case 101:
415 m_HookedPlayer = -1;
416 m_NewHook = false;
417 if(m_HookState == HOOK_GRABBED)
418 m_HookState = HOOK_FLYING;
419 m_InputDirection = 0;
420 m_InputJump = 0;
421 m_InputFire = 0;
422 m_InputHook = 0;
423 m_ReloadTimer = 0;
424 [[fallthrough]];
425 case 108:
426 m_TeeStarted = true;
427 [[fallthrough]];
428 case 109:
429 m_LiveFrozen = false;
430 [[fallthrough]];
431 case 110:
432 if(m_aWeapons[WEAPON_NINJA].m_Got)
433 {
434 // remove ninja
435 m_aWeapons[WEAPON_NINJA].m_Got = false;
436 m_aWeapons[WEAPON_NINJA].m_Ammo = 0;
437 m_ActiveWeapon = m_LastWeapon;
438 }
439 m_Ninja.m_ActivationDir.x = 0.0;
440 m_Ninja.m_ActivationDir.y = 0.0;
441 m_Ninja.m_ActivationTick = 0;
442 m_Ninja.m_CurrentMoveTime = 0;
443 m_Ninja.m_OldVelAmount = 0;
444 [[fallthrough]];
445 case 115:
446 return 0;
447 default:
448 dbg_msg(sys: "load", fmt: "failed to load tee-string");
449 dbg_msg(sys: "load", fmt: "loaded %d vars", Num);
450 return Num + 1; // never 0 here
451 }
452}
453
454void CSaveTee::LoadHookedPlayer(const CSaveTeam *pTeam)
455{
456 if(m_HookedPlayer == -1)
457 return;
458 m_HookedPlayer = pTeam->m_pSavedTees[m_HookedPlayer].GetClientId();
459}
460
461bool CSaveTee::IsHooking() const
462{
463 return m_HookState == HOOK_GRABBED || m_HookState == HOOK_FLYING;
464}
465
466CSaveTeam::CSaveTeam()
467{
468 m_aString[0] = '\0';
469}
470
471CSaveTeam::~CSaveTeam()
472{
473 delete[] m_pSwitchers;
474 delete[] m_pSavedTees;
475}
476
477int CSaveTeam::Save(CGameContext *pGameServer, int Team, bool Dry)
478{
479 if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && (Team <= 0 || MAX_CLIENTS <= Team))
480 return 1;
481
482 IGameController *pController = pGameServer->m_pController;
483 CGameTeams *pTeams = &pController->Teams();
484
485 if(pTeams->TeamFlock(Team))
486 {
487 return 5;
488 }
489
490 m_MembersCount = pTeams->Count(Team);
491 if(m_MembersCount <= 0)
492 {
493 return 2;
494 }
495
496 m_TeamState = pTeams->GetTeamState(Team);
497
498 if(m_TeamState != CGameTeams::TEAMSTATE_STARTED)
499 {
500 return 4;
501 }
502
503 m_HighestSwitchNumber = pGameServer->Collision()->m_HighestSwitchNumber;
504 m_TeamLocked = pTeams->TeamLocked(Team);
505 m_Practice = pTeams->IsPractice(Team);
506
507 m_pSavedTees = new CSaveTee[m_MembersCount];
508 int aPlayerCids[MAX_CLIENTS];
509 int j = 0;
510 CCharacter *p = (CCharacter *)pGameServer->m_World.FindFirst(Type: CGameWorld::ENTTYPE_CHARACTER);
511 for(; p; p = (CCharacter *)p->TypeNext())
512 {
513 if(pTeams->m_Core.Team(ClientId: p->GetPlayer()->GetCid()) != Team)
514 continue;
515 if(m_MembersCount == j)
516 return 3;
517 m_pSavedTees[j].Save(pChr: p);
518 aPlayerCids[j] = p->GetPlayer()->GetCid();
519 j++;
520 }
521 if(m_MembersCount != j)
522 return 3;
523
524 if(pGameServer->Collision()->m_HighestSwitchNumber)
525 {
526 m_pSwitchers = new SSimpleSwitchers[pGameServer->Collision()->m_HighestSwitchNumber + 1];
527
528 for(int i = 1; i < pGameServer->Collision()->m_HighestSwitchNumber + 1; i++)
529 {
530 m_pSwitchers[i].m_Status = pGameServer->Switchers()[i].m_aStatus[Team];
531 if(pGameServer->Switchers()[i].m_aEndTick[Team])
532 m_pSwitchers[i].m_EndTime = pController->Server()->Tick() - pGameServer->Switchers()[i].m_aEndTick[Team];
533 else
534 m_pSwitchers[i].m_EndTime = 0;
535 m_pSwitchers[i].m_Type = pGameServer->Switchers()[i].m_aType[Team];
536 }
537 }
538 if(!Dry)
539 {
540 pGameServer->m_World.RemoveEntitiesFromPlayers(PlayerIds: aPlayerCids, NumPlayers: m_MembersCount);
541 }
542 return 0;
543}
544
545bool CSaveTeam::HandleSaveError(int Result, int ClientId, CGameContext *pGameContext)
546{
547 switch(Result)
548 {
549 case 0:
550 return false;
551 case 1:
552 pGameContext->SendChatTarget(To: ClientId, pText: "You have to be in a team (from 1-63)");
553 break;
554 case 2:
555 pGameContext->SendChatTarget(To: ClientId, pText: "Could not find your Team");
556 break;
557 case 3:
558 pGameContext->SendChatTarget(To: ClientId, pText: "To save all players in your team have to be alive and not in '/spec'");
559 break;
560 case 4:
561 pGameContext->SendChatTarget(To: ClientId, pText: "Your team has not started yet");
562 break;
563 case 5:
564 pGameContext->SendChatTarget(To: ClientId, pText: "Team can't be saved while in team 0 mode");
565 break;
566 default: // this state should never be reached
567 pGameContext->SendChatTarget(To: ClientId, pText: "Unknown error while saving");
568 break;
569 }
570 return true;
571}
572
573void CSaveTeam::Load(CGameContext *pGameServer, int Team, bool KeepCurrentWeakStrong)
574{
575 IGameController *pController = pGameServer->m_pController;
576 CGameTeams *pTeams = &pController->Teams();
577
578 pTeams->ChangeTeamState(Team, State: m_TeamState);
579 pTeams->SetTeamLock(Team, Lock: m_TeamLocked);
580 pTeams->SetPractice(Team, Enabled: m_Practice);
581
582 int aPlayerCids[MAX_CLIENTS];
583 for(int i = m_MembersCount; i-- > 0;)
584 {
585 int ClientId = m_pSavedTees[i].GetClientId();
586 aPlayerCids[i] = ClientId;
587 if(pGameServer->m_apPlayers[ClientId] && pTeams->m_Core.Team(ClientId) == Team)
588 {
589 CCharacter *pChr = MatchCharacter(pGameServer, ClientId: m_pSavedTees[i].GetClientId(), SaveId: i, KeepCurrentCharacter: KeepCurrentWeakStrong);
590 m_pSavedTees[i].Load(pChr, Team);
591 }
592 }
593
594 if(pGameServer->Collision()->m_HighestSwitchNumber)
595 {
596 for(int i = 1; i < minimum(a: m_HighestSwitchNumber, b: pGameServer->Collision()->m_HighestSwitchNumber) + 1; i++)
597 {
598 pGameServer->Switchers()[i].m_aStatus[Team] = m_pSwitchers[i].m_Status;
599 if(m_pSwitchers[i].m_EndTime)
600 pGameServer->Switchers()[i].m_aEndTick[Team] = pController->Server()->Tick() - m_pSwitchers[i].m_EndTime;
601 pGameServer->Switchers()[i].m_aType[Team] = m_pSwitchers[i].m_Type;
602 }
603 }
604 // remove projectiles and laser
605 pGameServer->m_World.RemoveEntitiesFromPlayers(PlayerIds: aPlayerCids, NumPlayers: m_MembersCount);
606}
607
608CCharacter *CSaveTeam::MatchCharacter(CGameContext *pGameServer, int ClientId, int SaveId, bool KeepCurrentCharacter) const
609{
610 if(KeepCurrentCharacter && pGameServer->m_apPlayers[ClientId]->GetCharacter())
611 {
612 // keep old character to retain current weak/strong order
613 return pGameServer->m_apPlayers[ClientId]->GetCharacter();
614 }
615 pGameServer->m_apPlayers[ClientId]->KillCharacter(Weapon: WEAPON_GAME);
616 return pGameServer->m_apPlayers[ClientId]->ForceSpawn(Pos: m_pSavedTees[SaveId].GetPos());
617}
618
619char *CSaveTeam::GetString()
620{
621 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);
622
623 for(int i = 0; i < m_MembersCount; i++)
624 {
625 char aBuf[1024];
626 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "\n%s", m_pSavedTees[i].GetString(pTeam: this));
627 str_append(dst&: m_aString, src: aBuf);
628 }
629
630 if(m_pSwitchers && m_HighestSwitchNumber)
631 {
632 for(int i = 1; i < m_HighestSwitchNumber + 1; i++)
633 {
634 char aBuf[64];
635 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);
636 str_append(dst&: m_aString, src: aBuf);
637 }
638 }
639
640 return m_aString;
641}
642
643int CSaveTeam::FromString(const char *pString)
644{
645 char aTeamStats[MAX_CLIENTS];
646 char aSwitcher[64];
647 char aSaveTee[1024];
648
649 char *pCopyPos;
650 unsigned int Pos = 0;
651 unsigned int LastPos = 0;
652 unsigned int StrSize;
653
654 str_copy(dst: m_aString, src: pString, dst_size: sizeof(m_aString));
655
656 while(m_aString[Pos] != '\n' && Pos < sizeof(m_aString) && m_aString[Pos]) // find next \n or \0
657 Pos++;
658
659 pCopyPos = m_aString + LastPos;
660 StrSize = Pos - LastPos + 1;
661 if(m_aString[Pos] == '\n')
662 {
663 Pos++; // skip \n
664 LastPos = Pos;
665 }
666
667 if(StrSize <= 0)
668 {
669 dbg_msg(sys: "load", fmt: "savegame: wrong format (couldn't load teamstats)");
670 return 1;
671 }
672
673 if(StrSize < sizeof(aTeamStats))
674 {
675 str_copy(dst: aTeamStats, src: pCopyPos, dst_size: StrSize);
676 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);
677 switch(Num) // Don't forget to update this when you save / load more / less.
678 {
679 case 4:
680 m_Practice = false;
681 [[fallthrough]];
682 case 5:
683 break;
684 default:
685 dbg_msg(sys: "load", fmt: "failed to load teamstats");
686 dbg_msg(sys: "load", fmt: "loaded %d vars", Num);
687 return Num + 1; // never 0 here
688 }
689 }
690 else
691 {
692 dbg_msg(sys: "load", fmt: "savegame: wrong format (couldn't load teamstats, too big)");
693 return 1;
694 }
695
696 if(m_pSavedTees)
697 {
698 delete[] m_pSavedTees;
699 m_pSavedTees = 0;
700 }
701
702 if(m_MembersCount > 64)
703 {
704 dbg_msg(sys: "load", fmt: "savegame: team has too many players");
705 return 1;
706 }
707 else if(m_MembersCount)
708 {
709 m_pSavedTees = new CSaveTee[m_MembersCount];
710 }
711
712 for(int n = 0; n < m_MembersCount; n++)
713 {
714 while(m_aString[Pos] != '\n' && Pos < sizeof(m_aString) && m_aString[Pos]) // find next \n or \0
715 Pos++;
716
717 pCopyPos = m_aString + LastPos;
718 StrSize = Pos - LastPos + 1;
719 if(m_aString[Pos] == '\n')
720 {
721 Pos++; // skip \n
722 LastPos = Pos;
723 }
724
725 if(StrSize <= 0)
726 {
727 dbg_msg(sys: "load", fmt: "savegame: wrong format (couldn't load tee)");
728 return 1;
729 }
730
731 if(StrSize < sizeof(aSaveTee))
732 {
733 str_copy(dst: aSaveTee, src: pCopyPos, dst_size: StrSize);
734 int Num = m_pSavedTees[n].FromString(pString: aSaveTee);
735 if(Num)
736 {
737 dbg_msg(sys: "load", fmt: "failed to load tee");
738 dbg_msg(sys: "load", fmt: "loaded %d vars", Num - 1);
739 return 1;
740 }
741 }
742 else
743 {
744 dbg_msg(sys: "load", fmt: "savegame: wrong format (couldn't load tee, too big)");
745 return 1;
746 }
747 }
748
749 if(m_pSwitchers)
750 {
751 delete[] m_pSwitchers;
752 m_pSwitchers = 0;
753 }
754
755 if(m_HighestSwitchNumber)
756 m_pSwitchers = new SSimpleSwitchers[m_HighestSwitchNumber + 1];
757
758 for(int n = 1; n < m_HighestSwitchNumber + 1; n++)
759 {
760 while(m_aString[Pos] != '\n' && Pos < sizeof(m_aString) && m_aString[Pos]) // find next \n or \0
761 Pos++;
762
763 pCopyPos = m_aString + LastPos;
764 StrSize = Pos - LastPos + 1;
765 if(m_aString[Pos] == '\n')
766 {
767 Pos++; // skip \n
768 LastPos = Pos;
769 }
770
771 if(StrSize <= 0)
772 {
773 dbg_msg(sys: "load", fmt: "savegame: wrong format (couldn't load switcher)");
774 return 1;
775 }
776
777 if(StrSize < sizeof(aSwitcher))
778 {
779 str_copy(dst: aSwitcher, src: pCopyPos, dst_size: StrSize);
780 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));
781 if(Num != 3)
782 {
783 dbg_msg(sys: "load", fmt: "failed to load switcher");
784 dbg_msg(sys: "load", fmt: "loaded %d vars", Num - 1);
785 }
786 }
787 else
788 {
789 dbg_msg(sys: "load", fmt: "savegame: wrong format (couldn't load switcher, too big)");
790 return 1;
791 }
792 }
793
794 return 0;
795}
796
797bool CSaveTeam::MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientId, int NumPlayer, char *pMessage, int MessageLen) const
798{
799 if(NumPlayer > m_MembersCount)
800 {
801 str_format(buffer: pMessage, buffer_size: MessageLen, format: "Too many players in this team, should be %d", m_MembersCount);
802 return false;
803 }
804 // check for wrong players
805 for(int i = 0; i < NumPlayer; i++)
806 {
807 int Found = false;
808 for(int j = 0; j < m_MembersCount; j++)
809 {
810 if(str_comp(a: paNames[i], b: m_pSavedTees[j].GetName()) == 0)
811 {
812 Found = true;
813 }
814 }
815 if(!Found)
816 {
817 str_format(buffer: pMessage, buffer_size: MessageLen, format: "'%s' doesn't belong to this team", paNames[i]);
818 return false;
819 }
820 }
821 // check for missing players
822 for(int i = 0; i < m_MembersCount; i++)
823 {
824 int Found = false;
825 for(int j = 0; j < NumPlayer; j++)
826 {
827 if(str_comp(a: m_pSavedTees[i].GetName(), b: paNames[j]) == 0)
828 {
829 m_pSavedTees[i].SetClientId(pClientId[j]);
830 Found = true;
831 break;
832 }
833 }
834 if(!Found)
835 {
836 str_format(buffer: pMessage, buffer_size: MessageLen, format: "'%s' has to be in this team", m_pSavedTees[i].GetName());
837 return false;
838 }
839 }
840 // match hook to correct ClientId
841 for(int n = 0; n < m_MembersCount; n++)
842 m_pSavedTees[n].LoadHookedPlayer(pTeam: this);
843 return true;
844}
845