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, either version 3 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 {$INCLUDE ../shared/a_modes.inc}
23 e_graphics
, g_phys
, g_textures
;
26 function g_GetSaveName (n
: Integer; out valid
: Boolean): AnsiString
;
28 function g_SaveGameTo (const filename
: AnsiString
; const aname
: AnsiString
; deleteOnError
: Boolean=true): Boolean;
29 function g_LoadGameFrom (const filename
: AnsiString
): Boolean;
31 function g_SaveGame (n
: Integer; const aname
: AnsiString
): Boolean;
32 function g_LoadGame (n
: Integer): Boolean;
34 procedure Obj_SaveState (st
: TStream
; o
: PObj
);
35 procedure Obj_LoadState (o
: PObj
; st
: TStream
);
41 MAPDEF
, utils
, xstreams
,
42 g_game
, g_items
, g_map
, g_monsters
, g_triggers
,
43 g_basic
, g_main
, Math
, wadreader
,
44 g_weapons
, g_player
, g_console
,
48 SAVE_SIGNATURE
= $56534644; // 'DFSV'
50 END_MARKER_STRING
= 'END';
51 PLAYER_VIEW_SIGNATURE
= $57564C50; // 'PLVW'
52 OBJ_SIGNATURE
= $4A424F5F; // '_OBJ'
55 procedure Obj_SaveState (st
: TStream
; o
: PObj
);
57 if (st
= nil) then exit
;
59 utils
.writeSign(st
, '_OBJ');
60 utils
.writeInt(st
, Byte(0)); // version
61 // Ïîëîæåíèå ïî-ãîðèçîíòàëè
62 utils
.writeInt(st
, LongInt(o
^.X
));
63 // Ïîëîæåíèå ïî-âåðòèêàëè
64 utils
.writeInt(st
, LongInt(o
^.Y
));
65 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
66 utils
.writeInt(st
, LongInt(o
^.Rect
.X
));
67 utils
.writeInt(st
, LongInt(o
^.Rect
.Y
));
68 utils
.writeInt(st
, Word(o
^.Rect
.Width
));
69 utils
.writeInt(st
, Word(o
^.Rect
.Height
));
71 utils
.writeInt(st
, LongInt(o
^.Vel
.X
));
72 utils
.writeInt(st
, LongInt(o
^.Vel
.Y
));
74 utils
.writeInt(st
, LongInt(o
^.Accel
.X
));
75 utils
.writeInt(st
, LongInt(o
^.Accel
.Y
));
79 procedure Obj_LoadState (o
: PObj
; st
: TStream
);
81 if (st
= nil) then exit
;
83 if not utils
.checkSign(st
, '_OBJ') then raise XStreamError
.Create('invalid object signature');
84 if (utils
.readByte(st
) <> 0) then raise XStreamError
.Create('invalid object version');
85 // Ïîëîæåíèå ïî-ãîðèçîíòàëè
86 o
^.X
:= utils
.readLongInt(st
);
87 // Ïîëîæåíèå ïî-âåðòèêàëè
88 o
^.Y
:= utils
.readLongInt(st
);
89 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
90 o
^.Rect
.X
:= utils
.readLongInt(st
);
91 o
^.Rect
.Y
:= utils
.readLongInt(st
);
92 o
^.Rect
.Width
:= utils
.readWord(st
);
93 o
^.Rect
.Height
:= utils
.readWord(st
);
95 o
^.Vel
.X
:= utils
.readLongInt(st
);
96 o
^.Vel
.Y
:= utils
.readLongInt(st
);
98 o
^.Accel
.X
:= utils
.readLongInt(st
);
99 o
^.Accel
.Y
:= utils
.readLongInt(st
);
103 function buildSaveName (n
: Integer): AnsiString
;
106 if (n
< 0) or (n
> 65535) then exit
;
107 result
:= formatstrf('%sSAVGAME%s.DAT', [DataDir
, n
]);
111 function g_GetSaveName (n
: Integer; out valid
: Boolean): AnsiString
;
116 filename
: AnsiString
;
120 if (n
< 0) or (n
> 65535) then exit
;
122 // Îòêðûâàåì ôàéë ñîõðàíåíèé
123 filename
:= buildSaveName(n
);
124 st
:= openDiskFileRO(filename
);
126 if not utils
.checkSign(st
, 'DFSV') then
128 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st
], TMsgType
.Warning
);
129 //raise XStreamError.Create('invalid save game signature');
132 ver
:= utils
.readByte(st
);
135 utils
.readLongWord(st
); // section size
136 stlen
:= utils
.readWord(st
);
137 if (stlen
< 1) or (stlen
> 64) then
139 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st
], TMsgType
.Warning
);
140 //raise XStreamError.Create('invalid save game version');
144 SetLength(result
, stlen
);
145 st
.ReadBuffer(result
[1], stlen
);
151 result
:= utils
.readStr(st
, 64);
153 valid
:= (ver
= SAVE_VERSION
);
154 //if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
160 //e_WriteLog('GetSaveName Error: '+e.message, MSG_WARNING);
161 //{$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
168 function g_SaveGameTo (const filename
: AnsiString
; const aname
: AnsiString
; deleteOnError
: Boolean=true): Boolean;
176 st
:= createDiskFile(filename
);
178 utils
.writeSign(st
, 'DFSV');
179 utils
.writeInt(st
, Byte(SAVE_VERSION
));
181 utils
.writeStr(st
, aname
, 64);
182 // Ïîëíûé ïóòü ê âàäó è êàðòà
183 //if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('SAVE: current map is ''%s''...', [gCurrentMapFileName]);
184 utils
.writeStr(st
, gCurrentMapFileName
);
186 utils
.writeStr(st
, gGameSettings
.WAD
);
188 utils
.writeStr(st
, g_ExtractFileName(gMapInfo
.Map
));
189 // Êîëè÷åñòâî èãðîêîâ
190 utils
.writeInt(st
, Word(g_Player_GetCount
));
192 utils
.writeInt(st
, LongWord(gTime
));
194 utils
.writeInt(st
, Byte(gGameSettings
.GameType
));
196 utils
.writeInt(st
, Byte(gGameSettings
.GameMode
));
198 utils
.writeInt(st
, Word(gGameSettings
.TimeLimit
));
200 utils
.writeInt(st
, Word(gGameSettings
.GoalLimit
));
202 utils
.writeInt(st
, Byte(gGameSettings
.MaxLives
));
204 utils
.writeInt(st
, LongWord(gGameSettings
.Options
));
206 utils
.writeInt(st
, Word(gCoopMonstersKilled
));
207 utils
.writeInt(st
, Word(gCoopSecretsFound
));
208 utils
.writeInt(st
, Word(gCoopTotalMonstersKilled
));
209 utils
.writeInt(st
, Word(gCoopTotalSecretsFound
));
210 utils
.writeInt(st
, Word(gCoopTotalMonsters
));
211 utils
.writeInt(st
, Word(gCoopTotalSecrets
));
213 ///// Ñîõðàíÿåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
214 utils
.writeSign(st
, 'PLVW');
215 utils
.writeInt(st
, Byte(0)); // version
218 if (gPlayer1
<> nil) then PID1
:= gPlayer1
.UID
;
219 if (gPlayer2
<> nil) then PID2
:= gPlayer2
.UID
;
220 utils
.writeInt(st
, Word(PID1
));
221 utils
.writeInt(st
, Word(PID2
));
224 ///// Ñîñòîÿíèå êàðòû /////
228 ///// Ñîñòîÿíèå ïðåäìåòîâ /////
229 g_Items_SaveState(st
);
232 ///// Ñîñòîÿíèå òðèããåðîâ /////
233 g_Triggers_SaveState(st
);
236 ///// Ñîñòîÿíèå îðóæèÿ /////
237 g_Weapon_SaveState(st
);
240 ///// Ñîñòîÿíèå ìîíñòðîâ /////
241 g_Monsters_SaveState(st
);
244 ///// Ñîñòîÿíèå òðóïîâ /////
245 g_Player_Corpses_SaveState(st
);
248 ///// Ñîõðàíÿåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
249 if (g_Player_GetCount
> 0) then
252 for i
:= 0 to High(gPlayers
) do
254 if (gPlayers
[i
] <> nil) then
257 gPlayers
[i
].SaveState(st
);
262 // Âñå ëè èãðîêè íà ìåñòå
263 if (k
<> g_Player_GetCount
) then raise XStreamError
.Create('g_SaveGame: wrong players count');
267 ///// Ìàðêåð îêîí÷àíèÿ /////
268 utils
.writeSign(st
, 'END');
269 utils
.writeInt(st
, Byte(0));
280 g_Console_Add(_lc
[I_GAME_ERROR_SAVE
]);
281 e_WriteLog('SaveState Error: '+e
.message, TMsgType
.Warning
);
282 if deleteOnError
then DeleteFile(filename
);
283 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e
.message);{$ENDIF}
290 function g_LoadGameFrom (const filename
: AnsiString
): Boolean;
293 WAD_Path
, Map_Name
: AnsiString
;
295 Game_Type
, Game_Mode
, Game_MaxLives
: Byte;
296 Game_TimeLimit
, Game_GoalLimit
: Word;
297 Game_Time
, Game_Options
: Cardinal;
298 Game_CoopMonstersKilled
,
299 Game_CoopSecretsFound
,
300 Game_CoopTotalMonstersKilled
,
301 Game_CoopTotalSecretsFound
,
302 Game_CoopTotalMonsters
,
303 Game_CoopTotalSecrets
,
306 gameCleared
: Boolean = false;
307 curmapfile
: AnsiString
= '';
308 {$IF DEFINED(D2F_DEBUG)}
309 errpos
: LongWord
= 0;
315 st
:= openDiskFileRO(filename
);
317 if not utils
.checkSign(st
, 'DFSV') then raise XStreamError
.Create('invalid save game signature');
318 if (utils
.readByte(st
) <> SAVE_VERSION
) then raise XStreamError
.Create('invalid save game version');
320 e_WriteLog('Loading saved game...', TMsgType
.Notify
);
322 {$IF DEFINED(D2F_DEBUG)}try{$ENDIF}
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_GoalLimit
:= 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
.GoalLimit
:= Game_GoalLimit
;
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 if not g_Game_StartMap(WAD_Path
+':\'+Map_Name
, True, curmapfile
) then
410 g_FatalError(Format(_lc
[I_GAME_ERROR_MAP_LOAD
], [WAD_Path
+ ':\' + Map_Name
]));
414 // Íàñòðîéêè èãðîêîâ è áîòîâ
417 // Óñòàíàâëèâàåì âðåìÿ
420 gCoopMonstersKilled
:= Game_CoopMonstersKilled
;
421 gCoopSecretsFound
:= Game_CoopSecretsFound
;
422 gCoopTotalMonstersKilled
:= Game_CoopTotalMonstersKilled
;
423 gCoopTotalSecretsFound
:= Game_CoopTotalSecretsFound
;
424 gCoopTotalMonsters
:= Game_CoopTotalMonsters
;
425 gCoopTotalSecrets
:= Game_CoopTotalSecrets
;
427 ///// Çàãðóæàåì ñîñòîÿíèå êàðòû /////
431 ///// Çàãðóæàåì ñîñòîÿíèå ïðåäìåòîâ /////
432 g_Items_LoadState(st
);
435 ///// Çàãðóæàåì ñîñòîÿíèå òðèããåðîâ /////
436 g_Triggers_LoadState(st
);
439 ///// Çàãðóæàåì ñîñòîÿíèå îðóæèÿ /////
440 g_Weapon_LoadState(st
);
443 ///// Çàãðóæàåì ñîñòîÿíèå ìîíñòðîâ /////
444 g_Monsters_LoadState(st
);
447 ///// Çàãðóæàåì ñîñòîÿíèå òðóïîâ /////
448 g_Player_Corpses_LoadState(st
);
451 ///// Çàãðóæàåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
455 for i
:= 0 to nPlayers
-1 do g_Player_CreateFromState(st
);
458 // Ïðèâÿçûâàåì îñíîâíûõ èãðîêîâ ê îáëàñòÿì ïðîñìîòðà
459 gPlayer1
:= g_Player_Get(PID1
);
460 gPlayer2
:= g_Player_Get(PID2
);
462 if (gPlayer1
<> nil) then
464 gPlayer1
.Name
:= gPlayer1Settings
.Name
;
465 gPlayer1
.FPreferredTeam
:= gPlayer1Settings
.Team
;
466 gPlayer1
.FActualModelName
:= gPlayer1Settings
.Model
;
467 gPlayer1
.SetModel(gPlayer1
.FActualModelName
);
468 gPlayer1
.SetColor(gPlayer1Settings
.Color
);
471 if (gPlayer2
<> nil) then
473 gPlayer2
.Name
:= gPlayer2Settings
.Name
;
474 gPlayer2
.FPreferredTeam
:= gPlayer2Settings
.Team
;
475 gPlayer2
.FActualModelName
:= gPlayer2Settings
.Model
;
476 gPlayer2
.SetModel(gPlayer2
.FActualModelName
);
477 gPlayer2
.SetColor(gPlayer2Settings
.Color
);
481 ///// Ìàðêåð îêîí÷àíèÿ /////
482 if not utils
.checkSign(st
, 'END') then raise XStreamError
.Create('no end marker');
483 if (utils
.readByte(st
) <> 0) then raise XStreamError
.Create('invalid end marker');
486 // Èùåì òðèããåðû ñ óñëîâèåì ñìåðòè ìîíñòðîâ
487 if (gTriggers
<> nil) then g_Map_ReAdd_DieTriggers();
490 gLoadGameMode
:= false;
492 {$IF DEFINED(D2F_DEBUG)}
495 errpos
:= LongWord(st
.position
);
506 g_Console_Add(_lc
[I_GAME_ERROR_LOAD
]);
507 e_WriteLog('LoadState Error: '+e
.message, TMsgType
.Warning
);
508 {$IF DEFINED(D2F_DEBUG)}e_LogWritefln('stream error position: 0x%08x', [errpos
], TMsgType
.Warning
);{$ENDIF}
509 gLoadGameMode
:= false;
511 if not gameCleared
then g_Game_Free();
512 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e
.message);{$ENDIF}
518 function g_SaveGame (n
: Integer; const aname
: AnsiString
): Boolean;
521 if (n
< 0) or (n
> 65535) then exit
;
522 result
:= g_SaveGameTo(buildSaveName(n
), aname
, true);
526 function g_LoadGame (n
: Integer): Boolean;
529 if (n
< 0) or (n
> 65535) then exit
;
530 result
:= g_LoadGameFrom(buildSaveName(n
));