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}
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
);
42 {$IFDEF ENABLE_CORPSES}
45 MAPDEF
, utils
, xstreams
,
46 g_game
, g_items
, g_map
, g_monsters
, g_triggers
,
47 g_basic
, Math
, wadreader
,
48 g_weapons
, g_player
, g_console
,
49 e_log
, e_res
, g_language
, g_options
;
52 SAVE_SIGNATURE
= $56534644; // 'DFSV'
54 END_MARKER_STRING
= 'END';
55 PLAYER_VIEW_SIGNATURE
= $57564C50; // 'PLVW'
56 OBJ_SIGNATURE
= $4A424F5F; // '_OBJ'
59 procedure Obj_SaveState (st
: TStream
; o
: PObj
);
61 if (st
= nil) then exit
;
63 utils
.writeSign(st
, '_OBJ');
64 utils
.writeInt(st
, Byte(0)); // version
65 // Ïîëîæåíèå ïî-ãîðèçîíòàëè
66 utils
.writeInt(st
, LongInt(o
^.X
));
67 // Ïîëîæåíèå ïî-âåðòèêàëè
68 utils
.writeInt(st
, LongInt(o
^.Y
));
69 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
70 utils
.writeInt(st
, LongInt(o
^.Rect
.X
));
71 utils
.writeInt(st
, LongInt(o
^.Rect
.Y
));
72 utils
.writeInt(st
, Word(o
^.Rect
.Width
));
73 utils
.writeInt(st
, Word(o
^.Rect
.Height
));
75 utils
.writeInt(st
, LongInt(o
^.Vel
.X
));
76 utils
.writeInt(st
, LongInt(o
^.Vel
.Y
));
78 utils
.writeInt(st
, LongInt(o
^.Accel
.X
));
79 utils
.writeInt(st
, LongInt(o
^.Accel
.Y
));
83 procedure Obj_LoadState (o
: PObj
; st
: TStream
);
85 if (st
= nil) then exit
;
87 if not utils
.checkSign(st
, '_OBJ') then raise XStreamError
.Create('invalid object signature');
88 if (utils
.readByte(st
) <> 0) then raise XStreamError
.Create('invalid object version');
89 // Ïîëîæåíèå ïî-ãîðèçîíòàëè
90 o
^.X
:= utils
.readLongInt(st
);
91 // Ïîëîæåíèå ïî-âåðòèêàëè
92 o
^.Y
:= utils
.readLongInt(st
);
93 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
94 o
^.Rect
.X
:= utils
.readLongInt(st
);
95 o
^.Rect
.Y
:= utils
.readLongInt(st
);
96 o
^.Rect
.Width
:= utils
.readWord(st
);
97 o
^.Rect
.Height
:= utils
.readWord(st
);
99 o
^.Vel
.X
:= utils
.readLongInt(st
);
100 o
^.Vel
.Y
:= utils
.readLongInt(st
);
102 o
^.Accel
.X
:= utils
.readLongInt(st
);
103 o
^.Accel
.Y
:= utils
.readLongInt(st
);
107 function buildSaveName (n
: Integer): AnsiString;
109 result
:= 'SAVGAME' + IntToStr(n
) + '.DAT'
113 function g_GetSaveName (n
: Integer; out valid
: Boolean): AnsiString;
118 filename
: AnsiString;
122 if (n
< 0) or (n
> 65535) then exit
;
124 // Îòêðûâàåì ôàéë ñîõðàíåíèé
125 filename
:= buildSaveName(n
);
126 st
:= e_OpenResourceRO(SaveDirs
, filename
);
128 if not utils
.checkSign(st
, 'DFSV') then
130 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st
], TMsgType
.Warning
);
131 //raise XStreamError.Create('invalid save game signature');
134 ver
:= utils
.readByte(st
);
137 utils
.readLongWord(st
); // section size
138 stlen
:= utils
.readWord(st
);
139 if (stlen
< 1) or (stlen
> 64) then
141 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st
], TMsgType
.Warning
);
142 //raise XStreamError.Create('invalid save game version');
146 SetLength(result
, stlen
);
147 st
.ReadBuffer(result
[1], stlen
);
153 result
:= utils
.readStr(st
, 64);
155 valid
:= (ver
= SAVE_VERSION
);
156 //if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
162 //e_WriteLog('GetSaveName Error: '+e.message, MSG_WARNING);
163 //{$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
169 procedure g_Player_Corpses_SaveState (st
: TStream
);
170 {$IFDEF ENABLE_CORPSES}
176 {$IFDEF ENABLE_CORPSES}
177 for i
:= 0 to High(gCorpses
) do
178 if (gCorpses
[i
] <> nil) then
181 utils
.writeInt(st
, LongInt(count
));
182 {$IFDEF ENABLE_CORPSES}
185 for i
:= 0 to High(gCorpses
) do
187 if gCorpses
[i
] <> nil then
189 utils
.writeStr(st
, gCorpses
[i
].Model
.GetName());
190 utils
.writeBool(st
, gCorpses
[i
].Mess
);
191 gCorpses
[i
].SaveState(st
);
198 procedure g_Player_Corpses_LoadState (st
: TStream
);
199 {$IFDEF ENABLE_CORPSES}
200 var str
: String; b
: Boolean; i
: Integer;
209 {$IFDEF ENALBE_SHELLS}
210 g_Shells_RemoveAll
; // ???
212 {$IFDEF ENABLE_CORPSES}
216 count
:= utils
.readLongInt(st
);
218 {$IFDEF ENABLE_CORPSES}
219 if (count
< 0) or (count
> Length(gCorpses
)) then
220 raise XStreamError
.Create('invalid number of corpses');
221 for i
:= 0 to count
- 1 do
223 str
:= utils
.readStr(st
);
224 b
:= utils
.readBool(st
);
225 gCorpses
[i
] := TCorpse
.Create(0, 0, str
, b
);
226 gCorpses
[i
].LoadState(st
);
230 raise XStreamError
.Create('corpses not supported in this version');
234 function g_SaveGameTo (const filename
: AnsiString; const aname
: AnsiString; deleteOnError
: Boolean=true): Boolean;
242 st
:= e_CreateResource(SaveDirs
, filename
);
244 utils
.writeSign(st
, 'DFSV');
245 utils
.writeInt(st
, Byte(SAVE_VERSION
));
247 utils
.writeStr(st
, aname
, 64);
248 // Ïîëíûé ïóòü ê âàäó è êàðòà
249 //if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('SAVE: current map is ''%s''...', [gCurrentMapFileName]);
250 utils
.writeStr(st
, gCurrentMapFileName
);
252 utils
.writeStr(st
, ExtractFileName(gGameSettings
.WAD
));
254 utils
.writeStr(st
, g_ExtractFileName(gMapInfo
.Map
));
255 // Êîëè÷åñòâî èãðîêîâ
256 utils
.writeInt(st
, Word(g_Player_GetCount
));
258 utils
.writeInt(st
, LongWord(gTime
));
260 utils
.writeInt(st
, Byte(gGameSettings
.GameType
));
262 utils
.writeInt(st
, Byte(gGameSettings
.GameMode
));
264 utils
.writeInt(st
, Word(gGameSettings
.TimeLimit
));
266 utils
.writeInt(st
, Word(gGameSettings
.GoalLimit
));
268 utils
.writeInt(st
, Byte(gGameSettings
.MaxLives
));
270 utils
.writeInt(st
, LongWord(gGameSettings
.Options
));
272 utils
.writeInt(st
, Word(gCoopMonstersKilled
));
273 utils
.writeInt(st
, Word(gCoopSecretsFound
));
274 utils
.writeInt(st
, Word(gCoopTotalMonstersKilled
));
275 utils
.writeInt(st
, Word(gCoopTotalSecretsFound
));
276 utils
.writeInt(st
, Word(gCoopTotalMonsters
));
277 utils
.writeInt(st
, Word(gCoopTotalSecrets
));
279 ///// Ñîõðàíÿåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
280 utils
.writeSign(st
, 'PLVW');
281 utils
.writeInt(st
, Byte(0)); // version
284 if (gPlayer1
<> nil) then PID1
:= gPlayer1
.UID
;
285 if (gPlayer2
<> nil) then PID2
:= gPlayer2
.UID
;
286 utils
.writeInt(st
, Word(PID1
));
287 utils
.writeInt(st
, Word(PID2
));
290 ///// Ñîñòîÿíèå êàðòû /////
294 ///// Ñîñòîÿíèå ïðåäìåòîâ /////
295 g_Items_SaveState(st
);
298 ///// Ñîñòîÿíèå òðèããåðîâ /////
299 g_Triggers_SaveState(st
);
302 ///// Ñîñòîÿíèå îðóæèÿ /////
303 g_Weapon_SaveState(st
);
306 ///// Ñîñòîÿíèå ìîíñòðîâ /////
307 g_Monsters_SaveState(st
);
310 ///// Ñîñòîÿíèå òðóïîâ /////
311 g_Player_Corpses_SaveState(st
);
314 ///// Ñîõðàíÿåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
315 if (g_Player_GetCount
> 0) then
318 for i
:= 0 to High(gPlayers
) do
320 if (gPlayers
[i
] <> nil) then
323 gPlayers
[i
].SaveState(st
);
328 // Âñå ëè èãðîêè íà ìåñòå
329 if (k
<> g_Player_GetCount
) then raise XStreamError
.Create('g_SaveGame: wrong players count');
333 ///// Ìàðêåð îêîí÷àíèÿ /////
334 utils
.writeSign(st
, 'END');
335 utils
.writeInt(st
, Byte(0));
346 g_Console_Add(_lc
[I_GAME_ERROR_SAVE
]);
347 e_WriteLog('SaveState Error: '+e
.message, TMsgType
.Warning
);
348 if deleteOnError
then DeleteFile(filename
);
349 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e
.message);{$ENDIF}
350 e_WriteStackTrace(e
.message);
357 function g_LoadGameFrom (const filename
: AnsiString): Boolean;
360 WAD_Path
, Map_Name
: AnsiString;
362 Game_Type
, Game_Mode
, Game_MaxLives
: Byte;
363 Game_TimeLimit
, Game_GoalLimit
: Word;
364 Game_Time
, Game_Options
: Cardinal;
365 Game_CoopMonstersKilled
,
366 Game_CoopSecretsFound
,
367 Game_CoopTotalMonstersKilled
,
368 Game_CoopTotalSecretsFound
,
369 Game_CoopTotalMonsters
,
370 Game_CoopTotalSecrets
,
373 gameCleared
: Boolean = false;
374 curmapfile
: AnsiString = '';
375 {$IF DEFINED(D2F_DEBUG)}
376 errpos
: LongWord = 0;
382 st
:= e_OpenResourceRO(SaveDirs
, filename
);
384 if not utils
.checkSign(st
, 'DFSV') then raise XStreamError
.Create('invalid save game signature');
385 if (utils
.readByte(st
) <> SAVE_VERSION
) then raise XStreamError
.Create('invalid save game version');
387 e_WriteLog('Loading saved game...', TMsgType
.Notify
);
389 {$IF DEFINED(D2F_DEBUG)}try{$ENDIF}
390 //g_Game_Free(false); // don't free textures for the same map
391 g_Game_ClearLoading();
392 g_Game_SetLoadingText(_lc
[I_LOAD_SAVE_FILE
], 0, False);
393 gLoadGameMode
:= True;
395 ///// Çàãðóæàåì ñîñòîÿíèå èãðû /////
397 {str :=} utils
.readStr(st
, 64);
399 // Ïîëíûé ïóòü ê âàäó è êàðòà
400 curmapfile
:= utils
.readStr(st
);
402 if (Length(gCurrentMapFileName
) <> 0) then e_LogWritefln('LOAD: previous map was ''%s''...', [gCurrentMapFileName
]);
403 if (Length(curmapfile
) <> 0) then e_LogWritefln('LOAD: new map is ''%s''...', [curmapfile
]);
404 // À âîò òóò, íàêîíåö, ÷èñòèì ðåñóðñû
405 g_Game_Free(curmapfile
<> gCurrentMapFileName
); // don't free textures for the same map
409 WAD_Path
:= utils
.readStr(st
);
411 Map_Name
:= utils
.readStr(st
);
412 // Êîëè÷åñòâî èãðîêîâ
413 nPlayers
:= utils
.readWord(st
);
415 Game_Time
:= utils
.readLongWord(st
);
417 Game_Type
:= utils
.readByte(st
);
419 Game_Mode
:= utils
.readByte(st
);
421 Game_TimeLimit
:= utils
.readWord(st
);
423 Game_GoalLimit
:= utils
.readWord(st
);
425 Game_MaxLives
:= utils
.readByte(st
);
427 Game_Options
:= utils
.readLongWord(st
);
429 Game_CoopMonstersKilled
:= utils
.readWord(st
);
430 Game_CoopSecretsFound
:= utils
.readWord(st
);
431 Game_CoopTotalMonstersKilled
:= utils
.readWord(st
);
432 Game_CoopTotalSecretsFound
:= utils
.readWord(st
);
433 Game_CoopTotalMonsters
:= utils
.readWord(st
);
434 Game_CoopTotalSecrets
:= utils
.readWord(st
);
437 ///// Çàãðóæàåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
438 if not utils
.checkSign(st
, 'PLVW') then raise XStreamError
.Create('invalid viewport signature');
439 if (utils
.readByte(st
) <> 0) then raise XStreamError
.Create('invalid viewport version');
440 PID1
:= utils
.readWord(st
);
441 PID2
:= utils
.readWord(st
);
445 ZeroMemory(@gGameSettings
, sizeof(TGameSettings
));
448 if (Game_Type
= GT_NONE
) or (Game_Type
= GT_SINGLE
) then
451 gGameSettings
.GameType
:= GT_SINGLE
;
452 gGameSettings
.MaxLives
:= 0;
453 gGameSettings
.Options
:= gGameSettings
.Options
+GAME_OPTION_ALLOWEXIT
;
454 gGameSettings
.Options
:= gGameSettings
.Options
+GAME_OPTION_MONSTERS
;
455 gGameSettings
.Options
:= gGameSettings
.Options
+GAME_OPTION_BOTVSMONSTER
;
456 gSwitchGameMode
:= GM_SINGLE
;
461 gGameSettings
.GameType
:= GT_CUSTOM
;
462 gGameSettings
.GameMode
:= Game_Mode
;
463 gSwitchGameMode
:= Game_Mode
;
464 gGameSettings
.TimeLimit
:= Game_TimeLimit
;
465 gGameSettings
.GoalLimit
:= Game_GoalLimit
;
466 gGameSettings
.MaxLives
:= IfThen(Game_Mode
= GM_CTF
, 0, Game_MaxLives
);
467 gGameSettings
.Options
:= Game_Options
;
469 g_Game_ExecuteEvent('ongamestart');
471 // Çàãðóçêà è çàïóñê êàðòû
472 //FIXME: save/load `asMegawad`
473 if not g_Game_StartMap(false{asMegawad}, WAD_Path
+':\'+Map_Name
, True, curmapfile
) then
475 g_FatalError(Format(_lc
[I_GAME_ERROR_MAP_LOAD
], [WAD_Path
+ ':\' + Map_Name
]));
479 // Íàñòðîéêè èãðîêîâ è áîòîâ
482 // Óñòàíàâëèâàåì âðåìÿ
485 gCoopMonstersKilled
:= Game_CoopMonstersKilled
;
486 gCoopSecretsFound
:= Game_CoopSecretsFound
;
487 gCoopTotalMonstersKilled
:= Game_CoopTotalMonstersKilled
;
488 gCoopTotalSecretsFound
:= Game_CoopTotalSecretsFound
;
489 gCoopTotalMonsters
:= Game_CoopTotalMonsters
;
490 gCoopTotalSecrets
:= Game_CoopTotalSecrets
;
492 ///// Çàãðóæàåì ñîñòîÿíèå êàðòû /////
496 ///// Çàãðóæàåì ñîñòîÿíèå ïðåäìåòîâ /////
497 g_Items_LoadState(st
);
500 ///// Çàãðóæàåì ñîñòîÿíèå òðèããåðîâ /////
501 g_Triggers_LoadState(st
);
504 ///// Çàãðóæàåì ñîñòîÿíèå îðóæèÿ /////
505 g_Weapon_LoadState(st
);
508 ///// Çàãðóæàåì ñîñòîÿíèå ìîíñòðîâ /////
509 g_Monsters_LoadState(st
);
512 ///// Çàãðóæàåì ñîñòîÿíèå òðóïîâ /////
513 g_Player_Corpses_LoadState(st
);
516 ///// Çàãðóæàåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
520 for i
:= 0 to nPlayers
-1 do g_Player_CreateFromState(st
);
523 // Ïðèâÿçûâàåì îñíîâíûõ èãðîêîâ ê îáëàñòÿì ïðîñìîòðà
524 gPlayer1
:= g_Player_Get(PID1
);
525 gPlayer2
:= g_Player_Get(PID2
);
527 if (gPlayer1
<> nil) then
529 gPlayer1
.Name
:= gPlayer1Settings
.Name
;
530 gPlayer1
.FPreferredTeam
:= gPlayer1Settings
.Team
;
531 gPlayer1
.FActualModelName
:= gPlayer1Settings
.Model
;
532 gPlayer1
.SetModel(gPlayer1
.FActualModelName
);
533 gPlayer1
.SetColor(gPlayer1Settings
.Color
);
536 if (gPlayer2
<> nil) then
538 gPlayer2
.Name
:= gPlayer2Settings
.Name
;
539 gPlayer2
.FPreferredTeam
:= gPlayer2Settings
.Team
;
540 gPlayer2
.FActualModelName
:= gPlayer2Settings
.Model
;
541 gPlayer2
.SetModel(gPlayer2
.FActualModelName
);
542 gPlayer2
.SetColor(gPlayer2Settings
.Color
);
546 ///// Ìàðêåð îêîí÷àíèÿ /////
547 if not utils
.checkSign(st
, 'END') then raise XStreamError
.Create('no end marker');
548 if (utils
.readByte(st
) <> 0) then raise XStreamError
.Create('invalid end marker');
551 // Èùåì òðèããåðû ñ óñëîâèåì ñìåðòè ìîíñòðîâ
552 if (gTriggers
<> nil) then g_Map_ReAdd_DieTriggers();
555 gLoadGameMode
:= false;
557 {$IF DEFINED(D2F_DEBUG)}
560 errpos
:= LongWord(st
.position
);
569 on e
: EFileNotFoundException
do
571 g_Console_Add(_lc
[I_GAME_ERROR_LOAD
]);
572 g_Console_Add('LoadState Error: '+e
.message);
573 e_WriteLog('LoadState Error: '+e
.message, TMsgType
.Warning
);
574 gLoadGameMode
:= false;
579 g_Console_Add(_lc
[I_GAME_ERROR_LOAD
]);
580 g_Console_Add('LoadState Error: '+e
.message);
581 e_WriteLog('LoadState Error: '+e
.message, TMsgType
.Warning
);
582 {$IF DEFINED(D2F_DEBUG)}e_LogWritefln('stream error position: 0x%08x', [errpos
], TMsgType
.Warning
);{$ENDIF}
583 gLoadGameMode
:= false;
585 if gState
<> STATE_MENU
then
586 g_FatalError(_lc
[I_GAME_ERROR_LOAD
])
587 else if not gameCleared
then
589 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e
.message);{$ENDIF}
595 function g_SaveGame (n
: Integer; const aname
: AnsiString): Boolean;
597 result
:= g_SaveGameTo(buildSaveName(n
), aname
, true);
601 function g_LoadGame (n
: Integer): Boolean;
603 result
:= g_LoadGameFrom(buildSaveName(n
));