DEADSOFTWARE

game: disable corpses for server
[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, version 3 of the License ONLY.
6 *
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.
11 *
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/>.
14 *)
15 {$INCLUDE ../shared/a_modes.inc}
16 unit g_saveload;
18 interface
20 uses
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);
36 implementation
38 uses
39 {$IFDEF ENABLE_GIBS}
40 g_gibs,
41 {$ENDIF}
42 {$IFDEF ENABLE_CORPSES}
43 g_corpses,
44 {$ENDIF}
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;
51 const
52 SAVE_SIGNATURE = $56534644; // 'DFSV'
53 SAVE_VERSION = $07;
54 END_MARKER_STRING = 'END';
55 PLAYER_VIEW_SIGNATURE = $57564C50; // 'PLVW'
56 OBJ_SIGNATURE = $4A424F5F; // '_OBJ'
59 procedure Obj_SaveState (st: TStream; o: PObj);
60 begin
61 if (st = nil) then exit;
62 // Ñèãíàòóðà îáúåêòà
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));
74 // Ñêîðîñòü
75 utils.writeInt(st, LongInt(o^.Vel.X));
76 utils.writeInt(st, LongInt(o^.Vel.Y));
77 // Óñêîðåíèå
78 utils.writeInt(st, LongInt(o^.Accel.X));
79 utils.writeInt(st, LongInt(o^.Accel.Y));
80 end;
83 procedure Obj_LoadState (o: PObj; st: TStream);
84 begin
85 if (st = nil) then exit;
86 // Ñèãíàòóðà îáúåêòà:
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);
98 // Ñêîðîñòü
99 o^.Vel.X := utils.readLongInt(st);
100 o^.Vel.Y := utils.readLongInt(st);
101 // Óñêîðåíèå
102 o^.Accel.X := utils.readLongInt(st);
103 o^.Accel.Y := utils.readLongInt(st);
104 end;
107 function buildSaveName (n: Integer): AnsiString;
108 begin
109 result := 'SAVGAME' + IntToStr(n) + '.DAT'
110 end;
113 function g_GetSaveName (n: Integer; out valid: Boolean): AnsiString;
114 var
115 st: TStream = nil;
116 ver: Byte;
117 stlen: Word;
118 filename: AnsiString;
119 begin
120 valid := false;
121 result := '';
122 if (n < 0) or (n > 65535) then exit;
123 try
124 // Îòêðûâàåì ôàéë ñîõðàíåíèé
125 filename := buildSaveName(n);
126 st := e_OpenResourceRO(SaveDirs, filename);
127 try
128 if not utils.checkSign(st, 'DFSV') then
129 begin
130 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st], TMsgType.Warning);
131 //raise XStreamError.Create('invalid save game signature');
132 exit;
133 end;
134 ver := utils.readByte(st);
135 if (ver < 7) then
136 begin
137 utils.readLongWord(st); // section size
138 stlen := utils.readWord(st);
139 if (stlen < 1) or (stlen > 64) then
140 begin
141 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st], TMsgType.Warning);
142 //raise XStreamError.Create('invalid save game version');
143 exit;
144 end;
145 // Èìÿ ñýéâà
146 SetLength(result, stlen);
147 st.ReadBuffer(result[1], stlen);
148 end
149 else
150 begin
151 // 7+
152 // Èìÿ ñýéâà
153 result := utils.readStr(st, 64);
154 end;
155 valid := (ver = SAVE_VERSION);
156 //if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
157 finally
158 st.Free();
159 end;
160 except
161 begin
162 //e_WriteLog('GetSaveName Error: '+e.message, MSG_WARNING);
163 //{$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
164 result := '';
165 end;
166 end;
167 end;
169 procedure g_Player_Corpses_SaveState (st: TStream);
170 {$IFDEF ENABLE_CORPSES}
171 var i: Integer;
172 {$ENDIF}
173 var count: Integer;
174 begin
175 count := 0;
176 {$IFDEF ENABLE_CORPSES}
177 for i := 0 to High(gCorpses) do
178 if (gCorpses[i] <> nil) then
179 Inc(count);
180 {$ENDIF}
181 utils.writeInt(st, LongInt(count));
182 {$IFDEF ENABLE_CORPSES}
183 if count > 0 then
184 begin
185 for i := 0 to High(gCorpses) do
186 begin
187 if gCorpses[i] <> nil then
188 begin
189 utils.writeStr(st, gCorpses[i].Model.GetName());
190 utils.writeBool(st, gCorpses[i].Mess);
191 gCorpses[i].SaveState(st);
192 end;
193 end;
194 end;
195 {$ENDIF}
196 end;
198 procedure g_Player_Corpses_LoadState (st: TStream);
199 {$IFDEF ENABLE_CORPSES}
200 var str: String; b: Boolean; i: Integer;
201 {$ENDIF}
202 var count: Integer;
203 begin
204 assert(st <> nil);
206 {$IFDEF ENABLE_GIBS}
207 g_Gibs_RemoveAll;
208 {$ENDIF}
209 {$IFDEF ENALBE_SHELLS}
210 g_Shells_RemoveAll; // ???
211 {$ENDIF}
212 {$IFDEF ENABLE_CORPSES}
213 g_Corpses_RemoveAll;
214 {$ENDIF}
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
222 begin
223 str := utils.readStr(st);
224 b := utils.readBool(st);
225 gCorpses[i] := TCorpse.Create(0, 0, str, b);
226 gCorpses[i].LoadState(st);
227 end;
228 {$ELSE}
229 if count <> 0 then
230 raise XStreamError.Create('corpses not supported in this version');
231 {$ENDIF}
232 end;
234 function g_SaveGameTo (const filename: AnsiString; const aname: AnsiString; deleteOnError: Boolean=true): Boolean;
235 var
236 st: TStream = nil;
237 i, k: Integer;
238 PID1, PID2: Word;
239 begin
240 result := false;
241 try
242 st := e_CreateResource(SaveDirs, filename);
243 try
244 utils.writeSign(st, 'DFSV');
245 utils.writeInt(st, Byte(SAVE_VERSION));
246 // Èìÿ ñýéâà
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);
251 // Ïóòü ê êàðòå
252 utils.writeStr(st, ExtractFileName(gGameSettings.WAD));
253 // Èìÿ êàðòû
254 utils.writeStr(st, g_ExtractFileName(gMapInfo.Map));
255 // Êîëè÷åñòâî èãðîêîâ
256 utils.writeInt(st, Word(g_Player_GetCount));
257 // Èãðîâîå âðåìÿ
258 utils.writeInt(st, LongWord(gTime));
259 // Òèï èãðû
260 utils.writeInt(st, Byte(gGameSettings.GameType));
261 // Ðåæèì èãðû
262 utils.writeInt(st, Byte(gGameSettings.GameMode));
263 // Ëèìèò âðåìåíè
264 utils.writeInt(st, Word(gGameSettings.TimeLimit));
265 // Ëèìèò î÷êîâ
266 utils.writeInt(st, Word(gGameSettings.GoalLimit));
267 // Ëèìèò æèçíåé
268 utils.writeInt(st, Byte(gGameSettings.MaxLives));
269 // Èãðîâûå îïöèè
270 utils.writeInt(st, LongWord(gGameSettings.Options));
271 // Äëÿ êîîïà
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
282 PID1 := 0;
283 PID2 := 0;
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));
288 ///// /////
290 ///// Ñîñòîÿíèå êàðòû /////
291 g_Map_SaveState(st);
292 ///// /////
294 ///// Ñîñòîÿíèå ïðåäìåòîâ /////
295 g_Items_SaveState(st);
296 ///// /////
298 ///// Ñîñòîÿíèå òðèããåðîâ /////
299 g_Triggers_SaveState(st);
300 ///// /////
302 ///// Ñîñòîÿíèå îðóæèÿ /////
303 g_Weapon_SaveState(st);
304 ///// /////
306 ///// Ñîñòîÿíèå ìîíñòðîâ /////
307 g_Monsters_SaveState(st);
308 ///// /////
310 ///// Ñîñòîÿíèå òðóïîâ /////
311 g_Player_Corpses_SaveState(st);
312 ///// /////
314 ///// Ñîõðàíÿåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
315 if (g_Player_GetCount > 0) then
316 begin
317 k := 0;
318 for i := 0 to High(gPlayers) do
319 begin
320 if (gPlayers[i] <> nil) then
321 begin
322 // Ñîñòîÿíèå èãðîêà
323 gPlayers[i].SaveState(st);
324 Inc(k);
325 end;
326 end;
328 // Âñå ëè èãðîêè íà ìåñòå
329 if (k <> g_Player_GetCount) then raise XStreamError.Create('g_SaveGame: wrong players count');
330 end;
331 ///// /////
333 ///// Ìàðêåð îêîí÷àíèÿ /////
334 utils.writeSign(st, 'END');
335 utils.writeInt(st, Byte(0));
336 ///// /////
337 result := true;
338 finally
339 st.Free();
340 end;
342 except
343 on e: Exception do
344 begin
345 st.Free();
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);
351 result := false;
352 end;
353 end;
354 end;
357 function g_LoadGameFrom (const filename: AnsiString): Boolean;
358 var
359 st: TStream = nil;
360 WAD_Path, Map_Name: AnsiString;
361 nPlayers: Integer;
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,
371 PID1, PID2: Word;
372 i: Integer;
373 gameCleared: Boolean = false;
374 curmapfile: AnsiString = '';
375 {$IF DEFINED(D2F_DEBUG)}
376 errpos: LongWord = 0;
377 {$ENDIF}
378 begin
379 result := false;
381 try
382 st := e_OpenResourceRO(SaveDirs, filename);
383 try
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 ///// Çàãðóæàåì ñîñòîÿíèå èãðû /////
396 // Èìÿ ñýéâà
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
406 gameCleared := true;
408 // Ïóòü ê êàðòå
409 WAD_Path := utils.readStr(st);
410 // Èìÿ êàðòû
411 Map_Name := utils.readStr(st);
412 // Êîëè÷åñòâî èãðîêîâ
413 nPlayers := utils.readWord(st);
414 // Èãðîâîå âðåìÿ
415 Game_Time := utils.readLongWord(st);
416 // Òèï èãðû
417 Game_Type := utils.readByte(st);
418 // Ðåæèì èãðû
419 Game_Mode := utils.readByte(st);
420 // Ëèìèò âðåìåíè
421 Game_TimeLimit := utils.readWord(st);
422 // Ëèìèò î÷êîâ
423 Game_GoalLimit := utils.readWord(st);
424 // Ëèìèò æèçíåé
425 Game_MaxLives := utils.readByte(st);
426 // Èãðîâûå îïöèè
427 Game_Options := utils.readLongWord(st);
428 // Äëÿ êîîïà
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);
435 ///// /////
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);
442 ///// /////
444 // Çàãðóæàåì êàðòó:
445 ZeroMemory(@gGameSettings, sizeof(TGameSettings));
446 gAimLine := false;
447 gShowMap := false;
448 if (Game_Type = GT_NONE) or (Game_Type = GT_SINGLE) then
449 begin
450 // Íàñòðîéêè èãðû
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;
457 end
458 else
459 begin
460 // Íàñòðîéêè èãðû
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;
468 end;
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
474 begin
475 g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [WAD_Path + ':\' + Map_Name]));
476 exit;
477 end;
479 // Íàñòðîéêè èãðîêîâ è áîòîâ
480 g_Player_Init();
482 // Óñòàíàâëèâàåì âðåìÿ
483 gTime := Game_Time;
484 // Âîçâðàùàåì ñòàòû
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 ///// Çàãðóæàåì ñîñòîÿíèå êàðòû /////
493 g_Map_LoadState(st);
494 ///// /////
496 ///// Çàãðóæàåì ñîñòîÿíèå ïðåäìåòîâ /////
497 g_Items_LoadState(st);
498 ///// /////
500 ///// Çàãðóæàåì ñîñòîÿíèå òðèããåðîâ /////
501 g_Triggers_LoadState(st);
502 ///// /////
504 ///// Çàãðóæàåì ñîñòîÿíèå îðóæèÿ /////
505 g_Weapon_LoadState(st);
506 ///// /////
508 ///// Çàãðóæàåì ñîñòîÿíèå ìîíñòðîâ /////
509 g_Monsters_LoadState(st);
510 ///// /////
512 ///// Çàãðóæàåì ñîñòîÿíèå òðóïîâ /////
513 g_Player_Corpses_LoadState(st);
514 ///// /////
516 ///// Çàãðóæàåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
517 if nPlayers > 0 then
518 begin
519 // Çàãðóæàåì
520 for i := 0 to nPlayers-1 do g_Player_CreateFromState(st);
521 end;
523 // Ïðèâÿçûâàåì îñíîâíûõ èãðîêîâ ê îáëàñòÿì ïðîñìîòðà
524 gPlayer1 := g_Player_Get(PID1);
525 gPlayer2 := g_Player_Get(PID2);
527 if (gPlayer1 <> nil) then
528 begin
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);
534 end;
536 if (gPlayer2 <> nil) then
537 begin
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);
543 end;
544 ///// /////
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');
549 ///// /////
551 // Èùåì òðèããåðû ñ óñëîâèåì ñìåðòè ìîíñòðîâ
552 if (gTriggers <> nil) then g_Map_ReAdd_DieTriggers();
554 // done
555 gLoadGameMode := false;
556 result := true;
557 {$IF DEFINED(D2F_DEBUG)}
558 except
559 begin
560 errpos := LongWord(st.position);
561 raise;
562 end;
563 end;
564 {$ENDIF}
565 finally
566 st.Free();
567 end;
568 except
569 on e: EFileNotFoundException do
570 begin
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;
575 result := false;
576 end;
577 on e: Exception do
578 begin
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;
584 result := false;
585 if gState <> STATE_MENU then
586 g_FatalError(_lc[I_GAME_ERROR_LOAD])
587 else if not gameCleared then
588 g_Game_Free();
589 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
590 end;
591 end;
592 end;
595 function g_SaveGame (n: Integer; const aname: AnsiString): Boolean;
596 begin
597 result := g_SaveGameTo(buildSaveName(n), aname, true);
598 end;
601 function g_LoadGame (n: Integer): Boolean;
602 begin
603 result := g_LoadGameFrom(buildSaveName(n));
604 end;
607 end.