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->ForceSetRescue(RescueMode: RESCUEMODE_AUTO); |
240 | pChr->ForceSetRescue(RescueMode: RESCUEMODE_MANUAL); |
241 | } |
242 | } |
243 | |
244 | char *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 | |
331 | int 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 | |
454 | void CSaveTee::LoadHookedPlayer(const CSaveTeam *pTeam) |
455 | { |
456 | if(m_HookedPlayer == -1) |
457 | return; |
458 | m_HookedPlayer = pTeam->m_pSavedTees[m_HookedPlayer].GetClientId(); |
459 | } |
460 | |
461 | bool CSaveTee::IsHooking() const |
462 | { |
463 | return m_HookState == HOOK_GRABBED || m_HookState == HOOK_FLYING; |
464 | } |
465 | |
466 | CSaveTeam::CSaveTeam() |
467 | { |
468 | m_aString[0] = '\0'; |
469 | } |
470 | |
471 | CSaveTeam::~CSaveTeam() |
472 | { |
473 | delete[] m_pSwitchers; |
474 | delete[] m_pSavedTees; |
475 | } |
476 | |
477 | int 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 | |
545 | bool 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 | |
573 | void 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 | |
608 | CCharacter *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 | |
619 | char *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 | |
643 | int 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 | |
797 | bool 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 | |