OXYGENHUB
◆ main openmsx / src / input / JoyMega.cc
7.0 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "JoyMega.hh"
2
3
#include "CommandController.hh"
4
#include "Event.hh"
5
#include "IntegerSetting.hh"
6
#include "JoystickManager.hh"
7
#include "MSXEventDistributor.hh"
8
#include "StateChange.hh"
9
#include "StateChangeDistributor.hh"
10
#include "serialize.hh"
11
#include "serialize_meta.hh"
12
13
#include "join.hh"
14
#include "ranges.hh"
15
#include "unreachable.hh"
16
#include "xrange.hh"
17
18
#include <algorithm>
19
20
namespace openmsx {
21
22
TclObject JoyMega::getDefaultConfig(JoystickId joyId, const JoystickManager& joystickManager)
23
{
24
	auto buttons = joystickManager.getNumButtons(joyId);
25
	if (!buttons) return {};
26
27
	std::array<TclObject, 8> lists;
28
	auto joy = joyId.str();
29
	for (auto b : xrange(*buttons)) {
30
		lists[b % 8].addListElement(tmpStrCat(joy, " button", b));
31
	}
32
	return TclObject(TclObject::MakeDictTag{},
33
		"UP",    makeTclList(tmpStrCat(joy, " -axis1"), tmpStrCat(joy, " hat0 up")),
34
		"DOWN",  makeTclList(tmpStrCat(joy, " +axis1"), tmpStrCat(joy, " hat0 down")),
35
		"LEFT",  makeTclList(tmpStrCat(joy, " -axis0"), tmpStrCat(joy, " hat0 left")),
36
		"RIGHT", makeTclList(tmpStrCat(joy, " +axis0"), tmpStrCat(joy, " hat0 right")),
37
		"A",     lists[0],
38
		"B",     lists[1],
39
		"C",     lists[2],
40
		"X",     lists[3],
41
		"Y",     lists[4],
42
		"Z",     lists[5],
43
		"SELECT",lists[6],
44
		"START", lists[7]);
45
}
46
47
JoyMega::JoyMega(CommandController& commandController_,
48
                 MSXEventDistributor& eventDistributor_,
49
                 StateChangeDistributor& stateChangeDistributor_,
50
                 JoystickManager& joystickManager_,
51
                 uint8_t id_)
52
	: commandController(commandController_)
53
	, eventDistributor(eventDistributor_)
54
	, stateChangeDistributor(stateChangeDistributor_)
55
	, joystickManager(joystickManager_)
56
	, configSetting(commandController, tmpStrCat("joymega", id_, "_config"),
57
		"joymega mapping configuration", getDefaultConfig(JoystickId(id_ - 1), joystickManager).getString())
58
	, description(strCat("JoyMega based Mega Drive controller ", id_, ". Mapping is fully configurable."))
59
	, id(id_)
60
{
61
	configSetting.setChecker([this](const TclObject& newValue) {
62
		this->checkJoystickConfig(newValue); });
63
	try {
64
		// fill in 'bindings'
65
		checkJoystickConfig(configSetting.getValue());
66
	} catch (MSXException& /*e*/) {
67
		configSetting.setValue(getDefaultConfig(JoystickId(id - 1), joystickManager));
68
	}
69
}
70
71
JoyMega::~JoyMega()
72
{
73
	if (isPluggedIn()) {
74
		JoyMega::unplugHelper(EmuTime::dummy());
75
	}
76
}
77
78
void JoyMega::checkJoystickConfig(const TclObject& newValue)
79
{
80
	std::array<std::vector<BooleanInput>, 12> newBindings;
81
82
	auto& interp = commandController.getInterpreter();
83
	auto n = newValue.getListLength(interp);
84
	if (n & 1) {
85
		throw CommandException("Need an even number of elements");
86
	}
87
	for (decltype(n) i = 0; i < n; i += 2) {
88
		static constexpr std::array<std::string_view, 12> keys = {
89
			// order is important!
90
			"UP", "DOWN", "LEFT", "RIGHT",
91
			"A", "B", "C", "START",
92
			"X", "Y", "Z", "SELECT",
93
		};
94
		std::string_view key  = newValue.getListIndex(interp, i + 0).getString();
95
		auto it = std::ranges::find(keys, key);
96
		if (it == keys.end()) {
97
			throw CommandException(
98
				"Invalid key: must be one of ", join(keys, ", "));
99
		}
100
		auto idx = std::distance(keys.begin(), it);
101
102
		TclObject value = newValue.getListIndex(interp, i + 1);
103
		for (auto j : xrange(value.getListLength(interp))) {
104
			std::string_view val = value.getListIndex(interp, j).getString();
105
			auto bind = parseBooleanInput(val);
106
			if (!bind) {
107
				throw CommandException("Invalid binding: ", val);
108
			}
109
			newBindings[idx].push_back(*bind);
110
		}
111
	}
112
113
	// only change current bindings when parsing was fully successful
114
	copy_to_range(newBindings, bindings);
115
}
116
117
// Pluggable
118
zstring_view JoyMega::getName() const
119
{
120
	switch (id) {
121
		case 1: return "joymega1";
122
		case 2: return "joymega2";
123
		default: UNREACHABLE;
124
	}
125
}
126
127
zstring_view JoyMega::getDescription() const
128
{
129
	return description;
130
}
131
132
void JoyMega::plugHelper(Connector& /*connector*/, EmuTime /*time*/)
133
{
134
	plugHelper2();
135
	status = 0xfff;
136
	cycle = 0;
137
	// when mode button is pressed when joystick is plugged in, then
138
	// act as a 3-button joypad (otherwise 6-button)
139
	cycleMask = (status & 0x800) ? 7 : 1;
140
}
141
142
void JoyMega::plugHelper2()
143
{
144
	eventDistributor.registerEventListener(*this);
145
	stateChangeDistributor.registerListener(*this);
146
}
147
148
void JoyMega::unplugHelper(EmuTime /*time*/)
149
{
150
	stateChangeDistributor.unregisterListener(*this);
151
	eventDistributor.unregisterEventListener(*this);
152
}
153
154
155
// JoystickDevice
156
uint8_t JoyMega::read(EmuTime time)
157
{
158
	// See http://segaretro.org/Control_Pad_(Mega_Drive)
159
	// and http://frs.badcoffee.info/hardware/joymega-en.html
160
	// for a detailed description of the MegaDrive joystick.
161
	checkTime(time);
162
	switch (cycle) {
163
	case 0: case 2: case 4:
164
		// up/down/left/right/b/c
165
		return (status & 0x00f) | ((status & 0x060) >> 1);
166
	case 1: case 3:
167
		// up/down/0/0/a/start
168
		return (status & 0x013) | ((status & 0x080) >> 2);
169
	case 5:
170
		// 0/0/0/0/a/start
171
		return (status & 0x010) | ((status & 0x080) >> 2);
172
	case 6:
173
		// z/y/x/mode/b/c
174
		return ((status & 0x400) >> 10) | // z
175
		       ((status & 0xA00) >>  8) | // start+y
176
		       ((status & 0x100) >>  6) | // x
177
		       ((status & 0x060) >>  1);  // c+b
178
	case 7:
179
		// 1/1/1/1/a/start
180
		return (status & 0x010) | ((status & 0x080) >> 2) | 0x0f;
181
	default:
182
		UNREACHABLE;
183
	}
184
}
185
186
void JoyMega::write(uint8_t value, EmuTime time)
187
{
188
	checkTime(time);
189
	lastTime = time;
190
	if (((value >> 2) & 1) != (cycle & 1)) {
191
		cycle = (cycle + 1) & cycleMask;
192
	}
193
	assert(((value >> 2) & 1) == (cycle & 1));
194
}
195
196
void JoyMega::checkTime(EmuTime time)
197
{
198
	if ((time - lastTime) > EmuDuration::usec(1500)) {
199
		// longer than 1.5ms since last write -> reset cycle
200
		cycle = 0;
201
	}
202
}
203
204
// MSXEventListener
205
void JoyMega::signalMSXEvent(const Event& event, EmuTime time) noexcept
206
{
207
	unsigned press = 0;
208
	unsigned release = 0;
209
210
	auto getJoyDeadThreshold = [&](JoystickId joyId) {
211
		return joystickManager.getJoyDeadThreshold(joyId);
212
	};
213
	for (int i : xrange(12)) {
214
		for (const auto& binding : bindings[i]) {
215
			if (auto onOff = match(binding, event, getJoyDeadThreshold)) {
216
				(*onOff ? press : release) |= 1 << i;
217
			}
218
		}
219
	}
220
221
	if (((status & ~press) | release) != status) {
222
		stateChangeDistributor.distributeNew<JoyMegaState>(
223
			time, id, press, release);
224
	}
225
}
226
227
// StateChangeListener
228
void JoyMega::signalStateChange(const StateChange& event)
229
{
230
	const auto* js = std::get_if<JoyMegaState>(&event);
231
	if (!js) return;
232
	if (js->getId() != id) return;
233
234
	status = (status & ~js->getPress()) | js->getRelease();
235
}
236
237
void JoyMega::stopReplay(EmuTime time) noexcept
238
{
239
	unsigned newStatus = 0xfff;
240
	if (newStatus != status) {
241
		auto release = newStatus & ~status;
242
		stateChangeDistributor.distributeNew<JoyMegaState>(
243
			time, id, 0, release);
244
	}
245
}
246
247
template<typename Archive>
248
void JoyMega::serialize(Archive& ar, unsigned /*version*/)
249
{
250
	ar.serialize("lastTime",  lastTime,
251
	             "status",    status,
252
	             "cycle",     cycle,
253
	             "cycleMask", cycleMask);
254
	if constexpr (Archive::IS_LOADER) {
255
		if (isPluggedIn()) {
256
			plugHelper2();
257
		}
258
	}
259
}
260
INSTANTIATE_SERIALIZE_METHODS(JoyMega);
261
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, JoyMega, "JoyMega");
262
263
} // namespace openmsx