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