DEADSOFTWARE

gl: implement load screen
[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;
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, g_window,
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.ScoreLimit));
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_ScoreLimit: 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)}
390 try
391 {$ENDIF}
392 //g_Game_Free(false); // don't free textures for the same map
393 g_Game_ClearLoading();
394 g_Game_SetLoadingText(_lc[I_LOAD_SAVE_FILE], 0, False);
395 gLoadGameMode := True;
397 ///// Çàãðóæàåì ñîñòîÿíèå èãðû /////
398 // Èìÿ ñýéâà
399 {str :=} utils.readStr(st, 64);
401 // Ïîëíûé ïóòü ê âàäó è êàðòà
402 curmapfile := utils.readStr(st);
404 if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('LOAD: previous map was ''%s''...', [gCurrentMapFileName]);
405 if (Length(curmapfile) <> 0) then e_LogWritefln('LOAD: new map is ''%s''...', [curmapfile]);
406 // À âîò òóò, íàêîíåö, ÷èñòèì ðåñóðñû
407 g_Game_Free(curmapfile <> gCurrentMapFileName); // don't free textures for the same map
408 gameCleared := true;
410 // Ïóòü ê êàðòå
411 WAD_Path := utils.readStr(st);
412 // Èìÿ êàðòû
413 Map_Name := utils.readStr(st);
414 // Êîëè÷åñòâî èãðîêîâ
415 nPlayers := utils.readWord(st);
416 // Èãðîâîå âðåìÿ
417 Game_Time := utils.readLongWord(st);
418 // Òèï èãðû
419 Game_Type := utils.readByte(st);
420 // Ðåæèì èãðû
421 Game_Mode := utils.readByte(st);
422 // Ëèìèò âðåìåíè
423 Game_TimeLimit := utils.readWord(st);
424 // Ëèìèò î÷êîâ
425 Game_ScoreLimit := utils.readWord(st);
426 // Ëèìèò æèçíåé
427 Game_MaxLives := utils.readByte(st);
428 // Èãðîâûå îïöèè
429 Game_Options := utils.readLongWord(st);
430 // Äëÿ êîîïà
431 Game_CoopMonstersKilled := utils.readWord(st);
432 Game_CoopSecretsFound := utils.readWord(st);
433 Game_CoopTotalMonstersKilled := utils.readWord(st);
434 Game_CoopTotalSecretsFound := utils.readWord(st);
435 Game_CoopTotalMonsters := utils.readWord(st);
436 Game_CoopTotalSecrets := utils.readWord(st);
437 ///// /////
439 ///// Çàãðóæàåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
440 if not utils.checkSign(st, 'PLVW') then raise XStreamError.Create('invalid viewport signature');
441 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid viewport version');
442 PID1 := utils.readWord(st);
443 PID2 := utils.readWord(st);
444 ///// /////
446 // Çàãðóæàåì êàðòó:
447 ZeroMemory(@gGameSettings, sizeof(TGameSettings));
448 gAimLine := false;
449 gShowMap := false;
450 if (Game_Type = GT_NONE) or (Game_Type = GT_SINGLE) then
451 begin
452 // Íàñòðîéêè èãðû
453 gGameSettings.GameType := GT_SINGLE;
454 gGameSettings.MaxLives := 0;
455 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_ALLOWEXIT;
456 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_MONSTERS;
457 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_BOTVSMONSTER;
458 gSwitchGameMode := GM_SINGLE;
459 end
460 else
461 begin
462 // Íàñòðîéêè èãðû
463 gGameSettings.GameType := GT_CUSTOM;
464 gGameSettings.GameMode := Game_Mode;
465 gSwitchGameMode := Game_Mode;
466 gGameSettings.TimeLimit := Game_TimeLimit;
467 gGameSettings.ScoreLimit := Game_ScoreLimit;
468 gGameSettings.MaxLives := IfThen(Game_Mode = GM_CTF, 0, Game_MaxLives);
469 gGameSettings.Options := Game_Options;
470 end;
471 g_Game_ExecuteEvent('ongamestart');
473 // Çàãðóçêà è çàïóñê êàðòû
474 //FIXME: save/load `asMegawad`
475 if not g_Game_StartMap(false{asMegawad}, WAD_Path+':\'+Map_Name, True, curmapfile) then
476 raise Exception.Create(Format(_lc[I_GAME_ERROR_MAP_LOAD], [WAD_Path + ':\' + Map_Name]));
478 // Íàñòðîéêè èãðîêîâ è áîòîâ
479 g_Player_Init();
481 // Óñòàíàâëèâàåì âðåìÿ
482 gTime := Game_Time;
483 // Âîçâðàùàåì ñòàòû
484 gCoopMonstersKilled := Game_CoopMonstersKilled;
485 gCoopSecretsFound := Game_CoopSecretsFound;
486 gCoopTotalMonstersKilled := Game_CoopTotalMonstersKilled;
487 gCoopTotalSecretsFound := Game_CoopTotalSecretsFound;
488 gCoopTotalMonsters := Game_CoopTotalMonsters;
489 gCoopTotalSecrets := Game_CoopTotalSecrets;
491 ///// Çàãðóæàåì ñîñòîÿíèå êàðòû /////
492 g_Map_LoadState(st);
493 ///// /////
495 ///// Çàãðóæàåì ñîñòîÿíèå ïðåäìåòîâ /////
496 g_Items_LoadState(st);
497 ///// /////
499 ///// Çàãðóæàåì ñîñòîÿíèå òðèããåðîâ /////
500 g_Triggers_LoadState(st);
501 ///// /////
503 ///// Çàãðóæàåì ñîñòîÿíèå îðóæèÿ /////
504 g_Weapon_LoadState(st);
505 ///// /////
507 ///// Çàãðóæàåì ñîñòîÿíèå ìîíñòðîâ /////
508 g_Monsters_LoadState(st);
509 ///// /////
511 ///// Çàãðóæàåì ñîñòîÿíèå òðóïîâ /////
512 g_Player_Corpses_LoadState(st);
513 ///// /////
515 ///// Çàãðóæàåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
516 if nPlayers > 0 then
517 begin
518 // Çàãðóæàåì
519 for i := 0 to nPlayers-1 do g_Player_CreateFromState(st);
520 end;
522 // Ïðèâÿçûâàåì îñíîâíûõ èãðîêîâ ê îáëàñòÿì ïðîñìîòðà
523 gPlayer1 := g_Player_Get(PID1);
524 gPlayer2 := g_Player_Get(PID2);
526 if (gPlayer1 <> nil) then
527 begin
528 gPlayer1.Name := gPlayer1Settings.Name;
529 gPlayer1.FPreferredTeam := gPlayer1Settings.Team;
530 gPlayer1.FActualModelName := gPlayer1Settings.Model;
531 gPlayer1.SetModel(gPlayer1.FActualModelName);
532 gPlayer1.SetColor(gPlayer1Settings.Color);
533 end;
535 if (gPlayer2 <> nil) then
536 begin
537 gPlayer2.Name := gPlayer2Settings.Name;
538 gPlayer2.FPreferredTeam := gPlayer2Settings.Team;
539 gPlayer2.FActualModelName := gPlayer2Settings.Model;
540 gPlayer2.SetModel(gPlayer2.FActualModelName);
541 gPlayer2.SetColor(gPlayer2Settings.Color);
542 end;
543 ///// /////
545 ///// Ìàðêåð îêîí÷àíèÿ /////
546 if not utils.checkSign(st, 'END') then raise XStreamError.Create('no end marker');
547 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid end marker');
548 ///// /////
550 // Èùåì òðèããåðû ñ óñëîâèåì ñìåðòè ìîíñòðîâ
551 if (gTriggers <> nil) then g_Map_ReAdd_DieTriggers();
553 // done
554 gLoadGameMode := false;
555 result := true;
556 {$IF DEFINED(D2F_DEBUG)}
557 except
558 begin
559 errpos := LongWord(st.position);
560 raise;
561 end;
562 end;
563 {$ENDIF}
564 finally
565 st.Free();
566 end;
567 except
568 on e: EFileNotFoundException do
569 begin
570 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
571 g_Console_Add('LoadState Error: '+e.message);
572 e_WriteLog('LoadState Error: '+e.message, TMsgType.Warning);
573 gLoadGameMode := false;
574 result := false;
575 end;
576 on e: Exception do
577 begin
578 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
579 g_Console_Add('LoadState Error: '+e.message);
580 e_WriteLog('LoadState Error: '+e.message, TMsgType.Warning);
581 {$IF DEFINED(D2F_DEBUG)}e_LogWritefln('stream error position: 0x%08x', [errpos], TMsgType.Warning);{$ENDIF}
582 gLoadGameMode := false;
583 result := false;
584 if gState <> STATE_MENU then
585 g_FatalError(_lc[I_GAME_ERROR_LOAD])
586 else if not gameCleared then
587 g_Game_Free();
588 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
589 end;
590 end;
591 end;
594 function g_SaveGame (n: Integer; const aname: AnsiString): Boolean;
595 begin
596 result := g_SaveGameTo(buildSaveName(n), aname, true);
597 end;
600 function g_LoadGame (n: Integer): Boolean;
601 begin
602 result := g_LoadGameFrom(buildSaveName(n));
603 end;
606 end.