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 | |
12 | CSaveTee::CSaveTee() = default; |
13 | |
14 | void 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 | |
124 | void 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 | |
243 | char *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 | |
330 | int 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 | |
453 | void CSaveTee::LoadHookedPlayer(const CSaveTeam *pTeam) |
454 | { |
455 | if(m_HookedPlayer == -1) |
456 | return; |
457 | m_HookedPlayer = pTeam->m_pSavedTees[m_HookedPlayer].GetClientId(); |
458 | } |
459 | |
460 | bool CSaveTee::IsHooking() const |
461 | { |
462 | return m_HookState == HOOK_GRABBED || m_HookState == HOOK_FLYING; |
463 | } |
464 | |
465 | CSaveTeam::CSaveTeam() |
466 | { |
467 | m_aString[0] = '\0'; |
468 | } |
469 | |
470 | CSaveTeam::~CSaveTeam() |
471 | { |
472 | delete[] m_pSwitchers; |
473 | delete[] m_pSavedTees; |
474 | } |
475 | |
476 | int 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 | |
544 | bool 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 | |
572 | void 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 | |
607 | CCharacter *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 | |
618 | char *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 | |
642 | int 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 | |
796 | bool 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 | |