DEADSOFTWARE

a7477581a189541fd1de6a9ea8f0530f44d99ef8
[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, 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 := '';
105 if (n < 0) or (n > 65535) then exit;
106 result := formatstrf('%sSAVGAME%s.DAT', [DataDir, n]);
107 end;
110 function g_GetSaveName (n: Integer; out valid: Boolean): AnsiString;
111 var
112 st: TStream = nil;
113 ver: Byte;
114 stlen: Word;
115 filename: AnsiString;
116 begin
117 valid := false;
118 result := '';
119 if (n < 0) or (n > 65535) then exit;
120 try
121 // Îòêðûâàåì ôàéë ñîõðàíåíèé
122 filename := buildSaveName(n);
123 st := openDiskFileRO(filename);
124 try
125 if not utils.checkSign(st, 'DFSV') then
126 begin
127 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st], TMsgType.Warning);
128 //raise XStreamError.Create('invalid save game signature');
129 exit;
130 end;
131 ver := utils.readByte(st);
132 if (ver < 7) then
133 begin
134 utils.readLongWord(st); // section size
135 stlen := utils.readWord(st);
136 if (stlen < 1) or (stlen > 64) then
137 begin
138 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st], TMsgType.Warning);
139 //raise XStreamError.Create('invalid save game version');
140 exit;
141 end;
142 // Èìÿ ñýéâà
143 SetLength(result, stlen);
144 st.ReadBuffer(result[1], stlen);
145 end
146 else
147 begin
148 // 7+
149 // Èìÿ ñýéâà
150 result := utils.readStr(st, 64);
151 end;
152 valid := (ver = SAVE_VERSION);
153 //if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
154 finally
155 st.Free();
156 end;
157 except
158 begin
159 //e_WriteLog('GetSaveName Error: '+e.message, MSG_WARNING);
160 //{$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
161 result := '';
162 end;
163 end;
164 end;
167 function g_SaveGameTo (const filename: AnsiString; const aname: AnsiString; deleteOnError: Boolean=true): Boolean;
168 var
169 st: TStream = nil;
170 i, k: Integer;
171 PID1, PID2: Word;
172 begin
173 result := false;
174 try
175 st := createDiskFile(filename);
176 try
177 utils.writeSign(st, 'DFSV');
178 utils.writeInt(st, Byte(SAVE_VERSION));
179 // Èìÿ ñýéâà
180 utils.writeStr(st, aname, 64);
181 // Ïîëíûé ïóòü ê âàäó è êàðòà
182 //if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('SAVE: current map is ''%s''...', [gCurrentMapFileName]);
183 utils.writeStr(st, gCurrentMapFileName);
184 // Ïóòü ê êàðòå
185 utils.writeStr(st, gGameSettings.WAD);
186 // Èìÿ êàðòû
187 utils.writeStr(st, g_ExtractFileName(gMapInfo.Map));
188 // Êîëè÷åñòâî èãðîêîâ
189 utils.writeInt(st, Word(g_Player_GetCount));
190 // Èãðîâîå âðåìÿ
191 utils.writeInt(st, LongWord(gTime));
192 // Òèï èãðû
193 utils.writeInt(st, Byte(gGameSettings.GameType));
194 // Ðåæèì èãðû
195 utils.writeInt(st, Byte(gGameSettings.GameMode));
196 // Ëèìèò âðåìåíè
197 utils.writeInt(st, Word(gGameSettings.TimeLimit));
198 // Ëèìèò î÷êîâ
199 utils.writeInt(st, Word(gGameSettings.GoalLimit));
200 // Ëèìèò æèçíåé
201 utils.writeInt(st, Byte(gGameSettings.MaxLives));
202 // Èãðîâûå îïöèè
203 utils.writeInt(st, LongWord(gGameSettings.Options));
204 // Äëÿ êîîïà
205 utils.writeInt(st, Word(gCoopMonstersKilled));
206 utils.writeInt(st, Word(gCoopSecretsFound));
207 utils.writeInt(st, Word(gCoopTotalMonstersKilled));
208 utils.writeInt(st, Word(gCoopTotalSecretsFound));
209 utils.writeInt(st, Word(gCoopTotalMonsters));
210 utils.writeInt(st, Word(gCoopTotalSecrets));
212 ///// Ñîõðàíÿåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
213 utils.writeSign(st, 'PLVW');
214 utils.writeInt(st, Byte(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 utils.writeInt(st, Word(PID1));
220 utils.writeInt(st, Word(PID2));
221 ///// /////
223 ///// Ñîñòîÿíèå êàðòû /////
224 g_Map_SaveState(st);
225 ///// /////
227 ///// Ñîñòîÿíèå ïðåäìåòîâ /////
228 g_Items_SaveState(st);
229 ///// /////
231 ///// Ñîñòîÿíèå òðèããåðîâ /////
232 g_Triggers_SaveState(st);
233 ///// /////
235 ///// Ñîñòîÿíèå îðóæèÿ /////
236 g_Weapon_SaveState(st);
237 ///// /////
239 ///// Ñîñòîÿíèå ìîíñòðîâ /////
240 g_Monsters_SaveState(st);
241 ///// /////
243 ///// Ñîñòîÿíèå òðóïîâ /////
244 g_Player_Corpses_SaveState(st);
245 ///// /////
247 ///// Ñîõðàíÿåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
248 if (g_Player_GetCount > 0) then
249 begin
250 k := 0;
251 for i := 0 to High(gPlayers) do
252 begin
253 if (gPlayers[i] <> nil) then
254 begin
255 // Ñîñòîÿíèå èãðîêà
256 gPlayers[i].SaveState(st);
257 Inc(k);
258 end;
259 end;
261 // Âñå ëè èãðîêè íà ìåñòå
262 if (k <> g_Player_GetCount) then raise XStreamError.Create('g_SaveGame: wrong players count');
263 end;
264 ///// /////
266 ///// Ìàðêåð îêîí÷àíèÿ /////
267 utils.writeSign(st, 'END');
268 utils.writeInt(st, Byte(0));
269 ///// /////
270 result := true;
271 finally
272 st.Free();
273 end;
275 except
276 on e: Exception do
277 begin
278 st.Free();
279 g_Console_Add(_lc[I_GAME_ERROR_SAVE]);
280 e_WriteLog('SaveState Error: '+e.message, TMsgType.Warning);
281 if deleteOnError then DeleteFile(filename);
282 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
283 result := false;
284 end;
285 end;
286 end;
289 function g_LoadGameFrom (const filename: AnsiString): Boolean;
290 var
291 st: TStream = nil;
292 WAD_Path, Map_Name: AnsiString;
293 nPlayers: Integer;
294 Game_Type, Game_Mode, Game_MaxLives: Byte;
295 Game_TimeLimit, Game_GoalLimit: Word;
296 Game_Time, Game_Options: Cardinal;
297 Game_CoopMonstersKilled,
298 Game_CoopSecretsFound,
299 Game_CoopTotalMonstersKilled,
300 Game_CoopTotalSecretsFound,
301 Game_CoopTotalMonsters,
302 Game_CoopTotalSecrets,
303 PID1, PID2: Word;
304 i: Integer;
305 gameCleared: Boolean = false;
306 curmapfile: AnsiString = '';
307 {$IF DEFINED(D2F_DEBUG)}
308 errpos: LongWord = 0;
309 {$ENDIF}
310 begin
311 result := false;
313 try
314 st := openDiskFileRO(filename);
315 try
316 if not utils.checkSign(st, 'DFSV') then raise XStreamError.Create('invalid save game signature');
317 if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
319 e_WriteLog('Loading saved game...', TMsgType.Notify);
321 {$IF DEFINED(D2F_DEBUG)}try{$ENDIF}
322 //g_Game_Free(false); // don't free textures for the same map
323 g_Game_ClearLoading();
324 g_Game_SetLoadingText(_lc[I_LOAD_SAVE_FILE], 0, False);
325 gLoadGameMode := True;
327 ///// Çàãðóæàåì ñîñòîÿíèå èãðû /////
328 // Èìÿ ñýéâà
329 {str :=} utils.readStr(st, 64);
331 // Ïîëíûé ïóòü ê âàäó è êàðòà
332 curmapfile := utils.readStr(st);
334 if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('LOAD: previous map was ''%s''...', [gCurrentMapFileName]);
335 if (Length(curmapfile) <> 0) then e_LogWritefln('LOAD: new map is ''%s''...', [curmapfile]);
336 // À âîò òóò, íàêîíåö, ÷èñòèì ðåñóðñû
337 g_Game_Free(curmapfile <> gCurrentMapFileName); // don't free textures for the same map
338 gameCleared := true;
340 // Ïóòü ê êàðòå
341 WAD_Path := utils.readStr(st);
342 // Èìÿ êàðòû
343 Map_Name := utils.readStr(st);
344 // Êîëè÷åñòâî èãðîêîâ
345 nPlayers := utils.readWord(st);
346 // Èãðîâîå âðåìÿ
347 Game_Time := utils.readLongWord(st);
348 // Òèï èãðû
349 Game_Type := utils.readByte(st);
350 // Ðåæèì èãðû
351 Game_Mode := utils.readByte(st);
352 // Ëèìèò âðåìåíè
353 Game_TimeLimit := utils.readWord(st);
354 // Ëèìèò î÷êîâ
355 Game_GoalLimit := utils.readWord(st);
356 // Ëèìèò æèçíåé
357 Game_MaxLives := utils.readByte(st);
358 // Èãðîâûå îïöèè
359 Game_Options := utils.readLongWord(st);
360 // Äëÿ êîîïà
361 Game_CoopMonstersKilled := utils.readWord(st);
362 Game_CoopSecretsFound := utils.readWord(st);
363 Game_CoopTotalMonstersKilled := utils.readWord(st);
364 Game_CoopTotalSecretsFound := utils.readWord(st);
365 Game_CoopTotalMonsters := utils.readWord(st);
366 Game_CoopTotalSecrets := utils.readWord(st);
367 ///// /////
369 ///// Çàãðóæàåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
370 if not utils.checkSign(st, 'PLVW') then raise XStreamError.Create('invalid viewport signature');
371 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid viewport version');
372 PID1 := utils.readWord(st);
373 PID2 := utils.readWord(st);
374 ///// /////
376 // Çàãðóæàåì êàðòó:
377 ZeroMemory(@gGameSettings, sizeof(TGameSettings));
378 gAimLine := false;
379 gShowMap := false;
380 if (Game_Type = GT_NONE) or (Game_Type = GT_SINGLE) then
381 begin
382 // Íàñòðîéêè èãðû
383 gGameSettings.GameType := GT_SINGLE;
384 gGameSettings.MaxLives := 0;
385 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_ALLOWEXIT;
386 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_MONSTERS;
387 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_BOTVSMONSTER;
388 gSwitchGameMode := GM_SINGLE;
389 end
390 else
391 begin
392 // Íàñòðîéêè èãðû
393 gGameSettings.GameType := GT_CUSTOM;
394 gGameSettings.GameMode := Game_Mode;
395 gSwitchGameMode := Game_Mode;
396 gGameSettings.TimeLimit := Game_TimeLimit;
397 gGameSettings.GoalLimit := Game_GoalLimit;
398 gGameSettings.MaxLives := IfThen(Game_Mode = GM_CTF, 0, Game_MaxLives);
399 gGameSettings.Options := Game_Options;
400 end;
401 g_Game_ExecuteEvent('ongamestart');
403 // Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ
404 g_Game_SetupScreenSize();
406 // Çàãðóçêà è çàïóñê êàðòû
407 if not g_Game_StartMap(WAD_Path+':\'+Map_Name, True, curmapfile) then
408 begin
409 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [WAD_Path + ':\' + Map_Name]));
410 exit;
411 end;
413 // Íàñòðîéêè èãðîêîâ è áîòîâ
414 g_Player_Init();
416 // Óñòàíàâëèâàåì âðåìÿ
417 gTime := Game_Time;
418 // Âîçâðàùàåì ñòàòû
419 gCoopMonstersKilled := Game_CoopMonstersKilled;
420 gCoopSecretsFound := Game_CoopSecretsFound;
421 gCoopTotalMonstersKilled := Game_CoopTotalMonstersKilled;
422 gCoopTotalSecretsFound := Game_CoopTotalSecretsFound;
423 gCoopTotalMonsters := Game_CoopTotalMonsters;
424 gCoopTotalSecrets := Game_CoopTotalSecrets;
426 ///// Çàãðóæàåì ñîñòîÿíèå êàðòû /////
427 g_Map_LoadState(st);
428 ///// /////
430 ///// Çàãðóæàåì ñîñòîÿíèå ïðåäìåòîâ /////
431 g_Items_LoadState(st);
432 ///// /////
434 ///// Çàãðóæàåì ñîñòîÿíèå òðèããåðîâ /////
435 g_Triggers_LoadState(st);
436 ///// /////
438 ///// Çàãðóæàåì ñîñòîÿíèå îðóæèÿ /////
439 g_Weapon_LoadState(st);
440 ///// /////
442 ///// Çàãðóæàåì ñîñòîÿíèå ìîíñòðîâ /////
443 g_Monsters_LoadState(st);
444 ///// /////
446 ///// Çàãðóæàåì ñîñòîÿíèå òðóïîâ /////
447 g_Player_Corpses_LoadState(st);
448 ///// /////
450 ///// Çàãðóæàåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
451 if nPlayers > 0 then
452 begin
453 // Çàãðóæàåì
454 for i := 0 to nPlayers-1 do g_Player_CreateFromState(st);
455 end;
457 // Ïðèâÿçûâàåì îñíîâíûõ èãðîêîâ ê îáëàñòÿì ïðîñìîòðà
458 gPlayer1 := g_Player_Get(PID1);
459 gPlayer2 := g_Player_Get(PID2);
461 if (gPlayer1 <> nil) then
462 begin
463 gPlayer1.Name := gPlayer1Settings.Name;
464 gPlayer1.FPreferredTeam := gPlayer1Settings.Team;
465 gPlayer1.FActualModelName := gPlayer1Settings.Model;
466 gPlayer1.SetModel(gPlayer1.FActualModelName);
467 gPlayer1.SetColor(gPlayer1Settings.Color);
468 end;
470 if (gPlayer2 <> nil) then
471 begin
472 gPlayer2.Name := gPlayer2Settings.Name;
473 gPlayer2.FPreferredTeam := gPlayer2Settings.Team;
474 gPlayer2.FActualModelName := gPlayer2Settings.Model;
475 gPlayer2.SetModel(gPlayer2.FActualModelName);
476 gPlayer2.SetColor(gPlayer2Settings.Color);
477 end;
478 ///// /////
480 ///// Ìàðêåð îêîí÷àíèÿ /////
481 if not utils.checkSign(st, 'END') then raise XStreamError.Create('no end marker');
482 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid end marker');
483 ///// /////
485 // Èùåì òðèããåðû ñ óñëîâèåì ñìåðòè ìîíñòðîâ
486 if (gTriggers <> nil) then g_Map_ReAdd_DieTriggers();
488 // done
489 gLoadGameMode := false;
490 result := true;
491 {$IF DEFINED(D2F_DEBUG)}
492 except
493 begin
494 errpos := LongWord(st.position);
495 raise;
496 end;
497 end;
498 {$ENDIF}
499 finally
500 st.Free();
501 end;
502 except
503 on e: EFileNotFoundException do
504 begin
505 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
506 g_Console_Add('LoadState Error: '+e.message);
507 e_WriteLog('LoadState Error: '+e.message, TMsgType.Warning);
508 gLoadGameMode := false;
509 result := false;
510 end;
511 on e: Exception do
512 begin
513 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
514 g_Console_Add('LoadState Error: '+e.message);
515 e_WriteLog('LoadState Error: '+e.message, TMsgType.Warning);
516 {$IF DEFINED(D2F_DEBUG)}e_LogWritefln('stream error position: 0x%08x', [errpos], TMsgType.Warning);{$ENDIF}
517 gLoadGameMode := false;
518 result := false;
519 if gState <> STATE_MENU then
520 g_FatalError(_lc[I_GAME_ERROR_LOAD])
521 else if not gameCleared then
522 g_Game_Free();
523 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
524 end;
525 end;
526 end;
529 function g_SaveGame (n: Integer; const aname: AnsiString): Boolean;
530 begin
531 result := false;
532 if (n < 0) or (n > 65535) then exit;
533 result := g_SaveGameTo(buildSaveName(n), aname, true);
534 end;
537 function g_LoadGame (n: Integer): Boolean;
538 begin
539 result := false;
540 if (n < 0) or (n > 65535) then exit;
541 result := g_LoadGameFrom(buildSaveName(n));
542 end;
545 end.