DEADSOFTWARE

Game: Handle load errors correctly
[d2df-sdl.git] / src / game / g_saveload.pas
1 (* Copyright (C) Doom 2D: Forever Developers
2 *
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, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *)
16 {$INCLUDE ../shared/a_modes.inc}
17 unit g_saveload;
19 interface
21 uses
22 SysUtils, Classes,
23 e_graphics, g_phys, g_textures;
26 function g_GetSaveName (n: Integer; out valid: Boolean): AnsiString;
28 function g_SaveGameTo (const filename: AnsiString; const aname: AnsiString; deleteOnError: Boolean=true): Boolean;
29 function g_LoadGameFrom (const filename: AnsiString): Boolean;
31 function g_SaveGame (n: Integer; const aname: AnsiString): Boolean;
32 function g_LoadGame (n: Integer): Boolean;
34 procedure Obj_SaveState (st: TStream; o: PObj);
35 procedure Obj_LoadState (o: PObj; st: TStream);
38 implementation
40 uses
41 MAPDEF, utils, xstreams,
42 g_game, g_items, g_map, g_monsters, g_triggers,
43 g_basic, g_main, Math, wadreader,
44 g_weapons, g_player, g_console,
45 e_log, g_language;
47 const
48 SAVE_SIGNATURE = $56534644; // 'DFSV'
49 SAVE_VERSION = $07;
50 END_MARKER_STRING = 'END';
51 PLAYER_VIEW_SIGNATURE = $57564C50; // 'PLVW'
52 OBJ_SIGNATURE = $4A424F5F; // '_OBJ'
55 procedure Obj_SaveState (st: TStream; o: PObj);
56 begin
57 if (st = nil) then exit;
58 // Ñèãíàòóðà îáúåêòà
59 utils.writeSign(st, '_OBJ');
60 utils.writeInt(st, Byte(0)); // version
61 // Ïîëîæåíèå ïî-ãîðèçîíòàëè
62 utils.writeInt(st, LongInt(o^.X));
63 // Ïîëîæåíèå ïî-âåðòèêàëè
64 utils.writeInt(st, LongInt(o^.Y));
65 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
66 utils.writeInt(st, LongInt(o^.Rect.X));
67 utils.writeInt(st, LongInt(o^.Rect.Y));
68 utils.writeInt(st, Word(o^.Rect.Width));
69 utils.writeInt(st, Word(o^.Rect.Height));
70 // Ñêîðîñòü
71 utils.writeInt(st, LongInt(o^.Vel.X));
72 utils.writeInt(st, LongInt(o^.Vel.Y));
73 // Óñêîðåíèå
74 utils.writeInt(st, LongInt(o^.Accel.X));
75 utils.writeInt(st, LongInt(o^.Accel.Y));
76 end;
79 procedure Obj_LoadState (o: PObj; st: TStream);
80 begin
81 if (st = nil) then exit;
82 // Ñèãíàòóðà îáúåêòà:
83 if not utils.checkSign(st, '_OBJ') then raise XStreamError.Create('invalid object signature');
84 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid object version');
85 // Ïîëîæåíèå ïî-ãîðèçîíòàëè
86 o^.X := utils.readLongInt(st);
87 // Ïîëîæåíèå ïî-âåðòèêàëè
88 o^.Y := utils.readLongInt(st);
89 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
90 o^.Rect.X := utils.readLongInt(st);
91 o^.Rect.Y := utils.readLongInt(st);
92 o^.Rect.Width := utils.readWord(st);
93 o^.Rect.Height := utils.readWord(st);
94 // Ñêîðîñòü
95 o^.Vel.X := utils.readLongInt(st);
96 o^.Vel.Y := utils.readLongInt(st);
97 // Óñêîðåíèå
98 o^.Accel.X := utils.readLongInt(st);
99 o^.Accel.Y := utils.readLongInt(st);
100 end;
103 function buildSaveName (n: Integer): AnsiString;
104 begin
105 result := '';
106 if (n < 0) or (n > 65535) then exit;
107 result := formatstrf('%sSAVGAME%s.DAT', [DataDir, n]);
108 end;
111 function g_GetSaveName (n: Integer; out valid: Boolean): AnsiString;
112 var
113 st: TStream = nil;
114 ver: Byte;
115 stlen: Word;
116 filename: AnsiString;
117 begin
118 valid := false;
119 result := '';
120 if (n < 0) or (n > 65535) then exit;
121 try
122 // Îòêðûâàåì ôàéë ñîõðàíåíèé
123 filename := buildSaveName(n);
124 st := openDiskFileRO(filename);
125 try
126 if not utils.checkSign(st, 'DFSV') then
127 begin
128 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st], TMsgType.Warning);
129 //raise XStreamError.Create('invalid save game signature');
130 exit;
131 end;
132 ver := utils.readByte(st);
133 if (ver < 7) then
134 begin
135 utils.readLongWord(st); // section size
136 stlen := utils.readWord(st);
137 if (stlen < 1) or (stlen > 64) then
138 begin
139 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st], TMsgType.Warning);
140 //raise XStreamError.Create('invalid save game version');
141 exit;
142 end;
143 // Èìÿ ñýéâà
144 SetLength(result, stlen);
145 st.ReadBuffer(result[1], stlen);
146 end
147 else
148 begin
149 // 7+
150 // Èìÿ ñýéâà
151 result := utils.readStr(st, 64);
152 end;
153 valid := (ver = SAVE_VERSION);
154 //if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
155 finally
156 st.Free();
157 end;
158 except
159 begin
160 //e_WriteLog('GetSaveName Error: '+e.message, MSG_WARNING);
161 //{$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
162 result := '';
163 end;
164 end;
165 end;
168 function g_SaveGameTo (const filename: AnsiString; const aname: AnsiString; deleteOnError: Boolean=true): Boolean;
169 var
170 st: TStream = nil;
171 i, k: Integer;
172 PID1, PID2: Word;
173 begin
174 result := false;
175 try
176 st := createDiskFile(filename);
177 try
178 utils.writeSign(st, 'DFSV');
179 utils.writeInt(st, Byte(SAVE_VERSION));
180 // Èìÿ ñýéâà
181 utils.writeStr(st, aname, 64);
182 // Ïîëíûé ïóòü ê âàäó è êàðòà
183 //if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('SAVE: current map is ''%s''...', [gCurrentMapFileName]);
184 utils.writeStr(st, gCurrentMapFileName);
185 // Ïóòü ê êàðòå
186 utils.writeStr(st, gGameSettings.WAD);
187 // Èìÿ êàðòû
188 utils.writeStr(st, g_ExtractFileName(gMapInfo.Map));
189 // Êîëè÷åñòâî èãðîêîâ
190 utils.writeInt(st, Word(g_Player_GetCount));
191 // Èãðîâîå âðåìÿ
192 utils.writeInt(st, LongWord(gTime));
193 // Òèï èãðû
194 utils.writeInt(st, Byte(gGameSettings.GameType));
195 // Ðåæèì èãðû
196 utils.writeInt(st, Byte(gGameSettings.GameMode));
197 // Ëèìèò âðåìåíè
198 utils.writeInt(st, Word(gGameSettings.TimeLimit));
199 // Ëèìèò î÷êîâ
200 utils.writeInt(st, Word(gGameSettings.GoalLimit));
201 // Ëèìèò æèçíåé
202 utils.writeInt(st, Byte(gGameSettings.MaxLives));
203 // Èãðîâûå îïöèè
204 utils.writeInt(st, LongWord(gGameSettings.Options));
205 // Äëÿ êîîïà
206 utils.writeInt(st, Word(gCoopMonstersKilled));
207 utils.writeInt(st, Word(gCoopSecretsFound));
208 utils.writeInt(st, Word(gCoopTotalMonstersKilled));
209 utils.writeInt(st, Word(gCoopTotalSecretsFound));
210 utils.writeInt(st, Word(gCoopTotalMonsters));
211 utils.writeInt(st, Word(gCoopTotalSecrets));
213 ///// Ñîõðàíÿåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
214 utils.writeSign(st, 'PLVW');
215 utils.writeInt(st, Byte(0)); // version
216 PID1 := 0;
217 PID2 := 0;
218 if (gPlayer1 <> nil) then PID1 := gPlayer1.UID;
219 if (gPlayer2 <> nil) then PID2 := gPlayer2.UID;
220 utils.writeInt(st, Word(PID1));
221 utils.writeInt(st, Word(PID2));
222 ///// /////
224 ///// Ñîñòîÿíèå êàðòû /////
225 g_Map_SaveState(st);
226 ///// /////
228 ///// Ñîñòîÿíèå ïðåäìåòîâ /////
229 g_Items_SaveState(st);
230 ///// /////
232 ///// Ñîñòîÿíèå òðèããåðîâ /////
233 g_Triggers_SaveState(st);
234 ///// /////
236 ///// Ñîñòîÿíèå îðóæèÿ /////
237 g_Weapon_SaveState(st);
238 ///// /////
240 ///// Ñîñòîÿíèå ìîíñòðîâ /////
241 g_Monsters_SaveState(st);
242 ///// /////
244 ///// Ñîñòîÿíèå òðóïîâ /////
245 g_Player_Corpses_SaveState(st);
246 ///// /////
248 ///// Ñîõðàíÿåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
249 if (g_Player_GetCount > 0) then
250 begin
251 k := 0;
252 for i := 0 to High(gPlayers) do
253 begin
254 if (gPlayers[i] <> nil) then
255 begin
256 // Ñîñòîÿíèå èãðîêà
257 gPlayers[i].SaveState(st);
258 Inc(k);
259 end;
260 end;
262 // Âñå ëè èãðîêè íà ìåñòå
263 if (k <> g_Player_GetCount) then raise XStreamError.Create('g_SaveGame: wrong players count');
264 end;
265 ///// /////
267 ///// Ìàðêåð îêîí÷àíèÿ /////
268 utils.writeSign(st, 'END');
269 utils.writeInt(st, Byte(0));
270 ///// /////
271 result := true;
272 finally
273 st.Free();
274 end;
276 except
277 on e: Exception do
278 begin
279 st.Free();
280 g_Console_Add(_lc[I_GAME_ERROR_SAVE]);
281 e_WriteLog('SaveState Error: '+e.message, TMsgType.Warning);
282 if deleteOnError then DeleteFile(filename);
283 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
284 result := false;
285 end;
286 end;
287 end;
290 function g_LoadGameFrom (const filename: AnsiString): Boolean;
291 var
292 st: TStream = nil;
293 WAD_Path, Map_Name: AnsiString;
294 nPlayers: Integer;
295 Game_Type, Game_Mode, Game_MaxLives: Byte;
296 Game_TimeLimit, Game_GoalLimit: Word;
297 Game_Time, Game_Options: Cardinal;
298 Game_CoopMonstersKilled,
299 Game_CoopSecretsFound,
300 Game_CoopTotalMonstersKilled,
301 Game_CoopTotalSecretsFound,
302 Game_CoopTotalMonsters,
303 Game_CoopTotalSecrets,
304 PID1, PID2: Word;
305 i: Integer;
306 gameCleared: Boolean = false;
307 curmapfile: AnsiString = '';
308 {$IF DEFINED(D2F_DEBUG)}
309 errpos: LongWord = 0;
310 {$ENDIF}
311 begin
312 result := false;
314 try
315 st := openDiskFileRO(filename);
316 try
317 if not utils.checkSign(st, 'DFSV') then raise XStreamError.Create('invalid save game signature');
318 if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
320 e_WriteLog('Loading saved game...', TMsgType.Notify);
322 {$IF DEFINED(D2F_DEBUG)}try{$ENDIF}
323 //g_Game_Free(false); // don't free textures for the same map
324 g_Game_ClearLoading();
325 g_Game_SetLoadingText(_lc[I_LOAD_SAVE_FILE], 0, False);
326 gLoadGameMode := True;
328 ///// Çàãðóæàåì ñîñòîÿíèå èãðû /////
329 // Èìÿ ñýéâà
330 {str :=} utils.readStr(st, 64);
332 // Ïîëíûé ïóòü ê âàäó è êàðòà
333 curmapfile := utils.readStr(st);
335 if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('LOAD: previous map was ''%s''...', [gCurrentMapFileName]);
336 if (Length(curmapfile) <> 0) then e_LogWritefln('LOAD: new map is ''%s''...', [curmapfile]);
337 // À âîò òóò, íàêîíåö, ÷èñòèì ðåñóðñû
338 g_Game_Free(curmapfile <> gCurrentMapFileName); // don't free textures for the same map
339 gameCleared := true;
341 // Ïóòü ê êàðòå
342 WAD_Path := utils.readStr(st);
343 // Èìÿ êàðòû
344 Map_Name := utils.readStr(st);
345 // Êîëè÷åñòâî èãðîêîâ
346 nPlayers := utils.readWord(st);
347 // Èãðîâîå âðåìÿ
348 Game_Time := utils.readLongWord(st);
349 // Òèï èãðû
350 Game_Type := utils.readByte(st);
351 // Ðåæèì èãðû
352 Game_Mode := utils.readByte(st);
353 // Ëèìèò âðåìåíè
354 Game_TimeLimit := utils.readWord(st);
355 // Ëèìèò î÷êîâ
356 Game_GoalLimit := utils.readWord(st);
357 // Ëèìèò æèçíåé
358 Game_MaxLives := utils.readByte(st);
359 // Èãðîâûå îïöèè
360 Game_Options := utils.readLongWord(st);
361 // Äëÿ êîîïà
362 Game_CoopMonstersKilled := utils.readWord(st);
363 Game_CoopSecretsFound := utils.readWord(st);
364 Game_CoopTotalMonstersKilled := utils.readWord(st);
365 Game_CoopTotalSecretsFound := utils.readWord(st);
366 Game_CoopTotalMonsters := utils.readWord(st);
367 Game_CoopTotalSecrets := utils.readWord(st);
368 ///// /////
370 ///// Çàãðóæàåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
371 if not utils.checkSign(st, 'PLVW') then raise XStreamError.Create('invalid viewport signature');
372 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid viewport version');
373 PID1 := utils.readWord(st);
374 PID2 := utils.readWord(st);
375 ///// /////
377 // Çàãðóæàåì êàðòó:
378 ZeroMemory(@gGameSettings, sizeof(TGameSettings));
379 gAimLine := false;
380 gShowMap := false;
381 if (Game_Type = GT_NONE) or (Game_Type = GT_SINGLE) then
382 begin
383 // Íàñòðîéêè èãðû
384 gGameSettings.GameType := GT_SINGLE;
385 gGameSettings.MaxLives := 0;
386 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_ALLOWEXIT;
387 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_MONSTERS;
388 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_BOTVSMONSTER;
389 gSwitchGameMode := GM_SINGLE;
390 end
391 else
392 begin
393 // Íàñòðîéêè èãðû
394 gGameSettings.GameType := GT_CUSTOM;
395 gGameSettings.GameMode := Game_Mode;
396 gSwitchGameMode := Game_Mode;
397 gGameSettings.TimeLimit := Game_TimeLimit;
398 gGameSettings.GoalLimit := Game_GoalLimit;
399 gGameSettings.MaxLives := IfThen(Game_Mode = GM_CTF, 0, Game_MaxLives);
400 gGameSettings.Options := Game_Options;
401 end;
402 g_Game_ExecuteEvent('ongamestart');
404 // Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ
405 g_Game_SetupScreenSize();
407 // Çàãðóçêà è çàïóñê êàðòû
408 if not g_Game_StartMap(WAD_Path+':\'+Map_Name, True, curmapfile) then
409 begin
410 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [WAD_Path + ':\' + Map_Name]));
411 exit;
412 end;
414 // Íàñòðîéêè èãðîêîâ è áîòîâ
415 g_Player_Init();
417 // Óñòàíàâëèâàåì âðåìÿ
418 gTime := Game_Time;
419 // Âîçâðàùàåì ñòàòû
420 gCoopMonstersKilled := Game_CoopMonstersKilled;
421 gCoopSecretsFound := Game_CoopSecretsFound;
422 gCoopTotalMonstersKilled := Game_CoopTotalMonstersKilled;
423 gCoopTotalSecretsFound := Game_CoopTotalSecretsFound;
424 gCoopTotalMonsters := Game_CoopTotalMonsters;
425 gCoopTotalSecrets := Game_CoopTotalSecrets;
427 ///// Çàãðóæàåì ñîñòîÿíèå êàðòû /////
428 g_Map_LoadState(st);
429 ///// /////
431 ///// Çàãðóæàåì ñîñòîÿíèå ïðåäìåòîâ /////
432 g_Items_LoadState(st);
433 ///// /////
435 ///// Çàãðóæàåì ñîñòîÿíèå òðèããåðîâ /////
436 g_Triggers_LoadState(st);
437 ///// /////
439 ///// Çàãðóæàåì ñîñòîÿíèå îðóæèÿ /////
440 g_Weapon_LoadState(st);
441 ///// /////
443 ///// Çàãðóæàåì ñîñòîÿíèå ìîíñòðîâ /////
444 g_Monsters_LoadState(st);
445 ///// /////
447 ///// Çàãðóæàåì ñîñòîÿíèå òðóïîâ /////
448 g_Player_Corpses_LoadState(st);
449 ///// /////
451 ///// Çàãðóæàåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
452 if nPlayers > 0 then
453 begin
454 // Çàãðóæàåì
455 for i := 0 to nPlayers-1 do g_Player_CreateFromState(st);
456 end;
458 // Ïðèâÿçûâàåì îñíîâíûõ èãðîêîâ ê îáëàñòÿì ïðîñìîòðà
459 gPlayer1 := g_Player_Get(PID1);
460 gPlayer2 := g_Player_Get(PID2);
462 if (gPlayer1 <> nil) then
463 begin
464 gPlayer1.Name := gPlayer1Settings.Name;
465 gPlayer1.FPreferredTeam := gPlayer1Settings.Team;
466 gPlayer1.FActualModelName := gPlayer1Settings.Model;
467 gPlayer1.SetModel(gPlayer1.FActualModelName);
468 gPlayer1.SetColor(gPlayer1Settings.Color);
469 end;
471 if (gPlayer2 <> nil) then
472 begin
473 gPlayer2.Name := gPlayer2Settings.Name;
474 gPlayer2.FPreferredTeam := gPlayer2Settings.Team;
475 gPlayer2.FActualModelName := gPlayer2Settings.Model;
476 gPlayer2.SetModel(gPlayer2.FActualModelName);
477 gPlayer2.SetColor(gPlayer2Settings.Color);
478 end;
479 ///// /////
481 ///// Ìàðêåð îêîí÷àíèÿ /////
482 if not utils.checkSign(st, 'END') then raise XStreamError.Create('no end marker');
483 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid end marker');
484 ///// /////
486 // Èùåì òðèããåðû ñ óñëîâèåì ñìåðòè ìîíñòðîâ
487 if (gTriggers <> nil) then g_Map_ReAdd_DieTriggers();
489 // done
490 gLoadGameMode := false;
491 result := true;
492 {$IF DEFINED(D2F_DEBUG)}
493 except
494 begin
495 errpos := LongWord(st.position);
496 raise;
497 end;
498 end;
499 {$ENDIF}
500 finally
501 st.Free();
502 end;
503 except
504 on e: EFileNotFoundException do
505 begin
506 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
507 g_Console_Add('LoadState Error: '+e.message);
508 e_WriteLog('LoadState Error: '+e.message, TMsgType.Warning);
509 gLoadGameMode := false;
510 result := false;
511 end;
512 on e: Exception do
513 begin
514 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
515 g_Console_Add('LoadState Error: '+e.message);
516 e_WriteLog('LoadState Error: '+e.message, TMsgType.Warning);
517 {$IF DEFINED(D2F_DEBUG)}e_LogWritefln('stream error position: 0x%08x', [errpos], TMsgType.Warning);{$ENDIF}
518 gLoadGameMode := false;
519 result := false;
520 if gState <> STATE_MENU then
521 g_FatalError(_lc[I_GAME_ERROR_LOAD])
522 else if not gameCleared then
523 g_Game_Free();
524 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
525 end;
526 end;
527 end;
530 function g_SaveGame (n: Integer; const aname: AnsiString): Boolean;
531 begin
532 result := false;
533 if (n < 0) or (n > 65535) then exit;
534 result := g_SaveGameTo(buildSaveName(n), aname, true);
535 end;
538 function g_LoadGame (n: Integer): Boolean;
539 begin
540 result := false;
541 if (n < 0) or (n > 65535) then exit;
542 result := g_LoadGameFrom(buildSaveName(n));
543 end;
546 end.