DEADSOFTWARE

compiler hint fix
[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, either version 3 of the License, or
6 * (at your option) any later version.
7 *
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.
12 *
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/>.
15 *)
16 {$INCLUDE ../shared/a_modes.inc}
17 unit g_saveload;
19 interface
21 uses
22 SysUtils, Classes,
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);
38 implementation
40 uses
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,
45 e_log, g_language;
47 const
48 SAVE_SIGNATURE = $56534644; // 'DFSV'
49 SAVE_VERSION = $07;
50 END_MARKER_STRING = 'END';
51 PLAYER_VIEW_SIGNATURE = $57564C50; // 'PLVW'
52 OBJ_SIGNATURE = $4A424F5F; // '_OBJ'
55 procedure Obj_SaveState (st: TStream; o: PObj);
56 begin
57 if (st = nil) then exit;
58 // Ñèãíàòóðà îáúåêòà
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));
70 // Ñêîðîñòü
71 utils.writeInt(st, LongInt(o^.Vel.X));
72 utils.writeInt(st, LongInt(o^.Vel.Y));
73 // Óñêîðåíèå
74 utils.writeInt(st, LongInt(o^.Accel.X));
75 utils.writeInt(st, LongInt(o^.Accel.Y));
76 end;
79 procedure Obj_LoadState (o: PObj; st: TStream);
80 begin
81 if (st = nil) then exit;
82 // Ñèãíàòóðà îáúåêòà:
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);
94 // Ñêîðîñòü
95 o^.Vel.X := utils.readLongInt(st);
96 o^.Vel.Y := utils.readLongInt(st);
97 // Óñêîðåíèå
98 o^.Accel.X := utils.readLongInt(st);
99 o^.Accel.Y := utils.readLongInt(st);
100 end;
103 function buildSaveName (n: Integer): AnsiString;
104 begin
105 result := '';
106 if (n < 0) or (n > 65535) then exit;
107 result := formatstrf('%sSAVGAME%s.DAT', [DataDir, n]);
108 end;
111 function g_GetSaveName (n: Integer; out valid: Boolean): AnsiString;
112 var
113 st: TStream = nil;
114 ver: Byte;
115 stlen: Word;
116 begin
117 valid := false;
118 result := '';
119 if (n < 0) or (n > 65535) then exit;
120 try
121 // Îòêðûâàåì ôàéë ñîõðàíåíèé
122 st := openDiskFileRO(buildSaveName(n));
123 try
124 if not utils.checkSign(st, 'DFSV') then raise XStreamError.Create('invalid save game signature');
125 ver := utils.readByte(st);
126 if (ver < 7) then
127 begin
128 utils.readLongWord(st); // section size
129 stlen := utils.readWord(st);
130 if (stlen < 1) or (stlen > 64) then raise XStreamError.Create('invalid save game version');
131 // Èìÿ ñýéâà
132 SetLength(result, stlen);
133 st.ReadBuffer(result[1], stlen);
134 end
135 else
136 begin
137 // 7+
138 // Èìÿ ñýéâà
139 result := utils.readStr(st, 64);
140 end;
141 valid := (ver = SAVE_VERSION);
142 //if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
143 finally
144 st.Free();
145 end;
146 except
147 on e: Exception do
148 begin
149 e_WriteLog('GetSaveName Error: '+e.message, MSG_WARNING);
150 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
151 result := '';
152 end;
153 end;
154 end;
157 function g_SaveGameTo (const filename: AnsiString; const aname: AnsiString; deleteOnError: Boolean=true): Boolean;
158 var
159 st: TStream = nil;
160 i, k: Integer;
161 PID1, PID2: Word;
162 begin
163 result := false;
164 try
165 st := createDiskFile(filename);
166 try
167 utils.writeSign(st, 'DFSV');
168 utils.writeInt(st, Byte(SAVE_VERSION));
169 // Èìÿ ñýéâà
170 utils.writeStr(st, aname, 64);
171 // Ïîëíûé ïóòü ê âàäó è êàðòà
172 //if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('SAVE: current map is ''%s''...', [gCurrentMapFileName]);
173 utils.writeStr(st, gCurrentMapFileName);
174 // Ïóòü ê êàðòå
175 utils.writeStr(st, gGameSettings.WAD);
176 // Èìÿ êàðòû
177 utils.writeStr(st, g_ExtractFileName(gMapInfo.Map));
178 // Êîëè÷åñòâî èãðîêîâ
179 utils.writeInt(st, Word(g_Player_GetCount));
180 // Èãðîâîå âðåìÿ
181 utils.writeInt(st, LongWord(gTime));
182 // Òèï èãðû
183 utils.writeInt(st, Byte(gGameSettings.GameType));
184 // Ðåæèì èãðû
185 utils.writeInt(st, Byte(gGameSettings.GameMode));
186 // Ëèìèò âðåìåíè
187 utils.writeInt(st, Word(gGameSettings.TimeLimit));
188 // Ëèìèò î÷êîâ
189 utils.writeInt(st, Word(gGameSettings.GoalLimit));
190 // Ëèìèò æèçíåé
191 utils.writeInt(st, Byte(gGameSettings.MaxLives));
192 // Èãðîâûå îïöèè
193 utils.writeInt(st, LongWord(gGameSettings.Options));
194 // Äëÿ êîîïà
195 utils.writeInt(st, Word(gCoopMonstersKilled));
196 utils.writeInt(st, Word(gCoopSecretsFound));
197 utils.writeInt(st, Word(gCoopTotalMonstersKilled));
198 utils.writeInt(st, Word(gCoopTotalSecretsFound));
199 utils.writeInt(st, Word(gCoopTotalMonsters));
200 utils.writeInt(st, Word(gCoopTotalSecrets));
202 ///// Ñîõðàíÿåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
203 utils.writeSign(st, 'PLVW');
204 utils.writeInt(st, Byte(0)); // version
205 PID1 := 0;
206 PID2 := 0;
207 if (gPlayer1 <> nil) then PID1 := gPlayer1.UID;
208 if (gPlayer2 <> nil) then PID2 := gPlayer2.UID;
209 utils.writeInt(st, Word(PID1));
210 utils.writeInt(st, Word(PID2));
211 ///// /////
213 ///// Ñîñòîÿíèå êàðòû /////
214 g_Map_SaveState(st);
215 ///// /////
217 ///// Ñîñòîÿíèå ïðåäìåòîâ /////
218 g_Items_SaveState(st);
219 ///// /////
221 ///// Ñîñòîÿíèå òðèããåðîâ /////
222 g_Triggers_SaveState(st);
223 ///// /////
225 ///// Ñîñòîÿíèå îðóæèÿ /////
226 g_Weapon_SaveState(st);
227 ///// /////
229 ///// Ñîñòîÿíèå ìîíñòðîâ /////
230 g_Monsters_SaveState(st);
231 ///// /////
233 ///// Ñîñòîÿíèå òðóïîâ /////
234 g_Player_Corpses_SaveState(st);
235 ///// /////
237 ///// Ñîõðàíÿåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
238 if (g_Player_GetCount > 0) then
239 begin
240 k := 0;
241 for i := 0 to High(gPlayers) do
242 begin
243 if (gPlayers[i] <> nil) then
244 begin
245 // Ñîñòîÿíèå èãðîêà
246 gPlayers[i].SaveState(st);
247 Inc(k);
248 end;
249 end;
251 // Âñå ëè èãðîêè íà ìåñòå
252 if (k <> g_Player_GetCount) then raise XStreamError.Create('g_SaveGame: wrong players count');
253 end;
254 ///// /////
256 ///// Ìàðêåð îêîí÷àíèÿ /////
257 utils.writeSign(st, 'END');
258 utils.writeInt(st, Byte(0));
259 ///// /////
260 result := true;
261 finally
262 st.Free();
263 end;
265 except
266 on e: Exception do
267 begin
268 st.Free();
269 g_Console_Add(_lc[I_GAME_ERROR_SAVE]);
270 e_WriteLog('SaveState Error: '+e.message, MSG_WARNING);
271 if deleteOnError then DeleteFile(filename);
272 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
273 result := false;
274 end;
275 end;
276 end;
279 function g_LoadGameFrom (const filename: AnsiString): Boolean;
280 var
281 st: TStream = nil;
282 WAD_Path, Map_Name: AnsiString;
283 nPlayers: Integer;
284 Game_Type, Game_Mode, Game_MaxLives: Byte;
285 Game_TimeLimit, Game_GoalLimit: Word;
286 Game_Time, Game_Options: Cardinal;
287 Game_CoopMonstersKilled,
288 Game_CoopSecretsFound,
289 Game_CoopTotalMonstersKilled,
290 Game_CoopTotalSecretsFound,
291 Game_CoopTotalMonsters,
292 Game_CoopTotalSecrets,
293 PID1, PID2: Word;
294 i: Integer;
295 gameCleared: Boolean = false;
296 curmapfile: AnsiString = '';
297 {$IF DEFINED(D2F_DEBUG)}
298 errpos: LongWord = 0;
299 {$ENDIF}
300 begin
301 result := false;
303 try
304 st := openDiskFileRO(filename);
305 try
306 if not utils.checkSign(st, 'DFSV') then raise XStreamError.Create('invalid save game signature');
307 if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
309 e_WriteLog('Loading saved game...', MSG_NOTIFY);
311 {$IF DEFINED(D2F_DEBUG)}try{$ENDIF}
312 //g_Game_Free(false); // don't free textures for the same map
313 g_Game_ClearLoading();
314 g_Game_SetLoadingText(_lc[I_LOAD_SAVE_FILE], 0, False);
315 gLoadGameMode := True;
317 ///// Çàãðóæàåì ñîñòîÿíèå èãðû /////
318 // Èìÿ ñýéâà
319 {str :=} utils.readStr(st, 64);
321 // Ïîëíûé ïóòü ê âàäó è êàðòà
322 curmapfile := utils.readStr(st);
324 if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('LOAD: previous map was ''%s''...', [gCurrentMapFileName]);
325 if (Length(curmapfile) <> 0) then e_LogWritefln('LOAD: new map is ''%s''...', [curmapfile]);
326 // À âîò òóò, íàêîíåö, ÷èñòèì ðåñóðñû
327 g_Game_Free(curmapfile <> gCurrentMapFileName); // don't free textures for the same map
328 gameCleared := true;
330 // Ïóòü ê êàðòå
331 WAD_Path := utils.readStr(st);
332 // Èìÿ êàðòû
333 Map_Name := utils.readStr(st);
334 // Êîëè÷åñòâî èãðîêîâ
335 nPlayers := utils.readWord(st);
336 // Èãðîâîå âðåìÿ
337 Game_Time := utils.readLongWord(st);
338 // Òèï èãðû
339 Game_Type := utils.readByte(st);
340 // Ðåæèì èãðû
341 Game_Mode := utils.readByte(st);
342 // Ëèìèò âðåìåíè
343 Game_TimeLimit := utils.readWord(st);
344 // Ëèìèò î÷êîâ
345 Game_GoalLimit := utils.readWord(st);
346 // Ëèìèò æèçíåé
347 Game_MaxLives := utils.readByte(st);
348 // Èãðîâûå îïöèè
349 Game_Options := utils.readLongWord(st);
350 // Äëÿ êîîïà
351 Game_CoopMonstersKilled := utils.readWord(st);
352 Game_CoopSecretsFound := utils.readWord(st);
353 Game_CoopTotalMonstersKilled := utils.readWord(st);
354 Game_CoopTotalSecretsFound := utils.readWord(st);
355 Game_CoopTotalMonsters := utils.readWord(st);
356 Game_CoopTotalSecrets := utils.readWord(st);
357 ///// /////
359 ///// Çàãðóæàåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
360 if not utils.checkSign(st, 'PLVW') then raise XStreamError.Create('invalid viewport signature');
361 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid viewport version');
362 PID1 := utils.readWord(st);
363 PID2 := utils.readWord(st);
364 ///// /////
366 // Çàãðóæàåì êàðòó:
367 ZeroMemory(@gGameSettings, sizeof(TGameSettings));
368 gAimLine := false;
369 gShowMap := false;
370 if (Game_Type = GT_NONE) or (Game_Type = GT_SINGLE) then
371 begin
372 // Íàñòðîéêè èãðû
373 gGameSettings.GameType := GT_SINGLE;
374 gGameSettings.MaxLives := 0;
375 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_ALLOWEXIT;
376 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_MONSTERS;
377 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_BOTVSMONSTER;
378 gSwitchGameMode := GM_SINGLE;
379 end
380 else
381 begin
382 // Íàñòðîéêè èãðû
383 gGameSettings.GameType := GT_CUSTOM;
384 gGameSettings.GameMode := Game_Mode;
385 gSwitchGameMode := Game_Mode;
386 gGameSettings.TimeLimit := Game_TimeLimit;
387 gGameSettings.GoalLimit := Game_GoalLimit;
388 gGameSettings.MaxLives := IfThen(Game_Mode = GM_CTF, 0, Game_MaxLives);
389 gGameSettings.Options := Game_Options;
390 end;
391 g_Game_ExecuteEvent('ongamestart');
393 // Óñòàíîâêà ðàçìåðîâ îêîí èãðîêîâ
394 g_Game_SetupScreenSize();
396 // Çàãðóçêà è çàïóñê êàðòû
397 if not g_Game_StartMap(WAD_Path+':\'+Map_Name, True, curmapfile) then
398 begin
399 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [WAD_Path + ':\' + Map_Name]));
400 exit;
401 end;
403 // Íàñòðîéêè èãðîêîâ è áîòîâ
404 g_Player_Init();
406 // Óñòàíàâëèâàåì âðåìÿ
407 gTime := Game_Time;
408 // Âîçâðàùàåì ñòàòû
409 gCoopMonstersKilled := Game_CoopMonstersKilled;
410 gCoopSecretsFound := Game_CoopSecretsFound;
411 gCoopTotalMonstersKilled := Game_CoopTotalMonstersKilled;
412 gCoopTotalSecretsFound := Game_CoopTotalSecretsFound;
413 gCoopTotalMonsters := Game_CoopTotalMonsters;
414 gCoopTotalSecrets := Game_CoopTotalSecrets;
416 ///// Çàãðóæàåì ñîñòîÿíèå êàðòû /////
417 g_Map_LoadState(st);
418 ///// /////
420 ///// Çàãðóæàåì ñîñòîÿíèå ïðåäìåòîâ /////
421 g_Items_LoadState(st);
422 ///// /////
424 ///// Çàãðóæàåì ñîñòîÿíèå òðèããåðîâ /////
425 g_Triggers_LoadState(st);
426 ///// /////
428 ///// Çàãðóæàåì ñîñòîÿíèå îðóæèÿ /////
429 g_Weapon_LoadState(st);
430 ///// /////
432 ///// Çàãðóæàåì ñîñòîÿíèå ìîíñòðîâ /////
433 g_Monsters_LoadState(st);
434 ///// /////
436 ///// Çàãðóæàåì ñîñòîÿíèå òðóïîâ /////
437 g_Player_Corpses_LoadState(st);
438 ///// /////
440 ///// Çàãðóæàåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
441 if nPlayers > 0 then
442 begin
443 // Çàãðóæàåì
444 for i := 0 to nPlayers-1 do g_Player_CreateFromState(st);
445 end;
447 // Ïðèâÿçûâàåì îñíîâíûõ èãðîêîâ ê îáëàñòÿì ïðîñìîòðà
448 gPlayer1 := g_Player_Get(PID1);
449 gPlayer2 := g_Player_Get(PID2);
451 if (gPlayer1 <> nil) then
452 begin
453 gPlayer1.Name := gPlayer1Settings.Name;
454 gPlayer1.FPreferredTeam := gPlayer1Settings.Team;
455 gPlayer1.FActualModelName := gPlayer1Settings.Model;
456 gPlayer1.SetModel(gPlayer1.FActualModelName);
457 gPlayer1.SetColor(gPlayer1Settings.Color);
458 end;
460 if (gPlayer2 <> nil) then
461 begin
462 gPlayer2.Name := gPlayer2Settings.Name;
463 gPlayer2.FPreferredTeam := gPlayer2Settings.Team;
464 gPlayer2.FActualModelName := gPlayer2Settings.Model;
465 gPlayer2.SetModel(gPlayer2.FActualModelName);
466 gPlayer2.SetColor(gPlayer2Settings.Color);
467 end;
468 ///// /////
470 ///// Ìàðêåð îêîí÷àíèÿ /////
471 if not utils.checkSign(st, 'END') then raise XStreamError.Create('no end marker');
472 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid end marker');
473 ///// /////
475 // Èùåì òðèããåðû ñ óñëîâèåì ñìåðòè ìîíñòðîâ
476 if (gTriggers <> nil) then g_Map_ReAdd_DieTriggers();
478 // done
479 gLoadGameMode := false;
480 result := true;
481 {$IF DEFINED(D2F_DEBUG)}
482 except
483 begin
484 errpos := LongWord(st.position);
485 raise;
486 end;
487 end;
488 {$ENDIF}
489 finally
490 st.Free();
491 end;
492 except
493 on e: Exception do
494 begin
495 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
496 e_WriteLog('LoadState Error: '+e.message, MSG_WARNING);
497 {$IF DEFINED(D2F_DEBUG)}e_LogWritefln('stream error position: 0x%08x', [errpos], MSG_WARNING);{$ENDIF}
498 gLoadGameMode := false;
499 result := true;
500 if not gameCleared then g_Game_Free();
501 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
502 end;
503 end;
504 end;
507 function g_SaveGame (n: Integer; const aname: AnsiString): Boolean;
508 begin
509 result := false;
510 if (n < 0) or (n > 65535) then exit;
511 result := g_SaveGameTo(buildSaveName(n), aname, true);
512 end;
515 function g_LoadGame (n: Integer): Boolean;
516 begin
517 result := false;
518 if (n < 0) or (n > 65535) then exit;
519 result := g_LoadGameFrom(buildSaveName(n));
520 end;
523 end.