Subversion Repositories plumbers

Rev

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

  1. unit Main;
  2.  
  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.  
  20. // BUG: If you drag the window, the dia show will stop playing, but the sound continues! This makes everything out of sync.
  21. // TODO: When the windows is only resized a little bit (A few pixels), the window should not centered
  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.
  26.  
  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.  
  38. interface
  39.  
  40. uses
  41.   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
  42.   Dialogs, ExtCtrls, StdCtrls, Game, MPlayer, SyncObjs;
  43.  
  44. type
  45.   TMainForm = class(TForm)
  46.     Image1: TImage;
  47.     Panel1: TPanel;
  48.     StartupTimer: TTimer;
  49.     MediaPlayer1: TMediaPlayer;
  50.     Panel2: TPanel;
  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);
  62.     function cbWait(ASender: TGame; AMilliseconds: Cardinal): boolean;
  63.     procedure cbSetHotspot(ASender: TGame; AIndex: THotspotIndex; AHotspot: THotspot);
  64.     procedure cbClearHotspots(ASender: TGame);
  65.     procedure ClickEvent(X, Y: Integer);
  66.   private
  67.     FCancelSceneRequest: boolean;
  68.     csCancelSceneRequest: TCriticalSection;
  69.   public
  70.     game: TGame;
  71.   end;
  72.  
  73. var
  74.   MainForm: TMainForm;
  75.  
  76. implementation
  77.  
  78. {$R *.dfm}
  79.  
  80. uses
  81.   MMSystem, IniFiles, Math, GameBinStruct;
  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
  105.     Temp := {$IF not Declared(ThousandSeparator)}FormatSettings.{$IFEND}ThousandSeparator + Copy (S, LS - 3 * I + 1, 3) + Temp;
  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);
  114. resourcestring
  115.   S_YOUR_SCORE = 'Your score is: %s';
  116. begin
  117.   csCancelSceneRequest.Acquire;
  118.   try
  119.     FCancelSceneRequest := false;
  120.   finally
  121.     csCancelSceneRequest.Release;
  122.   end;
  123.  
  124.   {$IFDEF DEBUG}
  125.   Caption := AFileName;
  126.   {$ENDIF}
  127.  
  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
  142.       ClientWidth := Min(Image1.Width, Screen.Width);
  143.       if (ClientWidth >= Screen.Width) then FullscreenMode := true;
  144.       Position := poScreenCenter;
  145.     end;
  146.     if Image1.Height > ClientHeight then
  147.     begin
  148.       ClientHeight := Min(Image1.Height, Screen.Height);
  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
  159.     // ShowMessageFmt('File not found: %s', [AFileName]);
  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.  
  173.   Panel1.Caption := Format(S_YOUR_SCORE, [AddThouSeps(IntToStr(ASender.Score))]);
  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.  
  208. function TMainForm.cbWait(ASender: TGame; AMilliseconds: Cardinal): boolean;
  209. var
  210.   FirstTickCount: DWord;
  211. begin
  212.   //Cursor := crHourglass;
  213.   try
  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;
  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
  242.   csCancelSceneRequest := TCriticalSection.Create;
  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
  266.   if Assigned(Game) then FreeAndNil(Game);
  267.  
  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);
  271.   FreeAndNil(csCancelSceneRequest);
  272. end;
  273.  
  274. procedure TMainForm.FormKeyDown(Sender: TObject; var Key: Word;
  275.   Shift: TShiftState);
  276. begin
  277.   if Key = VK_SPACE then
  278.   begin
  279.     if MediaPlayer1.Mode = mpPlaying then MediaPlayer1.Stop;
  280.   end;
  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;
  294.   if Key = VK_ESCAPE then Close;
  295. end;
  296.  
  297. procedure TMainForm.ClickEvent(X, Y: Integer);
  298. var
  299.   i: integer;
  300.   ac: TActionDef;
  301. begin
  302.   // Debug: Go to prev decision by clicking on the top left edge
  303.   if (X < 20) and (Y < 20) then
  304.   begin
  305.     // TODO: Also allow to go back multiple steps (we would need a stack instead of PrevDecisionScene/CurDecisionScene)
  306.     ac.scoreDelta := 0;
  307.     ac.nextSceneID := SCENEID_PREVDECISION;
  308.     ac.sceneSegment := 0;
  309.     Game.PerformAction(@ac);
  310.     Exit;
  311.   end;
  312.  
  313.   // If hotspots are overlaying, the lowest action will be chosen (same behavior as original game)
  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
  330.   {$IFDEF HOTSPOT_RELATIVE_ORIGIN}
  331.   ClickEvent(X, Y);
  332.   {$ELSE}
  333.   ClickEvent(X+TControl(Sender).Left, Y+TControl(Sender).Top);
  334.   {$ENDIF}
  335. end;
  336.  
  337. procedure TMainForm.StartupTimerTimer(Sender: TObject);
  338. begin
  339.   StartupTimer.Enabled := false;
  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('.');
  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;
  392.   except
  393.     on E: EAbort do
  394.     begin
  395.       Close;
  396.     end;
  397.     on E: Exception do
  398.     begin
  399.       MessageDlg(E.Message, mtError, [mbOK], 0);
  400.       Close;
  401.     end;
  402.   end;
  403. end;
  404.  
  405. end.
  406.