DEADSOFTWARE

cleanup: remove g_main.pas
[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, g_phys, g_textures;
24 function g_GetSaveName (n: Integer; out valid: Boolean): AnsiString;
26 function g_SaveGameTo (const filename: AnsiString; const aname: AnsiString; deleteOnError: Boolean=true): Boolean;
27 function g_LoadGameFrom (const filename: AnsiString): Boolean;
29 function g_SaveGame (n: Integer; const aname: AnsiString): Boolean;
30 function g_LoadGame (n: Integer): Boolean;
32 procedure Obj_SaveState (st: TStream; o: PObj);
33 procedure Obj_LoadState (o: PObj; st: TStream);
36 implementation
38 uses
39 MAPDEF, utils, xstreams,
40 g_game, g_items, g_map, g_monsters, g_triggers,
41 g_basic, Math, wadreader,
42 g_weapons, g_player, g_console,
43 e_log, e_res, g_language, g_options;
45 const
46 SAVE_SIGNATURE = $56534644; // 'DFSV'
47 SAVE_VERSION = $07;
48 END_MARKER_STRING = 'END';
49 PLAYER_VIEW_SIGNATURE = $57564C50; // 'PLVW'
50 OBJ_SIGNATURE = $4A424F5F; // '_OBJ'
53 procedure Obj_SaveState (st: TStream; o: PObj);
54 begin
55 if (st = nil) then exit;
56 // Ñèãíàòóðà îáúåêòà
57 utils.writeSign(st, '_OBJ');
58 utils.writeInt(st, Byte(0)); // version
59 // Ïîëîæåíèå ïî-ãîðèçîíòàëè
60 utils.writeInt(st, LongInt(o^.X));
61 // Ïîëîæåíèå ïî-âåðòèêàëè
62 utils.writeInt(st, LongInt(o^.Y));
63 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
64 utils.writeInt(st, LongInt(o^.Rect.X));
65 utils.writeInt(st, LongInt(o^.Rect.Y));
66 utils.writeInt(st, Word(o^.Rect.Width));
67 utils.writeInt(st, Word(o^.Rect.Height));
68 // Ñêîðîñòü
69 utils.writeInt(st, LongInt(o^.Vel.X));
70 utils.writeInt(st, LongInt(o^.Vel.Y));
71 // Óñêîðåíèå
72 utils.writeInt(st, LongInt(o^.Accel.X));
73 utils.writeInt(st, LongInt(o^.Accel.Y));
74 end;
77 procedure Obj_LoadState (o: PObj; st: TStream);
78 begin
79 if (st = nil) then exit;
80 // Ñèãíàòóðà îáúåêòà:
81 if not utils.checkSign(st, '_OBJ') then raise XStreamError.Create('invalid object signature');
82 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid object version');
83 // Ïîëîæåíèå ïî-ãîðèçîíòàëè
84 o^.X := utils.readLongInt(st);
85 // Ïîëîæåíèå ïî-âåðòèêàëè
86 o^.Y := utils.readLongInt(st);
87 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
88 o^.Rect.X := utils.readLongInt(st);
89 o^.Rect.Y := utils.readLongInt(st);
90 o^.Rect.Width := utils.readWord(st);
91 o^.Rect.Height := utils.readWord(st);
92 // Ñêîðîñòü
93 o^.Vel.X := utils.readLongInt(st);
94 o^.Vel.Y := utils.readLongInt(st);
95 // Óñêîðåíèå
96 o^.Accel.X := utils.readLongInt(st);
97 o^.Accel.Y := utils.readLongInt(st);
98 end;
101 function buildSaveName (n: Integer): AnsiString;
102 begin
103 result := 'SAVGAME' + IntToStr(n) + '.DAT'
104 end;
107 function g_GetSaveName (n: Integer; out valid: Boolean): AnsiString;
108 var
109 st: TStream = nil;
110 ver: Byte;
111 stlen: Word;
112 filename: AnsiString;
113 begin
114 valid := false;
115 result := '';
116 if (n < 0) or (n > 65535) then exit;
117 try
118 // Îòêðûâàåì ôàéë ñîõðàíåíèé
119 filename := buildSaveName(n);
120 st := e_OpenResourceRO(SaveDirs, filename);
121 try
122 if not utils.checkSign(st, 'DFSV') then
123 begin
124 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st], TMsgType.Warning);
125 //raise XStreamError.Create('invalid save game signature');
126 exit;
127 end;
128 ver := utils.readByte(st);
129 if (ver < 7) then
130 begin
131 utils.readLongWord(st); // section size
132 stlen := utils.readWord(st);
133 if (stlen < 1) or (stlen > 64) then
134 begin
135 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st], TMsgType.Warning);
136 //raise XStreamError.Create('invalid save game version');
137 exit;
138 end;
139 // Èìÿ ñýéâà
140 SetLength(result, stlen);
141 st.ReadBuffer(result[1], stlen);
142 end
143 else
144 begin
145 // 7+
146 // Èìÿ ñýéâà
147 result := utils.readStr(st, 64);
148 end;
149 valid := (ver = SAVE_VERSION);
150 //if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
151 finally
152 st.Free();
153 end;
154 except
155 begin
156 //e_WriteLog('GetSaveName Error: '+e.message, MSG_WARNING);
157 //{$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
158 result := '';
159 end;
160 end;
161 end;
164 function g_SaveGameTo (const filename: AnsiString; const aname: AnsiString; deleteOnError: Boolean=true): Boolean;
165 var
166 st: TStream = nil;
167 i, k: Integer;
168 PID1, PID2: Word;
169 begin
170 result := false;
171 try
172 st := e_CreateResource(SaveDirs, filename);
173 try
174 utils.writeSign(st, 'DFSV');
175 utils.writeInt(st, Byte(SAVE_VERSION));
176 // Èìÿ ñýéâà
177 utils.writeStr(st, aname, 64);
178 // Ïîëíûé ïóòü ê âàäó è êàðòà
179 //if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('SAVE: current map is ''%s''...', [gCurrentMapFileName]);
180 utils.writeStr(st, gCurrentMapFileName);
181 // Ïóòü ê êàðòå
182 utils.writeStr(st, ExtractFileName(gGameSettings.WAD));
183 // Èìÿ êàðòû
184 utils.writeStr(st, g_ExtractFileName(gMapInfo.Map));
185 // Êîëè÷åñòâî èãðîêîâ
186 utils.writeInt(st, Word(g_Player_GetCount));
187 // Èãðîâîå âðåìÿ
188 utils.writeInt(st, LongWord(gTime));
189 // Òèï èãðû
190 utils.writeInt(st, Byte(gGameSettings.GameType));
191 // Ðåæèì èãðû
192 utils.writeInt(st, Byte(gGameSettings.GameMode));
193 // Ëèìèò âðåìåíè
194 utils.writeInt(st, Word(gGameSettings.TimeLimit));
195 // Ëèìèò î÷êîâ
196 utils.writeInt(st, Word(gGameSettings.GoalLimit));
197 // Ëèìèò æèçíåé
198 utils.writeInt(st, Byte(gGameSettings.MaxLives));
199 // Èãðîâûå îïöèè
200 utils.writeInt(st, LongWord(gGameSettings.Options));
201 // Äëÿ êîîïà
202 utils.writeInt(st, Word(gCoopMonstersKilled));
203 utils.writeInt(st, Word(gCoopSecretsFound));
204 utils.writeInt(st, Word(gCoopTotalMonstersKilled));
205 utils.writeInt(st, Word(gCoopTotalSecretsFound));
206 utils.writeInt(st, Word(gCoopTotalMonsters));
207 utils.writeInt(st, Word(gCoopTotalSecrets));
209 ///// Ñîõðàíÿåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
210 utils.writeSign(st, 'PLVW');
211 utils.writeInt(st, Byte(0)); // version
212 PID1 := 0;
213 PID2 := 0;
214 if (gPlayer1 <> nil) then PID1 := gPlayer1.UID;
215 if (gPlayer2 <> nil) then PID2 := gPlayer2.UID;
216 utils.writeInt(st, Word(PID1));
217 utils.writeInt(st, Word(PID2));
218 ///// /////
220 ///// Ñîñòîÿíèå êàðòû /////
221 g_Map_SaveState(st);
222 ///// /////
224 ///// Ñîñòîÿíèå ïðåäìåòîâ /////
225 g_Items_SaveState(st);
226 ///// /////
228 ///// Ñîñòîÿíèå òðèããåðîâ /////
229 g_Triggers_SaveState(st);
230 ///// /////
232 ///// Ñîñòîÿíèå îðóæèÿ /////
233 g_Weapon_SaveState(st);
234 ///// /////
236 ///// Ñîñòîÿíèå ìîíñòðîâ /////
237 g_Monsters_SaveState(st);
238 ///// /////
240 ///// Ñîñòîÿíèå òðóïîâ /////
241 g_Player_Corpses_SaveState(st);
242 ///// /////
244 ///// Ñîõðàíÿåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
245 if (g_Player_GetCount > 0) then
246 begin
247 k := 0;
248 for i := 0 to High(gPlayers) do
249 begin
250 if (gPlayers[i] <> nil) then
251 begin
252 // Ñîñòîÿíèå èãðîêà
253 gPlayers[i].SaveState(st);
254 Inc(k);
255 end;
256 end;
258 // Âñå ëè èãðîêè íà ìåñòå
259 if (k <> g_Player_GetCount) then raise XStreamError.Create('g_SaveGame: wrong players count');
260 end;
261 ///// /////
263 ///// Ìàðêåð îêîí÷àíèÿ /////
264 utils.writeSign(st, 'END');
265 utils.writeInt(st, Byte(0));
266 ///// /////
267 result := true;
268 finally
269 st.Free();
270 end;
272 except
273 on e: Exception do
274 begin
275 st.Free();
276 g_Console_Add(_lc[I_GAME_ERROR_SAVE]);
277 e_WriteLog('SaveState Error: '+e.message, TMsgType.Warning);
278 if deleteOnError then DeleteFile(filename);
279 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
280 e_WriteStackTrace(e.message);
281 result := false;
282 end;
283 end;
284 end;
287 function g_LoadGameFrom (const filename: AnsiString): Boolean;
288 var
289 st: TStream = nil;
290 WAD_Path, Map_Name: AnsiString;
291 nPlayers: Integer;
292 Game_Type, Game_Mode, Game_MaxLives: Byte;
293 Game_TimeLimit, Game_GoalLimit: Word;
294 Game_Time, Game_Options: Cardinal;
295 Game_CoopMonstersKilled,
296 Game_CoopSecretsFound,
297 Game_CoopTotalMonstersKilled,
298 Game_CoopTotalSecretsFound,
299 Game_CoopTotalMonsters,
300 Game_CoopTotalSecrets,
301 PID1, PID2: Word;
302 i: Integer;
303 gameCleared: Boolean = false;
304 curmapfile: AnsiString = '';
305 {$IF DEFINED(D2F_DEBUG)}
306 errpos: LongWord = 0;
307 {$ENDIF}
308 begin
309 result := false;
311 try
312 st := e_OpenResourceRO(SaveDirs, filename);
313 try
314 if not utils.checkSign(st, 'DFSV') then raise XStreamError.Create('invalid save game signature');
315 if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
317 e_WriteLog('Loading saved game...', TMsgType.Notify);
319 {$IF DEFINED(D2F_DEBUG)}try{$ENDIF}
320 //g_Game_Free(false); // don't free textures for the same map
321 g_Game_ClearLoading();
322 g_Game_SetLoadingText(_lc[I_LOAD_SAVE_FILE], 0, False);
323 gLoadGameMode := True;
325 ///// Çàãðóæàåì ñîñòîÿíèå èãðû /////
326 // Èìÿ ñýéâà
327 {str :=} utils.readStr(st, 64);
329 // Ïîëíûé ïóòü ê âàäó è êàðòà
330 curmapfile := utils.readStr(st);
332 if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('LOAD: previous map was ''%s''...', [gCurrentMapFileName]);
333 if (Length(curmapfile) <> 0) then e_LogWritefln('LOAD: new map is ''%s''...', [curmapfile]);
334 // À âîò òóò, íàêîíåö, ÷èñòèì ðåñóðñû
335 g_Game_Free(curmapfile <> gCurrentMapFileName); // don't free textures for the same map
336 gameCleared := true;
338 // Ïóòü ê êàðòå
339 WAD_Path := utils.readStr(st);
340 // Èìÿ êàðòû
341 Map_Name := utils.readStr(st);
342 // Êîëè÷åñòâî èãðîêîâ
343 nPlayers := utils.readWord(st);
344 // Èãðîâîå âðåìÿ
345 Game_Time := utils.readLongWord(st);
346 // Òèï èãðû
347 Game_Type := utils.readByte(st);
348 // Ðåæèì èãðû
349 Game_Mode := utils.readByte(st);
350 // Ëèìèò âðåìåíè
351 Game_TimeLimit := utils.readWord(st);
352 // Ëèìèò î÷êîâ
353 Game_GoalLimit := utils.readWord(st);
354 // Ëèìèò æèçíåé
355 Game_MaxLives := utils.readByte(st);
356 // Èãðîâûå îïöèè
357 Game_Options := utils.readLongWord(st);
358 // Äëÿ êîîïà
359 Game_CoopMonstersKilled := utils.readWord(st);
360 Game_CoopSecretsFound := utils.readWord(st);
361 Game_CoopTotalMonstersKilled := utils.readWord(st);
362 Game_CoopTotalSecretsFound := utils.readWord(st);
363 Game_CoopTotalMonsters := utils.readWord(st);
364 Game_CoopTotalSecrets := utils.readWord(st);
365 ///// /////
367 ///// Çàãðóæàåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
368 if not utils.checkSign(st, 'PLVW') then raise XStreamError.Create('invalid viewport signature');
369 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid viewport version');
370 PID1 := utils.readWord(st);
371 PID2 := utils.readWord(st);
372 ///// /////
374 // Çàãðóæàåì êàðòó:
375 ZeroMemory(@gGameSettings, sizeof(TGameSettings));
376 gAimLine := false;
377 gShowMap := false;
378 if (Game_Type = GT_NONE) or (Game_Type = GT_SINGLE) then
379 begin
380 // Íàñòðîéêè èãðû
381 gGameSettings.GameType := GT_SINGLE;
382 gGameSettings.MaxLives := 0;
383 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_ALLOWEXIT;
384 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_MONSTERS;
385 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_BOTVSMONSTER;
386 gSwitchGameMode := GM_SINGLE;
387 end
388 else
389 begin
390 // Íàñòðîéêè èãðû
391 gGameSettings.GameType := GT_CUSTOM;
392 gGameSettings.GameMode := Game_Mode;
393 gSwitchGameMode := Game_Mode;
394 gGameSettings.TimeLimit := Game_TimeLimit;
395 gGameSettings.GoalLimit := Game_GoalLimit;
396 gGameSettings.MaxLives := IfThen(Game_Mode = GM_CTF, 0, Game_MaxLives);
397 gGameSettings.Options := Game_Options;
398 end;
399 g_Game_ExecuteEvent('ongamestart');
401 // Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ
402 g_Game_SetupScreenSize();
404 // Çàãðóçêà è çàïóñê êàðòû
405 //FIXME: save/load `asMegawad`
406 if not g_Game_StartMap(false{asMegawad}, WAD_Path+':\'+Map_Name, True, curmapfile) then
407 begin
408 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [WAD_Path + ':\' + Map_Name]));
409 exit;
410 end;
412 // Íàñòðîéêè èãðîêîâ è áîòîâ
413 g_Player_Init();
415 // Óñòàíàâëèâàåì âðåìÿ
416 gTime := Game_Time;
417 // Âîçâðàùàåì ñòàòû
418 gCoopMonstersKilled := Game_CoopMonstersKilled;
419 gCoopSecretsFound := Game_CoopSecretsFound;
420 gCoopTotalMonstersKilled := Game_CoopTotalMonstersKilled;
421 gCoopTotalSecretsFound := Game_CoopTotalSecretsFound;
422 gCoopTotalMonsters := Game_CoopTotalMonsters;
423 gCoopTotalSecrets := Game_CoopTotalSecrets;
425 ///// Çàãðóæàåì ñîñòîÿíèå êàðòû /////
426 g_Map_LoadState(st);
427 ///// /////
429 ///// Çàãðóæàåì ñîñòîÿíèå ïðåäìåòîâ /////
430 g_Items_LoadState(st);
431 ///// /////
433 ///// Çàãðóæàåì ñîñòîÿíèå òðèããåðîâ /////
434 g_Triggers_LoadState(st);
435 ///// /////
437 ///// Çàãðóæàåì ñîñòîÿíèå îðóæèÿ /////
438 g_Weapon_LoadState(st);
439 ///// /////
441 ///// Çàãðóæàåì ñîñòîÿíèå ìîíñòðîâ /////
442 g_Monsters_LoadState(st);
443 ///// /////
445 ///// Çàãðóæàåì ñîñòîÿíèå òðóïîâ /////
446 g_Player_Corpses_LoadState(st);
447 ///// /////
449 ///// Çàãðóæàåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
450 if nPlayers > 0 then
451 begin
452 // Çàãðóæàåì
453 for i := 0 to nPlayers-1 do g_Player_CreateFromState(st);
454 end;
456 // Ïðèâÿçûâàåì îñíîâíûõ èãðîêîâ ê îáëàñòÿì ïðîñìîòðà
457 gPlayer1 := g_Player_Get(PID1);
458 gPlayer2 := g_Player_Get(PID2);
460 if (gPlayer1 <> nil) then
461 begin
462 gPlayer1.Name := gPlayer1Settings.Name;
463 gPlayer1.FPreferredTeam := gPlayer1Settings.Team;
464 gPlayer1.FActualModelName := gPlayer1Settings.Model;
465 gPlayer1.SetModel(gPlayer1.FActualModelName);
466 gPlayer1.SetColor(gPlayer1Settings.Color);
467 end;
469 if (gPlayer2 <> nil) then
470 begin
471 gPlayer2.Name := gPlayer2Settings.Name;
472 gPlayer2.FPreferredTeam := gPlayer2Settings.Team;
473 gPlayer2.FActualModelName := gPlayer2Settings.Model;
474 gPlayer2.SetModel(gPlayer2.FActualModelName);
475 gPlayer2.SetColor(gPlayer2Settings.Color);
476 end;
477 ///// /////
479 ///// Ìàðêåð îêîí÷àíèÿ /////
480 if not utils.checkSign(st, 'END') then raise XStreamError.Create('no end marker');
481 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid end marker');
482 ///// /////
484 // Èùåì òðèããåðû ñ óñëîâèåì ñìåðòè ìîíñòðîâ
485 if (gTriggers <> nil) then g_Map_ReAdd_DieTriggers();
487 // done
488 gLoadGameMode := false;
489 result := true;
490 {$IF DEFINED(D2F_DEBUG)}
491 except
492 begin
493 errpos := LongWord(st.position);
494 raise;
495 end;
496 end;
497 {$ENDIF}
498 finally
499 st.Free();
500 end;
501 except
502 on e: EFileNotFoundException do
503 begin
504 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
505 g_Console_Add('LoadState Error: '+e.message);
506 e_WriteLog('LoadState Error: '+e.message, TMsgType.Warning);
507 gLoadGameMode := false;
508 result := false;
509 end;
510 on e: Exception do
511 begin
512 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
513 g_Console_Add('LoadState Error: '+e.message);
514 e_WriteLog('LoadState Error: '+e.message, TMsgType.Warning);
515 {$IF DEFINED(D2F_DEBUG)}e_LogWritefln('stream error position: 0x%08x', [errpos], TMsgType.Warning);{$ENDIF}
516 gLoadGameMode := false;
517 result := false;
518 if gState <> STATE_MENU then
519 g_FatalError(_lc[I_GAME_ERROR_LOAD])
520 else if not gameCleared then
521 g_Game_Free();
522 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
523 end;
524 end;
525 end;
528 function g_SaveGame (n: Integer; const aname: AnsiString): Boolean;
529 begin
530 result := g_SaveGameTo(buildSaveName(n), aname, true);
531 end;
534 function g_LoadGame (n: Integer): Boolean;
535 begin
536 result := g_LoadGameFrom(buildSaveName(n));
537 end;
540 end.