Subversion Repositories spacemission

Rev

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