Subversion Repositories plumbers

Rev

Rev 10 | Rev 12 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. unit GameBinStruct;
  2.  
  3. {$A-}
  4.  
  5. interface
  6.  
  7. const
  8.   SCENEID_PREVDECISION = -1;
  9.   SCENEID_ENDGAME      = 32767;
  10.  
  11. type
  12.   PCoord = ^TCoord;
  13.   TCoord = packed record
  14.     x: Word;
  15.     y: Word;
  16.   end;
  17.  
  18.   PActionDef = ^TActionDef;
  19.   TActionDef = packed record
  20.     scoreDelta: Integer;
  21.     nextSceneID: SmallInt;  // will jump to the scene with the name "SC<nextSceneID>"
  22.                             // 7FFF (32767) = end game
  23.                             // FFFF (   -1) = go back to the last decision
  24.     sceneSegment: SmallInt; // 0 = scene from beginning, 1 = decision page
  25.     cHotspotTopLeft: TCoord;
  26.     cHotspotBottomRight: TCoord;
  27.   end;
  28.  
  29.   PAnsiFileName = ^TAnsiFileName;
  30.   TAnsiFileName = array[0..13] of AnsiChar;
  31.  
  32.   PSceneDef = ^TSceneDef;
  33.   TSceneDef = packed record
  34.     numPics: Word;
  35.     pictureIndex: Word;
  36.     numActions: Word;
  37.     szSceneFolder: TAnsiFileName; // Foldername *must* be "SCxx" (case sensitive) where xx stands for a 2 digit ID
  38.     szDialogWav:   TAnsiFileName;
  39.     szDecisionBmp: TAnsiFileName;
  40.     actions: array[0..2] of TActionDef;
  41.   end;
  42.  
  43.   PPictureDef = ^TPictureDef;
  44.   TPictureDef = packed record
  45.     duration: Word; // deciseconds
  46.     szBitmapFile: TAnsiFileName;
  47.   end;
  48.  
  49.   PGameBinFile = ^TGameBinFile;
  50.   TGameBinFile = packed record
  51.     unknown1: array[0..6] of Word;
  52.     numScenes: Word;
  53.     numPics: Word;
  54.     unknown2: array[0..1] of Word;
  55.     scenes: array[0..99] of TSceneDef;        // Scenes start at 0x0016
  56.     pictures: array[0..1999] of TPictureDef;  // Pictures start at 0x2596
  57.  
  58.     class function MaxSceneCount: integer; static;
  59.     class function MaxPictureCount: integer; static;
  60.  
  61.     function AddSceneAtEnd(SceneID: integer): PSceneDef;
  62.     procedure DeleteScene(SceneIndex: integer);
  63.     procedure SwapScene(IndexA, IndexB: integer);
  64.     procedure DeletePicture(PictureIndex: integer);
  65.     procedure SwapPicture(IndexA, IndexB: integer);
  66.     function AddPictureBetween(Index: integer; assignToUpperScene: boolean=true): PPictureDef;
  67.     function RealPictureCount: integer;
  68.     function RealActionCount: integer;
  69.     function Defrag(OnlyCheck: boolean=false): boolean;
  70.     function BiggestUsedPictureIndex: integer;
  71.   end;
  72.  
  73. procedure _WriteStringToFilename(x: PAnsiFileName; s: AnsiString);
  74.  
  75. implementation
  76.  
  77. uses
  78.   Windows, SysUtils, Math;
  79.  
  80. procedure _WriteStringToFilename(x: PAnsiFileName; s: AnsiString);
  81. begin
  82.   ZeroMemory(x, Length(x^));
  83.   StrPLCopy(x^, s, Length(x^));
  84. end;
  85.  
  86. function TGameBinFile.BiggestUsedPictureIndex: integer;
  87. var
  88.   iScene, candidate: integer;
  89. begin
  90.   result := -1;
  91.   for iScene := 0 to Self.numScenes - 1 do
  92.   begin
  93.     candidate := Self.scenes[iScene].pictureIndex + Self.scenes[iScene].numPics - 1;
  94.     if candidate > result then result := candidate;
  95.   end;
  96. end;
  97.  
  98. function TGameBinFile.AddSceneAtEnd(SceneID: integer): PSceneDef;
  99. begin
  100.   if Self.numScenes >= Length(Self.scenes) then raise Exception.Create('Not enough space for another scene');
  101.   if SceneID < 0 then raise Exception.Create('Invalid scene ID');
  102.   if sceneID >= Length(Self.scenes) then raise Exception.Create('SceneID is too large.'); // TODO: I think this is not correct. This is the scene *ID*
  103.   result := @Self.scenes[Self.numScenes];
  104.   ZeroMemory(result, SizeOf(TSceneDef));
  105.   _WriteStringToFilename(@result.szSceneFolder, AnsiString(Format('SC%.2d', [sceneID])));
  106.   Inc(Self.numScenes);
  107. end;
  108.  
  109. procedure TGameBinFile.DeleteScene(SceneIndex: integer);
  110. begin
  111.   if ((SceneIndex < 0) or (SceneIndex >= Length(Self.scenes))) then raise Exception.Create('Invalid scene index');
  112.   If SceneIndex < Length(Self.scenes)-1 then
  113.   begin
  114.     CopyMemory(@Self.scenes[SceneIndex], @Self.scenes[SceneIndex+1], (Length(Self.scenes)-SceneIndex-1)*SizeOf(TSceneDef));
  115.   end;
  116.   ZeroMemory(@Self.scenes[Length(Self.scenes)-1], SizeOf(TSceneDef));
  117.   Dec(Self.numScenes);
  118. end;
  119.  
  120. class function TGameBinFile.MaxPictureCount: integer;
  121. var
  122.   dummy: TGameBinFile;
  123. begin
  124.   dummy.numScenes := 1; // avoid compiler give a warning
  125.   result := Length(dummy.pictures);
  126. end;
  127.  
  128. class function TGameBinFile.MaxSceneCount: integer;
  129. var
  130.   dummy: TGameBinFile;
  131. begin
  132.   dummy.numScenes := 1; // avoid compiler give a warning
  133.   result := Length(dummy.scenes);
  134. end;
  135.  
  136. function TGameBinFile.RealActionCount: integer;
  137. var
  138.   iScene: integer;
  139. begin
  140.   result := 0;
  141.   for iScene := 0 to Self.numScenes - 1 do
  142.   begin
  143.     result := result + Self.scenes[iScene].numActions;
  144.   end;
  145. end;
  146.  
  147. function TGameBinFile.RealPictureCount: integer;
  148. var
  149.   iScene: integer;
  150. begin
  151.   result := 0;
  152.   for iScene := 0 to Self.numScenes - 1 do
  153.   begin
  154.     result := result + Self.scenes[iScene].numPics;
  155.   end;
  156. end;
  157.  
  158. procedure TGameBinFile.SwapScene(IndexA, IndexB: integer);
  159. var
  160.   bakScene: TSceneDef;
  161. begin
  162.   if IndexA = IndexB then exit;
  163.   if ((Min(IndexA, IndexB) < 0) or (Max(IndexA, IndexB) >= Length(Self.scenes))) then raise Exception.Create('Invalid scene index');
  164.   CopyMemory(@bakScene, @Self.scenes[IndexA], SizeOf(TSceneDef));
  165.   CopyMemory(@Self.scenes[IndexA], @Self.scenes[IndexB], SizeOf(TSceneDef));
  166.   CopyMemory(@Self.scenes[IndexB], @bakScene, SizeOf(TSceneDef));
  167. end;
  168.  
  169. function TGameBinFile.Defrag(OnlyCheck: boolean=false): boolean;
  170. var
  171.   iPicture, iScene, iScene2: integer;
  172.   isGap: boolean;
  173. begin
  174.   result := false;
  175.   for iPicture := MaxPictureCount - 1 downto 0 do
  176.   begin
  177.     isGap := true;
  178.     for iScene := 0 to Self.numScenes - 1 do
  179.     begin
  180.       if (iPicture >= Self.scenes[iScene].pictureIndex) and
  181.          (iPicture <= Self.scenes[iScene].pictureIndex + Self.scenes[iScene].numPics - 1) then
  182.       begin
  183.         isGap := false;
  184.         break;
  185.       end;
  186.     end;
  187.     if isGap then
  188.     begin
  189.       result := true;
  190.       if not OnlyCheck then
  191.       begin
  192.         {$REGION 'Close the gap'}
  193.         for iScene2 := 0 to Self.numScenes - 1 do
  194.         begin
  195.           if (iPicture < Self.scenes[iScene2].pictureIndex) then
  196.           begin
  197.             Dec(Self.scenes[iScene2].pictureIndex);
  198.           end;
  199.         end;
  200.         CopyMemory(@Self.pictures[iPicture], @Self.pictures[iPicture+1], (Length(Self.pictures)-iPicture-1)*SizeOf(TPictureDef));
  201.         ZeroMemory(@Self.pictures[Length(Self.pictures)-1], SizeOf(TPictureDef));
  202.         {$ENDREGION}
  203.       end;
  204.     end;
  205.   end;
  206. end;
  207.  
  208. procedure TGameBinFile.DeletePicture(PictureIndex: integer);
  209. var
  210.   iScene, iScene2: integer;
  211.   protection: integer; // prevents that two scenes get the same picture index when all pictures in a scene are deleted
  212. begin
  213.   if (PictureIndex < 0) or (PictureIndex >= Length(Self.pictures)) then raise Exception.Create('Invalid picture index');
  214.  
  215.   protection := 0;
  216.   for iScene := 0 to Self.numScenes-1 do
  217.   begin
  218.     if (PictureIndex >= Self.scenes[iScene].pictureIndex) and
  219.        (PictureIndex <= Self.scenes[iScene].pictureIndex + Self.scenes[iScene].numPics - 1) then
  220.     begin
  221.       Dec(Self.scenes[iScene].numPics);
  222.       if Self.scenes[iScene].numPics = 0 then
  223.       begin
  224.          for iScene2 := 0 to Self.numScenes-1 do
  225.          begin
  226.            if Self.scenes[iScene2].pictureIndex = PictureIndex+1 then
  227.            begin
  228.              protection := 1;
  229.              break;
  230.            end;
  231.          end;
  232.       end;
  233.     end
  234.     else if (PictureIndex+protection < Self.scenes[iScene].pictureIndex) then
  235.     begin
  236.       Dec(Self.scenes[iScene].pictureIndex);
  237.     end;
  238.   end;
  239.  
  240.   If (PictureIndex < Length(Self.pictures)-1) and (protection = 0) then
  241.   begin
  242.     CopyMemory(@Self.pictures[PictureIndex], @Self.pictures[PictureIndex+1], (Length(Self.pictures)-PictureIndex-1)*SizeOf(TPictureDef));
  243.     ZeroMemory(@Self.pictures[Length(Self.pictures)-1], SizeOf(TPictureDef));
  244.   end;
  245.  
  246.   Dec(Self.numPics);
  247. end;
  248.  
  249. procedure TGameBinFile.SwapPicture(IndexA, IndexB: integer);
  250. var
  251.   bakPicture: TPictureDef;
  252. begin
  253.   // QUE: should we forbid that a picture between "scene borders" are swapped?
  254.  
  255.   if IndexA = IndexB then exit;
  256.   if ((Min(IndexA, IndexB) < 0) or (Max(IndexA, IndexB) >= Length(Self.pictures))) then raise Exception.Create('Invalid picture index');
  257.  
  258.   CopyMemory(@bakPicture, @Self.pictures[IndexA], SizeOf(TPictureDef));
  259.   CopyMemory(@Self.pictures[IndexA], @Self.pictures[IndexB], SizeOf(TPictureDef));
  260.   CopyMemory(@Self.pictures[IndexB], @bakPicture, SizeOf(TPictureDef));
  261. end;
  262.  
  263. function TGameBinFile.AddPictureBetween(Index: integer; assignToUpperScene: boolean=true): PPictureDef;
  264.  
  265.   function _HasBuffer(Index: integer): boolean;
  266.   var
  267.     iScene: integer;
  268.   begin
  269.     for iScene := 0 to Self.numScenes-1 do
  270.     begin
  271.       if Self.scenes[iScene].pictureIndex = Index+1 then
  272.       begin
  273.         result := true;
  274.         exit;
  275.       end;
  276.     end;
  277.     result := false;
  278.   end;
  279.  
  280. var
  281.   iScene: integer;
  282. begin
  283.   if ((Index < 0) or (Index >= Length(Self.pictures))) then raise Exception.Create('Invalid picture index');
  284.  
  285.   if (BiggestUsedPictureIndex = MaxPictureCount-1) and Defrag(true) then
  286.     raise Exception.Create('Not enough space for another picture. Please defrag to fill the gaps first.');
  287.  
  288.   if Self.numPics >= MaxPictureCount then
  289.     raise Exception.Create('Not enough space for another picture. Maximum limit reached.');
  290.  
  291.   if assignToUpperScene then
  292.   begin
  293.     // Sc1   Sc2       Sc1   Sc2
  294.     // A B | C    ==>  A B | X C
  295.     //       ^
  296.     for iScene := 0 to Self.numScenes-1 do
  297.     begin
  298.       if (Index >= Self.scenes[iScene].pictureIndex) and
  299.          (index <= Self.scenes[iScene].pictureIndex + Max(0,Self.scenes[iScene].numPics - 1)) then
  300.       begin
  301.         Inc(Self.scenes[iScene].numPics);
  302.       end
  303.       else if (index < Self.scenes[iScene].pictureIndex) and not _HasBuffer(index) then
  304.       begin
  305.         Inc(Self.scenes[iScene].pictureIndex);
  306.       end;
  307.     end;
  308.   end
  309.   else
  310.   begin
  311.     // Sc1   Sc2       Sc1     Sc2
  312.     // A B | C    ==>  A B X | C
  313.     //       ^
  314.     for iScene := 0 to Self.numScenes-1 do
  315.     begin
  316.       if (Index >= 1 + Self.scenes[iScene].pictureIndex) and
  317.          (index <= 1 + Self.scenes[iScene].pictureIndex + Max(0,Self.scenes[iScene].numPics-1)) then
  318.       begin
  319.         Inc(Self.scenes[iScene].numPics);
  320.       end
  321.       else if (index <= Self.scenes[iScene].pictureIndex) and not _HasBuffer(index) then
  322.       begin
  323.         Inc(Self.scenes[iScene].pictureIndex);
  324.       end;
  325.     end;
  326.   end;
  327.  
  328.   result := @Self.pictures[Index];
  329.   if not _HasBuffer(index) then
  330.   begin
  331.     CopyMemory(@Self.pictures[Index+1], result, (Length(Self.pictures)-Index-1)*SizeOf(TPictureDef));
  332.   end;
  333.    
  334.   ZeroMemory(result, SizeOf(TPictureDef));
  335.  
  336.   Inc(Self.numPics);
  337. end;
  338.  
  339. end.
  340.