saveload: fix read/write unexisting value
[d2df-sdl.git] / src / game / g_saveload.pas
blobd64469479fa7c0d10bdfb094c174bc6a05e38923
1 (* Copyright (C) Doom 2D: Forever Developers
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 {$INCLUDE ../shared/a_modes.inc}
16 unit g_saveload;
18 interface
20 uses
21 SysUtils, Classes,
22 e_graphics, g_phys, g_textures;
25 function g_GetSaveName (n: Integer; out valid: Boolean): AnsiString;
27 function g_SaveGameTo (const filename: AnsiString; const aname: AnsiString; deleteOnError: Boolean=true): Boolean;
28 function g_LoadGameFrom (const filename: AnsiString): Boolean;
30 function g_SaveGame (n: Integer; const aname: AnsiString): Boolean;
31 function g_LoadGame (n: Integer): Boolean;
33 procedure Obj_SaveState (st: TStream; o: PObj);
34 procedure Obj_LoadState (o: PObj; st: TStream);
37 implementation
39 uses
40 MAPDEF, utils, xstreams,
41 g_game, g_items, g_map, g_monsters, g_triggers,
42 g_basic, g_main, Math, wadreader,
43 g_weapons, g_player, g_console,
44 e_log, e_res, g_language;
46 const
47 SAVE_SIGNATURE = $56534644; // 'DFSV'
48 SAVE_VERSION = $07;
49 END_MARKER_STRING = 'END';
50 PLAYER_VIEW_SIGNATURE = $57564C50; // 'PLVW'
51 OBJ_SIGNATURE = $4A424F5F; // '_OBJ'
54 procedure Obj_SaveState (st: TStream; o: PObj);
55 begin
56 if st = nil then Exit;
58 // Ñèãíàòóðà îáúåêòà
59 utils.writeSign(st, '_OBJ');
60 st.WriteByte(0); // version
62 // Ïîëîæåíèå
63 st.WriteInt32LE(o^.X);
64 st.WriteInt32LE(o^.Y);
66 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
67 st.WriteInt32LE(o^.Rect.X);
68 st.WriteInt32LE(o^.Rect.Y);
69 st.WriteWordLE(o^.Rect.Width);
70 st.WriteWordLE(o^.Rect.Height);
72 // Ñêîðîñòü
73 st.WriteInt32LE(o^.Vel.X);
74 st.WriteInt32LE(o^.Vel.Y);
76 // Óñêîðåíèå
77 st.WriteInt32LE(o^.Accel.X);
78 st.WriteInt32LE(o^.Accel.Y);
79 end;
82 procedure Obj_LoadState (o: PObj; st: TStream);
83 begin
84 if st = nil then Exit;
86 // Ñèãíàòóðà îáúåêòà:
87 if not utils.checkSign(st, '_OBJ') then
88 Raise XStreamError.Create('invalid object signature');
89 if st.ReadByte() <> 0 then
90 Raise XStreamError.Create('invalid object version');
92 // Ïîëîæåíèå
93 o^.X := st.ReadInt32LE();
94 o^.Y := st.ReadInt32LE();
96 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
97 o^.Rect.X := st.ReadInt32LE();
98 o^.Rect.Y := st.ReadInt32LE();
99 o^.Rect.Width := st.ReadWordLE();
100 o^.Rect.Height := st.ReadWordLE();
102 // Ñêîðîñòü
103 o^.Vel.X := st.ReadInt32LE();
104 o^.Vel.Y := st.ReadInt32LE();
106 // Óñêîðåíèå
107 o^.Accel.X := st.ReadInt32LE();
108 o^.Accel.Y := st.ReadInt32LE();
109 end;
112 function buildSaveName (n: Integer): AnsiString;
113 begin
114 result := 'SAVGAME' + IntToStr(n) + '.DAT'
115 end;
118 function g_GetSaveName (n: Integer; out valid: Boolean): AnsiString;
120 st: TStream = nil;
121 ver: Byte;
122 stlen: Word;
123 filename: AnsiString;
124 begin
125 valid := false;
126 result := '';
127 if (n < 0) or (n > 65535) then exit;
129 // Îòêðûâàåì ôàéë ñîõðàíåíèé
130 filename := buildSaveName(n);
131 st := e_OpenResourceRO(SaveDirs, filename);
133 if not utils.checkSign(st, 'DFSV') then
134 begin
135 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st], TMsgType.Warning);
136 //raise XStreamError.Create('invalid save game signature');
137 exit;
138 end;
139 ver := st.ReadByte();
140 if ver < 7 then
141 begin
142 st.ReadDWordLE(); // section size
143 stlen := st.ReadWordLE();
144 if (stlen < 1) or (stlen > 64) then
145 begin
146 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st], TMsgType.Warning);
147 //raise XStreamError.Create('invalid save game version');
148 exit;
149 end;
150 // Èìÿ ñýéâà
151 SetLength(result, stlen);
152 st.ReadBuffer(result[1], stlen);
154 else
155 begin
156 // 7+
157 // Èìÿ ñýéâà
158 result := utils.readStr(st, 64);
159 end;
160 valid := (ver = SAVE_VERSION);
161 //if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
162 finally
163 st.Free();
164 end;
165 except
166 begin
167 //e_WriteLog('GetSaveName Error: '+e.message, MSG_WARNING);
168 //{$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
169 result := '';
170 end;
171 end;
172 end;
175 function g_SaveGameTo (const filename: AnsiString; const aname: AnsiString; deleteOnError: Boolean=true): Boolean;
177 st: TStream = nil;
178 i, k: Integer;
179 PID1, PID2: Word;
180 begin
181 Result := False;
183 st := e_CreateResource(SaveDirs, filename);
185 utils.writeSign(st, 'DFSV');
186 st.WriteByte(SAVE_VERSION);
187 utils.writeStr(st, aname, 64); // Èìÿ ñýéâà
189 // Ïîëíûé ïóòü ê âàäó è êàðòà
190 //if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('SAVE: current map is ''%s''...', [gCurrentMapFileName]);
191 utils.writeStr(st, gCurrentMapFileName);
193 utils.writeStr(st, ExtractFileName(gGameSettings.WAD)); // Ïóòü ê êàðòå
194 utils.writeStr(st, g_ExtractFileName(gMapInfo.Map)); // Èìÿ êàðòû
195 st.WriteWordLE(g_Player_GetCount); // Êîëè÷åñòâî èãðîêîâ
196 st.WriteDWordLE(gTime); // Èãðîâîå âðåìÿ
197 st.WriteByte(gGameSettings.GameType); // Òèï èãðû
198 st.WriteByte(gGameSettings.GameMode); // Ðåæèì èãðû
199 st.WriteWordLE(gGameSettings.TimeLimit); // Ëèìèò âðåìåíè
200 st.WriteWordLE(gGameSettings.ScoreLimit); // Ëèìèò î÷êîâ
201 st.WriteByte(gGameSettings.MaxLives); // Ëèìèò æèçíåé
202 st.WriteDWordLE(LongWord(gGameSettings.Options)); // Èãðîâûå îïöèè
204 // Äëÿ êîîïà
205 st.WriteWordLE(gCoopMonstersKilled);
206 st.WriteWordLE(gCoopSecretsFound);
207 st.WriteWordLE(gCoopTotalMonstersKilled);
208 st.WriteWordLE(gCoopTotalSecretsFound);
209 st.WriteWordLE(gCoopTotalMonsters);
210 st.WriteWordLE(gCoopTotalSecrets);
212 ///// Ñîõðàíÿåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
213 utils.writeSign(st, 'PLVW');
214 st.WriteByte(0); // version
215 PID1 := 0;
216 PID2 := 0;
217 if (gPlayer1 <> nil) then PID1 := gPlayer1.UID;
218 if (gPlayer2 <> nil) then PID2 := gPlayer2.UID;
219 st.WriteWordLE(PID1);
220 st.WriteWordLE(PID2);
221 ///// /////
223 g_Map_SaveState(st); // Ñîñòîÿíèå êàðòû
224 g_Items_SaveState(st); // Ñîñòîÿíèå ïðåäìåòîâ
225 g_Triggers_SaveState(st); // Ñîñòîÿíèå òðèããåðîâ
226 g_Weapon_SaveState(st); // Ñîñòîÿíèå îðóæèÿ
227 g_Monsters_SaveState(st); // Ñîñòîÿíèå ìîíñòðîâ
228 g_Player_Corpses_SaveState(st); // Ñîñòîÿíèå òðóïîâ
230 ///// Ñîõðàíÿåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
231 if g_Player_GetCount > 0 then
232 begin
233 k := 0;
234 for i := 0 to High(gPlayers) do
235 begin
236 if gPlayers[i] <> nil then
237 begin
238 // Ñîñòîÿíèå èãðîêà
239 gPlayers[i].SaveState(st);
240 k += 1;
241 end;
242 end;
244 // Âñå ëè èãðîêè íà ìåñòå
245 if k <> g_Player_GetCount then
246 Raise XStreamError.Create('g_SaveGame: wrong players count');
247 end;
248 ///// /////
250 ///// Ìàðêåð îêîí÷àíèÿ /////
251 utils.writeSign(st, 'END');
252 st.WriteByte(0);
253 ///// /////
254 Result := True;
255 finally
256 st.Free();
257 end;
259 except
260 on e: Exception do
261 begin
262 st.Free();
263 g_Console_Add(_lc[I_GAME_ERROR_SAVE]);
264 e_WriteLog('SaveState Error: '+e.message, TMsgType.Warning);
265 if deleteOnError then DeleteFile(filename);
266 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
267 Result := False;
268 end;
269 end;
270 end;
273 function g_LoadGameFrom (const filename: AnsiString): Boolean;
275 st: TStream = nil;
276 WAD_Path, Map_Name: AnsiString;
277 nPlayers: Integer;
278 Game_Type, Game_Mode, Game_MaxLives: Byte;
279 Game_TimeLimit, Game_ScoreLimit: Word;
280 Game_Time, Game_Options: Cardinal;
281 Game_CoopMonstersKilled,
282 Game_CoopSecretsFound,
283 Game_CoopTotalMonstersKilled,
284 Game_CoopTotalSecretsFound,
285 Game_CoopTotalMonsters,
286 Game_CoopTotalSecrets,
287 PID1, PID2: Word;
288 i: Integer;
289 gameCleared: Boolean = false;
290 curmapfile: AnsiString = '';
291 {$IF DEFINED(D2F_DEBUG)}
292 errpos: LongWord = 0;
293 {$ENDIF}
294 begin
295 result := false;
298 st := e_OpenResourceRO(SaveDirs, filename);
300 if not utils.checkSign(st, 'DFSV') then
301 Raise XStreamError.Create('invalid save game signature');
302 if st.ReadByte() <> SAVE_VERSION then
303 Raise XStreamError.Create('invalid save game version');
305 e_WriteLog('Loading saved game...', TMsgType.Notify);
307 {$IF DEFINED(D2F_DEBUG)}
309 {$ENDIF}
310 //g_Game_Free(false); // don't free textures for the same map
311 g_Game_ClearLoading();
312 g_Game_SetLoadingText(_lc[I_LOAD_SAVE_FILE], 0, False);
313 gLoadGameMode := True;
315 ///// Çàãðóæàåì ñîñòîÿíèå èãðû /////
316 // Èìÿ ñýéâà
317 {str :=} utils.readStr(st, 64);
319 // Ïîëíûé ïóòü ê âàäó è êàðòà
320 curmapfile := utils.readStr(st);
322 if Length(gCurrentMapFileName) <> 0 then
323 e_LogWritefln('LOAD: previous map was ''%s''...', [gCurrentMapFileName]);
324 if Length(curmapfile) <> 0 then
325 e_LogWritefln('LOAD: new map is ''%s''...', [curmapfile]);
327 // À âîò òóò, íàêîíåö, ÷èñòèì ðåñóðñû
328 g_Game_Free(curmapfile <> gCurrentMapFileName); // don't free textures for the same map
329 gameCleared := True;
331 WAD_Path := utils.readStr(st); // Ïóòü ê êàðòå
332 Map_Name := utils.readStr(st); // Èìÿ êàðòû
333 nPlayers := st.ReadWordLE(); // Êîëè÷åñòâî èãðîêîâ
334 Game_Time := st.ReadDWordLE(); // Èãðîâîå âðåìÿ
335 Game_Type := st.ReadByte(); // Òèï èãðû
336 Game_Mode := st.ReadByte(); // Ðåæèì èãðû
337 Game_TimeLimit := st.ReadWordLE(); // Ëèìèò âðåìåíè
338 Game_ScoreLimit := st.ReadWordLE(); // Ëèìèò î÷êîâ
339 Game_MaxLives := st.ReadByte(); // Ëèìèò æèçíåé
340 Game_Options := st.ReadDWordLE(); // Èãðîâûå îïöèè
342 // Äëÿ êîîïà
343 Game_CoopMonstersKilled := st.ReadWordLE();
344 Game_CoopSecretsFound := st.ReadWordLE();
345 Game_CoopTotalMonstersKilled := st.ReadWordLE();
346 Game_CoopTotalSecretsFound := st.ReadWordLE();
347 Game_CoopTotalMonsters := st.ReadWordLE();
348 Game_CoopTotalSecrets := st.ReadWordLE();
349 ///// /////
351 ///// Çàãðóæàåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
352 if not utils.checkSign(st, 'PLVW') then
353 Raise XStreamError.Create('invalid viewport signature');
354 if st.ReadByte() <> 0 then
355 Raise XStreamError.Create('invalid viewport version');
356 PID1 := st.ReadWordLE();
357 PID2 := st.ReadWordLE();
358 ///// /////
360 // Çàãðóæàåì êàðòó:
361 gGameSettings := Default(TGameSettings);
362 gAimLine := False;
363 gShowMap := False;
365 // Íàñòðîéêè èãðû
366 gGameSettings.GameType := Game_Type;
367 gGameSettings.GameMode := Game_Mode;
368 gGameSettings.TimeLimit := Game_TimeLimit;
369 gGameSettings.ScoreLimit := Game_ScoreLimit;
370 gGameSettings.MaxLives := Game_MaxLives;
371 gGameSettings.Options := TGameOptions(Game_Options);
372 gSwitchGameMode := Game_Mode;
373 g_Game_ExecuteEvent('ongamestart');
375 // Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ
376 g_Game_SetupScreenSize();
378 // Çàãðóçêà è çàïóñê êàðòû
379 // FIXME: save/load `asMegawad`
380 if not g_Game_StartMap(False{asMegawad}, WAD_Path+':\'+Map_Name, True, curmapfile) then
381 Raise Exception.Create(Format(_lc[I_GAME_ERROR_MAP_LOAD], [WAD_Path + ':\' + Map_Name]));
383 g_Player_Init(); // Íàñòðîéêè èãðîêîâ è áîòîâ
384 gTime := Game_Time; // Óñòàíàâëèâàåì âðåìÿ
386 // Âîçâðàùàåì ñòàòû
387 gCoopMonstersKilled := Game_CoopMonstersKilled;
388 gCoopSecretsFound := Game_CoopSecretsFound;
389 gCoopTotalMonstersKilled := Game_CoopTotalMonstersKilled;
390 gCoopTotalSecretsFound := Game_CoopTotalSecretsFound;
391 gCoopTotalMonsters := Game_CoopTotalMonsters;
392 gCoopTotalSecrets := Game_CoopTotalSecrets;
394 g_Map_LoadState(st); // Çàãðóæàåì ñîñòîÿíèå êàðòû
395 g_Items_LoadState(st); // Çàãðóæàåì ñîñòîÿíèå ïðåäìåòîâ
396 g_Triggers_LoadState(st); // Çàãðóæàåì ñîñòîÿíèå òðèããåðîâ
397 g_Weapon_LoadState(st); // Çàãðóæàåì ñîñòîÿíèå îðóæèÿ
398 g_Monsters_LoadState(st); // Çàãðóæàåì ñîñòîÿíèå ìîíñòðîâ
399 g_Player_Corpses_LoadState(st); // Çàãðóæàåì ñîñòîÿíèå òðóïîâ
401 ///// Çàãðóæàåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
402 for i := 0 to nPlayers-1 do
403 g_Player_CreateFromState(st);
405 // Ïðèâÿçûâàåì îñíîâíûõ èãðîêîâ ê îáëàñòÿì ïðîñìîòðà
406 gPlayer1 := g_Player_Get(PID1);
407 gPlayer2 := g_Player_Get(PID2);
409 if gPlayer1 <> nil then
410 begin
411 gPlayer1.Name := gPlayer1Settings.Name;
412 gPlayer1.FPreferredTeam := gPlayer1Settings.Team;
413 gPlayer1.FActualModelName := gPlayer1Settings.Model;
414 gPlayer1.SetModel(gPlayer1.FActualModelName);
415 gPlayer1.SetColor(gPlayer1Settings.Color);
416 end;
418 if gPlayer2 <> nil then
419 begin
420 gPlayer2.Name := gPlayer2Settings.Name;
421 gPlayer2.FPreferredTeam := gPlayer2Settings.Team;
422 gPlayer2.FActualModelName := gPlayer2Settings.Model;
423 gPlayer2.SetModel(gPlayer2.FActualModelName);
424 gPlayer2.SetColor(gPlayer2Settings.Color);
425 end;
426 ///// /////
428 ///// Ìàðêåð îêîí÷àíèÿ /////
429 if not utils.checkSign(st, 'END') then
430 Raise XStreamError.Create('no end marker');
431 if st.ReadByte() <> 0 then
432 Raise XStreamError.Create('invalid end marker');
433 ///// /////
435 // Èùåì òðèããåðû ñ óñëîâèåì ñìåðòè ìîíñòðîâ
436 if gTriggers <> nil then g_Map_ReAdd_DieTriggers();
438 // done
439 gLoadGameMode := False;
440 Result := True;
441 {$IF DEFINED(D2F_DEBUG)}
442 except
443 begin
444 errpos := LongWord(st.position);
445 raise;
446 end;
447 end;
448 {$ENDIF}
449 finally
450 st.Free();
451 end;
452 except
453 on e: EFileNotFoundException do
454 begin
455 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
456 g_Console_Add('LoadState Error: '+e.message);
457 e_WriteLog('LoadState Error: '+e.message, TMsgType.Warning);
458 gLoadGameMode := false;
459 result := false;
460 end;
461 on e: Exception do
462 begin
463 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
464 g_Console_Add('LoadState Error: '+e.message);
465 e_WriteLog('LoadState Error: '+e.message, TMsgType.Warning);
466 {$IF DEFINED(D2F_DEBUG)}e_LogWritefln('stream error position: 0x%08x', [errpos], TMsgType.Warning);{$ENDIF}
467 gLoadGameMode := false;
468 result := false;
469 if gState <> STATE_MENU then
470 g_FatalError(_lc[I_GAME_ERROR_LOAD])
471 else if not gameCleared then
472 g_Game_Free();
473 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
474 end;
475 end;
476 end;
479 function g_SaveGame (n: Integer; const aname: AnsiString): Boolean;
480 begin
481 result := g_SaveGameTo(buildSaveName(n), aname, true);
482 end;
485 function g_LoadGame (n: Integer): Boolean;
486 begin
487 result := g_LoadGameFrom(buildSaveName(n));
488 end;
491 end.