Subversion Repositories spacemission

Rev

Rev 53 | Rev 65 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. unit ComLevelReader;
  2.  
  3. interface
  4.  
  5. uses
  6.   Classes;
  7.  
  8. const
  9.   NumEnemyTypes = 7;
  10.  
  11. type
  12.   TEnemyType = (
  13.     etUnknown,
  14.     etEnemyAttacker,
  15.     etEnemyAttacker2,
  16.     etEnemyAttacker3,
  17.     etEnemyMeteor,
  18.     etEnemyUFO,
  19.     etEnemyUFO2,
  20.     etEnemyBoss
  21.   );
  22.  
  23.   TEnemyAdvent = record
  24.     enemyType: TEnemyType;
  25.     x: integer;
  26.     y: integer;
  27.     lifes: integer;
  28.   end;
  29.  
  30.   TLevelData = class(TPersistent)
  31.   strict private
  32.     procedure SortEnemies;
  33.   strict protected
  34.     procedure AssignTo(Dest: TPersistent); override;
  35.   public
  36.     RasterErzwingen: boolean;
  37.     LevelEditorLength: integer;
  38.     LevelName: string;
  39.     LevelAuthor: string;
  40.     EnemyAdventTable: array of TEnemyAdvent;
  41.     function IndexOfEnemy(x,y:integer;enemyType:TEnemyType;lifes:integer): integer;
  42.     procedure AddEnemy(x,y:integer;enemyType:TEnemyType;lifes:integer);
  43.     procedure DeleteEnemy(i: integer); overload;
  44.     procedure DeleteEnemy(x,y:integer;enemyType:TEnemyType;lifes:integer); overload;
  45.     function CountEnemies: integer;
  46.     function HasBoss: boolean;
  47.     procedure Clear;
  48.     procedure LoadFromStrings(sl: TStrings); // version 0.3 - version 1.2 files
  49.     procedure LoadFromFile(filename: string); // version 0.2 - version 1.2 files
  50.     procedure SaveToStrings(sl: TStrings);
  51.     procedure SaveToFile(filename: string);
  52.     destructor Destroy; override;
  53.   end;
  54.  
  55.   TGameMode = (gmUnknown, gmLevels, gmRandom);
  56.  
  57.   TSaveData = class(TPersistent)
  58.   strict protected
  59.     procedure AssignTo(Dest: TPersistent); override;
  60.   public
  61.     Score: integer;
  62.     Life: integer;
  63.     Level: integer;
  64.     GameMode: TGameMode;
  65.     LevelData: TLevelData;
  66.     procedure Clear;
  67.     procedure LoadFromStrings(sl: TStrings);
  68.     procedure LoadFromFile(filename: string);
  69.     procedure SaveToStrings(sl: TStrings);
  70.     procedure SaveToFile(filename: string);
  71.     destructor Destroy; override;
  72.   end;
  73.  
  74.   TLevelFile = record
  75.     levelNumber: integer;
  76.     fileLocation: string;
  77.     isUser: boolean;
  78.     found: boolean;
  79.   end;
  80.  
  81. function GetLevelFileName(lev: integer; forceuserdir: boolean): TLevelFile;
  82.  
  83. implementation
  84.  
  85. uses
  86.   SysUtils, StrUtils, Global, Windows, System.Types;
  87.  
  88. const
  89.   // { iso(1) identified-organization(3) dod(6) internet(1) private(4) enterprise(1) 37476 products(2) spacemission(8) file-format(1) lev-sav-v12(1) }
  90.   // https://hosted.oidplus.com/viathinksoft/?goto=oid%3A1.3.6.1.4.1.37476.2.8.1.1
  91.   OID_LEVSAV_VER12 = '1.3.6.1.4.1.37476.2.8.1.1';
  92.  
  93. function GetLevelFileName(lev: integer; forceuserdir: boolean): TLevelFile;
  94.  
  95.   function _GetLevelVerzeichnisSystem: string;
  96.   begin
  97.     // Für die Auslieferungs-Levels
  98.     result := OwnDirectory + 'Levels';
  99.   end;
  100.  
  101.   function _GetLevelVerzeichnisUser: string;
  102.   begin
  103.     try
  104.       result := GetKnownFolderPath(FOLDERID_SavedGames);
  105.     except
  106.       result := '';
  107.     end;
  108.     if result = '' then
  109.     begin
  110.       // Pre Vista
  111.       result := OwnDirectory + 'Levels';
  112.     end
  113.     else
  114.     begin
  115.       result := IncludeTrailingPathDelimiter(result);
  116.       result := result + 'SpaceMission';
  117.     end;
  118.     result := IncludeTrailingPathDelimiter(result);
  119.     ForceDirectories(result);
  120.   end;
  121.  
  122.   function _GetLevelFileNameUser(lev: integer): string;
  123.   var
  124.     old, new: string;
  125.   begin
  126.     new := IncludeTrailingPathDelimiter(_GetLevelVerzeichnisUser)+'Level '+inttostr(lev)+'.lev'; // Version 0.3+ Level Files
  127.     old := IncludeTrailingPathDelimiter(_GetLevelVerzeichnisUser)+'Lev'+inttostr(lev)+'A1.lev'; // Version 0.2 Level Files
  128.     if fileexists(new) then exit(new);
  129.     if fileexists(old) then exit(old);
  130.     exit(new);
  131.   end;
  132.  
  133.   function _GetLevelFileNameSystem(lev: integer): string;
  134.   var
  135.     old, new: string;
  136.   begin
  137.     new := IncludeTrailingPathDelimiter(_GetLevelVerzeichnisSystem)+'Level '+inttostr(lev)+'.lev'; // Version 0.3+ Level Files
  138.     old := IncludeTrailingPathDelimiter(_GetLevelVerzeichnisSystem)+'Lev'+inttostr(lev)+'A1.lev'; // Version 0.2 Level Files
  139.     if fileexists(new) then exit(new);
  140.     if fileexists(old) then exit(old);
  141.     exit(new);
  142.   end;
  143.  
  144. var
  145.   usr, sys: string;
  146.   bfound: boolean;
  147. begin
  148.   result.levelNumber := lev;
  149.   usr := _GetLevelFileNameUser(lev);
  150.   sys := _GetLevelFileNameSystem(lev);
  151.   bfound := fileexists(usr);
  152.   if bfound or forceuserdir then
  153.   begin
  154.     result.isUser := true;
  155.     result.fileLocation := usr;
  156.     result.found := bfound;
  157.     exit;
  158.   end;
  159.   bfound := fileexists(sys);
  160.   if bfound then
  161.   begin
  162.     result.isUser := false;
  163.     result.fileLocation := sys;
  164.     result.found := bfound;
  165.     exit;
  166.   end;
  167.   result.isUser := true;
  168.   result.fileLocation := usr;
  169.   result.found := false;
  170. end;
  171.  
  172. // this is just an example, there are many
  173. // different ways you can implement this
  174. // more efficiently, ie using a TStringBuilder,
  175. // or even modifying the String in-place...
  176. function CollapseSpaces(const S: string): string;
  177. var
  178.   P: PChar;
  179.   AddSpace: Boolean;
  180. begin
  181.   Result := '';
  182.   AddSpace := False;
  183.   P := PChar(S);
  184.   while P^ <> #0 do
  185.   begin
  186.     while CharInSet(P^, [#1..' ']) do Inc(P);
  187.     if P^ = #0 then Exit;
  188.     if AddSpace then
  189.       Result := Result + ' '
  190.     else
  191.       AddSpace := True;
  192.     repeat
  193.       Result := Result + P^;
  194.       Inc(P);
  195.     until P^ <= ' ';
  196.   end;
  197. end;
  198.  
  199. { TLevelData }
  200.  
  201. procedure TLevelData.AssignTo(Dest: TPersistent);
  202. var
  203.   DestLevelData: TLevelData;
  204.   i: integer;
  205. begin
  206.   DestLevelData := Dest as TLevelData;
  207.   if Assigned(DestLevelData) then
  208.   begin
  209.     DestLevelData.RasterErzwingen := Self.RasterErzwingen;
  210.     DestLevelData.LevelEditorLength := Self.LevelEditorLength;
  211.     DestLevelData.LevelName := Self.LevelName;
  212.     DestLevelData.LevelAuthor := Self.LevelAuthor;
  213.     SetLength(DestLevelData.EnemyAdventTable, Length(Self.EnemyAdventTable));
  214.     for i := 0 to Length(Self.EnemyAdventTable) do
  215.     begin
  216.       DestLevelData.EnemyAdventTable[i] := Self.EnemyAdventTable[i];
  217.     end;
  218.   end
  219.   else
  220.   begin
  221.     inherited;
  222.   end;
  223. end;
  224.  
  225. procedure TLevelData.Clear;
  226. begin
  227.   SetLength(EnemyAdventTable, 0);
  228.   LevelEditorLength := DefaultLevelLength;
  229.   LevelName := '';
  230.   LevelAuthor := '';
  231. end;
  232.  
  233. function TLevelData.CountEnemies: integer;
  234. begin
  235.   result := Length(EnemyAdventTable);
  236. end;
  237.  
  238. procedure TLevelData.DeleteEnemy(i: integer);
  239. var
  240.   j: integer;
  241. begin
  242.   for j := i+1 to CountEnemies-1 do
  243.   begin
  244.     EnemyAdventTable[j-1] := EnemyAdventTable[j];
  245.   end;
  246.   SetLength(EnemyAdventTable, Length(EnemyAdventTable)-1);
  247. end;
  248.  
  249. procedure TLevelData.DeleteEnemy(x, y: integer; enemyType: TEnemyType;
  250.   lifes: integer);
  251. begin
  252.   DeleteEnemy(IndexOfEnemy(x, y, enemyType, lifes));
  253. end;
  254.  
  255. destructor TLevelData.Destroy;
  256. begin
  257.   Clear;
  258.   inherited;
  259. end;
  260.  
  261. function TLevelData.HasBoss: boolean;
  262. var
  263.   i: integer;
  264. begin
  265.   for i := 0 to Length(EnemyAdventTable) - 1 do
  266.   begin
  267.     if EnemyAdventTable[i].enemyType = etEnemyBoss then
  268.     begin
  269.       result := true;
  270.       exit;
  271.     end;
  272.   end;
  273.   result := false;
  274. end;
  275.  
  276. procedure TLevelData.AddEnemy(x,y:integer;enemyType:TEnemyType;lifes:integer);
  277. begin
  278.   SetLength(EnemyAdventTable, Length(EnemyAdventTable)+1);
  279.  
  280.   if enemyType = etEnemyMeteor then lifes := 0;
  281.   if RasterErzwingen then
  282.   begin
  283.     if x mod RasterW <> 0 then raise Exception.CreateFmt('X-Koordinate muss ohne Rest durch %d teilbar sein', [RasterW]);
  284.     if y mod RasterH <> 0 then raise Exception.CreateFmt('Y-Koordinate muss ohne Rest durch %d teilbar sein', [RasterH]);
  285.   end;
  286.   if lifes > MaxPossibleEnemyLives then lifes := MaxPossibleEnemyLives;
  287.  
  288.   EnemyAdventTable[Length(EnemyAdventTable)-1].x         := x;
  289.   EnemyAdventTable[Length(EnemyAdventTable)-1].y         := y;
  290.   EnemyAdventTable[Length(EnemyAdventTable)-1].enemyType := enemyType;
  291.   EnemyAdventTable[Length(EnemyAdventTable)-1].lifes     := lifes;
  292. end;
  293.  
  294. function TLevelData.IndexOfEnemy(x, y: integer; enemyType: TEnemyType;
  295.   lifes: integer): integer;
  296. var
  297.   i: integer;
  298. begin
  299.   for i := 0 to Length(EnemyAdventTable) - 1 do
  300.   begin
  301.     if (EnemyAdventTable[i].x = x) and
  302.        (EnemyAdventTable[i].y = y) and
  303.        (EnemyAdventTable[i].enemyType = enemyType) and
  304.        (EnemyAdventTable[i].lifes = lifes) then
  305.     begin
  306.       result := i;
  307.       exit;
  308.     end;
  309.   end;
  310.   result := -1;
  311. end;
  312.  
  313. procedure TLevelData.LoadFromStrings(sl: TStrings);
  314. var
  315.   curline: integer;
  316.   z, act: integer;
  317.   sl2: TStringList;
  318.   tmpX, tmpY, tmpLifes: integer;
  319.   tmpEnemy: TEnemyType;
  320.   ergebnis: string;
  321.   ary: TStringDynArray;
  322.   sLine: string;
  323. begin
  324.   Clear;
  325.  
  326.   LevelEditorLength := DefaultLevelLength;
  327.   LevelName := '';
  328.   LevelAuthor := '';
  329.  
  330.   if sl.Strings[0] = '; SpaceMission 0.3' then // do not localize
  331.   begin
  332.     {$REGION 'Backwards compatibility level format 0.3 (convert to 0.4)'}
  333.     sl.Strings[0] := '; SpaceMission 0.4'; // do not localize
  334.     sl.Insert(1, '; LEV-File'); // do not localize
  335.     {$ENDREGION}
  336.   end;
  337.  
  338.   if (sl.Strings[0] = '; SpaceMission 0.4') and // do not localize
  339.      (sl.Strings[1] = '; LEV-File') then // do not localize
  340.   begin
  341.     {$REGION 'Backwards compatibility level format 0.4 (convert to 1.0)'}
  342.     sl2 := TStringList.Create;
  343.     try
  344.       z := 0;
  345.       act := 0;
  346.       while z < sl.Count do
  347.       begin
  348.         inc(z);
  349.         if z > 2 then inc(act);
  350.         if act = 5 then act := 1;
  351.         ergebnis := sl.Strings[z-1];
  352.         if ergebnis = '; SpaceMission 0.4' then
  353.           sl2.Add('; SpaceMission 1.0')
  354.         else
  355.         begin
  356.           if (ergebnis = '30000') and (z = 3) then
  357.             sl2.Add(IntTostr(DefaultLevelLength))
  358.           else
  359.           begin
  360.             //if not (((ergebnis = '0') and (z = 4)) or ((ergebnis = '-624') and (z = 5)) or ((ergebnis = '222') and (z = 6)) or ((ergebnis = '3') and (z = 7))) then
  361.             if (z < 4) or (z > 7) then
  362.             begin
  363.               if act = 4 then
  364.                 sl2.Add(inttostr(strtoint(ergebnis) + 32 - (5 * (strtoint(ergebnis) div 37))))
  365.               else
  366.                 sl2.Add(Ergebnis);
  367.             end;
  368.           end;
  369.         end;
  370.       end;
  371.       sl.Text := sl2.Text;
  372.     finally
  373.       FreeAndNil(sl2);
  374.     end;
  375.     {$ENDREGION}
  376.   end;
  377.  
  378.   if (sl.Strings[0] = '; SpaceMission 1.0') and // do not localize
  379.      (sl.Strings[1] = '; LEV-File') then // do not localize
  380.   begin
  381.     {$REGION 'Level format 1.0'}
  382.     LevelEditorLength := StrToInt(sl.Strings[2]);
  383.     curline := 3;
  384.     while curline < sl.Count do
  385.     begin
  386.       tmpEnemy := TEnemyType(strtoint(sl.Strings[curline]));
  387.       Inc(curline);
  388.       tmpX := strtoint(sl.Strings[curline]);
  389.       Inc(curline);
  390.       tmpY := strtoint(sl.Strings[curline]);
  391.       Inc(curline);
  392.       tmpLifes := strtoint(sl.Strings[curline]);
  393.       Inc(curline);
  394.       AddEnemy(tmpX, tmpY, tmpEnemy, tmpLifes);
  395.     end;
  396.     {$ENDREGION}
  397.   end
  398.   else if (SameText(sl.Strings[0], '['+OID_LEVSAV_VER12+']')) then
  399.   begin
  400.     {$REGION 'Level format 1.2'}
  401.     for curline := 1 to sl.Count-1 do
  402.     begin
  403.       sLine := sl.Strings[curline].Trim;
  404.       if (sLine = '') or (Copy(sLine, 1, 1) = ';') then continue;
  405.       ary := SplitString(CollapseSpaces(sLine), ' ');
  406.       if SameText(ary[0], 'Width') then // do not localize
  407.       begin
  408.         LevelEditorLength := StrToInt(ary[1]);
  409.         if (Length(ary) > 2) and (Copy(ary[2], 1, 1) <> ';') then
  410.           raise Exception.CreateFmt('Zeile %d ist ungültig (Zusatzinfo am Ende)', [curline+1]);
  411.       end
  412.       else if SameText(ary[0], 'Name') then // do not localize
  413.       begin
  414.         LevelName := Trim(Copy(sLine, Length(ary[0])+2, Length(sLine)));
  415.       end
  416.       else if SameText(ary[0], 'Author') then // do not localize
  417.       begin
  418.         LevelAuthor := Trim(Copy(sLine, Length(ary[0])+2, Length(sLine)));
  419.       end
  420.       else if SameText(ary[0], 'Enemy') then // do not localize
  421.       begin
  422.         tmpEnemy := TEnemyType(strtoint(ary[1]));
  423.         tmpX     := strtoint(ary[2]);
  424.         tmpY     := strtoint(ary[3]);
  425.         tmpLifes := strtoint(ary[4]);
  426.         if (Length(ary) > 5) and (Copy(ary[5], 1, 1) <> ';') then
  427.           raise Exception.CreateFmt('Zeile %d ist ungültig (Zusatzinfo am Ende)', [curline+1]);
  428.         AddEnemy(tmpX, tmpY, tmpEnemy, tmpLifes);
  429.       end;
  430.     end;
  431.     {$ENDREGION}
  432.   end
  433.   else
  434.   begin
  435.     raise Exception.Create('Level-Format nicht unterstützt oder Datei ist beschädigt');
  436.   end;
  437.  
  438.   SortEnemies; // Sortierung nach X-Koordinate ist sehr wichtig für das Spiel!
  439. end;
  440.  
  441. procedure TLevelData.LoadFromFile(filename: string);
  442. var
  443.   sl: TStringList;
  444.   i, j: integer;
  445.   temp: string;
  446.   m: array[1..6] of tstrings;
  447. begin
  448.   sl := TStringList.Create;
  449.   try
  450.     if EndsText('A1.lev', filename) then // do not localize
  451.     begin
  452.       {$REGION 'Backwards compatibility level format 0.2 (split into 5-6 files; convert to 0.3)'}
  453.       m[1] := TStringList.create;
  454.       m[2] := TStringList.create;
  455.       m[3] := TStringList.create;
  456.       m[4] := TStringList.create;
  457.       m[5] := TStringList.create;
  458.       m[6] := TStringList.create;
  459.       try
  460.         for i := 1 to 6 do
  461.         begin
  462.           filename[Length(filename)-4] := IntToStr(i)[1]; // ...A2.sav, ...A3.sav, etc.
  463.           if FileExists(filename) then
  464.             m[i].loadfromfile(filename);
  465.         end;
  466.         m[1].strings[0] := '-624';
  467.         if m[6].Text = '' then m[6].Text := '30000';
  468.  
  469.         sl.Add('; SpaceMission 0.3'); // do not localize
  470.         sl.Add(temp);
  471.         for j := 0 to m[1].count-2 do
  472.         begin
  473.           for i := 0 to m[1].count-2 do
  474.           begin
  475.             if strtoint(m[1].strings[i]) > strtoint(m[1].strings[i+1]) then
  476.             begin
  477.               m[1].exchange(i, i+1);
  478.               m[2].exchange(i, i+1);
  479.               m[3].exchange(i, i+1);
  480.               m[4].exchange(i, i+1);
  481.               m[5].exchange(i, i+1);
  482.             end;
  483.           end;
  484.         end;
  485.         for i := 0 to m[3].count-1 do
  486.         begin
  487.           for j := 1 to 4 do
  488.           begin
  489.             if j = 1 then sl.Add(m[3].strings[i]);
  490.             if j = 2 then sl.Add(m[1].strings[i]);
  491.             if j = 3 then sl.Add(m[2].strings[i]);
  492.             if j = 4 then sl.Add(m[4].strings[i]);
  493.           end;
  494.         end;
  495.       finally
  496.         FreeAndNil(m[1]);
  497.         FreeAndNil(m[2]);
  498.         FreeAndNil(m[3]);
  499.         FreeAndNil(m[4]);
  500.         FreeAndNil(m[5]);
  501.         FreeAndNil(m[6]);
  502.       end;
  503.       {$ENDREGION}
  504.     end
  505.     else
  506.     begin
  507.       sl.LoadFromFile(filename);
  508.     end;
  509.  
  510.     LoadFromStrings(sl);
  511.   finally
  512.     FreeAndNil(sl);
  513.   end;
  514. end;
  515.  
  516. procedure TLevelData.SaveToStrings(sl: TStrings);
  517. var
  518.   i: integer;
  519. begin
  520.   sl.Clear;
  521.   sl.Add('['+OID_LEVSAV_VER12+']');
  522.   if LevelName   <> '' then sl.Add('Name   ' + LevelName); // do not localize
  523.   if LevelAuthor <> '' then sl.Add('Author ' + LevelAuthor); // do not localize
  524.   sl.Add('Width  ' + IntToStr(LevelEditorLength)); // do not localize
  525.   SortEnemies;
  526.   sl.Add(';      Type   XCoord YCoord Lives');
  527.   for i := 0 to Length(EnemyAdventTable)-1 do
  528.   begin
  529.     sl.Add(Trim(
  530.       'Enemy'.PadRight(6, ' ')+ // do not localize
  531.       ' '+
  532.       IntToStr(Ord(EnemyAdventTable[i].enemyType)).PadRight(6, ' ')+
  533.       ' '+
  534.       IntToStr(EnemyAdventTable[i].x).PadRight(6, ' ')+
  535.       ' '+
  536.       IntToStr(EnemyAdventTable[i].y).PadRight(6, ' ')+
  537.       ' '+
  538.       IntToStr(EnemyAdventTable[i].lifes).PadRight(6, ' ')+
  539.       ' '
  540.     ));
  541.   end;
  542. end;
  543.  
  544. procedure TLevelData.SaveToFile(filename: string);
  545. var
  546.   sl: TStringList;
  547. begin
  548.   sl := TStringList.Create;
  549.   try
  550.     SaveToStrings(sl);
  551.     sl.SaveToFile(filename);
  552.   finally
  553.     FreeAndNil(sl);
  554.   end;
  555. end;
  556.  
  557. procedure TLevelData.SortEnemies;
  558. var
  559.   i, n: integer;
  560.   e: TEnemyAdvent;
  561. begin
  562.   // Bubble Sort Algorithmus
  563.   for n := Length(EnemyAdventTable) downto 2 do
  564.   begin
  565.     for i := 0 to n - 2 do
  566.     begin
  567.       if
  568.         // Sort by X-coord (important for the game!)
  569.         (EnemyAdventTable[i].x > EnemyAdventTable[i+1].x)
  570.         or
  571.         // Sort by Y-coord (just cosmetics)
  572.         ((EnemyAdventTable[i].x = EnemyAdventTable[i+1].x) and (EnemyAdventTable[i].y > EnemyAdventTable[i+1].y))
  573.       then
  574.       begin
  575.         e := EnemyAdventTable[i];
  576.         EnemyAdventTable[i] := EnemyAdventTable[i + 1];
  577.         EnemyAdventTable[i + 1] := e;
  578.       end;
  579.     end;
  580.   end;
  581. end;
  582.  
  583. { TSaveData }
  584.  
  585. procedure TSaveData.AssignTo(Dest: TPersistent);
  586. var
  587.   DestSaveData: TSaveData;
  588. begin
  589.   DestSaveData := Dest as TSaveData;
  590.   if Assigned(DestSaveData) then
  591.   begin
  592.     DestSaveData.Score := Self.Score;
  593.     DestSaveData.Life := Self.Life;
  594.     DestSaveData.Level := Self.Level;
  595.     DestSaveData.GameMode := Self.GameMode;
  596.     if not Assigned(DestSaveData.LevelData) then DestSaveData.LevelData := TLevelData.Create;
  597.     DestSaveData.LevelData.Assign(Self.LevelData);
  598.   end
  599.   else
  600.   begin
  601.     inherited;
  602.   end;
  603. end;
  604.  
  605. procedure TSaveData.Clear;
  606. begin
  607.   Score := 0;
  608.   Life := 0;
  609.   Level := 0;
  610.   GameMode := gmUnknown;
  611.   FreeAndNil(LevelData);
  612. end;
  613.  
  614. destructor TSaveData.Destroy;
  615. begin
  616.   Clear;
  617.   inherited;
  618. end;
  619.  
  620. procedure TSaveData.SaveToStrings(sl: TStrings);
  621. var
  622.   sl2: TStringList;
  623. begin
  624.   sl2 := TStringList.Create;
  625.   try
  626.     sl.Add('['+OID_LEVSAV_VER12+']');
  627.     sl.Add('Score  ' + IntToStr(Score)); // do not localize
  628.     sl.Add('Lives  ' + IntToStr(Life)); // do not localize
  629.     sl.Add('Level  ' + IntToStr(Level)); // do not localize
  630.     sl.Add('Mode   ' + IntToStr(Ord(GameMode))); // do not localize
  631.     LevelData.SaveToStrings(sl2);
  632.     sl2.Delete(0); // Delete additional level signature
  633.     sl.AddStrings(sl2);
  634.   finally
  635.     FreeAndNil(sl2);
  636.   end;
  637. end;
  638.  
  639. procedure TSaveData.LoadFromStrings(sl: TStrings);
  640. var
  641.   curline: Integer;
  642.   ary: TStringDynArray;
  643.   sLine: string;
  644. begin
  645.   if (sl.Strings[0] = '; SpaceMission 1.0') and // do not localize
  646.      (sl.Strings[1] = '; SAV-File') then // do not localize
  647.   begin
  648.     Score    := StrToInt(sl.Strings[2]);
  649.     Life     := StrToInt(sl.Strings[3]);
  650.     Level    := StrToInt(sl.Strings[4]);
  651.     GameMode := TGameMode(StrToInt(sl.Strings[5]));
  652.     if Assigned(LevelData) then FreeAndNil(LevelData);
  653.   end
  654.   else if SameText(sl.Strings[0], '['+OID_LEVSAV_VER12+']') then
  655.   begin
  656.     Score    := 0;
  657.     Life     := 0;
  658.     Level    := 0;
  659.     GameMode := gmUnknown;
  660.     for curline := 1 to sl.Count-1 do
  661.     begin
  662.       sLine := sl.Strings[curline].Trim;
  663.       if (sLine = '') or (Copy(sLine, 1, 1) = ';') then continue;
  664.       ary := SplitString(CollapseSpaces(sLine), ' ');
  665.       if SameText(ary[0], 'Score') then // do not localize
  666.       begin
  667.         Score := StrToInt(ary[1]);
  668.         if (Length(ary) > 2) and (Copy(ary[2], 1, 1) <> ';') then
  669.           raise Exception.CreateFmt('Zeile %d ist ungültig (Zusatzinfo am Ende)', [curline+1]);
  670.       end
  671.       else if SameText(ary[0], 'Lives') then // do not localize
  672.       begin
  673.         Life := StrToInt(ary[1]);
  674.         if (Length(ary) > 2) and (Copy(ary[2], 1, 1) <> ';') then
  675.           raise Exception.CreateFmt('Zeile %d ist ungültig (Zusatzinfo am Ende)', [curline+1]);
  676.       end
  677.       else if SameText(ary[0], 'Level') then // do not localize
  678.       begin
  679.         Level := StrToInt(ary[1]);
  680.         if (Length(ary) > 2) and (Copy(ary[2], 1, 1) <> ';') then
  681.           raise Exception.CreateFmt('Zeile %d ist ungültig (Zusatzinfo am Ende)', [curline+1]);
  682.       end
  683.       else if SameText(ary[0], 'Mode') then // do not localize
  684.       begin
  685.         GameMode := TGameMode(StrToInt(ary[1]));
  686.         if (Length(ary) > 2) and (Copy(ary[2], 1, 1) <> ';') then
  687.           raise Exception.CreateFmt('Zeile %d ist ungültig (Zusatzinfo am Ende)', [curline+1]);
  688.       end;
  689.     end;
  690.     if Assigned(LevelData) then FreeAndNil(LevelData);
  691.     LevelData := TLevelData.Create;
  692.     LevelData.RasterErzwingen := false;
  693.     LevelData.LoadFromStrings(sl);
  694.   end
  695.   else
  696.   begin
  697.     raise Exception.Create('Spielstand-Format nicht unterstützt oder Datei beschädigt');
  698.   end;
  699. end;
  700.  
  701. procedure TSaveData.LoadFromFile(filename: string);
  702. var
  703.   sl: TStringList;
  704. begin
  705.   sl := TStringList.Create;
  706.   try
  707.     sl.LoadFromFile(filename);
  708.     LoadFromStrings(sl);
  709.   finally
  710.     FreeAndNil(sl);
  711.   end;
  712. end;
  713.  
  714. procedure TSaveData.SaveToFile(filename: string);
  715. var
  716.   sl: TStringList;
  717. begin
  718.   sl := TStringList.Create;
  719.   try
  720.     SaveToStrings(sl);
  721.     sl.SaveToFile(filename);
  722.   finally
  723.     FreeAndNil(sl);
  724.   end;
  725. end;
  726.  
  727. end.
  728.