A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
KartState.cc
1#include "KartState.hh"
2
3#include "game/kart/CollisionGroup.hh"
4#include "game/kart/KartCollide.hh"
5#include "game/kart/KartDynamics.hh"
6#include "game/kart/KartJump.hh"
7#include "game/kart/KartMove.hh"
8
9#include "game/system/RaceManager.hh"
10
11namespace Kart {
12
14 f32 range;
15 s16 frames;
16};
17
20static constexpr std::array<StartBoostEntry, 6> START_BOOST_ENTRIES = {{
21 {0.85f, 0},
22 {0.88f, 10},
23 {0.905f, 20},
24 {0.925f, 30},
25 {0.94f, 45},
26 {0.95f, 70},
27}};
28
30KartState::KartState() {
31 m_status.makeAllZero();
32
33 m_status.changeBit(inputs()->driftIsAuto(), eStatus::AutoDrift);
34
35 m_airtime = 0;
36 m_cannonPointId = 0;
38}
39
41void KartState::init() {
42 reset();
43}
44
46void KartState::reset() {
47 // In the base game, we only wipe bitfield 0, 1, 2, and 3, so we need to bring back bitfield4.
48 bool isAutoDrift = m_status.onBit(eStatus::AutoDrift);
49 m_status.makeAllZero().changeBit(isAutoDrift, eStatus::AutoDrift);
50
51 m_airtime = 0;
52 m_top.setZero();
53 m_hwgTimer = 0;
54 m_boostRampType = -1;
55 m_jumpPadVariant = -1;
56 m_halfPipeInvisibilityTimer = 0;
57 m_startBoostCharge = 0.0f;
58 m_stickX = 0.0f;
60 m_trickableTimer = 0;
61}
62
67 const auto *raceMgr = System::RaceManager::Instance();
68 if (raceMgr->isStageReached(System::RaceManager::Stage::Race)) {
69 if (m_status.offBit(eStatus::InAction, eStatus::BeforeRespawn, eStatus::CannonStart,
70 eStatus::InCannon, eStatus::OverZipper)) {
71 const auto &currentState = inputs()->currentState();
72 const auto &lastState = inputs()->lastState();
73 m_stickX = currentState.stick.x;
74 m_stickY = currentState.stick.y;
75
76 if (m_status.offBit(eStatus::RejectRoadTrigger)) {
77 if (m_stickX < 0.0f) {
78 m_status.setBit(eStatus::StickLeft);
79 } else if (m_stickX > 0.0f) {
81 }
82 }
83
84 if (m_status.offBit(eStatus::Burnout)) {
85 m_status.changeBit(currentState.accelerate(), eStatus::Accelerate)
86 .changeBit(currentState.accelerate() && !lastState.accelerate(),
88 .changeBit(currentState.brake(), eStatus::Brake);
89
90 if (m_status.offBit(eStatus::AutoDrift)) {
91 m_status.changeBit(currentState.drift(), eStatus::DriftInput)
92 .changeBit(currentState.drift() && !lastState.drift(),
94 }
95 }
96 }
97
99 } else {
100 if (!raceMgr->isStageReached(System::RaceManager::Stage::Countdown)) {
101 return;
102 }
103
104 const auto &currentState = inputs()->currentState();
105 m_stickX = currentState.stick.x;
106 m_status.changeBit(currentState.accelerate(), eStatus::ChargeStartBoost);
107
109 }
110}
111
116 resetFlags();
117
118 collide()->calcBeforeRespawn();
119
121 collide()->calcBoundingRadius();
122}
123
125void KartState::resetFlags() {
129 eStatus::ZipperInvisibleWall, eStatus::JumpPadDisableYsusForce,
130 eStatus::CollidingOffroad, eStatus::JumpPadDisableYsusForce);
131
132 m_stickY = 0.0f;
133 m_stickX = 0.0f;
134}
135
144 bool wasTouchingGround = m_status.onBit(eStatus::TouchingGround);
145 bool wasWallCollision = m_status.onBit(eStatus::WallCollision, eStatus::Wall3Collision);
146
150
151 if (m_hwgTimer > 0) {
152 if (--m_hwgTimer == 0) {
153 m_status.resetBit(eStatus::UNK2, eStatus::SomethingWallCollision);
154 }
155 }
156
157 m_top.setZero();
158 bool softWallCollision = false;
159
160 if (collide()->someSoftWallTimer() > 0) {
161 if (collide()->someNonSoftWallTimer() == 0) {
162 softWallCollision = true;
163 } else {
164 f32 softSusp = collide()->suspBottomHeightSoftWall() /
165 static_cast<f32>(collide()->someSoftWallTimer());
166 f32 nonSusp = collide()->suspBottomHeightNonSoftWall() /
167 static_cast<f32>(collide()->someNonSoftWallTimer());
168
169 if (softSusp - nonSusp >= 40.0f) {
170 m_status.resetBit(eStatus::SoftWallDrift);
171 } else {
172 softWallCollision = true;
173 }
174 }
175 }
176
177 u16 wheelCollisions = 0;
178 u16 softWallCount = 0;
179 EGG::Vector3f wallNrm = EGG::Vector3f::zero;
180 bool trickable = false;
181
182 for (u16 tireIdx = 0; tireIdx < tireCount(); ++tireIdx) {
183 const auto &colData = collisionData(tireIdx);
184 if (hasFloorCollision(tirePhysics(tireIdx))) {
185 m_top += colData.floorNrm;
186 trickable = trickable || colData.bTrickable;
187 ++wheelCollisions;
188 }
189
190 if (softWallCollision && colData.bSoftWall) {
191 ++softWallCount;
192 wallNrm += colData.noBounceWallNrm;
193 }
194 }
195
196 if (wheelCollisions > 0) {
198 if (wheelCollisions == tireCount()) {
200 }
201 }
202
203 CollisionData &colData = collisionData();
204 if (colData.bFloor) {
206 m_top += colData.floorNrm;
207 trickable = trickable || colData.bTrickable;
208
209 if (m_status.onBit(eStatus::OverZipper) && m_status.offBit(eStatus::HalfpipeMidair)) {
210 halfPipe()->end(true);
211 }
212 }
213
214 bool hitboxGroupSoftWallCollision = false;
215 if (softWallCollision && colData.bSoftWall) {
216 hitboxGroupSoftWallCollision = true;
217 ++softWallCount;
218 wallNrm += colData.wallNrm;
219 }
220
221 bool bVar3 = colData.bInvisibleWallOnly && m_halfPipeInvisibilityTimer > 0;
222 m_halfPipeInvisibilityTimer = std::max(0, m_halfPipeInvisibilityTimer - 1);
223
224 m_wallBonkTimer = std::max(0, m_wallBonkTimer - 1);
225
226 bool hwg = false;
227
228 if ((colData.bWall || colData.bWall3) && !bVar3) {
229 if (colData.bWall) {
231 }
232
233 if (colData.bWall3) {
235 }
236
237 if (!wasWallCollision) {
239
240 if (wallKclType() == COL_TYPE_SPECIAL_WALL && wallKclVariant() == 0) {
241 if (m_status.offBit(eStatus::TriggerRespawn, eStatus::InRespawn,
242 eStatus::AfterRespawn, eStatus::BeforeRespawn, eStatus::InAction,
243 eStatus::CannonStart, eStatus::InCannon)) {
244 action()->start(Kart::Action::UNK_1);
245 }
246 }
247 }
248
249 m_wallBonkTimer = 2;
250
251 if (m_hwgTimer == 0 && colData.movement.y > 1.0f) {
252 EGG::Vector3f movement = colData.movement;
253 movement.normalise();
254
255 if (movement.dot(EGG::Vector3f::ey) > 0.8f &&
256 colData.wallNrm.dot(EGG::Vector3f::ey) > 0.85f &&
257 (movement.x * colData.wallNrm.x + movement.z * colData.wallNrm.z < 0.0f ||
258 collide()->colPerpendicularity() >= 1.0f)) {
259 colData.wallNrm.y = 0.0f;
260 colData.wallNrm.normalise();
261 wallNrm = colData.wallNrm;
262
263 if (wallNrm.length() < 0.05f) {
264 wallNrm = movement;
265 wallNrm.y = 0.0f;
266 }
267
268 hwg = true;
269 }
270 }
271 }
272
273 if (colData.bInvisibleWall && m_status.onBit(eStatus::HalfPipeRamp) &&
274 collide()->surfaceFlags().offBit(KartCollide::eSurfaceFlags::StopHalfPipeState)) {
276 }
277
278 if (softWallCount > 0 || hwg) {
279 m_status.setBit(eStatus::UNK2);
280 m_softWallSpeed = wallNrm;
281 m_softWallSpeed.normalise();
282 if (softWallCount > 0 && m_status.offBit(eStatus::Hop)) {
283 m_status.setBit(eStatus::SoftWallDrift);
284 }
285
286 if (hwg) {
287 m_status.setBit(eStatus::HWG);
288 }
289
290 if (hitboxGroupSoftWallCollision || hwg || isBike()) {
291 m_status.setBit(eStatus::SomethingWallCollision);
292 m_hwgTimer = 10;
293
294 if (hwg) {
295 m_hwgTimer *= 2;
296 }
297 }
298 }
299
301 m_trickableTimer = std::max(0, m_trickableTimer - 1);
302
303 if (wheelCollisions < 1 && !colData.bFloor) {
304 if (wasTouchingGround) {
305 m_status.setBit(eStatus::AirStart);
306 }
307
308 if (++m_airtime > 20) {
310 }
311 } else {
312 m_top.normalise();
313
314 m_status.setBit(eStatus::TouchingGround).resetBit(eStatus::AfterCannon);
315
316 if (m_status.offBit(eStatus::InAction)) {
317 m_status.resetBit(eStatus::ActionMidZipper, eStatus::EndHalfPipe);
318 }
319
320 if (m_status.onBit(eStatus::OverZipper)) {
321 halfPipe()->end(true);
322 }
323
324 if (trickable) {
325 m_trickableTimer = 3;
326 }
327
328 m_status.changeBit(m_trickableTimer > 0, eStatus::Trickable);
329
330 if (m_status.offBit(eStatus::JumpPad)) {
331 m_status.resetBit(eStatus::JumpPadMushroomCollision);
332 }
333
334 if (!wasTouchingGround) {
336 }
337
338 if (m_status.onBit(eStatus::InATrick) && jump()->cooldown() == 0) {
339 move()->landTrick();
340 dynamics()->setForceUpright(true);
341 jump()->end();
342 }
343
344 m_airtime = 0;
345 }
346}
347
353 constexpr f32 START_BOOST_DELTA_ONE = 0.02f;
354 constexpr f32 START_BOOST_DELTA_TWO = 0.002f;
355 constexpr f32 START_BOOST_FALLOFF = 0.96f;
356
357 if (m_status.onBit(eStatus::ChargeStartBoost)) {
358 m_startBoostCharge += START_BOOST_DELTA_ONE -
359 (START_BOOST_DELTA_ONE - START_BOOST_DELTA_TWO) * m_startBoostCharge;
360 } else {
361 m_startBoostCharge *= START_BOOST_FALLOFF;
362 }
363
364 m_startBoostCharge = std::max(0.0f, std::min(1.0f, m_startBoostCharge));
365}
366
371 if (System::RaceManager::Instance()->getCountdownTimer() != 0) {
372 return;
373 }
374
375 if (m_status.onBit(eStatus::Accelerate)) {
376 if (m_startBoostCharge > START_BOOST_ENTRIES.back().range) {
377 m_startBoostIdx = std::numeric_limits<size_t>::max();
378 } else if (m_startBoostCharge > START_BOOST_ENTRIES.front().range) {
379 // Ranges are exclusive on the lower bound and inclusive on the upper bound
380 for (size_t i = 1; i < START_BOOST_ENTRIES.size(); ++i) {
381 if (m_startBoostCharge > START_BOOST_ENTRIES[i - 1].range &&
382 m_startBoostCharge <= START_BOOST_ENTRIES[i].range) {
383 m_startBoostIdx = i;
384 break;
385 }
386 }
387 }
388 }
389
390 if (m_startBoostIdx <= 0) {
391 return;
392 }
393
396}
397
402 if (m_startBoostIdx == std::numeric_limits<size_t>::max()) {
403 move()->burnout().start();
404 } else {
405 move()->applyStartBoost(START_BOOST_ENTRIES[idx].frames);
406 }
407}
408
414
415} // namespace Kart
@ COL_TYPE_SPECIAL_WALL
Various other wall types, determined by variant.
constexpr TBitFlagExt< N, E > & changeBit(bool on, Es... es)
Changes the state of the corresponding bits for the provided enum values.
Definition BitFlag.hh:367
constexpr bool offBit(Es... es) const
Checks if all of the corresponding bits for the provided enum values are off.
Definition BitFlag.hh:412
constexpr TBitFlagExt< N, E > & makeAllZero()
Resets all the bits to zero across the entire bitfield array.
Definition BitFlag.hh:332
constexpr bool onBit(Es... es) const
Checks if any of the corresponding bits for the provided enum values are on.
Definition BitFlag.hh:379
constexpr TBitFlagExt< N, E > & resetBit(Es... es)
Resets the corresponding bits for the provided enum values.
Definition BitFlag.hh:355
constexpr TBitFlagExt< N, E > & setBit(Es... es)
Sets the corresponding bits for the provided enum values.
Definition BitFlag.hh:344
bool start(Action action)
Starts an action.
Definition KartAction.cc:49
size_t m_startBoostIdx
Used to map m_startBoostCharge to a start boost duration.
Definition KartState.hh:123
void calcStartBoost()
STAGE 1 - Each frame, calculates the start boost charge.
Definition KartState.cc:352
s16 m_wallBonkTimer
2f counter that stunts your speed after hitting a wall.
Definition KartState.hh:124
void calcCollisions()
Each frame, checks for collision and saves relevant bit flags.
Definition KartState.cc:143
void calcHandleStartBoost()
On countdown end, calculates and applies our start boost charge.
Definition KartState.cc:370
f32 m_stickY
One of 15 discrete stick values from [-1.0, 1.0].
Definition KartState.hh:121
void handleStartBoost(size_t idx)
Applies the relevant start boost duration.
Definition KartState.cc:401
void calc()
Every frame, resets the input state and saves collision-related bit flags.
Definition KartState.cc:115
f32 m_stickX
One of 15 discrete stick values from [-1.0, 1.0].
Definition KartState.hh:120
f32 m_startBoostCharge
0-1 representation of start boost charge. Burnout if >0.95f.
Definition KartState.hh:122
void calcInput()
Each frame, read input and save related bit flags. Also handles start boosts.
Definition KartState.cc:66
void resetEjection()
Resets certain bitfields pertaining to ejections (reject road, half pipe zippers, etc....
Definition KartState.cc:411
Pertains to kart-related functionality.
@ HopStart
Set if m_bDriftInput was toggled on this frame.
@ RejectRoad
Collision which causes a change in the player's pos and rot.
@ HalfPipeRamp
Set while colliding with zipper KCL.
@ HWG
Set when "Horizontal Wall Glitch" is active.
@ Hop
Set while we are in a drift hop. Clears when we land.
@ StickLeft
Set on left stick input. Mutually exclusive to m_bStickRight.
@ VehicleBodyFloorCollision
Set if the vehicle body is colliding with the floor.
@ ChargeStartBoost
Like m_bAccelerate but during countdown.
@ RejectRoadTrigger
e.g. DK Summit ending, and Maple Treeway side walls.
@ GroundStart
Set first frame landing from airtime.
@ AllWheelsCollision
Set when all wheels are touching floor collision.
@ Accelerate
Accel button is pressed.
@ StickRight
Set on right stick input. Mutually exclusive to m_bStickLeft.
@ ActionMidZipper
Set when we enter an action while mid-air from a zipper.
@ DriftInput
A "fake" button, normally set if you meet the speed requirement to hop.
@ AirtimeOver20
Set after 20 frames of airtime, resets on landing.
@ OverZipper
Set while mid-air from a zipper.
@ AccelerateStart
Set if m_bAccelerate was toggled on this frame.
@ Burnout
Set during a burnout on race start.
@ WallCollisionStart
Set if we have just started colliding with a wall.
@ AnyWheelCollision
Set when any wheel is touching floor collision.
@ AutoDrift
True if auto transmission, false if manual.
@ Wall3Collision
Set when colliding with wall KCL COL_TYPE_WALL_2.
@ TouchingGround
Set when any part of the vehicle is colliding with floor KCL.
@ ZipperInvisibleWall
Set when colliding with invisible wall above a zipper.
@ BeforeRespawn
Set on respawn collision, cleared on position snap.
@ WallCollision
Set if we are colliding with a wall.
A 3D float vector.
Definition Vector.hh:88
f32 normalise()
Normalizes the vector and returns the original length.
Definition Vector.cc:52
f32 dot(const Vector3f &rhs) const
The dot product between two vectors.
Definition Vector.hh:187
f32 length() const
The square root of the vector's dot product.
Definition Vector.hh:192
Information about the current collision and its properties.
bool bFloor
Set if colliding with KCL which satisfies KCL_TYPE_FLOOR.
bool bWall3
Set if colliding with COL_TYPE_WALL_2.
bool bWall
Set if colliding with KCL which satisfies KCL_TYPE_WALL.