Subversion Repositories plumbers

Rev

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