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 Main;
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
 
8 daniel-mar 20
// BUG: If you drag the window, the dia show will stop playing, but the sound continues! This makes everything out of sync.
2 daniel-mar 21
// TODO: When the windows is only resized a little bit (A few pixels), the window should not centered
8 daniel-mar 22
//       ... Calc the width and height of ALL pictures, and then size the form to the biggest value?
23
//       ... or hard code the resolution in the INI file?
24
// Idea: Ini Parameter if fullscreen is applied or not
25
// Idea: Savestates, speedup, pause, Use Space bar to go to the next decision point.
2 daniel-mar 26
 
8 daniel-mar 27
// -----------------------------------------------------------------------------
28
 
29
// HOTSPOT_RELATIVE_ORIGIN is a new behavior which is not compatible with the original engine.
30
// With HOTSPOT_RELATIVE_ORIGIN enabled, the coordinates will be relative to the picture
31
// The original game has the origin at the top left corner of the screen.
32
// This is a problem because the game as well as the scene editor does not know the
33
// desired resolution, as it is automatically determined.
34
// If we would hardcode the desired canvas (640x480) in <ExeName>.ini, then
35
// it would work, but then, the scene Editor can not know the desired resolution...
36
{$DEFINE HOTSPOT_RELATIVE_ORIGIN}
37
 
2 daniel-mar 38
interface
39
 
40
uses
41
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
19 daniel-mar 42
  Dialogs, ExtCtrls, StdCtrls, Game, MPlayer, SyncObjs;
2 daniel-mar 43
 
44
type
45
  TMainForm = class(TForm)
46
    Image1: TImage;
47
    Panel1: TPanel;
48
    StartupTimer: TTimer;
16 daniel-mar 49
    MediaPlayer1: TMediaPlayer;
50
    Panel2: TPanel;
2 daniel-mar 51
    procedure FormCreate(Sender: TObject);
52
    procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
53
    procedure StartupTimerTimer(Sender: TObject);
