DEADSOFTWARE

f311bb8f5a89ad8429c34646818c1d32d4ec1b87
[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, version 3 of the License ONLY.
6 *
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.
11 *
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/>.
14 *)
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;
57 // Ñèãíàòóðà îáúåêòà
58 utils.writeSign(st, '_OBJ');
59 utils.writeInt(st, Byte(0)); // version
60 // Ïîëîæåíèå ïî-ãîðèçîíòàëè
61 utils.writeInt(st, LongInt(o^.X));
62 // Ïîëîæåíèå ïî-âåðòèêàëè
63 utils.writeInt(st, LongInt(o^.Y));
64 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
65 utils.writeInt(st, LongInt(o^.Rect.X));
66 utils.writeInt(st, LongInt(o^.Rect.Y));
67 utils.writeInt(st, Word(o^.Rect.Width));
68 utils.writeInt(st, Word(o^.Rect.Height));
69 // Ñêîðîñòü
70 utils.writeInt(st, LongInt(o^.Vel.X));
71 utils.writeInt(st, LongInt(o^.Vel.Y));
72 // Óñêîðåíèå
73 utils.writeInt(st, LongInt(o^.Accel.X));
74 utils.writeInt(st, LongInt(o^.Accel.Y));
75 end;
78 procedure Obj_LoadState (o: PObj; st: TStream);
79 begin
80 if (st = nil) then exit;
81 // Ñèãíàòóðà îáúåêòà:
82 if not utils.checkSign(st, '_OBJ') then raise XStreamError.Create('invalid object signature');
83 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid object version');
84 // Ïîëîæåíèå ïî-ãîðèçîíòàëè
85 o^.X := utils.readLongInt(st);
86 // Ïîëîæåíèå ïî-âåðòèêàëè
87 o^.Y := utils.readLongInt(st);
88 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
89 o^.Rect.X := utils.readLongInt(st);
90 o^.Rect.Y := utils.readLongInt(st);
91 o^.Rect.Width := utils.readWord(st);
92 o^.Rect.Height := utils.readWord(st);
93 // Ñêîðîñòü
94 o^.Vel.X := utils.readLongInt(st);
95 o^.Vel.Y := utils.readLongInt(st);
96 // Óñêîðåíèå
97 o^.Accel.X := utils.readLongInt(st);
98 o^.Accel.Y := utils.readLongInt(st);
99 end;
102 function buildSaveName (n: Integer): AnsiString;
103 begin
104 result := 'SAVGAME' + IntToStr(n) + '.DAT'
105 end;
108 function g_GetSaveName (n: Integer; out valid: Boolean): AnsiString;
109 var
110 st: TStream = nil;
111 ver: Byte;
112 stlen: Word;
113 filename: AnsiString;
114 begin
115 valid := false;
116 result := '';
117 if (n < 0) or (n > 65535) then exit;
118 try
119 // Îòêðûâàåì ôàéë ñîõðàíåíèé
120 filename := buildSaveName(n);
121 st := e_OpenResourceRO(SaveDirs, filename);
122 try
123 if not utils.checkSign(st, 'DFSV') then
124 begin
125 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st], TMsgType.Warning);
126 //raise XStreamError.Create('invalid save game signature');
127 exit;
128 end;
129 ver := utils.readByte(st);
130 if (ver < 7) then
131 begin
132 utils.readLongWord(st); // section size
133 stlen := utils.readWord(st);
134 if (stlen < 1) or (stlen > 64) then
135 begin
136 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st], TMsgType.Warning);
137 //raise XStreamError.Create('invalid save game version');
138 exit;
139 end;
140 // Èìÿ ñýéâà
141 SetLength(result, stlen);
142 st.ReadBuffer(result[1], stlen);
143 end
144 else
145 begin
146 // 7+
147 // Èìÿ ñýéâà
148 result := utils.readStr(st, 64);
149 end;
150 valid := (ver = SAVE_VERSION);
151 //if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
152 finally
153 st.Free();
154 end;
155 except
156 begin
157 //e_WriteLog('GetSaveName Error: '+e.message, MSG_WARNING);
158 //{$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
159 result := '';
160 end;
161 end;
162 end;
165 function g_SaveGameTo (const filename: AnsiString; const aname: AnsiString; deleteOnError: Boolean=true): Boolean;
166 var
167 st: TStream = nil;
168 i, k: Integer;
169 PID1, PID2: Word;
170 begin
171 result := false;
172 try
173 st := e_CreateResource(SaveDirs, filename);
174 try
175 utils.writeSign(st, 'DFSV');
176 utils.writeInt(st, Byte(SAVE_VERSION));
177 // Èìÿ ñýéâà
178 utils.writeStr(st, aname, 64);
179 // Ïîëíûé ïóòü ê âàäó è êàðòà
180 //if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('SAVE: current map is ''%s''...', [gCurrentMapFileName]);
181 utils.writeStr(st, gCurrentMapFileName);
182 // Ïóòü ê êàðòå
183 utils.writeStr(st, ExtractFileName(gGameSettings.WAD));
184 // Èìÿ êàðòû
185 utils.writeStr(st, g_ExtractFileName(gMapInfo.Map));
186 // Êîëè÷åñòâî èãðîêîâ
187 utils.writeInt(st, Word(g_Player_GetCount));
188 // Èãðîâîå âðåìÿ
189 utils.writeInt(st, LongWord(gTime));
190 // Òèï èãðû
191 utils.writeInt(st, Byte(gGameSettings.GameType));
192 // Ðåæèì èãðû
193 utils.writeInt(st, Byte(gGameSettings.GameMode));
194 // Ëèìèò âðåìåíè
195 utils.writeInt(st, Word(gGameSettings.TimeLimit));
196 // Ëèìèò î÷êîâ
197 utils.writeInt(st, Word(gGameSettings.ScoreLimit));
198 // Ëèìèò æèçíåé
199 utils.writeInt(st, Byte(gGameSettings.MaxLives));
200 // Èãðîâûå îïöèè
201 utils.writeInt(st, LongWord(gGameSettings.Options));
202 // Äëÿ êîîïà
203 utils.writeInt(st, Word(gCoopMonstersKilled));
204 utils.writeInt(st, Word(gCoopSecretsFound));
205 utils.writeInt(st, Word(gCoopTotalMonstersKilled));
206 utils.writeInt(st, Word(gCoopTotalSecretsFound));
207 utils.writeInt(st, Word(gCoopTotalMonsters));
208 utils.writeInt(st, Word(gCoopTotalSecrets));
210 ///// Ñîõðàíÿåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
211 utils.writeSign(st, 'PLVW');
212 utils.writeInt(st, Byte(0)); // version
213 PID1 := 0;
214 PID2 := 0;
215 if (gPlayer1 <> nil) then PID1 := gPlayer1.UID;
216 if (gPlayer2 <> nil) then PID2 := gPlayer2.UID;
217 utils.writeInt(st, Word(PID1));
218 utils.writeInt(st, Word(PID2));
219 ///// /////
221 ///// Ñîñòîÿíèå êàðòû /////
222 g_Map_SaveState(st);
223 ///// /////
225 ///// Ñîñòîÿíèå ïðåäìåòîâ /////
226 g_Items_SaveState(st);
227 ///// /////
229 ///// Ñîñòîÿíèå òðèããåðîâ /////
230 g_Triggers_SaveState(st);
231 ///// /////
233 ///// Ñîñòîÿíèå îðóæèÿ /////
234 g_Weapon_SaveState(st);
235 ///// /////
237 ///// Ñîñòîÿíèå ìîíñòðîâ /////
238 g_Monsters_SaveState(st);
239 ///// /////
241 ///// Ñîñòîÿíèå òðóïîâ /////
242 g_Player_Corpses_SaveState(st);
243 ///// /////
245 ///// Ñîõðàíÿåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
246 if (g_Player_GetCount > 0) then
247 begin
248 k := 0;
249 for i := 0 to High(gPlayers) do
250 begin
251 if (gPlayers[i] <> nil) then
252 begin
253 // Ñîñòîÿíèå èãðîêà
254 gPlayers[i].SaveState(st);
255 Inc(k);
256 end;
257 end;
259 // Âñå ëè èãðîêè íà ìåñòå
260 if (k <> g_Player_GetCount) then raise XStreamError.Create('g_SaveGame: wrong players count');
261 end;
262 ///// /////
264 ///// Ìàðêåð îêîí÷àíèÿ /////
265 utils.writeSign(st, 'END');
266 utils.writeInt(st, Byte(0));
267 ///// /////
268 result := true;
269 finally
270 st.Free();
271 end;
273 except
274 on e: Exception do
275 begin
276 st.Free();
277 g_Console_Add(_lc[I_GAME_ERROR_SAVE]);
278 e_WriteLog('SaveState Error: '+e.message, TMsgType.Warning);
279 if deleteOnError then DeleteFile(filename);
280 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
281 e_WriteStackTrace(e.message);
282 result := false;
283 end;
284 end;
285 end;
288 function g_LoadGameFrom (const filename: AnsiString): Boolean;
289 var
290 st: TStream = nil;
291 WAD_Path, Map_Name: AnsiString;
292 nPlayers: Integer;
293 Game_Type, Game_Mode, Game_MaxLives: Byte;
294 Game_TimeLimit, Game_ScoreLimit: Word;
295 Game_Time, Game_Options: Cardinal;
296 Game_CoopMonstersKilled,
297 Game_CoopSecretsFound,
298 Game_CoopTotalMonstersKilled,
299 Game_CoopTotalSecretsFound,
300 Game_CoopTotalMonsters,
301 Game_CoopTotalSecrets,
302 PID1, PID2: Word;
303 i: Integer;
304 gameCleared: Boolean = false;
305 curmapfile: AnsiString = '';
306 {$IF DEFINED(D2F_DEBUG)}
307 errpos: LongWord = 0;
308 {$ENDIF}
309 begin
310 result := false;
312 try
313 st := e_OpenResourceRO(SaveDirs, filename);
314 try
315 if not utils.checkSign(st, 'DFSV') then raise XStreamError.Create('invalid save game signature');
316 if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
318 e_WriteLog('Loading saved game...', TMsgType.Notify);
320 {$IF DEFINED(D2F_DEBUG)}
321 try
322 {$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_ScoreLimit := 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 gGameSettings := Default(TGameSettings);
379 gAimLine := false;
380 gShowMap := false;
381 // Íàñòðîéêè èãðû
382 gGameSettings.GameType := Game_Type;
383 gGameSettings.GameMode := Game_Mode;
384 gGameSettings.TimeLimit := Game_TimeLimit;
385 gGameSettings.ScoreLimit := Game_ScoreLimit;
386 gGameSettings.MaxLives := Game_MaxLives;
387 gGameSettings.Options := Game_Options;
388 gSwitchGameMode := Game_Mode;
389 g_Game_ExecuteEvent('ongamestart');
391 // Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ
392 g_Game_SetupScreenSize();
394 // Çàãðóçêà è çàïóñê êàðòû
395 //FIXME: save/load `asMegawad`
396 if not g_Game_StartMap(false{asMegawad}, WAD_Path+':\'+Map_Name, True, curmapfile) then
397 raise Exception.Create(Format(_lc[I_GAME_ERROR_MAP_LOAD], [WAD_Path + ':\' + Map_Name]));
399 // Íàñòðîéêè èãðîêîâ è áîòîâ
400 g_Player_Init();
402 // Óñòàíàâëèâàåì âðåìÿ
403 gTime := Game_Time;
404 // Âîçâðàùàåì ñòàòû
405 gCoopMonstersKilled := Game_CoopMonstersKilled;
406 gCoopSecretsFound := Game_CoopSecretsFound;
407 gCoopTotalMonstersKilled := Game_CoopTotalMonstersKilled;
408 gCoopTotalSecretsFound := Game_CoopTotalSecretsFound;
409 gCoopTotalMonsters := Game_CoopTotalMonsters;
410 gCoopTotalSecrets := Game_CoopTotalSecrets;
412 ///// Çàãðóæàåì ñîñòîÿíèå êàðòû /////
413 g_Map_LoadState(st);
414 ///// /////
416 ///// Çàãðóæàåì ñîñòîÿíèå ïðåäìåòîâ /////
417 g_Items_LoadState(st);
418 ///// /////
420 ///// Çàãðóæàåì ñîñòîÿíèå òðèããåðîâ /////
421 g_Triggers_LoadState(st);
422 ///// /////
424 ///// Çàãðóæàåì ñîñòîÿíèå îðóæèÿ /////
425 g_Weapon_LoadState(st);
426 ///// /////
428 ///// Çàãðóæàåì ñîñòîÿíèå ìîíñòðîâ /////
429 g_Monsters_LoadState(st);
430 ///// /////
432 ///// Çàãðóæàåì ñîñòîÿíèå òðóïîâ /////
433 g_Player_Corpses_LoadState(st);
434 ///// /////
436 ///// Çàãðóæàåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
437 if nPlayers > 0 then
438 begin
439 // Çàãðóæàåì
440 for i := 0 to nPlayers-1 do g_Player_CreateFromState(st);
441 end;
443 // Ïðèâÿçûâàåì îñíîâíûõ èãðîêîâ ê îáëàñòÿì ïðîñìîòðà
444 gPlayer1 := g_Player_Get(PID1);
445 gPlayer2 := g_Player_Get(PID2);
447 if (gPlayer1 <> nil) then
448 begin
449 gPlayer1.Name := gPlayer1Settings.Name;
450 gPlayer1.FPreferredTeam := gPlayer1Settings.Team;
451 gPlayer1.FActualModelName := gPlayer1Settings.Model;
452 gPlayer1.SetModel(gPlayer1.FActualModelName);
453 gPlayer1.SetColor(gPlayer1Settings.Color);
454 end;
456 if (gPlayer2 <> nil) then
457 begin
458 gPlayer2.Name := gPlayer2Settings.Name;
459 gPlayer2.FPreferredTeam := gPlayer2Settings.Team;
460 gPlayer2.FActualModelName := gPlayer2Settings.Model;
461 gPlayer2.SetModel(gPlayer2.FActualModelName);
462 gPlayer2.SetColor(gPlayer2Settings.Color);
463 end;
464 ///// /////
466 ///// Ìàðêåð îêîí÷àíèÿ /////
467 if not utils.checkSign(st, 'END') then raise XStreamError.Create('no end marker');
468 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid end marker');
469 ///// /////
471 // Èùåì òðèããåðû ñ óñëîâèåì ñìåðòè ìîíñòðîâ
472 if (gTriggers <> nil) then g_Map_ReAdd_DieTriggers();
474 // done
475 gLoadGameMode := false;
476 result := true;
477 {$IF DEFINED(D2F_DEBUG)}
478 except
479 begin
480 errpos := LongWord(st.position);
481 raise;
482 end;
483 end;
484 {$ENDIF}
485 finally
486 st.Free();
487 end;
488 except
489 on e: EFileNotFoundException do
490 begin
491 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
492 g_Console_Add('LoadState Error: '+e.message);
493 e_WriteLog('LoadState Error: '+e.message, TMsgType.Warning);
494 gLoadGameMode := false;
495 result := false;
496 end;
497 on e: Exception do
498 begin
499 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
500 g_Console_Add('LoadState Error: '+e.message);
501 e_WriteLog('LoadState Error: '+e.message, TMsgType.Warning);
502 {$IF DEFINED(D2F_DEBUG)}e_LogWritefln('stream error position: 0x%08x', [errpos], TMsgType.Warning);{$ENDIF}
503 gLoadGameMode := false;
504 result := false;
505 if gState <> STATE_MENU then
506 g_FatalError(_lc[I_GAME_ERROR_LOAD])
507 else if not gameCleared then
508 g_Game_Free();
509 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
510 end;
511 end;
512 end;
515 function g_SaveGame (n: Integer; const aname: AnsiString): Boolean;
516 begin
517 result := g_SaveGameTo(buildSaveName(n), aname, true);
518 end;
521 function g_LoadGame (n: Integer): Boolean;
522 begin
523 result := g_LoadGameFrom(buildSaveName(n));
524 end;
527 end.