A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
KPadController.cc
1#include "KPadController.hh"
2
3#include <cstring>
4
5namespace Kinoko::System {
6
8KPadController::KPadController() : m_connected(false) {}
9
11void KPadController::calc() {
12 calcImpl();
13}
14
16KPadGhostController::KPadGhostController() : m_acceptingInputs(false) {
17 m_buttonsStreams[0] = EGG::egg_new<KPadGhostFaceButtonsStream>();
18 m_buttonsStreams[1] = EGG::egg_new<KPadGhostDirectionButtonsStream>();
19 m_buttonsStreams[2] = EGG::egg_new<KPadGhostTrickButtonsStream>();
20}
21
23KPadGhostController::~KPadGhostController() = default;
24
26void KPadGhostController::reset(bool driftIsAuto) {
27 m_driftIsAuto = driftIsAuto;
28 m_raceInputState.reset();
29
30 for (auto &stream : m_buttonsStreams) {
31 stream->currentSequence = 0;
32 stream->readSequenceFrames = 0;
33 stream->state = 1;
34 }
35
36 m_acceptingInputs = false;
37 m_connected = true;
38}
39
54void KPadGhostController::readGhostBuffer(const u8 *buffer, bool driftIsAuto) {
55 constexpr u32 SEQUENCE_SIZE = 0x2;
56
57 m_ghostBuffer = buffer;
58 m_driftIsAuto = driftIsAuto;
59
60 EGG::RamStream stream = EGG::RamStream(buffer, RKG_UNCOMPRESSED_INPUT_DATA_SECTION_SIZE);
61
62 u16 faceCount = stream.read_u16();
63 u16 directionCount = stream.read_u16();
64 u16 trickCount = stream.read_u16();
65
66 stream.skip(2);
67
68 m_buttonsStreams[0]->buffer = stream.split(faceCount * SEQUENCE_SIZE);
69 m_buttonsStreams[1]->buffer = stream.split(directionCount * SEQUENCE_SIZE);
70 m_buttonsStreams[2]->buffer = stream.split(trickCount * SEQUENCE_SIZE);
71}
72
74void KPadGhostController::calcImpl() {
75 if (!m_ghostBuffer || !m_acceptingInputs) {
76 return;
77 }
78
79 m_raceInputState.buttons = m_buttonsStreams[0]->readFrame();
80 u8 sticks = m_buttonsStreams[1]->readFrame();
81 m_raceInputState.stickXRaw = sticks >> 4 & 0xF;
82 m_raceInputState.stickYRaw = sticks & 0xF;
83 m_raceInputState.stick = EGG::Vector2f(RawStickToState(m_raceInputState.stickXRaw),
84 RawStickToState(m_raceInputState.stickYRaw));
85 m_raceInputState.trickRaw = m_buttonsStreams[2]->readFrame();
86
87 switch (m_raceInputState.trickRaw >> 4) {
88 case 1:
89 m_raceInputState.trick = Trick::Up;
90 break;
91 case 2:
92 m_raceInputState.trick = Trick::Down;
93 break;
94 case 3:
95 m_raceInputState.trick = Trick::Left;
96 break;
97 case 4:
98 m_raceInputState.trick = Trick::Right;
99 break;
100 default:
101 m_raceInputState.trick = Trick::None;
102 break;
103 }
104}
105
106RaceInputState::RaceInputState() {
107 reset();
108}
109
111void RaceInputState::reset() {
112 buttons = 0;
113 buttonsRaw = 0;
114 stick = EGG::Vector2f::zero;
115 stickXRaw = 7;
116 stickYRaw = 7;
117 trick = Trick::None;
118 trickRaw = 0;
119}
120
124 if (!isButtonsValid()) {
125 return false;
126 }
127
128 if (!isStickValid(stick.x) || !isStickValid(stick.y)) {
129 return false;
130 }
131
132 if (!isTrickValid()) {
133 return false;
134 }
135
136 return true;
137}
138
143bool RaceInputState::isStickValid(f32 stick) const {
144 if (stick > 1.0f || stick < -1.0f) {
145 return false;
146 }
147
148 for (size_t i = 0; i <= 14; ++i) {
149 auto cond = stick <=> RawStickToState(i);
150 ASSERT(cond != std::partial_ordering::unordered);
151
152 if (cond == std::partial_ordering::equivalent) {
153 return true;
154 } else if (cond == std::partial_ordering::less) {
155 return false;
156 }
157 }
158
159 // This is unreachable
160 return false;
161}
162
163KPadGhostButtonsStream::KPadGhostButtonsStream()
164 : currentSequence(std::numeric_limits<u32>::max()), state(2) {}
165
166KPadGhostButtonsStream::~KPadGhostButtonsStream() = default;
167
171 if (state != 1) {
172 return 0;
173 }
174
175 if (currentSequence == std::numeric_limits<u32>::max()) {
176 readSequenceFrames = 0;
177 currentSequence = buffer.read_u16();
178 } else {
179 if (readIsNewSequence()) {
180 readSequenceFrames = 0;
181 currentSequence = buffer.read_u16();
182 }
183 }
184
185 ++readSequenceFrames;
186
187 // In the base game, this check normally occurs before a new sequence is read. As a result, the
188 // base game does not know that it has run out of inputs until the frame that it tries to access
189 // past the last valid input. We stray from this behavior so that we can know when we are on the
190 // last frame of input.
191 if (buffer.eof() && readIsNewSequence()) {
192 state = 2;
193 }
194
195 return readVal();
196}
197
198KPadGhostFaceButtonsStream::KPadGhostFaceButtonsStream() = default;
199
200KPadGhostFaceButtonsStream::~KPadGhostFaceButtonsStream() = default;
201
202KPadGhostDirectionButtonsStream::KPadGhostDirectionButtonsStream() = default;
203
204KPadGhostDirectionButtonsStream::~KPadGhostDirectionButtonsStream() = default;
205
206KPadGhostTrickButtonsStream::KPadGhostTrickButtonsStream() = default;
207
208KPadGhostTrickButtonsStream::~KPadGhostTrickButtonsStream() = default;
209
210/* ================================ *
211 * HOST CONTROLLER
212 * ================================ */
213
214KPadHostController::KPadHostController() = default;
215
216KPadHostController::~KPadHostController() = default;
217
218void KPadHostController::reset(bool driftIsAuto) {
219 m_driftIsAuto = driftIsAuto;
220 m_raceInputState.reset();
221 m_connected = true;
222}
223
224/* ================================ *
225 * PADS
226 * ================================ */
227
229KPad::KPad() : m_controller(nullptr) {
230 reset();
231}
232
234KPad::~KPad() = default;
235
237void KPad::calc() {
238 m_lastInputState = m_currentInputState;
239 m_currentInputState = m_controller->raceInputState();
240}
241
243void KPad::reset() {
244 if (m_controller) {
245 m_controller->reset(m_controller->driftIsAuto());
246 }
247}
248
250KPadPlayer::KPadPlayer() = default;
251
253KPadPlayer::~KPadPlayer() = default;
254
256void KPadPlayer::setGhostController(KPadGhostController *controller, const u8 *inputs,
257 bool driftIsAuto) {
258 m_controller = controller;
259
260 if (inputs) {
261 memcpy(m_ghostBuffer, inputs, RKG_UNCOMPRESSED_INPUT_DATA_SECTION_SIZE);
262 }
263
264 controller->readGhostBuffer(m_ghostBuffer, driftIsAuto);
265}
266
267void KPadPlayer::setHostController(KPadHostController *controller, bool driftIsAuto) {
268 m_controller = controller;
269 m_controller->setDriftIsAuto(driftIsAuto);
270}
271
274 if (!m_controller || m_controller->controlSource() != ControlSource::Ghost) {
275 return;
276 }
277
278 KPadGhostController *ghostController = reinterpret_cast<KPadGhostController *>(m_controller);
279 ghostController->setAcceptingInputs(true);
280}
281
284 if (!m_controller || m_controller->controlSource() != ControlSource::Ghost) {
285 return;
286 }
287
288 KPadGhostController *ghostController = reinterpret_cast<KPadGhostController *>(m_controller);
289 ghostController->setAcceptingInputs(false);
290}
291
292} // namespace Kinoko::System
A stream of data stored in memory.
Definition Stream.hh:81
RamStream split(u32 size)
Splits the current stream into two.
Definition Stream.cc:138
bool m_driftIsAuto
True for auto transmission, false for manual.
bool m_connected
Whether the controller is active.
RaceInputState m_raceInputState
The current inputs from this controller.
The abstraction of a controller object but for ghost playback.
void readGhostBuffer(const u8 *buffer, bool driftIsAuto)
Reads in the raw input data section from the ghost RKG file.
The abstraction of a controller object but for external usage.
void startGhostProxy()
Signals to start reading ghost data after fade-in.
void endGhostProxy()
Signals to stop reading ghost data after race completion.
RaceInputState m_lastInputState
Used to determine changes in input state.
High-level handling for generic system operations, such as input reading, race configuration,...
Definition CourseMap.cc:5
static constexpr f32 RawStickToState(u8 rawStick)
Converts a raw stick input into an input usable by the state.
A 2D float vector.
Definition Vector.hh:12
virtual u8 readFrame()
Reads the data from the corresponding tuple in the buffer.
bool isTrickValid() const
Checks if the trick input is valid.
bool isButtonsValid() const
Checks if there are any invalid buttons.
bool isValid() const
Checks if the input state is valid.
bool isStickValid(f32 stick) const
Checks if the stick values are within the domain of the physics engine.