Subversion Repositories plumbers

Rev

Rev 24 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 daniel-mar 1
unit Game;
2
 
25 daniel-mar 3
(*
4
 * Plumbers Don't Wear Ties - Inofficial Win32 Player
5
 * Copyright 2017 - 2020 Daniel Marschall, ViaThinkSoft
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 *)
19
 
2 daniel-mar 20
interface
21
 
22
uses
23
  SysUtils, Classes, Forms, GameBinStruct;
24
 
25
type
26
  TPictureType = (ptDia, ptDecision);
27
 
28
  THotspotIndex = 0..2;
29
 
30
  TGame = class;
31
  PHotspot = ^THotspot;
32
  THotspot = record
33
    game: TGame;
34
    lpAction: PActionDef;
35
    cHotspotTopLeft: TCoord;
36
    cHotspotBottomRight: TCoord;
37
  end;
38
 
39
  TShowPictureCallback = procedure(Game: TGame; AFilename: string; AType: TPictureType) of object;
40
  TPlaySoundCallback = procedure(Game: TGame; AFilename: string) of object;
41
  TSimpleCallback = procedure(Game: TGame) of object;
24 daniel-mar 42
  TWaitCallback = function(Game: TGame; AMilliseconds: Cardinal): boolean of object;
2 daniel-mar 43
  TSetHotspotCallback = procedure(Game: TGame; AIndex: THotspotIndex; AHotspot: THotspot) of object;
44
  TClearHotspotsCallback = procedure(Game: TGame) of object;
45
 
46
  TGame = class(TObject)
47
  private
48
    FPictureShowCallback: TShowPictureCallback;
49
    FAsyncSoundCallback: TPlaySoundCallback;
50
    FExitCallback: TSimpleCallback;
51
    FWaitCallback: TWaitCallback;
52
    FSetHotspotCallback: TSetHotspotCallback;
53
    FClearHotspotsCallback: TClearHotspotsCallback;
54
    FDirectory: string;
55
    FScore: integer;
21 daniel-mar 56
    FCurDecisionScene: PSceneDef;
57
    FPrevDecisionScene: PSceneDef;
2 daniel-mar 58
    procedure TryExit;
59
    procedure PrevDecisionScene;
60
  protected
61
    GameData: TGameBinFile;
19 daniel-mar 62
    function Wait(AMilliseconds: integer): boolean;
2 daniel-mar 63
    procedure PlayScene(scene: PSceneDef; goToDecision: boolean);
23 daniel-mar 64
    function WavePrefix: string;
2 daniel-mar 65
  public
66
    procedure PerformAction(action: PActionDef);
67
    property PictureShowCallback: TShowPictureCallback read FPictureShowCallback write FPictureShowCallback;
68
    property AsyncSoundCallback: TPlaySoundCallback read FAsyncSoundCallback write FAsyncSoundCallback;
69
    property ExitCallback: TSimpleCallback read FExitCallback write FExitCallback;
70
    property WaitCallback: TWaitCallback read FWaitCallback write FWaitCallback;
71
    property SetHotspotCallback: TSetHotspotCallback read FSetHotspotCallback write FSetHotspotCallback;
72
    property ClearHotspotsCallback: TClearHotspotsCallback read FClearHotspotsCallback write FClearHotspotsCallback;
73
    property Directory: string read FDirectory;
74
    property Score: integer read FScore;
75
    constructor Create(ADirectory: string);
76
    procedure Run;
77
  end;
78
 
79
implementation
80
 
23 daniel-mar 81
uses
82
  MMSystem, Windows;
83
 
84
function Supports16BitWaveout: boolean;
85
var
86
  caps: TWaveOutCaps;
87
begin
88
  ZeroMemory(@caps, sizeof(caps));
89
  waveOutGetDevCaps(0, @caps, sizeof(caps));
90
  result := caps.dwFormats and $CCCCCCCC <> 0; // Note: Original SHOWTIME.EXE only checked $0CCC
91
end;
92
 
2 daniel-mar 93
{ TGame }
94
 
95
constructor TGame.Create(ADirectory: string);
96
var
97
  fs: TFileStream;
98
  gameBinFilename: string;
99
begin
100
  FDirectory := ADirectory;
101
 
102
  gameBinFilename := IncludeTrailingPathDelimiter(ADirectory) + 'GAME.BIN';
103
  if not FileExists(gameBinFilename) then
104
  begin
105
    raise Exception.Create('Cannot find GAME.BIN');
106
  end;
107
 
108
  fs := TFileStream.Create(gameBinFilename, fmOpenRead);
109
  try
110
    fs.ReadBuffer(GameData, SizeOf(GameData));
111
  finally
112
    FreeAndNil(fs);
113
  end;
114
end;
115
 
116
procedure TGame.TryExit;
117
begin
118
  if Assigned(ExitCallback) then ExitCallback(Self);
119
end;
120
 
