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}
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
);
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
;
47 SAVE_SIGNATURE
= $56534644; // 'DFSV'
49 END_MARKER_STRING
= 'END';
50 PLAYER_VIEW_SIGNATURE
= $57564C50; // 'PLVW'
51 OBJ_SIGNATURE
= $4A424F5F; // '_OBJ'
54 procedure Obj_SaveState (st
: TStream
; o
: PObj
);
56 if (st
= nil) then exit
;
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
));
70 utils
.writeInt(st
, LongInt(o
^.Vel
.X
));
71 utils
.writeInt(st
, LongInt(o
^.Vel
.Y
));
73 utils
.writeInt(st
, LongInt(o
^.Accel
.X
));
74 utils
.writeInt(st
, LongInt(o
^.Accel
.Y
));
78 procedure Obj_LoadState (o
: PObj
; st
: TStream
);
80 if (st
= nil) then exit
;
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
);
94 o
^.Vel
.X
:= utils
.readLongInt(st
);
95 o
^.Vel
.Y
:= utils
.readLongInt(st
);
97 o
^.Accel
.X
:= utils
.readLongInt(st
);
98 o
^.Accel
.Y
:= utils
.readLongInt(st
);
102 function buildSaveName (n
: Integer): AnsiString
;
104 result
:= 'SAVGAME' + IntToStr(n
) + '.DAT'
108 function g_GetSaveName (n
: Integer; out valid
: Boolean): AnsiString
;
113 filename
: AnsiString
;
117 if (n
< 0) or (n
> 65535) then exit
;
119 // Îòêðûâàåì ôàéë ñîõðàíåíèé
120 filename
:= buildSaveName(n
);
121 st
:= e_OpenResourceRO(SaveDirs
, filename
);
123 if not utils
.checkSign(st
, 'DFSV') then
125 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st
], TMsgType
.Warning
);
126 //raise XStreamError.Create('invalid save game signature');
129 ver
:= utils
.readByte(st
);
132 utils
.readLongWord(st
); // section size
133 stlen
:= utils
.readWord(st
);
134 if (stlen
< 1) or (stlen
> 64) then
136 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st
], TMsgType
.Warning
);
137 //raise XStreamError.Create('invalid save game version');
141 SetLength(result
, stlen
);
142 st
.ReadBuffer(result
[1], stlen
);
148 result
:= utils
.readStr(st
, 64);
150 valid
:= (ver
= SAVE_VERSION
);
151 //if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
157 //e_WriteLog('GetSaveName Error: '+e.message, MSG_WARNING);
158 //{$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
165 function g_SaveGameTo (const filename
: AnsiString
; const aname
: AnsiString
; deleteOnError
: Boolean=true): Boolean;
173 st
:= e_CreateResource(SaveDirs
, filename
);
175 utils
.writeSign(st
, 'DFSV');
176 utils
.writeInt(st
, Byte(SAVE_VERSION
));
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
);
183 utils
.writeStr(st
, ExtractFileName(gGameSettings
.WAD
));
185 utils
.writeStr(st
, g_ExtractFileName(gMapInfo
.Map
));
186 // Êîëè÷åñòâî èãðîêîâ
187 utils
.writeInt(st
, Word(g_Player_GetCount
));
189 utils
.writeInt(st
, LongWord(gTime
));
191 utils
.writeInt(st
, Byte(gGameSettings
.GameType
));
193 utils
.writeInt(st
, Byte(gGameSettings
.GameMode
));
195 utils
.writeInt(st
, Word(gGameSettings
.TimeLimit
));
197 utils
.writeInt(st
, Word(gGameSettings
.ScoreLimit
));
199 utils
.writeInt(st
, Byte(gGameSettings
.MaxLives
));
201 utils
.writeInt(st
, LongWord(gGameSettings
.Options
));
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
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
));
221 ///// Ñîñòîÿíèå êàðòû /////
225 ///// Ñîñòîÿíèå ïðåäìåòîâ /////
226 g_Items_SaveState(st
);
229 ///// Ñîñòîÿíèå òðèããåðîâ /////
230 g_Triggers_SaveState(st
);
233 ///// Ñîñòîÿíèå îðóæèÿ /////
234 g_Weapon_SaveState(st
);
237 ///// Ñîñòîÿíèå ìîíñòðîâ /////
238 g_Monsters_SaveState(st
);
241 ///// Ñîñòîÿíèå òðóïîâ /////
242 g_Player_Corpses_SaveState(st
);
245 ///// Ñîõðàíÿåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
246 if (g_Player_GetCount
> 0) then
249 for i
:= 0 to High(gPlayers
) do
251 if (gPlayers
[i
] <> nil) then
254 gPlayers
[i
].SaveState(st
);
259 // Âñå ëè èãðîêè íà ìåñòå
260 if (k
<> g_Player_GetCount
) then raise XStreamError
.Create('g_SaveGame: wrong players count');
264 ///// Ìàðêåð îêîí÷àíèÿ /////
265 utils
.writeSign(st
, 'END');
266 utils
.writeInt(st
, Byte(0));
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);
288 function g_LoadGameFrom (const filename
: AnsiString
): Boolean;
291 WAD_Path
, Map_Name
: AnsiString
;
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
,
304 gameCleared
: Boolean = false;
305 curmapfile
: AnsiString
= '';
306 {$IF DEFINED(D2F_DEBUG)}
307 errpos
: LongWord
= 0;
313 st
:= e_OpenResourceRO(SaveDirs
, filename
);
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)}
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 ///// Çàãðóæàåì ñîñòîÿíèå èãðû /////
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
342 WAD_Path
:= utils
.readStr(st
);
344 Map_Name
:= utils
.readStr(st
);
345 // Êîëè÷åñòâî èãðîêîâ
346 nPlayers
:= utils
.readWord(st
);
348 Game_Time
:= utils
.readLongWord(st
);
350 Game_Type
:= utils
.readByte(st
);
352 Game_Mode
:= utils
.readByte(st
);
354 Game_TimeLimit
:= utils
.readWord(st
);
356 Game_ScoreLimit
:= utils
.readWord(st
);
358 Game_MaxLives
:= utils
.readByte(st
);
360 Game_Options
:= utils
.readLongWord(st
);
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
);
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
);
378 ZeroMemory(@gGameSettings
, sizeof(TGameSettings
));
381 if (Game_Type
= GT_NONE
) or (Game_Type
= GT_SINGLE
) then
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
;
394 gGameSettings
.GameType
:= GT_CUSTOM
;
395 gGameSettings
.GameMode
:= Game_Mode
;
396 gSwitchGameMode
:= Game_Mode
;
397 gGameSettings
.TimeLimit
:= Game_TimeLimit
;
398 gGameSettings
.ScoreLimit
:= Game_ScoreLimit
;
399 gGameSettings
.MaxLives
:= IfThen(Game_Mode
= GM_CTF
, 0, Game_MaxLives
);
400 gGameSettings
.Options
:= Game_Options
;
402 g_Game_ExecuteEvent('ongamestart');
404 // Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ
405 g_Game_SetupScreenSize();
407 // Çàãðóçêà è çàïóñê êàðòû
408 //FIXME: save/load `asMegawad`
409 if not g_Game_StartMap(false{asMegawad}, WAD_Path
+':\'+Map_Name
, True, curmapfile
) then
410 raise Exception
.Create(Format(_lc
[I_GAME_ERROR_MAP_LOAD
], [WAD_Path
+ ':\' + Map_Name
]));
412 // Íàñòðîéêè èãðîêîâ è áîòîâ
415 // Óñòàíàâëèâàåì âðåìÿ
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 ///// Çàãðóæàåì ñîñòîÿíèå êàðòû /////
429 ///// Çàãðóæàåì ñîñòîÿíèå ïðåäìåòîâ /////
430 g_Items_LoadState(st
);
433 ///// Çàãðóæàåì ñîñòîÿíèå òðèããåðîâ /////
434 g_Triggers_LoadState(st
);
437 ///// Çàãðóæàåì ñîñòîÿíèå îðóæèÿ /////
438 g_Weapon_LoadState(st
);
441 ///// Çàãðóæàåì ñîñòîÿíèå ìîíñòðîâ /////
442 g_Monsters_LoadState(st
);
445 ///// Çàãðóæàåì ñîñòîÿíèå òðóïîâ /////
446 g_Player_Corpses_LoadState(st
);
449 ///// Çàãðóæàåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
453 for i
:= 0 to nPlayers
-1 do g_Player_CreateFromState(st
);
456 // Ïðèâÿçûâàåì îñíîâíûõ èãðîêîâ ê îáëàñòÿì ïðîñìîòðà
457 gPlayer1
:= g_Player_Get(PID1
);
458 gPlayer2
:= g_Player_Get(PID2
);
460 if (gPlayer1
<> nil) then
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
);
469 if (gPlayer2
<> nil) then
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
);
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');
484 // Èùåì òðèããåðû ñ óñëîâèåì ñìåðòè ìîíñòðîâ
485 if (gTriggers
<> nil) then g_Map_ReAdd_DieTriggers();
488 gLoadGameMode
:= false;
490 {$IF DEFINED(D2F_DEBUG)}
493 errpos
:= LongWord(st
.position
);
502 on e
: EFileNotFoundException
do
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;
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;
518 if gState
<> STATE_MENU
then
519 g_FatalError(_lc
[I_GAME_ERROR_LOAD
])
520 else if not gameCleared
then
522 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e
.message);{$ENDIF}
528 function g_SaveGame (n
: Integer; const aname
: AnsiString
): Boolean;
530 result
:= g_SaveGameTo(buildSaveName(n
), aname
, true);
534 function g_LoadGame (n
: Integer): Boolean;
536 result
:= g_LoadGameFrom(buildSaveName(n
));