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
;
59 utils
.writeSign(st
, '_OBJ');
60 st
.WriteByte(0); // version
63 st
.WriteInt32LE(o
^.X
);
64 st
.WriteInt32LE(o
^.Y
);
66 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
67 st
.WriteInt32LE(o
^.Rect
.X
);
68 st
.WriteInt32LE(o
^.Rect
.Y
);
69 st
.WriteWordLE(o
^.Rect
.Width
);
70 st
.WriteWordLE(o
^.Rect
.Height
);
73 st
.WriteInt32LE(o
^.Vel
.X
);
74 st
.WriteInt32LE(o
^.Vel
.Y
);
77 st
.WriteInt32LE(o
^.Accel
.X
);
78 st
.WriteInt32LE(o
^.Accel
.Y
);
82 procedure Obj_LoadState (o
: PObj
; st
: TStream
);
84 if st
= nil then Exit
;
87 if not utils
.checkSign(st
, '_OBJ') then
88 Raise XStreamError
.Create('invalid object signature');
89 if st
.ReadByte() <> 0 then
90 Raise XStreamError
.Create('invalid object version');
93 o
^.X
:= st
.ReadInt32LE();
94 o
^.Y
:= st
.ReadInt32LE();
96 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
97 o
^.Rect
.X
:= st
.ReadInt32LE();
98 o
^.Rect
.Y
:= st
.ReadInt32LE();
99 o
^.Rect
.Width
:= st
.ReadWordLE();
100 o
^.Rect
.Height
:= st
.ReadWordLE();
103 o
^.Vel
.X
:= st
.ReadInt32LE();
104 o
^.Vel
.Y
:= st
.ReadInt32LE();
107 o
^.Accel
.X
:= st
.ReadInt32LE();
108 o
^.Accel
.Y
:= st
.ReadInt32LE();
112 function buildSaveName (n
: Integer): AnsiString
;
114 result
:= 'SAVGAME' + IntToStr(n
) + '.DAT'
118 function g_GetSaveName (n
: Integer; out valid
: Boolean): AnsiString
;
123 filename
: AnsiString
;
127 if (n
< 0) or (n
> 65535) then exit
;
129 // Îòêðûâàåì ôàéë ñîõðàíåíèé
130 filename
:= buildSaveName(n
);
131 st
:= e_OpenResourceRO(SaveDirs
, filename
);
133 if not utils
.checkSign(st
, 'DFSV') then
135 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st
], TMsgType
.Warning
);
136 //raise XStreamError.Create('invalid save game signature');
139 ver
:= st
.ReadByte();
142 st
.ReadDWordLE(); // section size
143 stlen
:= st
.ReadWordLE();
144 if (stlen
< 1) or (stlen
> 64) then
146 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st
], TMsgType
.Warning
);
147 //raise XStreamError.Create('invalid save game version');
151 SetLength(result
, stlen
);
152 st
.ReadBuffer(result
[1], stlen
);
158 result
:= utils
.readStr(st
, 64);
160 valid
:= (ver
= SAVE_VERSION
);
161 //if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
167 //e_WriteLog('GetSaveName Error: '+e.message, MSG_WARNING);
168 //{$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
175 function g_SaveGameTo (const filename
: AnsiString
; const aname
: AnsiString
; deleteOnError
: Boolean=true): Boolean;
183 st
:= e_CreateResource(SaveDirs
, filename
);
185 utils
.writeSign(st
, 'DFSV');
186 st
.WriteByte(SAVE_VERSION
);
187 utils
.writeStr(st
, aname
, 64); // Èìÿ ñýéâà
189 // Ïîëíûé ïóòü ê âàäó è êàðòà
190 //if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('SAVE: current map is ''%s''...', [gCurrentMapFileName]);
191 utils
.writeStr(st
, gCurrentMapFileName
);
193 utils
.writeStr(st
, ExtractFileName(gGameSettings
.WAD
)); // Ïóòü ê êàðòå
194 utils
.writeStr(st
, g_ExtractFileName(gMapInfo
.Map
)); // Èìÿ êàðòû
195 st
.WriteWordLE(g_Player_GetCount
); // Êîëè÷åñòâî èãðîêîâ
196 st
.WriteDWordLE(gTime
); // Èãðîâîå âðåìÿ
197 st
.WriteByte(gGameSettings
.GameType
); // Òèï èãðû
198 st
.WriteByte(gGameSettings
.GameMode
); // Ðåæèì èãðû
199 st
.WriteWordLE(gGameSettings
.TimeLimit
); // Ëèìèò âðåìåíè
200 st
.WriteWordLE(gGameSettings
.ScoreLimit
); // Ëèìèò î÷êîâ
201 st
.WriteByte(gGameSettings
.MaxLives
); // Ëèìèò æèçíåé
202 st
.WriteDWordLE(LongWord(gGameSettings
.Options
)); // Èãðîâûå îïöèè
205 st
.WriteWordLE(gCoopMonstersKilled
);
206 st
.WriteWordLE(gCoopSecretsFound
);
207 st
.WriteWordLE(gCoopTotalMonstersKilled
);
208 st
.WriteWordLE(gCoopTotalSecretsFound
);
209 st
.WriteWordLE(gCoopTotalMonsters
);
210 st
.WriteWordLE(gCoopTotalSecrets
);
212 ///// Ñîõðàíÿåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
213 utils
.writeSign(st
, 'PLVW');
214 st
.WriteByte(0); // version
217 if (gPlayer1
<> nil) then PID1
:= gPlayer1
.UID
;
218 if (gPlayer2
<> nil) then PID2
:= gPlayer2
.UID
;
219 st
.WriteWordLE(PID1
);
220 st
.WriteWordLE(PID2
);
223 g_Map_SaveState(st
); // Ñîñòîÿíèå êàðòû
224 g_Items_SaveState(st
); // Ñîñòîÿíèå ïðåäìåòîâ
225 g_Triggers_SaveState(st
); // Ñîñòîÿíèå òðèããåðîâ
226 g_Weapon_SaveState(st
); // Ñîñòîÿíèå îðóæèÿ
227 g_Monsters_SaveState(st
); // Ñîñòîÿíèå ìîíñòðîâ
228 g_Player_Corpses_SaveState(st
); // Ñîñòîÿíèå òðóïîâ
230 ///// Ñîõðàíÿåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
231 if g_Player_GetCount
> 0 then
234 for i
:= 0 to High(gPlayers
) do
236 if gPlayers
[i
] <> nil then
239 gPlayers
[i
].SaveState(st
);
244 // Âñå ëè èãðîêè íà ìåñòå
245 if k
<> g_Player_GetCount
then
246 Raise XStreamError
.Create('g_SaveGame: wrong players count');
250 ///// Ìàðêåð îêîí÷àíèÿ /////
251 utils
.writeSign(st
, 'END');
263 g_Console_Add(_lc
[I_GAME_ERROR_SAVE
]);
264 e_WriteLog('SaveState Error: '+e
.message, TMsgType
.Warning
);
265 if deleteOnError
then DeleteFile(filename
);
266 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e
.message);{$ENDIF}
273 function g_LoadGameFrom (const filename
: AnsiString
): Boolean;
276 WAD_Path
, Map_Name
: AnsiString
;
278 Game_Type
, Game_Mode
, Game_MaxLives
: Byte;
279 Game_TimeLimit
, Game_ScoreLimit
: Word;
280 Game_Time
, Game_Options
: Cardinal;
281 Game_CoopMonstersKilled
,
282 Game_CoopSecretsFound
,
283 Game_CoopTotalMonstersKilled
,
284 Game_CoopTotalSecretsFound
,
285 Game_CoopTotalMonsters
,
286 Game_CoopTotalSecrets
,
289 gameCleared
: Boolean = false;
290 curmapfile
: AnsiString
= '';
291 {$IF DEFINED(D2F_DEBUG)}
292 errpos
: LongWord
= 0;
298 st
:= e_OpenResourceRO(SaveDirs
, filename
);
300 if not utils
.checkSign(st
, 'DFSV') then
301 Raise XStreamError
.Create('invalid save game signature');
302 if st
.ReadByte() <> SAVE_VERSION
then
303 Raise XStreamError
.Create('invalid save game version');
305 e_WriteLog('Loading saved game...', TMsgType
.Notify
);
307 {$IF DEFINED(D2F_DEBUG)}
310 //g_Game_Free(false); // don't free textures for the same map
311 g_Game_ClearLoading();
312 g_Game_SetLoadingText(_lc
[I_LOAD_SAVE_FILE
], 0, False);
313 gLoadGameMode
:= True;
315 ///// Çàãðóæàåì ñîñòîÿíèå èãðû /////
317 {str :=} utils
.readStr(st
, 64);
319 // Ïîëíûé ïóòü ê âàäó è êàðòà
320 curmapfile
:= utils
.readStr(st
);
322 if Length(gCurrentMapFileName
) <> 0 then
323 e_LogWritefln('LOAD: previous map was ''%s''...', [gCurrentMapFileName
]);
324 if Length(curmapfile
) <> 0 then
325 e_LogWritefln('LOAD: new map is ''%s''...', [curmapfile
]);
327 // À âîò òóò, íàêîíåö, ÷èñòèì ðåñóðñû
328 g_Game_Free(curmapfile
<> gCurrentMapFileName
); // don't free textures for the same map
331 WAD_Path
:= utils
.readStr(st
); // Ïóòü ê êàðòå
332 Map_Name
:= utils
.readStr(st
); // Èìÿ êàðòû
333 nPlayers
:= st
.ReadWordLE(); // Êîëè÷åñòâî èãðîêîâ
334 Game_Time
:= st
.ReadDWordLE(); // Èãðîâîå âðåìÿ
335 Game_Type
:= st
.ReadByte(); // Òèï èãðû
336 Game_Mode
:= st
.ReadByte(); // Ðåæèì èãðû
337 Game_TimeLimit
:= st
.ReadWordLE(); // Ëèìèò âðåìåíè
338 Game_ScoreLimit
:= st
.ReadWordLE(); // Ëèìèò î÷êîâ
339 Game_MaxLives
:= st
.ReadByte(); // Ëèìèò æèçíåé
340 Game_Options
:= st
.ReadDWordLE(); // Èãðîâûå îïöèè
343 Game_CoopMonstersKilled
:= st
.ReadWordLE();
344 Game_CoopSecretsFound
:= st
.ReadWordLE();
345 Game_CoopTotalMonstersKilled
:= st
.ReadWordLE();
346 Game_CoopTotalSecretsFound
:= st
.ReadWordLE();
347 Game_CoopTotalMonsters
:= st
.ReadWordLE();
348 Game_CoopTotalSecrets
:= st
.ReadWordLE();
351 ///// Çàãðóæàåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
352 if not utils
.checkSign(st
, 'PLVW') then
353 Raise XStreamError
.Create('invalid viewport signature');
354 if st
.ReadByte() <> 0 then
355 Raise XStreamError
.Create('invalid viewport version');
356 PID1
:= st
.ReadWordLE();
357 PID2
:= st
.ReadWordLE();
361 gGameSettings
:= Default(TGameSettings
);
366 gGameSettings
.GameType
:= Game_Type
;
367 gGameSettings
.GameMode
:= Game_Mode
;
368 gGameSettings
.TimeLimit
:= Game_TimeLimit
;
369 gGameSettings
.ScoreLimit
:= Game_ScoreLimit
;
370 gGameSettings
.MaxLives
:= Game_MaxLives
;
371 gGameSettings
.Options
:= TGameOptions(Game_Options
);
372 gSwitchGameMode
:= Game_Mode
;
373 g_Game_ExecuteEvent('ongamestart');
375 // Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ
376 g_Game_SetupScreenSize();
378 // Çàãðóçêà è çàïóñê êàðòû
379 // FIXME: save/load `asMegawad`
380 if not g_Game_StartMap(False{asMegawad}, WAD_Path
+':\'+Map_Name
, True, curmapfile
) then
381 Raise Exception
.Create(Format(_lc
[I_GAME_ERROR_MAP_LOAD
], [WAD_Path
+ ':\' + Map_Name
]));
383 g_Player_Init(); // Íàñòðîéêè èãðîêîâ è áîòîâ
384 gTime
:= Game_Time
; // Óñòàíàâëèâàåì âðåìÿ
387 gCoopMonstersKilled
:= Game_CoopMonstersKilled
;
388 gCoopSecretsFound
:= Game_CoopSecretsFound
;
389 gCoopTotalMonstersKilled
:= Game_CoopTotalMonstersKilled
;
390 gCoopTotalSecretsFound
:= Game_CoopTotalSecretsFound
;
391 gCoopTotalMonsters
:= Game_CoopTotalMonsters
;
392 gCoopTotalSecrets
:= Game_CoopTotalSecrets
;
394 g_Map_LoadState(st
); // Çàãðóæàåì ñîñòîÿíèå êàðòû
395 g_Items_LoadState(st
); // Çàãðóæàåì ñîñòîÿíèå ïðåäìåòîâ
396 g_Triggers_LoadState(st
); // Çàãðóæàåì ñîñòîÿíèå òðèããåðîâ
397 g_Weapon_LoadState(st
); // Çàãðóæàåì ñîñòîÿíèå îðóæèÿ
398 g_Monsters_LoadState(st
); // Çàãðóæàåì ñîñòîÿíèå ìîíñòðîâ
399 g_Player_Corpses_LoadState(st
); // Çàãðóæàåì ñîñòîÿíèå òðóïîâ
401 ///// Çàãðóæàåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
402 for i
:= 0 to nPlayers
-1 do
403 g_Player_CreateFromState(st
);
405 // Ïðèâÿçûâàåì îñíîâíûõ èãðîêîâ ê îáëàñòÿì ïðîñìîòðà
406 gPlayer1
:= g_Player_Get(PID1
);
407 gPlayer2
:= g_Player_Get(PID2
);
409 if gPlayer1
<> nil then
411 gPlayer1
.Name
:= gPlayer1Settings
.Name
;
412 gPlayer1
.FPreferredTeam
:= gPlayer1Settings
.Team
;
413 gPlayer1
.FActualModelName
:= gPlayer1Settings
.Model
;
414 gPlayer1
.SetModel(gPlayer1
.FActualModelName
);
415 gPlayer1
.SetColor(gPlayer1Settings
.Color
);
418 if gPlayer2
<> nil then
420 gPlayer2
.Name
:= gPlayer2Settings
.Name
;
421 gPlayer2
.FPreferredTeam
:= gPlayer2Settings
.Team
;
422 gPlayer2
.FActualModelName
:= gPlayer2Settings
.Model
;
423 gPlayer2
.SetModel(gPlayer2
.FActualModelName
);
424 gPlayer2
.SetColor(gPlayer2Settings
.Color
);
428 ///// Ìàðêåð îêîí÷àíèÿ /////
429 if not utils
.checkSign(st
, 'END') then
430 Raise XStreamError
.Create('no end marker');
431 if st
.ReadByte() <> 0 then
432 Raise XStreamError
.Create('invalid end marker');
435 // Èùåì òðèããåðû ñ óñëîâèåì ñìåðòè ìîíñòðîâ
436 if gTriggers
<> nil then g_Map_ReAdd_DieTriggers();
439 gLoadGameMode
:= False;
441 {$IF DEFINED(D2F_DEBUG)}
444 errpos
:= LongWord(st
.position
);
453 on e
: EFileNotFoundException
do
455 g_Console_Add(_lc
[I_GAME_ERROR_LOAD
]);
456 g_Console_Add('LoadState Error: '+e
.message);
457 e_WriteLog('LoadState Error: '+e
.message, TMsgType
.Warning
);
458 gLoadGameMode
:= false;
463 g_Console_Add(_lc
[I_GAME_ERROR_LOAD
]);
464 g_Console_Add('LoadState Error: '+e
.message);
465 e_WriteLog('LoadState Error: '+e
.message, TMsgType
.Warning
);
466 {$IF DEFINED(D2F_DEBUG)}e_LogWritefln('stream error position: 0x%08x', [errpos
], TMsgType
.Warning
);{$ENDIF}
467 gLoadGameMode
:= false;
469 if gState
<> STATE_MENU
then
470 g_FatalError(_lc
[I_GAME_ERROR_LOAD
])
471 else if not gameCleared
then
473 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e
.message);{$ENDIF}
479 function g_SaveGame (n
: Integer; const aname
: AnsiString
): Boolean;
481 result
:= g_SaveGameTo(buildSaveName(n
), aname
, true);
485 function g_LoadGame (n
: Integer): Boolean;
487 result
:= g_LoadGameFrom(buildSaveName(n
));