OXYGENHUB
◆ main openmsx / src / input / ArkanoidPad.cc
4.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "ArkanoidPad.hh"
2
3
#include "Event.hh"
4
#include "MSXEventDistributor.hh"
5
#include "StateChange.hh"
6
#include "StateChangeDistributor.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
21
namespace openmsx {
22
23
static constexpr int POS_MIN = 55; // measured by hap
24
static constexpr int POS_MAX = 325; // measured by hap
25
static constexpr int POS_CENTER = 236; // approx. middle used by games
26
static constexpr int SCALE = 2;
27
28
29
ArkanoidPad::ArkanoidPad(MSXEventDistributor& eventDistributor_,
30
                         StateChangeDistributor& stateChangeDistributor_)
31
	: eventDistributor(eventDistributor_)
32
	, stateChangeDistributor(stateChangeDistributor_)
33
	, dialPos(POS_CENTER)
34
{
35
}
36
37
ArkanoidPad::~ArkanoidPad()
38
{
39
	if (isPluggedIn()) {
40
		ArkanoidPad::unplugHelper(EmuTime::dummy());
41
	}
42
}
43
44
45
// Pluggable
46
zstring_view ArkanoidPad::getName() const
47
{
48
	return "arkanoidpad";
49
}
50
51
zstring_view ArkanoidPad::getDescription() const
52
{
53
	return "Arkanoid pad";
54
}
55
56
void ArkanoidPad::plugHelper(Connector& /*connector*/, EmuTime /*time*/)
57
{
58
	eventDistributor.registerEventListener(*this);
59
	stateChangeDistributor.registerListener(*this);
60
}
61
62
void ArkanoidPad::unplugHelper(EmuTime /*time*/)
63
{
64
	stateChangeDistributor.unregisterListener(*this);
65
	eventDistributor.unregisterEventListener(*this);
66
}
67
68
// JoystickDevice
69
uint8_t ArkanoidPad::read(EmuTime /*time*/)
70
{
71
	return buttonStatus | ((shiftReg & 0x100) >> 8);
72
}
73
74
void ArkanoidPad::write(uint8_t value, EmuTime /*time*/)
75
{
76
	uint8_t diff = lastValue ^ value;
77
	lastValue = value;
78
79
	if (diff & value & 0x4) {
80
		// pin 8 from low to high: copy dial position into shift reg
81
		shiftReg = dialPos;
82
	}
83
	if (diff & value & 0x1) {
84
		// pin 6 from low to high: shift the shift reg
85
		shiftReg = (shiftReg << 1) | (shiftReg & 1); // bit 0's value is 'shifted in'
86
	}
87
}
88
89
// MSXEventListener
90
void ArkanoidPad::signalMSXEvent(const Event& event,
91
                                 EmuTime time) noexcept
92
{
93
	visit(overloaded{
94
		[&](const MouseMotionEvent& e) {
95
			int newPos = std::min(POS_MAX,
96
					std::max(POS_MIN,
97
						dialPos + e.getX() / SCALE));
98
			int delta = newPos - dialPos;
99
			if (delta != 0) {
100
				stateChangeDistributor.distributeNew<ArkanoidState>(
101
					time, delta, false, false);
102
			}
103
		},
104
		[&](const MouseButtonDownEvent& /*e*/) {
105
			// any button will press the Arkanoid Pad button
106
			if (buttonStatus & 2) {
107
				stateChangeDistributor.distributeNew<ArkanoidState>(
108
					time, 0, true, false);
109
			}
110
		},
111
		[&](const MouseButtonUpEvent& /*e*/) {
112
			// any button will unpress the Arkanoid Pad button
113
			if (!(buttonStatus & 2)) {
114
				stateChangeDistributor.distributeNew<ArkanoidState>(
115
					time, 0, false, true);
116
			}
117
		},
118
		[](const EventBase&) { /*ignore */}
119
	}, event);
120
}
121
122
// StateChangeListener
123
void ArkanoidPad::signalStateChange(const StateChange& event)
124
{
125
	const auto* as = std::get_if<ArkanoidState>(&event);
126
	if (!as) return;
127
128
	dialPos += as->getDelta();
129
	if (as->getPress())   buttonStatus &= ~2;
130
	if (as->getRelease()) buttonStatus |=  2;
131
}
132
133
void ArkanoidPad::stopReplay(EmuTime time) noexcept
134
{
135
	// TODO Get actual mouse button(s) state. Is it worth the trouble?
136
	int delta = POS_CENTER - dialPos;
137
	bool release = (buttonStatus & 2) == 0;
138
	if ((delta != 0) || release) {
139
		stateChangeDistributor.distributeNew<ArkanoidState>(
140
			time, delta, false, release);
141
	}
142
}
143
144
145
// version 1: Initial version, the variables dialPos and buttonStatus were not
146
//            serialized.
147
// version 2: Also serialize the above variables, this is required for
148
//            record/replay, see comment in Keyboard.cc for more details.
149
template<typename Archive>
150
void ArkanoidPad::serialize(Archive& ar, unsigned version)
151
{
152
	ar.serialize("shiftreg",  shiftReg,
153
	             "lastValue", lastValue);
154
	if (ar.versionAtLeast(version, 2)) {
155
		ar.serialize("dialpos",      dialPos,
156
		             "buttonStatus", buttonStatus);
157
	}
158
	if constexpr (Archive::IS_LOADER) {
159
		if (isPluggedIn()) {
160
			plugHelper(*getConnector(), EmuTime::dummy());
161
		}
162
	}
163
}
164
INSTANTIATE_SERIALIZE_METHODS(ArkanoidPad);
165
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, ArkanoidPad, "ArkanoidPad");
166
167
} // namespace openmsx