openMSX
ArkanoidPad.cc
Go to the documentation of this file.
1#include "ArkanoidPad.hh"
2
3#include "Event.hh"
5#include "StateChange.hh"
7#include "serialize.hh"
8#include "serialize_meta.hh"
9
10#include <algorithm>
11
12// Implemented mostly according to the info here: http://www.msx.org/forumtopic7661.html
13// This is absolutely not accurate, but good enough to make the pad work in the
14// Arkanoid games.
15
16// The hardware works with a 556 dual timer that's unemulated here, it requires
17// short delays at each shift, and a long delay at latching. Too short delays
18// will cause garbage reads, and too long delays at shifting will eventually
19// cause the shift register bits to return to 0.
20
21namespace openmsx {
22
23static constexpr int POS_MIN = 55; // measured by hap
24static constexpr int POS_MAX = 325; // measured by hap
25static constexpr int POS_CENTER = 236; // approx. middle used by games
26static constexpr int SCALE = 2;
27
28
29class ArkanoidState final : public StateChange
30{
31public:
32 ArkanoidState() = default; // for serialize
33 ArkanoidState(EmuTime::param time_, int delta_, bool press_, bool release_)
34 : StateChange(time_)
35 , delta(delta_), press(press_), release(release_) {}
36 [[nodiscard]] int getDelta() const { return delta; }
37 [[nodiscard]] bool getPress() const { return press; }
38 [[nodiscard]] bool getRelease() const { return release; }
39
40 template<typename Archive> void serialize(Archive& ar, unsigned /*version*/)
41 {
42 ar.template serializeBase<StateChange>(*this);
43 ar.serialize("delta", delta,
44 "press", press,
45 "release", release);
46 }
47private:
48 int delta;
49 bool press, release;
50};
52
54 StateChangeDistributor& stateChangeDistributor_)
55 : eventDistributor(eventDistributor_)
56 , stateChangeDistributor(stateChangeDistributor_)
57 , dialPos(POS_CENTER)
58{
59}
60
62{
63 if (isPluggedIn()) {
64 ArkanoidPad::unplugHelper(EmuTime::dummy());
65 }
66}
67
68
69// Pluggable
70std::string_view ArkanoidPad::getName() const
71{
72 return "arkanoidpad";
73}
74
75std::string_view ArkanoidPad::getDescription() const
76{
77 return "Arkanoid pad";
78}
79
80void ArkanoidPad::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/)
81{
82 eventDistributor.registerEventListener(*this);
83 stateChangeDistributor.registerListener(*this);
84}
85
86void ArkanoidPad::unplugHelper(EmuTime::param /*time*/)
87{
88 stateChangeDistributor.unregisterListener(*this);
89 eventDistributor.unregisterEventListener(*this);
90}
91
92// JoystickDevice
93uint8_t ArkanoidPad::read(EmuTime::param /*time*/)
94{
95 return buttonStatus | ((shiftReg & 0x100) >> 8);
96}
97
98void ArkanoidPad::write(uint8_t value, EmuTime::param /*time*/)
99{
100 uint8_t diff = lastValue ^ value;
101 lastValue = value;
102
103 if (diff & value & 0x4) {
104 // pin 8 from low to high: copy dial position into shift reg
105 shiftReg = dialPos;
106 }
107 if (diff & value & 0x1) {
108 // pin 6 from low to high: shift the shift reg
109 shiftReg = (shiftReg << 1) | (shiftReg & 1); // bit 0's value is 'shifted in'
110 }
111}
112
113// MSXEventListener
114void ArkanoidPad::signalMSXEvent(const Event& event,
115 EmuTime::param time) noexcept
116{
117 visit(overloaded{
118 [&](const MouseMotionEvent& e) {
119 int newPos = std::min(POS_MAX,
120 std::max(POS_MIN,
121 dialPos + e.getX() / SCALE));
122 int delta = newPos - dialPos;
123 if (delta != 0) {
124 stateChangeDistributor.distributeNew<ArkanoidState>(
125 time, delta, false, false);
126 }
127 },
128 [&](const MouseButtonDownEvent& /*e*/) {
129 // any button will press the Arkanoid Pad button
130 if (buttonStatus & 2) {
131 stateChangeDistributor.distributeNew<ArkanoidState>(
132 time, 0, true, false);
133 }
134 },
135 [&](const MouseButtonUpEvent& /*e*/) {
136 // any button will unpress the Arkanoid Pad button
137 if (!(buttonStatus & 2)) {
138 stateChangeDistributor.distributeNew<ArkanoidState>(
139 time, 0, false, true);
140 }
141 },
142 [](const EventBase&) { /*ignore */}
143 }, event);
144}
145
146// StateChangeListener
147void ArkanoidPad::signalStateChange(const StateChange& event)
148{
149 const auto* as = dynamic_cast<const ArkanoidState*>(&event);
150 if (!as) return;
151
152 dialPos += as->getDelta();
153 if (as->getPress()) buttonStatus &= ~2;
154 if (as->getRelease()) buttonStatus |= 2;
155}
156
157void ArkanoidPad::stopReplay(EmuTime::param time) noexcept
158{
159 // TODO Get actual mouse button(s) state. Is it worth the trouble?
160 int delta = POS_CENTER - dialPos;
161 bool release = (buttonStatus & 2) == 0;
162 if ((delta != 0) || release) {
163 stateChangeDistributor.distributeNew<ArkanoidState>(
164 time, delta, false, release);
165 }
166}
167
168
169// version 1: Initial version, the variables dialPos and buttonStatus were not
170// serialized.
171// version 2: Also serialize the above variables, this is required for
172// record/replay, see comment in Keyboard.cc for more details.
173template<typename Archive>
174void ArkanoidPad::serialize(Archive& ar, unsigned version)
175{
176 ar.serialize("shiftreg", shiftReg,
177 "lastValue", lastValue);
178 if (ar.versionAtLeast(version, 2)) {
179 ar.serialize("dialpos", dialPos,
180 "buttonStatus", buttonStatus);
181 }
182 if constexpr (Archive::IS_LOADER) {
183 if (isPluggedIn()) {
184 plugHelper(*getConnector(), EmuTime::dummy());
185 }
186 }
187}
190
191} // namespace openmsx
~ArkanoidPad() override
void serialize(Archive &ar, unsigned version)
ArkanoidPad(MSXEventDistributor &eventDistributor, StateChangeDistributor &stateChangeDistributor)
bool getRelease() const
ArkanoidState(EmuTime::param time_, int delta_, bool press_, bool release_)
void serialize(Archive &ar, unsigned)
void registerEventListener(MSXEventListener &listener)
Registers a given object to receive certain events.
void unregisterEventListener(MSXEventListener &listener)
Unregisters a previously registered event listener.
bool isPluggedIn() const
Returns true if this pluggable is currently plugged into a connector.
Definition Pluggable.hh:49
Connector * getConnector() const
Get the connector this Pluggable is plugged into.
Definition Pluggable.hh:43
void registerListener(StateChangeListener &listener)
(Un)registers the given object to receive state change events.
void unregisterListener(StateChangeListener &listener)
Base class for all external MSX state changing events.
constexpr double e
Definition Math.hh:21
This file implemented 3 utility functions:
Definition Autofire.cc:11
std::variant< KeyUpEvent, KeyDownEvent, MouseMotionEvent, MouseButtonUpEvent, MouseButtonDownEvent, MouseWheelEvent, JoystickAxisMotionEvent, JoystickHatEvent, JoystickButtonUpEvent, JoystickButtonDownEvent, OsdControlReleaseEvent, OsdControlPressEvent, WindowEvent, TextEvent, FileDropEvent, QuitEvent, FinishFrameEvent, CliCommandEvent, GroupEvent, BootEvent, FrameDrawnEvent, BreakEvent, SwitchRendererEvent, TakeReverseSnapshotEvent, AfterTimedEvent, MachineLoadedEvent, MachineActivatedEvent, MachineDeactivatedEvent, MidiInReaderEvent, MidiInWindowsEvent, MidiInCoreMidiEvent, MidiInCoreMidiVirtualEvent, MidiInALSAEvent, Rs232TesterEvent, Rs232NetEvent, ImGuiDelayedActionEvent, ImGuiActiveEvent > Event
Definition Event.hh:445
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define REGISTER_POLYMORPHIC_INITIALIZER(BASE, CLASS, NAME)
#define REGISTER_POLYMORPHIC_CLASS(BASE, CLASS, NAME)