1 //============================================================================
2 //
3 //   SSSS    tt          lll  lll
4 //  SS  SS   tt           ll   ll
5 //  SS     tttttt  eeee   ll   ll   aaaa
6 //   SSSS    tt   ee  ee  ll   ll      aa
7 //      SS   tt   eeeeee  ll   ll   aaaaa  --  "An Atari 2600 VCS Emulator"
8 //  SS  SS   tt   ee      ll   ll  aa  aa
9 //   SSSS     ttt  eeeee llll llll  aaaaa
10 //
11 // Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
12 // and the Stella Team
13 //
14 // See the file "License.txt" for information on usage and redistribution of
15 // this file, and for a DISCLAIMER OF ALL WARRANTIES.
16 //============================================================================
17 
18 #include "Event.hxx"
19 #include "TIA.hxx"
20 #include "FrameBuffer.hxx"
21 
22 #include "Lightgun.hxx"
23 
24 // |              | Left port   | Right port  |
25 // | Fire button  | SWCHA bit 4 | SWCHA bit 0 | DP:1
26 // | Detect light | INPT4 bit 7 | INPT5 bit 7 | DP:6
27 
28 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Lightgun(Jack jack,const Event & event,const System & system,const string & romMd5,const FrameBuffer & frameBuffer)29 Lightgun::Lightgun(Jack jack, const Event& event, const System& system,
30                    const string& romMd5, const FrameBuffer& frameBuffer)
31   : Controller(jack, event, system, Controller::Type::Lightgun),
32     myFrameBuffer{frameBuffer}
33 {
34   // Right now, there are only three games and a test ROM that use the light gun
35   if (romMd5 == "8da51e0c4b6b46f7619425119c7d018e" ||
36       romMd5 == "7e5ee26bc31ae8e4aa61388c935b9332")
37   {
38     // Sentinel
39     myOfsX = -24;
40     myOfsY = -5;
41   }
42   else if (romMd5 == "10c47acca2ecd212b900ad3cf6942dbb" ||
43            romMd5 == "15c11ab6e4502b2010b18366133fc322" ||
44            romMd5 == "557e893616648c37a27aab5a47acbf10" ||
45            romMd5 == "5d7293f1892b66c014e8d222e06f6165" ||
46            romMd5 == "b2ab209976354ad4a0e1676fc1fe5a82" ||
47            romMd5 == "b5a1a189601a785bdb2f02a424080412" ||
48            romMd5 == "c5bf03028b2e8f4950ec8835c6811d47" ||
49            romMd5 == "f0ef9a1e5d4027a157636d7f19952bb5")
50   {
51     // Shooting Arcade
52     myOfsX = -21;
53     myOfsY = 5;
54   }
55   else if (romMd5 == "2559948f39b91682934ea99d90ede631" ||
56            romMd5 == "e75ab446017448045b152eea78bf7910")
57   {
58     // Booby is Hungry
59     myOfsX = -21;
60     myOfsY = 5;
61   }
62   else if (romMd5 == "d65900fefa7dc18ac3ad99c213e2fa4e")
63   {
64     // Guntest
65     myOfsX = -25;
66     myOfsY = 1;
67   }
68   else
69   {
70     // unknown game, use average values
71     myOfsX = -23;
72     myOfsY = 1;
73   }
74 }
75 
76 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
read(DigitalPin pin)77 bool Lightgun::read(DigitalPin pin)
78 {
79   // We need to override the Controller::read() method, since the lightgun
80   // checks this multiple times per frame
81   // (we can't just read 60 times per second in the ::update() method)
82   switch (pin)
83   {
84     case DigitalPin::Six: // INPT4/5
85     {
86       const Common::Rect& rect = myFrameBuffer.imageRect();
87 
88       // abort when no valid framebuffer exists
89       if (rect.w() == 0 || rect.h() == 0)
90         return false;
91 
92       TIA& tia = mySystem.tia();
93       // scale mouse coordinates into TIA coordinates
94       Int32 xMouse = (myEvent.get(Event::MouseAxisXValue) - rect.x())
95         * tia.width() / rect.w();
96       Int32 yMouse = (myEvent.get(Event::MouseAxisYValue) - rect.y())
97         * tia.height() / rect.h();
98 
99       // get adjusted TIA coordinates
100       Int32 xTia = tia.clocksThisLine() - TIAConstants::H_BLANK_CLOCKS + myOfsX;
101       Int32 yTia = tia.scanlines() - tia.startLine() + myOfsY;
102 
103       if (xTia < 0)
104         xTia += TIAConstants::H_CLOCKS;
105 
106       bool enable = !((xTia - xMouse) >= 0 && (xTia - xMouse) < 15 && (yTia - yMouse) >= 0);
107 
108       return enable;
109     }
110     default:
111       return Controller::read(pin);
112   }
113 }
114 
115 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
update()116 void Lightgun::update()
117 {
118   // Digital events (from keyboard or joystick hats & buttons)
119   bool firePressed = myEvent.get(Event::LeftJoystickFire) != 0;
120 
121   // We allow left and right mouse buttons for fire button
122   firePressed = firePressed
123     || myEvent.get(Event::MouseButtonLeftValue)
124     || myEvent.get(Event::MouseButtonRightValue);
125 
126   setPin(DigitalPin::One, !getAutoFireState(firePressed));
127 }
128