Subversion Repositories spacemission

Rev

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