121
procedure TGame.PrevDecisionScene;
122
begin
21 daniel-mar 123
  if Assigned(FPrevDecisionScene) then PlayScene(FPrevDecisionScene, true)
2 daniel-mar 124
end;
125
 
126
procedure TGame.PerformAction(action: PActionDef);
127
var
128
  nextScene: PSceneDef;
129
begin
130
  Inc(FScore, action^.scoreDelta);
131
  if action^.nextSceneID = SCENEID_PREVDECISION then
132
    PrevDecisionScene
133
  else if action^.nextSceneID = SCENEID_ENDGAME then
134
    TryExit
135
  else
136
  begin
12 daniel-mar 137
    nextScene := GameData.FindScene(action^.nextSceneID);
2 daniel-mar 138
    if Assigned(nextScene) then
12 daniel-mar 139
      PlayScene(nextScene, action^.sceneSegment=SEGMENT_DECISION)
2 daniel-mar 140
    (*
141
    else
142
      raise Exception.CreateFmt('Scene %d was not found in GAME.BIN', [action^.nextSceneID]);
143
    *)
144
  end;
145
end;
146
 
19 daniel-mar 147
function TGame.Wait(AMilliseconds: integer): boolean;
2 daniel-mar 148
begin
149
  if Assigned(WaitCallback) then
19 daniel-mar 150
  begin
151
    result := WaitCallback(Self, AMilliseconds)
152
  end
2 daniel-mar 153
  else
19 daniel-mar 154
  begin
2 daniel-mar 155
    Sleep(AMilliseconds);
19 daniel-mar 156
    result := false; // don't cancel
157
  end;
2 daniel-mar 158
end;
159
 
23 daniel-mar 160
function TGame.WavePrefix: string;
161
begin
162
  if Supports16BitWaveout then
163
    result := ''
164
  else
165
    result := 'E';
166
end;
167
 
2 daniel-mar 168
procedure TGame.PlayScene(scene: PSceneDef; goToDecision: boolean);
169
var
170
  i: integer;
171
  hotspot: THotspot;
172
begin
173
  if Assigned(ClearHotspotsCallback) then
174
  begin
175
    ClearHotspotsCallback(Self);
176
  end;
177
  if not goToDecision then
178
  begin
179
    if Assigned(AsyncSoundCallback) then
180
    begin
181
      AsyncSoundCallback(Self, IncludeTrailingPathDelimiter(FDirectory) +
23 daniel-mar 182
        scene^.szSceneFolder + PathDelim + WavePrefix + scene^.szDialogWav);
2 daniel-mar 183
    end;
184
    for i := scene^.pictureIndex to scene^.pictureIndex + scene^.numPics - 1 do
185
    begin
186
      if Assigned(PictureShowCallback) then
187
      begin
188
        PictureShowCallback(Self, IncludeTrailingPathDelimiter(FDirectory) +
189
          scene^.szSceneFolder + PathDelim + GameData.pictures[i].szBitmapFile, ptDia);
190
      end;
19 daniel-mar 191
      if Wait(GameData.pictures[i].duration * 100) then
192
      begin
21 daniel-mar 193
        // Wait was cancelled by VK_RETURN
19 daniel-mar 194
        AsyncSoundCallback(Self, '');
195
        break;
196
      end;
2 daniel-mar 197
      if Application.Terminated then Abort;
198
    end;
199
  end;
200
  if scene^.szDecisionBmp <> '' then
201
  begin
21 daniel-mar 202
    FPrevDecisionScene := FCurDecisionScene;
203
    FCurDecisionScene := scene;
2 daniel-mar 204
    if Assigned(PictureShowCallback) then
205
    begin
206
      PictureShowCallback(Self, IncludeTrailingPathDelimiter(FDirectory) +
207
        scene^.szSceneFolder + PathDelim + scene^.szDecisionBmp, ptDecision);
208
    end;
209
    if Assigned(SetHotspotCallback) then
210
    begin
211
      for i := 0 to scene^.numActions - 1 do
212
      begin
213
        hotspot.Game := Self;
214
        hotspot.lpAction := @scene^.actions[i];
215
        hotspot.cHotspotTopLeft.X := scene^.actions[i].cHotspotTopLeft.X;
216
        hotspot.cHotspotTopLeft.Y := scene^.actions[i].cHotspotTopLeft.Y;
217
        hotspot.cHotspotBottomRight.X := scene^.actions[i].cHotspotBottomRight.X;
218
        hotspot.cHotspotBottomRight.Y := scene^.actions[i].cHotspotBottomRight.Y;        
219
        SetHotspotCallback(Self, i, hotspot);
220
      end;
221
    end;
222
  end
223
  else
224
  begin
225
    if scene^.numActions > 0 then PerformAction(@scene^.actions[0]);
226
  end;
227
end;
228
 
229
procedure TGame.Run;
230
begin
231
  if GameData.numScenes = 0 then exit;
232
  PlayScene(@GameData.Scenes[0], false);
233
end;
234
 
235
end.