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