Subversion Repositories plumbers

Rev

Rev 22 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

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