Subversion Repositories plumbers

Rev

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

Rev Author Line No. Line
2 daniel-mar 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
10 daniel-mar 57
 
11 daniel-mar 58
    class function MaxSceneCount: integer; static;
59
    class function MaxPictureCount: integer; static;
60
 
2 daniel-mar 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);
10 daniel-mar 66
    function AddPictureBetween(Index: integer; assignToUpperScene: boolean=true): PPictureDef;
67
    function RealPictureCount: integer;
68
    function RealActionCount: integer;
11 daniel-mar 69
    function Defrag(OnlyCheck: boolean=false): boolean;
70
    function BiggestUsedPictureIndex: integer;
2 daniel-mar 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
 
11 daniel-mar 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
 
2 daniel-mar 98
function TGameBinFile.AddSceneAtEnd(SceneID: integer): PSceneDef;
99
begin
11 daniel-mar 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*
2 daniel-mar 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));
10 daniel-mar 117
  Dec(Self.numScenes);
2 daniel-mar 118
end;
119
 
11 daniel-mar 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
 
10 daniel-mar 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
 
2 daniel-mar 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
 
11 daniel-mar 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
 
2 daniel-mar 208
procedure TGameBinFile.DeletePicture(PictureIndex: integer);
209
var
10 daniel-mar 210
  iScene, iScene2: integer;
211
  protection: integer; // prevents that two scenes get the same picture index when all pictures in a scene are deleted
2 daniel-mar 212
begin
213
  if (PictureIndex < 0) or (PictureIndex >= Length(Self.pictures)) then raise Exception.Create('Invalid picture index');
214
 
10 daniel-mar 215
  protection := 0;
2 daniel-mar 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);
10 daniel-mar 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;
2 daniel-mar 233
    end
10 daniel-mar 234
    else if (PictureIndex+protection < Self.scenes[iScene].pictureIndex) then
2 daniel-mar 235
    begin
236
      Dec(Self.scenes[iScene].pictureIndex);
237
    end;
238
  end;
239
 
11 daniel-mar 240
  If (PictureIndex < Length(Self.pictures)-1) and (protection = 0) then
2 daniel-mar 241
  begin
11 daniel-mar 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));
2 daniel-mar 244
  end;
10 daniel-mar 245
 
246
  Dec(Self.numPics);
2 daniel-mar 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
 
10 daniel-mar 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
 
2 daniel-mar 280
var
281
  iScene: integer;
282
begin
283
  if ((Index < 0) or (Index >= Length(Self.pictures))) then raise Exception.Create('Invalid picture index');
284
 
11 daniel-mar 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
 
10 daniel-mar 291
  if assignToUpperScene then
2 daniel-mar 292
  begin
10 daniel-mar 293
    // Sc1   Sc2       Sc1   Sc2
294
    // A B | C    ==>  A B | X C
295
    //       ^
296
    for iScene := 0 to Self.numScenes-1 do
2 daniel-mar 297
    begin
10 daniel-mar 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
2 daniel-mar 315
    begin
10 daniel-mar 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;
2 daniel-mar 325
    end;
326
  end;
327
 
328
  result := @Self.pictures[Index];
10 daniel-mar 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
 
2 daniel-mar 334
  ZeroMemory(result, SizeOf(TPictureDef));
10 daniel-mar 335
 
336
  Inc(Self.numPics);
2 daniel-mar 337
end;
338
 
339
end.