Subversion Repositories spacemission

Rev

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