DEADSOFTWARE

shells: fix shell reset on map start
[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 {$IFDEF ENABLE_SHELLS}
46 g_shells,
47 {$ENDIF}
48 MAPDEF, utils, xstreams,
49 g_game, g_items, g_map, g_monsters, g_triggers,
50 g_basic, Math, wadreader,
51 g_weapons, g_player, g_console, g_window,
52 e_log, e_res, g_language, g_options;
54 const
55 SAVE_SIGNATURE = $56534644; // 'DFSV'
56 SAVE_VERSION = $07;
57 END_MARKER_STRING = 'END';
58 PLAYER_VIEW_SIGNATURE = $57564C50; // 'PLVW'
59 OBJ_SIGNATURE = $4A424F5F; // '_OBJ'
62 procedure Obj_SaveState (st: TStream; o: PObj);
63 begin
64 if (st = nil) then exit;
65 // Ñèãíàòóðà îáúåêòà
66 utils.writeSign(st, '_OBJ');
67 utils.writeInt(st, Byte(0)); // version
68 // Ïîëîæåíèå ïî-ãîðèçîíòàëè
69 utils.writeInt(st, LongInt(o^.X));
70 // Ïîëîæåíèå ïî-âåðòèêàëè
71 utils.writeInt(st, LongInt(o^.Y));
72 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
73 utils.writeInt(st, LongInt(o^.Rect.X));
74 utils.writeInt(st, LongInt(o^.Rect.Y));
75 utils.writeInt(st, Word(o^.Rect.Width));
76 utils.writeInt(st, Word(o^.Rect.Height));
77 // Ñêîðîñòü
78 utils.writeInt(st, LongInt(o^.Vel.X));
79 utils.writeInt(st, LongInt(o^.Vel.Y));
80 // Óñêîðåíèå
81 utils.writeInt(st, LongInt(o^.Accel.X));
82 utils.writeInt(st, LongInt(o^.Accel.Y));
83 end;
86 procedure Obj_LoadState (o: PObj; st: TStream);
87 begin
88 if (st = nil) then exit;
89 // Ñèãíàòóðà îáúåêòà:
90 if not utils.checkSign(st, '_OBJ') then raise XStreamError.Create('invalid object signature');
91 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid object version');
92 // Ïîëîæåíèå ïî-ãîðèçîíòàëè
93 o^.X := utils.readLongInt(st);
94 // Ïîëîæåíèå ïî-âåðòèêàëè
95 o^.Y := utils.readLongInt(st);
96 // Îãðàíè÷èâàþùèé ïðÿìîóãîëüíèê
97 o^.Rect.X := utils.readLongInt(st);
98 o^.Rect.Y := utils.readLongInt(st);
99 o^.Rect.Width := utils.readWord(st);
100 o^.Rect.Height := utils.readWord(st);
101 // Ñêîðîñòü
102 o^.Vel.X := utils.readLongInt(st);
103 o^.Vel.Y := utils.readLongInt(st);
104 // Óñêîðåíèå
105 o^.Accel.X := utils.readLongInt(st);
106 o^.Accel.Y := utils.readLongInt(st);
107 end;
110 function buildSaveName (n: Integer): AnsiString;
111 begin
112 result := 'SAVGAME' + IntToStr(n) + '.DAT'
113 end;
116 function g_GetSaveName (n: Integer; out valid: Boolean): AnsiString;
117 var
118 st: TStream = nil;
119 ver: Byte;
120 stlen: Word;
121 filename: AnsiString;
122 begin
123 valid := false;
124 result := '';
125 if (n < 0) or (n > 65535) then exit;
126 try
127 // Îòêðûâàåì ôàéë ñîõðàíåíèé
128 filename := buildSaveName(n);
129 st := e_OpenResourceRO(SaveDirs, filename);
130 try
131 if not utils.checkSign(st, 'DFSV') then
132 begin
133 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st], TMsgType.Warning);
134 //raise XStreamError.Create('invalid save game signature');
135 exit;
136 end;
137 ver := utils.readByte(st);
138 if (ver < 7) then
139 begin
140 utils.readLongWord(st); // section size
141 stlen := utils.readWord(st);
142 if (stlen < 1) or (stlen > 64) then
143 begin
144 e_LogWritefln('GetSaveName: not a save file: ''%s''', [st], TMsgType.Warning);
145 //raise XStreamError.Create('invalid save game version');
146 exit;
147 end;
148 // Èìÿ ñýéâà
149 SetLength(result, stlen);
150 st.ReadBuffer(result[1], stlen);
151 end
152 else
153 begin
154 // 7+
155 // Èìÿ ñýéâà
156 result := utils.readStr(st, 64);
157 end;
158 valid := (ver = SAVE_VERSION);
159 //if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
160 finally
161 st.Free();
162 end;
163 except
164 begin
165 //e_WriteLog('GetSaveName Error: '+e.message, MSG_WARNING);
166 //{$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
167 result := '';
168 end;
169 end;
170 end;
172 procedure g_Player_Corpses_SaveState (st: TStream);
173 {$IFDEF ENABLE_CORPSES}
174 var i: Integer;
175 {$ENDIF}
176 var count: Integer;
177 begin
178 count := 0;
179 {$IFDEF ENABLE_CORPSES}
180 for i := 0 to High(gCorpses) do
181 if (gCorpses[i] <> nil) then
182 Inc(count);
183 {$ENDIF}
184 utils.writeInt(st, LongInt(count));
185 {$IFDEF ENABLE_CORPSES}
186 if count > 0 then
187 begin
188 for i := 0 to High(gCorpses) do
189 begin
190 if gCorpses[i] <> nil then
191 begin
192 utils.writeStr(st, gCorpses[i].Model.GetName());
193 utils.writeBool(st, gCorpses[i].Mess);
194 gCorpses[i].SaveState(st);
195 end;
196 end;
197 end;
198 {$ENDIF}
199 end;
201 procedure g_Player_Corpses_LoadState (st: TStream);
202 {$IFDEF ENABLE_CORPSES}
203 var str: String; b: Boolean; i: Integer;
204 {$ENDIF}
205 var count: Integer;
206 begin
207 assert(st <> nil);
209 {$IFDEF ENABLE_GIBS}
210 g_Gibs_RemoveAll;
211 {$ENDIF}
212 {$IFDEF ENABLE_SHELLS}
213 g_Shells_RemoveAll; // ???
214 {$ENDIF}
215 {$IFDEF ENABLE_CORPSES}
216 g_Corpses_RemoveAll;
217 {$ENDIF}
219 count := utils.readLongInt(st);
221 {$IFDEF ENABLE_CORPSES}
222 if (count < 0) or (count > Length(gCorpses)) then
223 raise XStreamError.Create('invalid number of corpses');
224 for i := 0 to count - 1 do
225 begin
226 str := utils.readStr(st);
227 b := utils.readBool(st);
228 gCorpses[i] := TCorpse.Create(0, 0, str, b);
229 gCorpses[i].LoadState(st);
230 end;
231 {$ELSE}
232 if count <> 0 then
233 raise XStreamError.Create('corpses not supported in this version');
234 {$ENDIF}
235 end;
237 function g_SaveGameTo (const filename: AnsiString; const aname: AnsiString; deleteOnError: Boolean=true): Boolean;
238 var
239 st: TStream = nil;
240 i, k: Integer;
241 PID1, PID2: Word;
242 begin
243 result := false;
244 try
245 st := e_CreateResource(SaveDirs, filename);
246 try
247 utils.writeSign(st, 'DFSV');
248 utils.writeInt(st, Byte(SAVE_VERSION));
249 // Èìÿ ñýéâà
250 utils.writeStr(st, aname, 64);
251 // Ïîëíûé ïóòü ê âàäó è êàðòà
252 //if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('SAVE: current map is ''%s''...', [gCurrentMapFileName]);
253 utils.writeStr(st, gCurrentMapFileName);
254 // Ïóòü ê êàðòå
255 utils.writeStr(st, ExtractFileName(gGameSettings.WAD));
256 // Èìÿ êàðòû
257 utils.writeStr(st, g_ExtractFileName(gMapInfo.Map));
258 // Êîëè÷åñòâî èãðîêîâ
259 utils.writeInt(st, Word(g_Player_GetCount));
260 // Èãðîâîå âðåìÿ
261 utils.writeInt(st, LongWord(gTime));
262 // Òèï èãðû
263 utils.writeInt(st, Byte(gGameSettings.GameType));
264 // Ðåæèì èãðû
265 utils.writeInt(st, Byte(gGameSettings.GameMode));
266 // Ëèìèò âðåìåíè
267 utils.writeInt(st, Word(gGameSettings.TimeLimit));
268 // Ëèìèò î÷êîâ
269 utils.writeInt(st, Word(gGameSettings.ScoreLimit));
270 // Ëèìèò æèçíåé
271 utils.writeInt(st, Byte(gGameSettings.MaxLives));
272 // Èãðîâûå îïöèè
273 utils.writeInt(st, LongWord(gGameSettings.Options));
274 // Äëÿ êîîïà
275 utils.writeInt(st, Word(gCoopMonstersKilled));
276 utils.writeInt(st, Word(gCoopSecretsFound));
277 utils.writeInt(st, Word(gCoopTotalMonstersKilled));
278 utils.writeInt(st, Word(gCoopTotalSecretsFound));
279 utils.writeInt(st, Word(gCoopTotalMonsters));
280 utils.writeInt(st, Word(gCoopTotalSecrets));
282 ///// Ñîõðàíÿåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
283 utils.writeSign(st, 'PLVW');
284 utils.writeInt(st, Byte(0)); // version
285 PID1 := 0;
286 PID2 := 0;
287 if (gPlayer1 <> nil) then PID1 := gPlayer1.UID;
288 if (gPlayer2 <> nil) then PID2 := gPlayer2.UID;
289 utils.writeInt(st, Word(PID1));
290 utils.writeInt(st, Word(PID2));
291 ///// /////
293 ///// Ñîñòîÿíèå êàðòû /////
294 g_Map_SaveState(st);
295 ///// /////
297 ///// Ñîñòîÿíèå ïðåäìåòîâ /////
298 g_Items_SaveState(st);
299 ///// /////
301 ///// Ñîñòîÿíèå òðèããåðîâ /////
302 g_Triggers_SaveState(st);
303 ///// /////
305 ///// Ñîñòîÿíèå îðóæèÿ /////
306 g_Weapon_SaveState(st);
307 ///// /////
309 ///// Ñîñòîÿíèå ìîíñòðîâ /////
310 g_Monsters_SaveState(st);
311 ///// /////
313 ///// Ñîñòîÿíèå òðóïîâ /////
314 g_Player_Corpses_SaveState(st);
315 ///// /////
317 ///// Ñîõðàíÿåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
318 if (g_Player_GetCount > 0) then
319 begin
320 k := 0;
321 for i := 0 to High(gPlayers) do
322 begin
323 if (gPlayers[i] <> nil) then
324 begin
325 // Ñîñòîÿíèå èãðîêà
326 gPlayers[i].SaveState(st);
327 Inc(k);
328 end;
329 end;
331 // Âñå ëè èãðîêè íà ìåñòå
332 if (k <> g_Player_GetCount) then raise XStreamError.Create('g_SaveGame: wrong players count');
333 end;
334 ///// /////
336 ///// Ìàðêåð îêîí÷àíèÿ /////
337 utils.writeSign(st, 'END');
338 utils.writeInt(st, Byte(0));
339 ///// /////
340 result := true;
341 finally
342 st.Free();
343 end;
345 except
346 on e: Exception do
347 begin
348 st.Free();
349 g_Console_Add(_lc[I_GAME_ERROR_SAVE]);
350 e_WriteLog('SaveState Error: '+e.message, TMsgType.Warning);
351 if deleteOnError then DeleteFile(filename);
352 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
353 e_WriteStackTrace(e.message);
354 result := false;
355 end;
356 end;
357 end;
360 function g_LoadGameFrom (const filename: AnsiString): Boolean;
361 var
362 st: TStream = nil;
363 WAD_Path, Map_Name: AnsiString;
364 nPlayers: Integer;
365 Game_Type, Game_Mode, Game_MaxLives: Byte;
366 Game_TimeLimit, Game_ScoreLimit: Word;
367 Game_Time, Game_Options: Cardinal;
368 Game_CoopMonstersKilled,
369 Game_CoopSecretsFound,
370 Game_CoopTotalMonstersKilled,
371 Game_CoopTotalSecretsFound,
372 Game_CoopTotalMonsters,
373 Game_CoopTotalSecrets,
374 PID1, PID2: Word;
375 i: Integer;
376 gameCleared: Boolean = false;
377 curmapfile: AnsiString = '';
378 {$IF DEFINED(D2F_DEBUG)}
379 errpos: LongWord = 0;
380 {$ENDIF}
381 begin
382 result := false;
384 try
385 st := e_OpenResourceRO(SaveDirs, filename);
386 try
387 if not utils.checkSign(st, 'DFSV') then raise XStreamError.Create('invalid save game signature');
388 if (utils.readByte(st) <> SAVE_VERSION) then raise XStreamError.Create('invalid save game version');
390 e_WriteLog('Loading saved game...', TMsgType.Notify);
392 {$IF DEFINED(D2F_DEBUG)}
393 try
394 {$ENDIF}
395 //g_Game_Free(false); // don't free textures for the same map
396 g_Game_ClearLoading();
397 g_Game_SetLoadingText(_lc[I_LOAD_SAVE_FILE], 0, False);
398 gLoadGameMode := True;
400 ///// Çàãðóæàåì ñîñòîÿíèå èãðû /////
401 // Èìÿ ñýéâà
402 {str :=} utils.readStr(st, 64);
404 // Ïîëíûé ïóòü ê âàäó è êàðòà
405 curmapfile := utils.readStr(st);
407 if (Length(gCurrentMapFileName) <> 0) then e_LogWritefln('LOAD: previous map was ''%s''...', [gCurrentMapFileName]);
408 if (Length(curmapfile) <> 0) then e_LogWritefln('LOAD: new map is ''%s''...', [curmapfile]);
409 // À âîò òóò, íàêîíåö, ÷èñòèì ðåñóðñû
410 g_Game_Free(curmapfile <> gCurrentMapFileName); // don't free textures for the same map
411 gameCleared := true;
413 // Ïóòü ê êàðòå
414 WAD_Path := utils.readStr(st);
415 // Èìÿ êàðòû
416 Map_Name := utils.readStr(st);
417 // Êîëè÷åñòâî èãðîêîâ
418 nPlayers := utils.readWord(st);
419 // Èãðîâîå âðåìÿ
420 Game_Time := utils.readLongWord(st);
421 // Òèï èãðû
422 Game_Type := utils.readByte(st);
423 // Ðåæèì èãðû
424 Game_Mode := utils.readByte(st);
425 // Ëèìèò âðåìåíè
426 Game_TimeLimit := utils.readWord(st);
427 // Ëèìèò î÷êîâ
428 Game_ScoreLimit := utils.readWord(st);
429 // Ëèìèò æèçíåé
430 Game_MaxLives := utils.readByte(st);
431 // Èãðîâûå îïöèè
432 Game_Options := utils.readLongWord(st);
433 // Äëÿ êîîïà
434 Game_CoopMonstersKilled := utils.readWord(st);
435 Game_CoopSecretsFound := utils.readWord(st);
436 Game_CoopTotalMonstersKilled := utils.readWord(st);
437 Game_CoopTotalSecretsFound := utils.readWord(st);
438 Game_CoopTotalMonsters := utils.readWord(st);
439 Game_CoopTotalSecrets := utils.readWord(st);
440 ///// /////
442 ///// Çàãðóæàåì ñîñòîÿíèå îáëàñòåé ïðîñìîòðà /////
443 if not utils.checkSign(st, 'PLVW') then raise XStreamError.Create('invalid viewport signature');
444 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid viewport version');
445 PID1 := utils.readWord(st);
446 PID2 := utils.readWord(st);
447 ///// /////
449 // Çàãðóæàåì êàðòó:
450 ZeroMemory(@gGameSettings, sizeof(TGameSettings));
451 gAimLine := false;
452 gShowMap := false;
453 if (Game_Type = GT_NONE) or (Game_Type = GT_SINGLE) then
454 begin
455 // Íàñòðîéêè èãðû
456 gGameSettings.GameType := GT_SINGLE;
457 gGameSettings.MaxLives := 0;
458 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_ALLOWEXIT;
459 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_MONSTERS;
460 gGameSettings.Options := gGameSettings.Options+GAME_OPTION_BOTVSMONSTER;
461 gSwitchGameMode := GM_SINGLE;
462 end
463 else
464 begin
465 // Íàñòðîéêè èãðû
466 gGameSettings.GameType := GT_CUSTOM;
467 gGameSettings.GameMode := Game_Mode;
468 gSwitchGameMode := Game_Mode;
469 gGameSettings.TimeLimit := Game_TimeLimit;
470 gGameSettings.ScoreLimit := Game_ScoreLimit;
471 gGameSettings.MaxLives := IfThen(Game_Mode = GM_CTF, 0, Game_MaxLives);
472 gGameSettings.Options := Game_Options;
473 end;
474 g_Game_ExecuteEvent('ongamestart');
476 // Çàãðóçêà è çàïóñê êàðòû
477 //FIXME: save/load `asMegawad`
478 if not g_Game_StartMap(false{asMegawad}, WAD_Path+':\'+Map_Name, True, curmapfile) then
479 raise Exception.Create(Format(_lc[I_GAME_ERROR_MAP_LOAD], [WAD_Path + ':\' + Map_Name]));
481 // Íàñòðîéêè èãðîêîâ è áîòîâ
482 g_Player_Init();
484 // Óñòàíàâëèâàåì âðåìÿ
485 gTime := Game_Time;
486 // Âîçâðàùàåì ñòàòû
487 gCoopMonstersKilled := Game_CoopMonstersKilled;
488 gCoopSecretsFound := Game_CoopSecretsFound;
489 gCoopTotalMonstersKilled := Game_CoopTotalMonstersKilled;
490 gCoopTotalSecretsFound := Game_CoopTotalSecretsFound;
491 gCoopTotalMonsters := Game_CoopTotalMonsters;
492 gCoopTotalSecrets := Game_CoopTotalSecrets;
494 ///// Çàãðóæàåì ñîñòîÿíèå êàðòû /////
495 g_Map_LoadState(st);
496 ///// /////
498 ///// Çàãðóæàåì ñîñòîÿíèå ïðåäìåòîâ /////
499 g_Items_LoadState(st);
500 ///// /////
502 ///// Çàãðóæàåì ñîñòîÿíèå òðèããåðîâ /////
503 g_Triggers_LoadState(st);
504 ///// /////
506 ///// Çàãðóæàåì ñîñòîÿíèå îðóæèÿ /////
507 g_Weapon_LoadState(st);
508 ///// /////
510 ///// Çàãðóæàåì ñîñòîÿíèå ìîíñòðîâ /////
511 g_Monsters_LoadState(st);
512 ///// /////
514 ///// Çàãðóæàåì ñîñòîÿíèå òðóïîâ /////
515 g_Player_Corpses_LoadState(st);
516 ///// /////
518 ///// Çàãðóæàåì èãðîêîâ (â òîì ÷èñëå áîòîâ) /////
519 if nPlayers > 0 then
520 begin
521 // Çàãðóæàåì
522 for i := 0 to nPlayers-1 do g_Player_CreateFromState(st);
523 end;
525 // Ïðèâÿçûâàåì îñíîâíûõ èãðîêîâ ê îáëàñòÿì ïðîñìîòðà
526 gPlayer1 := g_Player_Get(PID1);
527 gPlayer2 := g_Player_Get(PID2);
529 if (gPlayer1 <> nil) then
530 begin
531 gPlayer1.Name := gPlayer1Settings.Name;
532 gPlayer1.FPreferredTeam := gPlayer1Settings.Team;
533 gPlayer1.FActualModelName := gPlayer1Settings.Model;
534 gPlayer1.SetModel(gPlayer1.FActualModelName);
535 gPlayer1.SetColor(gPlayer1Settings.Color);
536 end;
538 if (gPlayer2 <> nil) then
539 begin
540 gPlayer2.Name := gPlayer2Settings.Name;
541 gPlayer2.FPreferredTeam := gPlayer2Settings.Team;
542 gPlayer2.FActualModelName := gPlayer2Settings.Model;
543 gPlayer2.SetModel(gPlayer2.FActualModelName);
544 gPlayer2.SetColor(gPlayer2Settings.Color);
545 end;
546 ///// /////
548 ///// Ìàðêåð îêîí÷àíèÿ /////
549 if not utils.checkSign(st, 'END') then raise XStreamError.Create('no end marker');
550 if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid end marker');
551 ///// /////
553 // Èùåì òðèããåðû ñ óñëîâèåì ñìåðòè ìîíñòðîâ
554 if (gTriggers <> nil) then g_Map_ReAdd_DieTriggers();
556 // done
557 gLoadGameMode := false;
558 result := true;
559 {$IF DEFINED(D2F_DEBUG)}
560 except
561 begin
562 errpos := LongWord(st.position);
563 raise;
564 end;
565 end;
566 {$ENDIF}
567 finally
568 st.Free();
569 end;
570 except
571 on e: EFileNotFoundException do
572 begin
573 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
574 g_Console_Add('LoadState Error: '+e.message);
575 e_WriteLog('LoadState Error: '+e.message, TMsgType.Warning);
576 gLoadGameMode := false;
577 result := false;
578 end;
579 on e: Exception do
580 begin
581 g_Console_Add(_lc[I_GAME_ERROR_LOAD]);
582 g_Console_Add('LoadState Error: '+e.message);
583 e_WriteLog('LoadState Error: '+e.message, TMsgType.Warning);
584 {$IF DEFINED(D2F_DEBUG)}e_LogWritefln('stream error position: 0x%08x', [errpos], TMsgType.Warning);{$ENDIF}
585 gLoadGameMode := false;
586 result := false;
587 if gState <> STATE_MENU then
588 g_FatalError(_lc[I_GAME_ERROR_LOAD])
589 else if not gameCleared then
590 g_Game_Free();
591 {$IF DEFINED(D2F_DEBUG)}e_WriteStackTrace(e.message);{$ENDIF}
592 end;
593 end;
594 end;
597 function g_SaveGame (n: Integer; const aname: AnsiString): Boolean;
598 begin
599 result := g_SaveGameTo(buildSaveName(n), aname, true);
600 end;
603 function g_LoadGame (n: Integer): Boolean;
604 begin
605 result := g_LoadGameFrom(buildSaveName(n));
606 end;
609 end.