Subversion Repositories spacemission

Rev

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