Subversion Repositories spacemission

Rev

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