54
    procedure ControlClick(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
55
    procedure FormDestroy(Sender: TObject);
56
  private
57
    FHotspots: array[0..2] of THotspot;
58
    FullscreenMode: boolean;
59
    procedure cbPictureShow(ASender: TGame; AFilename: string; AType: TPictureType);
60
    procedure cbAsyncSound(ASender: TGame; AFilename: string);
61
    procedure cbExit(ASender: TGame);
24 daniel-mar 62
    function cbWait(ASender: TGame; AMilliseconds: Cardinal): boolean;
2 daniel-mar 63
    procedure cbSetHotspot(ASender: TGame; AIndex: THotspotIndex; AHotspot: THotspot);
64
    procedure cbClearHotspots(ASender: TGame);
65
    procedure ClickEvent(X, Y: Integer);
19 daniel-mar 66
  private
67
    FCancelSceneRequest: boolean;
68
    csCancelSceneRequest: TCriticalSection;
2 daniel-mar 69
  public
70
    game: TGame;
71
  end;
72
 
73
var
74
  MainForm: TMainForm;
75
 
76
implementation
77
 
78
{$R *.dfm}
79
 
80
uses
19 daniel-mar 81
  MMSystem, IniFiles, Math, GameBinStruct;
2 daniel-mar 82
 
83
function AddThouSeps(const S: string): string;
84
var
85
  LS, L2, I, N: Integer;
86
  Temp: string;
87
begin
88
  // http://www.delphigroups.info/2/11/471892.html
89
  result := S ;
90
  LS := Length(S);
91
  N := 1 ;
92
  if LS > 1 then
93
  begin
94
    if S [1] = '-' then  // check for negative value
95
    begin
96
      N := 2;
97
      LS := LS - 1;
98
    end;
99
  end;
100
  if LS <= 3 then exit;
101
  L2 := (LS - 1) div 3;
102
  Temp := '';
103
  for I := 1 to L2 do
104
  begin
12 daniel-mar 105
    Temp := {$IF not Declared(ThousandSeparator)}FormatSettings.{$IFEND}ThousandSeparator + Copy (S, LS - 3 * I + 1, 3) + Temp;
2 daniel-mar 106
  end;
107
  Result := Copy (S, N, (LS - 1) mod 3 + 1) + Temp;
108
  if N > 1 then Result := '-' + Result;
109
end;
110
 
111
{ TMainForm }
112
 
113
procedure TMainForm.cbPictureShow(ASender: TGame; AFilename: string; AType: TPictureType);
12 daniel-mar 114
resourcestring
115
  S_YOUR_SCORE = 'Your score is: %s';
2 daniel-mar 116
begin
20 daniel-mar 117
  csCancelSceneRequest.Acquire;
118
  try
119
    FCancelSceneRequest := false;
120
  finally
121
    csCancelSceneRequest.Release;
122
  end;
123
 
19 daniel-mar 124
  {$IFDEF DEBUG}
125
  Caption := AFileName;
126
  {$ENDIF}
127
 
2 daniel-mar 128
  if FileExists(AFilename) then
129
  begin
130
    Image1.Visible := false;
131
    try
132
      Image1.Picture.LoadFromFile(AFilename);
133
      Image1.Autosize := true;
134
    finally
135
      // This speeds up the picture loading on very old computers
136
      Image1.Visible := true;
137
    end;
138
 
139
    // Make form bigger if necessary
140
    if Image1.Width > ClientWidth then
141
    begin
8 daniel-mar 142
      ClientWidth := Min(Image1.Width, Screen.Width);
2 daniel-mar 143
      if (ClientWidth >= Screen.Width) then FullscreenMode := true;
144
      Position := poScreenCenter;
145
    end;
146
    if Image1.Height > ClientHeight then
147
    begin
8 daniel-mar 148
      ClientHeight := Min(Image1.Height, Screen.Height);
2 daniel-mar 149
      if (ClientHeight >= Screen.Height) then FullscreenMode := true;
150
      Position := poScreenCenter;
151
    end;
152
 
153
    // Center image
154
    Image1.Left := ClientWidth div 2 - Image1.Width div 2;
155
    Image1.Top := ClientHeight div 2 - Image1.Height div 2;
156
  end
157
  else
158
  begin
22 daniel-mar 159
    // ShowMessageFmt('File not found: %s', [AFileName]);
2 daniel-mar 160
    Image1.Picture := nil;
161
  end;
162
 
163
  if FullScreenMode then
164
  begin
165
    BorderStyle := bsNone;
166
    FormStyle := fsStayOnTop;
167
    Case AType of
168
      ptDia: Screen.Cursor := -1;
169
      ptDecision: Screen.Cursor := 0;
170
    End;
171
  end;
172
 
12 daniel-mar 173
  Panel1.Caption := Format(S_YOUR_SCORE, [AddThouSeps(IntToStr(ASender.Score))]);
2 daniel-mar 174
  Panel1.Left := 8;
175
  Panel1.Top := Min(ClientHeight, Screen.Height) - Panel1.Height - 8;
176
  Panel1.Visible := AType = ptDecision;
177
end;
178
 
179
procedure TMainForm.cbAsyncSound(ASender: TGame; AFilename: string);
180
begin
181
  PlaySound(nil, hinstance, 0);
182
  if FileExists(AFilename) then
183
  begin
184
    PlaySound(PChar(AFilename), hinstance, SND_FILENAME or SND_ASYNC);
185
  end;
186
end;
187
 
188
procedure TMainForm.cbSetHotspot(ASender: TGame; AIndex: THotspotIndex; AHotspot: THotspot);
189
begin
190
  FHotspots[AIndex] := AHotspot;
191
end;
192
 
193
procedure TMainForm.cbClearHotspots(ASender: TGame);
194
var
195
  i: Integer;
196
begin
197
  for i := Low(FHotspots) to High(FHotspots) - 1 do
198
  begin
199
    FHotspots[i].lpAction := nil;
200
  end;
201
end;
202
 
203
procedure TMainForm.cbExit(ASender: TGame);
204
begin
205
  Application.Terminate;
206
end;
207
 
24 daniel-mar 208
function TMainForm.cbWait(ASender: TGame; AMilliseconds: Cardinal): boolean;
19 daniel-mar 209
var
210
  FirstTickCount: DWord;
2 daniel-mar 211
begin
212
  //Cursor := crHourglass;
213
  try
19 daniel-mar 214
    result := false; // don't cancel
215
    FirstTickCount := GetTickCount; // TODO: Attention, GetTickCount can overflow
216
    while not Application.Terminated and ((GetTickCount - FirstTickCount) < AMilliseconds) do
217
    begin
218
      csCancelSceneRequest.Acquire;
219
      try
220
        if FCancelSceneRequest then
221
        begin
222
          FCancelSceneRequest := false;
223
          result := true; // cancel
224
          exit;
225
        end;
226
      finally
227
        csCancelSceneRequest.Release;
228
      end;
229
      Application.ProcessMessages;
230
      Sleep(0);
231
    end;
2 daniel-mar 232
  finally
233
    //Cursor := crDefault;
234
  end;
235
end;
236
 
237
procedure TMainForm.FormCreate(Sender: TObject);
238
var
239
  ini: TMemIniFile;
240
  iniFilename: string;
241
begin
19 daniel-mar 242
  csCancelSceneRequest := TCriticalSection.Create;
2 daniel-mar 243
  iniFilename := ChangeFileExt(ExtractFileName(ParamStr(0)), '.ini');
244
 
245
  DoubleBuffered := true;
246
 
247
  if FileExists(iniFilename) then
248
  begin
249
    ini := TMemIniFile.Create(iniFilename);
250
    try
251
      Caption := ini.ReadString('Config', 'Title', '');
252
    finally
253
      FreeAndNil(ini);
254
    end;
255
  end;
256
 
257
  try
258
    StartupTimer.Enabled := true;
259
  except
260
    Application.Terminate;
261
  end;
262
end;
263
 
264
procedure TMainForm.FormDestroy(Sender: TObject);
265
begin
21 daniel-mar 266
  if Assigned(Game) then FreeAndNil(Game);
17 daniel-mar 267
 
2 daniel-mar 268
  // Without this, some audio drivers could crash if you press ESC to end the game.
269
  // (VPC 2007 with Win95; cpsman.dll crashes sometimes)
270
  PlaySound(nil, hinstance, 0);
19 daniel-mar 271
  FreeAndNil(csCancelSceneRequest);
2 daniel-mar 272
end;
273
 
274
procedure TMainForm.FormKeyDown(Sender: TObject; var Key: Word;
275
  Shift: TShiftState);
276
begin
16 daniel-mar 277
  if Key = VK_SPACE then
278
  begin
279
    if MediaPlayer1.Mode = mpPlaying then MediaPlayer1.Stop;
280
  end;
19 daniel-mar 281
  if KEY = VK_RETURN then
282
  begin
283
    if MediaPlayer1.Mode = mpPlaying then
284
    begin
285
      MediaPlayer1.Position := MediaPlayer1.EndPos;
286
    end;
287
    csCancelSceneRequest.Acquire;
288
    try
289
      FCancelSceneRequest := true;
290
    finally
291
      csCancelSceneRequest.Release;
292
    end;
293
  end;
2 daniel-mar 294
  if Key = VK_ESCAPE then Close;
295
end;
296
 
297
procedure TMainForm.ClickEvent(X, Y: Integer);
298
var
299
  i: integer;
19 daniel-mar 300
  ac: TActionDef;
2 daniel-mar 301
begin
19 daniel-mar 302
  // Debug: Go to prev decision by clicking on the top left edge
303
  if (X < 20) and (Y < 20) then
304
  begin
20 daniel-mar 305
    // TODO: Also allow to go back multiple steps (we would need a stack instead of PrevDecisionScene/CurDecisionScene)
19 daniel-mar 306
    ac.scoreDelta := 0;
307
    ac.nextSceneID := SCENEID_PREVDECISION;
308
    ac.sceneSegment := 0;
309
    Game.PerformAction(@ac);
310
    Exit;
311
  end;
312
 
8 daniel-mar 313
  // If hotspots are overlaying, the lowest action will be chosen (same behavior as original game)
2 daniel-mar 314
  for i := Low(FHotspots) to High(FHotspots) do
315
  begin
316
    if Assigned(FHotspots[i].lpAction) and
317
       (X >= FHotspots[i].cHotspotTopLeft.X) and
318
       (Y >= FHotspots[i].cHotspotTopLeft.Y) and
319
       (X <= FHotspots[i].cHotspotBottomRight.X) and
320
       (Y <= FHotspots[i].cHotspotBottomRight.Y) then
321
    begin
322
      FHotspots[i].Game.PerformAction(FHotspots[i].lpAction);
323
      Exit;
324
    end;
325
  end;
326
end;
327
 
328
procedure TMainForm.ControlClick(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
329
begin
8 daniel-mar 330
  {$IFDEF HOTSPOT_RELATIVE_ORIGIN}
331
  ClickEvent(X, Y);
332
  {$ELSE}
2 daniel-mar 333
  ClickEvent(X+TControl(Sender).Left, Y+TControl(Sender).Top);
8 daniel-mar 334
  {$ENDIF}
2 daniel-mar 335
end;
336
 
337
procedure TMainForm.StartupTimerTimer(Sender: TObject);
338
begin
339
  StartupTimer.Enabled := false;
16 daniel-mar 340
 
341
  if FileExists('INTRO.AVI') then
342
  begin
343
    MediaPlayer1.FileName := 'INTRO.AVI';
344
    MediaPlayer1.Open;
345
 
346
    Panel2.Visible := true;
347
    Panel2.Top := 0;
348
    Panel2.Left := 0;
349
    Panel2.Width  := MediaPlayer1.DisplayRect.Right;
350
    Panel2.Height := MediaPlayer1.DisplayRect.Bottom;
351
 
352
    ClientWidth := Panel2.Width;
353
    if (ClientWidth >= Screen.Width) then FullscreenMode := true;
354
    ClientHeight := Panel2.Height;
355
    if (ClientHeight >= Screen.Height) then FullscreenMode := true;
356
    Position := poScreenCenter;
357
 
358
    if FullScreenMode then
359
    begin
360
      BorderStyle := bsNone;
361
      FormStyle := fsStayOnTop;
362
      Screen.Cursor := -1;
363
    end;
364
 
365
    // For some reason, "Position := poScreenCenter" causes the video handle to break!
366
    // we need to close+open it again!
367
    MediaPlayer1.Close;
368
    MediaPlayer1.Open;
369
 
370
    MediaPlayer1.Play;
371
    while MediaPlayer1.Mode <> mpStopped do
372
    begin
373
      Sleep(100);
374
      Application.ProcessMessages;
375
      if Application.Terminated then break;
376
    end;
377
 
378
    MediaPlayer1.Close;
379
    Panel2.Visible := false;
380
    Screen.Cursor := 0;
381
  end;
382
 
383
  try
384
    Game := TGame.Create('.');
17 daniel-mar 385
    Game.PictureShowCallback := cbPictureShow;
386
    Game.AsyncSoundCallback := cbAsyncSound;
387
    Game.ExitCallback := cbExit;
388
    Game.WaitCallback := cbWait;
389
    Game.SetHotspotCallback := cbSetHotspot;
390
    Game.ClearHotspotsCallback := cbClearHotspots;
391
    Game.Run;
16 daniel-mar 392
  except
21 daniel-mar 393
    on E: EAbort do
394
    begin
395
      Close;
396
    end;
16 daniel-mar 397
    on E: Exception do
398
    begin
399
      MessageDlg(E.Message, mtError, [mbOK], 0);
400
      Close;
401
    end;
402
  end;
2 daniel-mar 403
end;
404
 
405
end.