Subversion Repositories spacemission

Rev

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