A reimplementation of Mario Kart Wii's physics engine in C++
Loading...
Searching...
No Matches
KartMove.cc
1#include "KartMove.hh"
2
3#include "game/kart/KartCollide.hh"
4#include "game/kart/KartDynamics.hh"
5#include "game/kart/KartJump.hh"
6#include "game/kart/KartParam.hh"
7#include "game/kart/KartPhysics.hh"
8#include "game/kart/KartScale.hh"
9#include "game/kart/KartSub.hh"
10#include "game/kart/KartSuspension.hh"
11
12#include "game/field/CollisionDirector.hh"
14#include "game/field/ObjectDirector.hh"
15
16#include "game/item/ItemDirector.hh"
17#include "game/item/KartItem.hh"
18
19#include "game/system/CourseMap.hh"
20#include "game/system/RaceManager.hh"
21#include "game/system/map/MapdataCannonPoint.hh"
22#include "game/system/map/MapdataJugemPoint.hh"
23
24#include <egg/math/Math.hh>
25#include <egg/math/Quat.hh>
26
27namespace Kart {
28
30 f32 speed;
31 f32 height;
32 f32 decelFactor;
33 f32 endDecel;
34};
35
36static constexpr std::array<CannonParameter, 3> CANNON_PARAMETERS = {{
37 {500.0f, 0.0f, 6000.0f, -1.0f},
38 {500.0f, 5000.0f, 6000.0f, -1.0f},
39 {120.0f, 2000.0f, 1000.0f, 45.0f},
40}};
41
43KartMove::KartMove() : m_smoothedUp(EGG::Vector3f::ey), m_scale(1.0f, 1.0f, 1.0f) {
44 m_totalScale = 1.0f;
45 m_hitboxScale = 1.0f;
46 m_shockSpeedMultiplier = 1.0f;
47 m_invScale = 1.0f;
48 m_padType.makeAllZero();
49 m_flags.makeAllZero();
50 m_jump = nullptr;
51}
52
54KartMove::~KartMove() {
55 delete m_jump;
56 delete m_halfPipe;
57 delete m_kartScale;
58}
59
61void KartMove::createSubsystems(const KartParam::Stats &stats) {
62 m_jump = new KartJump(this);
63 m_halfPipe = new KartHalfPipe;
64 m_kartScale = new KartScale(stats);
65}
66
71 m_realTurn = 0.0f;
72 m_rawTurn = 0.0f;
73
74 auto &status = KartObjectProxy::status();
75
76 if (status.onBit(eStatus::InAction, eStatus::CannonStart, eStatus::InCannon,
78 return;
79 }
80
81 if (status.onBit(eStatus::BeforeRespawn)) {
82 return;
83 }
84
85 if (status.offBit(eStatus::Hop) || m_hopStickX == 0) {
86 m_rawTurn = -state()->stickX();
87 if (status.onBit(eStatus::JumpPadMushroomCollision)) {
88 m_rawTurn *= 0.35f;
89 } else if (status.onBit(eStatus::AirtimeOver20)) {
90 m_rawTurn *= 0.01f;
91 }
92 } else {
93 m_rawTurn = static_cast<f32>(m_hopStickX);
94 }
95
96 f32 reactivity;
97 if (state()->isDrifting()) {
98 reactivity = param()->stats().driftReactivity;
99 } else {
100 reactivity = param()->stats().handlingReactivity;
101 }
102
103 m_weightedTurn = m_rawTurn * reactivity + m_weightedTurn * (1.0f - reactivity);
104 m_weightedTurn = std::max(-1.0f, std::min(1.0f, m_weightedTurn));
105
107
108 if (!state()->isDrifting()) {
109 return;
110 }
111
112 m_realTurn = (m_weightedTurn + static_cast<f32>(m_hopStickX)) * 0.5f;
113 m_realTurn = m_realTurn * 0.8f + 0.2f * static_cast<f32>(m_hopStickX);
114 m_realTurn = std::max(-1.0f, std::min(1.0f, m_realTurn));
115}
116
118void KartMove::setTurnParams() {
119 static constexpr std::array<DriftingParameters, 3> DRIFTING_PARAMS_ARRAY = {{
120 {10.0f, 0.5f, 0.5f, 1.0f},
121 {10.0f, 0.5f, 0.5f, 0.2f},
122 {10.0f, 0.22f, 0.5f, 0.2f},
123 }};
124
125 init(false, false);
126 m_dir = bodyFront();
127 m_lastDir = m_dir;
128 m_vel1Dir = m_dir;
129 m_landingDir = m_dir;
130 m_outsideDriftLastDir = m_dir;
131 m_driftingParams = &DRIFTING_PARAMS_ARRAY[static_cast<u32>(param()->stats().driftType)];
132 m_kartScale->reset();
133}
134
136void KartMove::init(bool b1, bool b2) {
137 m_lastSpeed = 0.0f;
138 m_baseSpeed = param()->stats().speed;
139 m_jumpPadSoftSpeedLimit = m_softSpeedLimit = param()->stats().speed;
140 m_speed = 0.0f;
141 setKartSpeedLimit();
142 m_acceleration = 0.0f;
144 m_up = EGG::Vector3f::ey;
145 m_smoothedUp = EGG::Vector3f::ey;
146 m_vel1Dir = EGG::Vector3f::ez;
147 m_lastDir = EGG::Vector3f::ez;
148 m_dir = EGG::Vector3f::ez;
149 m_landingDir = EGG::Vector3f::ez;
150 m_dirDiff = EGG::Vector3f::zero;
151 m_hasLandingDir = false;
152 m_outsideDriftAngle = 0.0f;
153 m_landingAngle = 0.0f;
154 m_outsideDriftLastDir = EGG::Vector3f::ez;
155 m_speedRatio = 0.0f;
156 m_speedRatioCapped = 0.0f;
157 m_kclSpeedFactor = 1.0f;
158 m_kclRotFactor = 1.0f;
160 m_kclWheelRotFactor = 1.0f;
161
162 if (!b2) {
164 }
165
166 m_hopStickX = 0;
167 m_hopFrame = 0;
168 m_hopUp = EGG::Vector3f::ey;
169 m_hopDir = EGG::Vector3f::ez;
170 m_divingRot = 0.0f;
171 m_standStillBoostRot = 0.0f;
172 m_driftState = DriftState::NotDrifting;
173 m_smtCharge = 0;
174 m_mtCharge = 0;
175 m_outsideDriftBonus = 0.0f;
176 m_boost.reset();
177 m_zipperBoostTimer = 0;
178 m_zipperBoostMax = 0;
179 m_reject.reset();
181 m_ssmtCharge = 0;
184 m_nonZipperAirtime = 0;
185 m_realTurn = 0.0f;
186 m_weightedTurn = 0.0f;
187
188 if (!b1) {
189 m_scale.set(1.0f);
190 m_totalScale = 1.0f;
191 m_hitboxScale = 1.0f;
192 m_shockSpeedMultiplier = 1.0f;
193 m_invScale = 1.0f;
195 m_shockTimer = 0;
196 m_crushTimer = 0;
197 }
198
199 m_jumpPadMinSpeed = 0.0f;
200 m_jumpPadMaxSpeed = 0.0f;
201 m_jumpPadBoostMultiplier = 0.0f;
202 m_jumpPadProperties = nullptr;
203 m_rampBoost = 0;
204 m_autoDriftAngle = 0.0f;
205 m_autoDriftStartFrameCounter = 0;
206
207 m_cannonEntryOfsLength = 0.0f;
208 m_cannonEntryPos.setZero();
209 m_cannonEntryOfs.setZero();
210 m_cannonOrthog.setZero();
211 m_cannonProgress.setZero();
212
213 m_hopVelY = 0.0f;
214 m_hopPosY = 0.0f;
215 m_hopGravity = 0.0f;
216 m_timeInRespawn = 0;
219 m_respawnTimer = 0;
220 m_bumpTimer = 0;
221 m_drivingDirection = DrivingDirection::Forwards;
222 m_padType.makeAllZero();
223 m_flags.makeAllZero();
224 m_jump->reset();
225 m_halfPipe->reset();
226 m_rawTurn = 0.0f;
227}
228
230void KartMove::clear() {
231 auto &status = KartObjectProxy::status();
232
233 if (status.onBit(eStatus::OverZipper)) {
235 }
236
237 clearBoost();
238 clearJumpPad();
239 clearRampBoost();
240 clearZipperBoost();
241 clearSsmt();
242 clearOffroadInvincibility();
243 m_halfPipe->end(false);
244 m_jump->end();
245 clearRejectRoad();
246}
247
251 EGG::Quatf quaternion = EGG::Quatf::FromRPY(angles * DEG2RAD);
252 EGG::Vector3f newPos = position;
254 Field::KCLTypeMask kcl_flags = KCL_NONE;
255
256 bool bColliding = Field::CollisionDirector::Instance()->checkSphereFullPush(100.0f, newPos,
257 EGG::Vector3f::inf, KCL_ANY, &info, &kcl_flags, 0);
258
259 if (bColliding && (kcl_flags & KCL_TYPE_FLOOR)) {
260 newPos = newPos + info.tangentOff + (info.floorNrm * -100.0f);
261 newPos += info.floorNrm * bsp().initialYPos;
262 }
263
264 setPos(newPos);
265 setRot(quaternion);
266
267 sub()->initPhysicsValues();
268
269 physics()->setPos(pos());
270 physics()->setVelocity(dynamics()->velocity());
271
272 m_landingDir = bodyFront();
273 m_dir = bodyFront();
274 m_up = bodyUp();
275 dynamics()->setTop(m_up);
276
277 for (u16 tireIdx = 0; tireIdx < suspCount(); ++tireIdx) {
278 suspension(tireIdx)->setInitialState();
279 }
280}
281
288 auto &status = KartObjectProxy::status();
289
290 if (status.onBit(eStatus::InRespawn)) {
291 calcInRespawn();
292 return;
293 }
294
295 dynamics()->resetInternalVelocity();
296 m_burnout.calc();
298 m_halfPipe->calc();
299 calcTop();
300 tryEndJumpPad();
301 calcRespawnBoost();
303 m_jump->calc();
304
305 m_bumpTimer = std::max(m_bumpTimer - 1, 0);
306
308 calcDirs();
309 calcStickyRoad();
310 calcOffroad();
311 calcTurn();
312
313 if (status.offBit(eStatus::AutoDrift)) {
315 }
316
317 calcWheelie();
318 calcSsmt();
319 calcBoost();
321 calcZipperBoost();
322 calcShock();
323 calcCrushed();
324 calcScale();
325
326 if (status.onBit(eStatus::InCannon)) {
327 calcCannon();
328 }
329
333 calcRotation();
334}
335
337void KartMove::calcRespawnStart() {
338 constexpr float RESPAWN_HEIGHT = 700.0f;
339
340 const auto *jugemPoint = System::RaceManager::Instance()->jugemPoint();
341 const EGG::Vector3f &jugemPos = jugemPoint->pos();
342 const EGG::Vector3f &jugemRot = jugemPoint->rot();
343
344 EGG::Vector3f respawnPos = jugemPos;
345 respawnPos.y += RESPAWN_HEIGHT;
346 EGG::Vector3f respawnRot = EGG::Vector3f(0.0f, jugemRot.y, 0.0f);
347
348 setInitialPhysicsValues(respawnPos, respawnRot);
349
350 Item::ItemDirector::Instance()->kartItem(0).clear();
351
352 status().resetBit(eStatus::TriggerRespawn).setBit(eStatus::InRespawn);
353}
354
356void KartMove::calcInRespawn() {
357 constexpr f32 LAKITU_VELOCITY = 1.5f;
358 constexpr u16 RESPAWN_DURATION = 110;
359
360 auto &status = KartObjectProxy::status();
361
362 if (status.offBit(eStatus::InRespawn)) {
363 return;
364 }
365
366 EGG::Vector3f newPos = pos();
367 newPos.y -= LAKITU_VELOCITY;
368 dynamics()->setPos(newPos);
369 dynamics()->setNoGravity(true);
370
371 if (++m_timeInRespawn > RESPAWN_DURATION) {
372 status.resetBit(eStatus::InRespawn).setBit(eStatus::AfterRespawn, eStatus::RespawnKillY);
373 m_timeInRespawn = 0;
374 m_flags.setBit(eFlags::Respawned);
375 dynamics()->setNoGravity(false);
376 }
377}
378
380void KartMove::calcRespawnBoost() {
381 constexpr s16 RESPAWN_BOOST_DURATION = 30;
382 constexpr s16 RESPAWN_BOOST_INPUT_LENIENCY = 4;
383
384 auto &status = KartObjectProxy::status();
385
386 if (status.onBit(eStatus::AfterRespawn)) {
387 if (status.onBit(eStatus::TouchingGround)) {
388 if (m_respawnPreLandTimer > 0) {
389 if (status.offBit(eStatus::BeforeRespawn, eStatus::InAction)) {
390 activateBoost(KartBoost::Type::AllMt, RESPAWN_BOOST_DURATION);
391 m_respawnTimer = RESPAWN_BOOST_DURATION;
392 }
393 } else {
394 m_respawnPostLandTimer = RESPAWN_BOOST_INPUT_LENIENCY;
395 }
396
397 status.resetBit(eStatus::AfterRespawn);
399 }
400
402
403 if (m_flags.onBit(eFlags::Respawned) && status.onBit(eStatus::AccelerateStart)) {
404 m_respawnPreLandTimer = RESPAWN_BOOST_INPUT_LENIENCY;
406 }
407 } else {
408 if (m_respawnPostLandTimer > 0) {
409 if (status.onBit(eStatus::AccelerateStart)) {
410 if (status.offBit(eStatus::BeforeRespawn, eStatus::InAction)) {
411 activateBoost(KartBoost::Type::AllMt, RESPAWN_BOOST_DURATION);
412 m_respawnTimer = RESPAWN_BOOST_DURATION;
413 }
414
416 }
417
419 } else {
421 }
422 }
423
424 m_respawnTimer = std::max(0, m_respawnTimer - 1);
425}
426
428void KartMove::calcTop() {
429 f32 stabilizationFactor = 0.1f;
430 m_hasLandingDir = false;
431 EGG::Vector3f inputTop = state()->top();
432 auto &status = KartObjectProxy::status();
433
434 if (status.onBit(eStatus::GroundStart) && m_nonZipperAirtime >= 3) {
435 m_smoothedUp = inputTop;
436 m_up = inputTop;
437 m_landingDir = m_dir.perpInPlane(m_smoothedUp, true);
438 m_dirDiff = m_landingDir.proj(m_landingDir);
439 m_hasLandingDir = true;
440 } else {
441 if (status.onBit(eStatus::Hop) && m_hopPosY > 0.0f) {
442 stabilizationFactor = m_driftingParams->stabilizationFactor;
443 } else if (status.onBit(eStatus::TouchingGround)) {
444 if ((m_flags.onBit(eFlags::TrickableSurface) || state()->trickableTimer() > 0) &&
445 inputTop.dot(m_dir) > 0.0f && m_speed > 50.0f &&
446 collide()->surfaceFlags().onBit(KartCollide::eSurfaceFlags::NotTrickable)) {
447 inputTop = m_up;
448 } else {
449 m_up = inputTop;
450 }
451
452 f32 scalar = 0.8f;
453
454 if (status.onBit(eStatus::HalfPipeRamp) ||
455 (status.offBit(eStatus::Boost, eStatus::RampBoost, eStatus::Wheelie,
457 (status.offBit(eStatus::ZipperBoost) || m_zipperBoostTimer > 15))) {
458 f32 topDotZ = 0.8f - 6.0f * (EGG::Mathf::abs(inputTop.dot(componentZAxis())));
459 scalar = std::min(0.8f, std::max(0.3f, topDotZ));
460 }
461
462 m_smoothedUp += (inputTop - m_smoothedUp) * scalar;
464
465 f32 bodyDotFront = bodyFront().dot(m_smoothedUp);
466
467 if (bodyDotFront < -0.1f) {
468 stabilizationFactor += std::min(0.2f, EGG::Mathf::abs(bodyDotFront) * 0.5f);
469 }
470
471 if (collide()->surfaceFlags().onBit(KartCollide::eSurfaceFlags::BoostRamp)) {
472 stabilizationFactor = 0.4f;
473 }
474 } else {
476 }
477 }
478
479 dynamics()->setStabilizationFactor(stabilizationFactor);
480
481 m_nonZipperAirtime = status.onBit(eStatus::OverZipper) ? 0 : state()->airtime();
482 m_flags.changeBit(collide()->surfaceFlags().onBit(KartCollide::eSurfaceFlags::Trickable),
484}
485
489 auto &status = KartObjectProxy::status();
490
492 return;
493 }
494
495 if (m_smoothedUp.y <= 0.99f) {
496 m_smoothedUp += (EGG::Vector3f::ey - m_smoothedUp) * 0.03f;
498 } else {
499 m_smoothedUp = EGG::Vector3f::ey;
500 }
501
502 if (m_up.y <= 0.99f) {
503 m_up += (EGG::Vector3f::ey - m_up) * 0.03f;
504 m_up.normalise();
505 } else {
506 m_up = EGG::Vector3f::ey;
507 }
508}
509
514 const auto *raceMgr = System::RaceManager::Instance();
515 if (!raceMgr->isStageReached(System::RaceManager::Stage::Race)) {
516 return;
517 }
518
519 if (m_padType.onBit(ePadType::BoostPanel)) {
520 tryStartBoostPanel();
521 }
522
523 if (m_padType.onBit(ePadType::BoostRamp)) {
525 }
526
527 if (m_padType.onBit(ePadType::JumpPad)) {
529 }
530
531 m_padType.makeAllZero();
532}
533
535void KartMove::calcDirs() {
536 EGG::Vector3f right = dynamics()->mainRot().rotateVector(EGG::Vector3f::ex);
537 EGG::Vector3f local_88 = right.cross(m_smoothedUp);
538 local_88.normalise();
539 m_flags.setBit(eFlags::LaunchBoost);
540 auto &status = KartObjectProxy::status();
541
542 if (status.offBit(eStatus::InATrick, eStatus::OverZipper) &&
543 (((status.onBit(eStatus::TouchingGround) || status.offBit(eStatus::RampBoost) ||
544 !m_jump->isBoostRampEnabled()) &&
545 status.offBit(eStatus::JumpPad) && state()->airtime() <= 5) ||
546 status.onBit(eStatus::JumpPadMushroomCollision,
547 eStatus::NoSparkInvisibleWall))) {
548 if (status.onBit(eStatus::Hop)) {
549 local_88 = m_hopDir;
550 }
551
552 EGG::Matrix34f mat;
553 mat.setAxisRotation(DEG2RAD * (m_autoDriftAngle + m_outsideDriftAngle + m_landingAngle),
555 EGG::Vector3f local_b8 = mat.multVector(local_88);
556 local_b8 = local_b8.perpInPlane(m_smoothedUp, true);
557
558 EGG::Vector3f dirDiff = local_b8 - m_dir;
559
560 if (dirDiff.squaredLength() <= std::numeric_limits<f32>::epsilon()) {
561 m_dir = local_b8;
562 m_dirDiff.setZero();
563 } else {
564 EGG::Vector3f origDirCross = m_dir.cross(local_b8);
565 m_dirDiff += m_kclRotFactor * dirDiff;
566 m_dir += m_dirDiff;
567 m_dir.normalise();
568 m_dirDiff *= 0.1f;
569 EGG::Vector3f newDirCross = m_dir.cross(local_b8);
570
571 if (origDirCross.dot(newDirCross) < 0.0f) {
572 m_dir = local_b8;
573 m_dirDiff.setZero();
574 }
575 }
576
577 m_vel1Dir = m_dir.perpInPlane(m_smoothedUp, true);
578 m_flags.resetBit(eFlags::LaunchBoost);
579 } else {
580 m_vel1Dir = m_dir;
581 }
582
583 if (status.offBit(eStatus::OverZipper)) {
584 m_jump->tryStart(m_smoothedUp.cross(m_dir));
585 }
586
587 if (m_hasLandingDir) {
588 f32 dot = m_dir.dot(m_landingDir);
589 EGG::Vector3f cross = m_dir.cross(m_landingDir);
590 f32 crossDot = cross.length();
591 f32 angle = EGG::Mathf::atan2(crossDot, dot);
592 angle = EGG::Mathf::abs(angle);
593
594 f32 fVar4 = 1.0f;
595 if (cross.dot(m_smoothedUp) < 0.0f) {
596 fVar4 = -1.0f;
597 }
598
599 m_landingAngle += (angle * RAD2DEG) * fVar4;
600 }
601
602 if (m_landingAngle <= 0.0f) {
603 if (m_landingAngle < 0.0f) {
604 m_landingAngle = std::min(0.0f, m_landingAngle + 2.0f);
605 }
606 } else {
607 m_landingAngle = std::max(0.0f, m_landingAngle - 2.0f);
608 }
609}
610
612void KartMove::calcStickyRoad() {
613 constexpr f32 STICKY_RADIUS = 200.0f;
614 constexpr Field::KCLTypeMask STICKY_MASK =
616
617 auto &status = KartObjectProxy::status();
618
619 if (status.onBit(eStatus::OverZipper)) {
621 return;
622 }
623
624 if ((status.offBit(eStatus::StickyRoad) &&
625 collide()->surfaceFlags().offBit(KartCollide::eSurfaceFlags::Trickable)) ||
626 EGG::Mathf::abs(m_speed) <= 20.0f) {
627 return;
628 }
629
630 EGG::Vector3f pos = dynamics()->pos();
631 EGG::Vector3f vel = dynamics()->movingObjVel() + m_speed * m_vel1Dir;
632 EGG::Vector3f down = -STICKY_RADIUS * componentYAxis();
633 Field::CollisionInfo colInfo;
634 colInfo.bbox.setZero();
635 Field::KCLTypeMask kcl_flags = KCL_NONE;
636 bool stickyRoad = false;
637
638 for (size_t i = 0; i < 3; ++i) {
639 EGG::Vector3f newPos = pos + vel;
640 if (Field::CollisionDirector::Instance()->checkSphereFull(STICKY_RADIUS, newPos,
641 EGG::Vector3f::inf, STICKY_MASK, &colInfo, &kcl_flags, 0)) {
642 m_vel1Dir = m_vel1Dir.perpInPlane(colInfo.floorNrm, true);
643 dynamics()->setMovingObjVel(dynamics()->movingObjVel().rej(colInfo.floorNrm));
644 dynamics()->setMovingRoadVel(dynamics()->movingRoadVel().rej(colInfo.floorNrm));
645
646 if (status.onBit(eStatus::MovingWaterStickyRoad)) {
647 m_up = colInfo.floorNrm;
648 m_smoothedUp = colInfo.floorNrm;
649 }
650
651 stickyRoad = true;
652
653 break;
654 }
655 vel *= 0.5f;
656 pos += -STICKY_RADIUS * componentYAxis();
657 }
658
659 if (!stickyRoad) {
661 }
662}
663
668 auto &status = KartObjectProxy::status();
669
671 m_kclSpeedFactor = 1.0f;
672 m_kclRotFactor = param()->stats().kclRot[0];
673 } else {
674 bool anyWheel = status.onBit(eStatus::AnyWheelCollision);
675 if (anyWheel) {
679 }
680
682 const CollisionData &colData = collisionData();
683 if (anyWheel) {
684 if (colData.speedFactor < m_kclWheelSpeedFactor) {
685 m_kclSpeedFactor = colData.speedFactor;
686 }
687 m_kclRotFactor = (m_kclWheelRotFactor + colData.rotFactor) /
688 static_cast<f32>(m_floorCollisionCount + 1);
689 } else {
690 m_kclSpeedFactor = colData.speedFactor;
691 m_kclRotFactor = colData.rotFactor;
692 }
693 }
694 }
695
696 calcRisingWater();
697}
698
700void KartMove::calcRisingWater() {
701 auto *objDir = Field::ObjectDirector::Instance();
702 auto *psea = objDir->psea();
703 if (!psea) {
704 return;
705 }
706
707 f32 pos = wheelPos(0).y;
708 u16 count = tireCount();
709 for (u16 wheelIdx = 0; wheelIdx < count; ++wheelIdx) {
710 f32 tmp = wheelEdgePos(wheelIdx).y;
711 if (wheelIdx == 0 || tmp < pos) {
712 pos = tmp;
713 }
714 }
715
716 if (objDir->risingWaterKillPlaneHeight() > pos) {
717 collide()->activateOob(true, nullptr, false, false);
718 }
719
720 f32 dist = -objDir->distAboveRisingWater(pos);
721 if (dist > 0.0f && status().offBit(Kart::eStatus::BoostOffroadInvincibility)) {
722 f32 speedScale = std::min(1.0f, dist / 100.0f);
723 m_kclSpeedFactor = 1.0f - (1.0f - param()->stats().kclSpeed[3]) * speedScale;
724 }
725}
726
728void KartMove::calcBoost() {
729 auto &status = KartObjectProxy::status();
730
731 if (m_boost.calc()) {
733 } else {
734 status.resetBit(eStatus::Boost);
735 }
736
737 calcRampBoost();
738}
739
741void KartMove::calcRampBoost() {
742 auto &status = KartObjectProxy::status();
743
744 if (status.offBit(eStatus::RampBoost)) {
745 return;
746 }
747
749 if (--m_rampBoost < 1) {
750 m_rampBoost = 0;
751 status.resetBit(eStatus::RampBoost);
752 }
753}
754
759 auto &status = KartObjectProxy::status();
760
762 return;
763 }
764
765 if (--m_ssmtDisableAccelTimer < 0 ||
766 (m_flags.offBit(eFlags::SsmtLeeway) && status.offBit(eStatus::Brake))) {
769 }
770}
771
776 constexpr s16 MAX_SSMT_CHARGE = 75;
777 constexpr s16 SSMT_BOOST_FRAMES = 30;
778 constexpr s16 LEEWAY_FRAMES = 1;
779 constexpr s16 DISABLE_ACCEL_FRAMES = 20;
780
782
783 auto &status = KartObjectProxy::status();
784
785 if (status.onBit(eStatus::ChargingSSMT)) {
786 if (++m_ssmtCharge > MAX_SSMT_CHARGE) {
787 m_ssmtCharge = MAX_SSMT_CHARGE;
790 }
791
792 return;
793 }
794
795 m_ssmtCharge = 0;
796
797 if (m_flags.offBit(eFlags::SsmtCharged)) {
798 return;
799 }
800
801 if (m_flags.onBit(eFlags::SsmtLeeway)) {
802 if (--m_ssmtLeewayTimer < 0) {
805 m_ssmtDisableAccelTimer = DISABLE_ACCEL_FRAMES;
807 } else {
809 activateBoost(KartBoost::Type::AllMt, SSMT_BOOST_FRAMES);
812 }
813 }
814 } else {
815 if (status.onBit(eStatus::Accelerate) && status.offBit(eStatus::Brake)) {
816 activateBoost(KartBoost::Type::AllMt, SSMT_BOOST_FRAMES);
819 } else {
820 m_ssmtLeewayTimer = LEEWAY_FRAMES;
821 m_flags.setBit(eFlags::SsmtLeeway);
823 m_ssmtDisableAccelTimer = LEEWAY_FRAMES;
824 }
825 }
826}
827
833 auto &status = KartObjectProxy::status();
834
837 if (status.offBit(eStatus::DriftInput)) {
839 } else if (status.offBit(eStatus::SlipdriftCharge)) {
840 if (m_hopStickX == 0) {
841 if (status.onBit(eStatus::StickRight)) {
842 m_hopStickX = -1;
843 } else if (status.onBit(eStatus::StickLeft)) {
844 m_hopStickX = 1;
845 }
847 onHop();
848 }
849 }
850 }
851 }
852
853 if (status.onBit(eStatus::Hop)) {
854 if (m_hopStickX == 0) {
855 if (status.onBit(eStatus::StickRight)) {
856 m_hopStickX = -1;
857 } else if (status.onBit(eStatus::StickLeft)) {
858 m_hopStickX = 1;
859 }
860 }
861 if (m_hopFrame < 3) {
862 ++m_hopFrame;
863 }
864 } else if (status.onBit(eStatus::SlipdriftCharge)) {
865 m_hopFrame = 0;
866 }
867
869}
870
875 m_hopStickX = 0;
876 m_hopFrame = 0;
878 m_driftState = DriftState::NotDrifting;
879 m_smtCharge = 0;
880 m_mtCharge = 0;
881}
882
887 m_outsideDriftAngle = 0.0f;
888 m_hopStickX = 0;
889 m_hopFrame = 0;
890 m_driftState = DriftState::NotDrifting;
891 m_smtCharge = 0;
892 m_mtCharge = 0;
893 m_outsideDriftBonus = 0.0f;
895 eStatus::DriftAuto);
896 m_autoDriftAngle = 0.0f;
897 m_hopStickX = 0;
898 m_autoDriftStartFrameCounter = 0;
899}
900
902void KartMove::clearJumpPad() {
903 m_jumpPadMinSpeed = 0.0f;
904 status().resetBit(eStatus::JumpPad);
905}
906
908void KartMove::clearRampBoost() {
909 m_rampBoost = 0;
910 status().resetBit(eStatus::RampBoost);
911}
912
914void KartMove::clearZipperBoost() {
915 m_zipperBoostTimer = 0;
917}
918
920void KartMove::clearBoost() {
921 m_boost.resetActive();
922 status().resetBit(eStatus::Boost);
923}
924
926void KartMove::clearSsmt() {
927 m_ssmtCharge = 0;
931}
932
934void KartMove::clearOffroadInvincibility() {
937}
938
939void KartMove::clearRejectRoad() {
940 status().resetBit(eStatus::RejectRoadTrigger, eStatus::NoSparkInvisibleWall);
941}
942
947 constexpr s16 AUTO_DRIFT_START_DELAY = 12;
948
949 auto &status = KartObjectProxy::status();
950
951 if (status.offBit(eStatus::AutoDrift)) {
952 return;
953 }
954
955 if (canStartDrift() &&
957 EGG::Mathf::abs(state()->stickX()) > 0.85f) {
958 m_autoDriftStartFrameCounter =
959 std::min<s16>(AUTO_DRIFT_START_DELAY, m_autoDriftStartFrameCounter + 1);
960 } else {
961 m_autoDriftStartFrameCounter = 0;
962 }
963
964 if (m_autoDriftStartFrameCounter >= AUTO_DRIFT_START_DELAY) {
965 status.setBit(eStatus::DriftAuto);
966
967 if (status.onBit(eStatus::TouchingGround)) {
968 if (state()->stickX() < 0.0f) {
969 m_hopStickX = 1;
970 m_autoDriftAngle -= 30.0f * param()->stats().driftAutomaticTightness;
971
972 } else {
973 m_hopStickX = -1;
974 m_autoDriftAngle += 30.0f * param()->stats().driftAutomaticTightness;
975 }
976 }
977
978 f32 halfTarget = 0.5f * param()->stats().driftOutsideTargetAngle;
979 m_autoDriftAngle = std::min(halfTarget, std::max(-halfTarget, m_autoDriftAngle));
980 } else {
981 status.resetBit(eStatus::DriftAuto);
982 m_hopStickX = 0;
983
984 if (m_autoDriftAngle > 0.0f) {
985 m_autoDriftAngle =
986 std::max(0.0f, m_autoDriftAngle - param()->stats().driftOutsideDecrement);
987 } else {
988 m_autoDriftAngle =
989 std::min(0.0f, m_autoDriftAngle + param()->stats().driftOutsideDecrement);
990 }
991 }
992
993 EGG::Quatf angleAxis;
994 angleAxis.setAxisRotation(-m_autoDriftAngle * DEG2RAD, m_up);
995 physics()->composeExtraRot(angleAxis);
996}
997
1002 bool isHopping = calcPreDrift();
1003 auto &status = KartObjectProxy::status();
1004
1005 if (status.offBit(eStatus::OverZipper)) {
1006 const EGG::Vector3f rotZ = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1007
1008 if (status.offBit(eStatus::TouchingGround) &&
1009 param()->stats().driftType != KartParam::Stats::DriftType::Inside_Drift_Bike &&
1010 status.offBit(eStatus::JumpPadMushroomCollision) &&
1012 m_flags.onBit(eFlags::LaunchBoost)) {
1013 const EGG::Vector3f up = dynamics()->mainRot().rotateVector(EGG::Vector3f::ey);
1015
1016 if (driftRej.normalise() != 0.0f) {
1017 f32 rejCrossDirMag = driftRej.cross(rotZ).length();
1018 f32 angle = EGG::Mathf::atan2(rejCrossDirMag, driftRej.dot(rotZ));
1019 f32 sign = 1.0f;
1020 if ((rotZ.z * (rotZ.x - driftRej.x)) - (rotZ.x * (rotZ.z - driftRej.z)) > 0.0f) {
1021 sign = -1.0f;
1022 }
1023
1024 m_outsideDriftAngle += angle * RAD2DEG * sign;
1025 }
1026 }
1027
1028 m_outsideDriftLastDir = rotZ;
1029 }
1030
1031 // TODO: Is this backwards/inverted?
1032 if (((status.offBit(eStatus::Hop) || m_hopFrame < 3) &&
1034 (status.onBit(eStatus::InAction) || status.offBit(eStatus::TouchingGround))) {
1035 if (canHop()) {
1036 hop();
1037 isHopping = true;
1038 }
1039 } else {
1041 isHopping = false;
1042 }
1043
1045
1046 if (status.offBit(eStatus::DriftManual)) {
1047 if (!isHopping && status.onBit(eStatus::TouchingGround)) {
1049
1050 if (action()->flags().offBit(KartAction::eFlags::Rotating) || m_speed <= 20.0f) {
1051 f32 driftAngleDecr = param()->stats().driftOutsideDecrement;
1052 if (m_outsideDriftAngle > 0.0f) {
1053 m_outsideDriftAngle = std::max(0.0f, m_outsideDriftAngle - driftAngleDecr);
1054 } else if (m_outsideDriftAngle < 0.0f) {
1055 m_outsideDriftAngle = std::min(0.0f, m_outsideDriftAngle + driftAngleDecr);
1056 }
1057 }
1058 }
1059 } else {
1060 // This is a different comparison than @ref KartMove::canStartDrift().
1061 bool canStartDrift = m_speed > MINIMUM_DRIFT_THRESOLD * m_baseSpeed;
1062
1063 if (status.offBit(eStatus::OverZipper) &&
1065 status.onBit(eStatus::InAction, eStatus::RejectRoadTrigger,
1067 !canStartDrift)) {
1068 if (canStartDrift) {
1069 releaseMt();
1070 }
1071
1073 m_flags.setBit(eFlags::DriftReset);
1074 } else {
1076 }
1077 }
1078}
1079
1084 constexpr f32 OUTSIDE_DRIFT_BONUS = 0.5f;
1085
1086 const auto &stats = param()->stats();
1087 auto &status = KartObjectProxy::status();
1088
1089 if (stats.driftType != KartParam::Stats::DriftType::Inside_Drift_Bike) {
1090 f32 driftAngle = 0.0f;
1091
1092 if (status.onBit(eStatus::Hop)) {
1093 const EGG::Vector3f rotZ = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1094 EGG::Vector3f rotRej = rotZ.rej(m_hopUp);
1095
1096 if (rotRej.normalise() != 0.0f) {
1097 const EGG::Vector3f hopCrossRot = m_hopDir.cross(rotRej);
1098 driftAngle =
1099 EGG::Mathf::atan2(hopCrossRot.length(), m_hopDir.dot(rotRej)) * RAD2DEG;
1100 }
1101 }
1102
1103 m_outsideDriftAngle += driftAngle * static_cast<f32>(-m_hopStickX);
1104 m_outsideDriftAngle = std::max(-60.0f, std::min(60.0f, m_outsideDriftAngle));
1105 }
1106
1108
1109 if (status.offBit(eStatus::DriftInput)) {
1110 return;
1111 }
1112
1113 if (getAppliedHopStickX() == 0) {
1114 return;
1115 }
1116
1117 status.setBit(eStatus::DriftManual).resetBit(eStatus::Hop);
1118 m_driftState = DriftState::ChargingMt;
1119 m_outsideDriftBonus = OUTSIDE_DRIFT_BONUS * (m_speedRatioCapped * stats.driftManualTightness);
1120}
1121
1126 constexpr f32 SMT_LENGTH_FACTOR = 3.0f;
1127
1128 auto &status = KartObjectProxy::status();
1129
1130 if (m_driftState < DriftState::ChargedMt || status.onBit(eStatus::Brake)) {
1131 m_driftState = DriftState::NotDrifting;
1132 return;
1133 }
1134
1135 u16 mtLength = param()->stats().miniTurbo;
1136
1137 if (m_driftState == DriftState::ChargedSmt) {
1138 mtLength *= SMT_LENGTH_FACTOR;
1139 }
1140
1141 if (status.offBit(eStatus::BeforeRespawn, eStatus::InAction)) {
1142 activateBoost(KartBoost::Type::AllMt, mtLength);
1143 }
1144
1145 m_driftState = DriftState::NotDrifting;
1146}
1147
1152 if (state()->airtime() > 5) {
1153 return;
1154 }
1155
1156 if (param()->stats().driftType != KartParam::Stats::DriftType::Inside_Drift_Bike) {
1157 if (m_hopStickX == -1) {
1158 f32 angle = m_outsideDriftAngle;
1159 f32 targetAngle = param()->stats().driftOutsideTargetAngle;
1160 if (angle > targetAngle) {
1161 m_outsideDriftAngle = std::max(m_outsideDriftAngle - 2.0f, targetAngle);
1162 } else if (angle < targetAngle) {
1163 m_outsideDriftAngle += 150.0f * param()->stats().driftManualTightness;
1164 m_outsideDriftAngle = std::min(m_outsideDriftAngle, targetAngle);
1165 }
1166 } else if (m_hopStickX == 1) {
1167 f32 angle = m_outsideDriftAngle;
1168 f32 targetAngle = -param()->stats().driftOutsideTargetAngle;
1169 if (targetAngle > angle) {
1170 m_outsideDriftAngle = std::min(m_outsideDriftAngle + 2.0f, targetAngle);
1171 } else if (targetAngle < angle) {
1172 m_outsideDriftAngle -= 150.0f * param()->stats().driftManualTightness;
1173 m_outsideDriftAngle = std::max(m_outsideDriftAngle, targetAngle);
1174 }
1175 }
1176 }
1177
1178 calcMtCharge();
1179}
1180
1185 f32 turn;
1186 auto &status = KartObjectProxy::status();
1187 bool drifting = state()->isDrifting() && status.offBit(eStatus::JumpPadMushroomCollision);
1188 bool autoDrift = status.onBit(eStatus::AutoDrift);
1189 const auto &stats = param()->stats();
1190
1191 if (drifting) {
1192 turn = autoDrift ? stats.driftAutomaticTightness : stats.driftManualTightness;
1193 } else {
1194 turn = autoDrift ? stats.handlingAutomaticTightness : stats.handlingManualTightness;
1195 }
1196
1197 if (drifting && stats.driftType != KartParam::Stats::DriftType::Inside_Drift_Bike) {
1198 m_outsideDriftBonus *= 0.99f;
1199 turn += m_outsideDriftBonus;
1200 }
1201
1202 bool forwards = true;
1203 if (status.onBit(eStatus::Brake) && m_speed <= 0.0f) {
1204 forwards = false;
1205 }
1206
1207 turn *= m_realTurn;
1208 if (status.onBit(eStatus::ChargingSSMT)) {
1209 turn = m_realTurn * 0.04f;
1210 } else {
1211 if (status.onBit(eStatus::Hop) && m_hopPosY > 0.0f) {
1212 turn *= 1.4f;
1213 }
1214
1215 if (!drifting) {
1216 bool noTurn = false;
1218 EGG::Mathf::abs(m_speed) < 1.0f) {
1219 if (!(status.onBit(eStatus::Hop) && m_hopPosY > 0.0f)) {
1220 turn = 0.0f;
1221 noTurn = true;
1222 }
1223 }
1224 if (forwards && !noTurn) {
1225 if (m_speed >= 20.0f) {
1226 turn *= 0.5f;
1227 if (m_speed < 70.0f) {
1228 turn += (1.0f - (m_speed - 20.0f) / 50.0f) * turn;
1229 }
1230 } else {
1231 turn = (turn * 0.4f) + (m_speed / 20.0f) * (turn * 0.6f);
1232 }
1233 }
1234 }
1235
1236 if (!forwards) {
1237 turn = -turn;
1238 }
1239
1240 if (status.onBit(eStatus::ZipperBoost) && status.offBit(eStatus::DriftManual)) {
1241 turn *= 2.0f;
1242 }
1243
1244 f32 stickX = EGG::Mathf::abs(state()->stickX());
1245 if (autoDrift && stickX > 0.3f) {
1246 f32 stickScalar = (stickX - 0.3f) / 0.7f;
1247 stickX = drifting ? 0.2f : 0.5f;
1248 turn += stickScalar * (turn * stickX * m_speedRatioCapped);
1249 }
1250 }
1251
1252 if (status.offBit(eStatus::InAction, eStatus::ZipperTrick)) {
1253 if (status.offBit(eStatus::TouchingGround)) {
1254 if (status.onBit(eStatus::RampBoost) && m_jump->isBoostRampEnabled()) {
1255 turn = 0.0f;
1256 } else if (status.offBit(eStatus::JumpPadMushroomCollision)) {
1257 u32 airtime = state()->airtime();
1258 if (airtime >= 70) {
1259 turn = 0.0f;
1260 } else if (airtime >= 30) {
1261 turn = std::max(0.0f, turn * (1.0f - (airtime - 30) * 0.025f));
1262 }
1263 }
1264 }
1265
1266 const EGG::Vector3f forward = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1267 f32 angle = EGG::Mathf::atan2(forward.cross(m_dir).length(), forward.dot(m_dir));
1268 angle = EGG::Mathf::abs(angle) * RAD2DEG;
1269
1270 if (angle > 60.0f) {
1271 turn *= std::max(0.0f, 1.0f - (angle - 60.0f) / 40.0f);
1272 }
1273 }
1274
1275 calcVehicleRotation(turn);
1276}
1277
1282 const auto *raceMgr = System::RaceManager::Instance();
1283 auto &status = KartObjectProxy::status();
1284
1285 if (raceMgr->isStageReached(System::RaceManager::Stage::Race)) {
1286 f32 speedFix = dynamics()->speedFix();
1287 if (status.onBit(eStatus::InAction) ||
1288 ((status.onBit(eStatus::WallCollisionStart) || state()->wallBonkTimer() == 0 ||
1289 EGG::Mathf::abs(speedFix) >= 3.0f) &&
1290 status.offBit(eStatus::DriftManual))) {
1291 m_speed += speedFix;
1292 }
1293 }
1294
1295 if (m_speed < -20.0f) {
1296 m_speed += 0.5f;
1297 }
1298
1299 bool water = false;
1300
1301 if (status.onBit(eStatus::MovingWaterVertical) ||
1302 (status.onBit(eStatus::MovingWaterDecaySpeed) &&
1303 status.offBit(eStatus::MushroomBoost) && EGG::Mathf::abs(m_speed) > 5.0f)) {
1304 water = true;
1305 m_speed *= collide()->pullPath().roadSpeedDecay();
1306 }
1307
1308 m_acceleration = 0.0f;
1309 m_speedDragMultiplier = 1.0f;
1310
1311 if (status.onBit(eStatus::InAction)) {
1312 action()->calcVehicleSpeed();
1313 return;
1314 }
1315
1316 if ((status.onAllBit(eStatus::SomethingWallCollision, eStatus::TouchingGround) &&
1319 status.onBit(eStatus::DisableAcceleration, eStatus::ChargingSSMT)) {
1320 if (status.onBit(eStatus::RampBoost) && state()->airtime() < 4) {
1321 m_acceleration = 7.0f;
1322 } else {
1323 if (status.onBit(eStatus::JumpPad) && status.offBit(eStatus::Accelerate)) {
1324 m_speedDragMultiplier = 0.99f;
1325 } else {
1326 if (status.onBit(eStatus::OverZipper)) {
1327 m_speedDragMultiplier = 0.999f;
1328 } else {
1329 if (state()->airtime() > 5) {
1330 m_speedDragMultiplier = 0.999f;
1331 }
1332 }
1333 }
1335 }
1336 } else if (status.offBit(eStatus::Boost)) {
1337 if (status.offBit(eStatus::JumpPad, eStatus::RampBoost)) {
1338 if (status.onBit(eStatus::Accelerate)) {
1341 } else {
1342 if (status.offBit(eStatus::Brake) ||
1344 eStatus::SomethingWallCollision)) {
1345 m_speed *= m_speed > 0.0f ? 0.98f : 0.95f;
1346 } else if (m_drivingDirection == DrivingDirection::Braking) {
1347 m_acceleration = -1.5f;
1349 if (++m_backwardsAllowCounter > 15) {
1350 m_drivingDirection = DrivingDirection::Backwards;
1351 }
1352 } else if (m_drivingDirection == DrivingDirection::Backwards) {
1353 m_acceleration = -2.0f;
1354 }
1355 }
1356
1358 const auto &stats = param()->stats();
1359
1360 f32 x = 1.0f - EGG::Mathf::abs(m_weightedTurn) * m_speedRatioCapped;
1361 m_speed *= stats.turningSpeed + (1.0f - stats.turningSpeed) * x;
1362 }
1363 } else {
1364 m_acceleration = water ? calcVehicleAcceleration() : 7.0f;
1365 }
1366 } else {
1367 m_acceleration = water ? calcVehicleAcceleration() : m_boost.acceleration();
1368 }
1369}
1370
1374 f32 vel = 0.0f;
1375 f32 initialVel = 1.0f - m_smoothedUp.y;
1376 if (EGG::Mathf::abs(m_speed) < 30.0f && m_smoothedUp.y > 0.0f && initialVel > 0.0f) {
1377 initialVel = std::min(initialVel * 2.0f, 2.0f);
1378 vel += initialVel;
1379 vel *= std::min(0.5f, std::max(-0.5f, -bodyFront().y));
1380 }
1381 m_speed += vel;
1382}
1383
1388 f32 ratio = m_speed / m_softSpeedLimit;
1389 if (ratio < 0.0f) {
1390 return 1.0f;
1391 }
1392
1393 std::span<const f32> as;
1394 std::span<const f32> ts;
1395 if (state()->isDrifting()) {
1396 as = param()->stats().accelerationDriftA;
1397 ts = param()->stats().accelerationDriftT;
1398 } else {
1399 as = param()->stats().accelerationStandardA;
1400 ts = param()->stats().accelerationStandardT;
1401 }
1402
1403 size_t i = 0;
1404 f32 acceleration = 0.0f;
1405 f32 t_curr = 0.0f;
1406 for (; i < ts.size(); ++i) {
1407 if (ratio < ts[i]) {
1408 acceleration = as[i] + ((as[i + 1] - as[i]) / (ts[i] - t_curr)) * (ratio - t_curr);
1409 break;
1410 }
1411
1412 t_curr = ts[i];
1413 }
1414
1415 return i < ts.size() ? acceleration : as.back();
1416}
1417
1422 constexpr f32 ROTATION_SCALAR_NORMAL = 0.5f;
1423 constexpr f32 ROTATION_SCALAR_MIDAIR = 0.2f;
1424 constexpr f32 ROTATION_SCALAR_BOOST_RAMP = 4.0f;
1425 constexpr f32 OOB_SLOWDOWN_RATE = 0.95f;
1426 constexpr f32 TERMINAL_VELOCITY = 90.0f;
1427
1429 auto &status = KartObjectProxy::status();
1430
1431 if (status.offBit(eStatus::InAction)) {
1432 dynamics()->setKillExtVelY(status.onBit(eStatus::RespawnKillY));
1433 }
1434
1435 if (status.onBit(eStatus::Burnout)) {
1436 m_speed = 0.0f;
1437 } else {
1438 if (m_acceleration < 0.0f) {
1439 if (m_speed < -20.0f) {
1440 m_acceleration = 0.0f;
1441 } else {
1442 if (m_speed + m_acceleration <= -20.0f) {
1443 m_acceleration = -20.0f - m_speed;
1444 }
1445 }
1446 }
1447
1449 }
1450
1451 if (status.onBit(eStatus::BeforeRespawn)) {
1452 m_speed *= OOB_SLOWDOWN_RATE;
1453 } else {
1454 if (status.onBit(eStatus::ChargingSSMT)) {
1455 m_speed *= 0.8f;
1456 } else {
1457 if (m_drivingDirection == DrivingDirection::Braking && m_speed < 0.0f) {
1458 m_speed = 0.0f;
1461 }
1462 }
1463 }
1464
1465 f32 speedLimit = status.onBit(eStatus::JumpPad) ? m_jumpPadMaxSpeed : m_baseSpeed;
1466 const f32 boostMultiplier = m_boost.multiplier();
1467 const f32 boostSpdLimit = m_boost.speedLimit();
1468 m_jumpPadBoostMultiplier = boostMultiplier;
1469
1470 f32 scaleMultiplier = m_shockSpeedMultiplier;
1471 if (status.onBit(eStatus::Crushed)) {
1472 scaleMultiplier *= 0.7f;
1473 }
1474
1475 f32 wheelieBonus = boostMultiplier + getWheelieSoftSpeedLimitBonus();
1476 speedLimit *= status.onBit(eStatus::JumpPadFixedSpeed) ?
1477 1.0f :
1478 scaleMultiplier * (wheelieBonus * m_kclSpeedFactor);
1479
1480 bool ignoreScale = status.onBit(eStatus::RampBoost, eStatus::ZipperInvisibleWall,
1482 f32 boostSpeed = ignoreScale ? 1.0f : scaleMultiplier;
1483 boostSpeed *= boostSpdLimit * m_kclSpeedFactor;
1484
1485 if (status.offBit(eStatus::JumpPad) && boostSpeed > 0.0f && boostSpeed > speedLimit) {
1486 speedLimit = boostSpeed;
1487 }
1488
1489 m_jumpPadSoftSpeedLimit = boostSpdLimit * m_kclSpeedFactor;
1490
1491 if (status.onBit(eStatus::RampBoost)) {
1492 speedLimit = std::max(speedLimit, 100.0f);
1493 }
1494
1495 m_lastDir = (m_speed > 0.0f) ? 1.0f * m_dir : -1.0f * m_dir;
1496
1497 f32 local_c8 = 1.0f;
1498 speedLimit *= calcWallCollisionSpeedFactor(local_c8);
1499
1500 if (m_softSpeedLimit <= speedLimit) {
1501 m_softSpeedLimit = speedLimit;
1503 m_softSpeedLimit = std::max(m_softSpeedLimit - 3.0f, speedLimit);
1504 } else {
1505 m_softSpeedLimit = speedLimit;
1506 }
1507
1509
1510 m_speed = std::min(m_softSpeedLimit, std::max(-m_softSpeedLimit, m_speed));
1511
1512 if (status.onBit(eStatus::JumpPad)) {
1513 m_speed = std::max(m_speed, m_jumpPadMinSpeed);
1514 }
1515
1516 calcWallCollisionStart(local_c8);
1517
1518 m_speedRatio = EGG::Mathf::abs(m_speed / m_baseSpeed);
1519 m_speedRatioCapped = std::min(1.0f, m_speedRatio);
1520
1521 EGG::Vector3f crossVec = m_smoothedUp.cross(m_dir);
1522 if (m_speed < 0.0f) {
1523 crossVec = -crossVec;
1524 }
1525
1526 f32 rotationScalar = ROTATION_SCALAR_NORMAL;
1527 if (collide()->surfaceFlags().onBit(KartCollide::eSurfaceFlags::BoostRamp)) {
1528 rotationScalar = ROTATION_SCALAR_BOOST_RAMP;
1529 } else if (status.offBit(eStatus::TouchingGround)) {
1530 rotationScalar = ROTATION_SCALAR_MIDAIR;
1531 }
1532
1533 EGG::Matrix34f local_90;
1534 local_90.setAxisRotation(DEG2RAD * rotationScalar, crossVec);
1535 m_vel1Dir = local_90.multVector33(m_vel1Dir);
1536
1537 const auto *raceMgr = System::RaceManager::Instance();
1538 if (status.offBit(eStatus::InAction, eStatus::DisableBackwardsAccel, eStatus::Accelerate) &&
1540 raceMgr->isStageReached(System::RaceManager::Stage::Race)) {
1542 }
1543
1545 EGG::Vector3f nextSpeed = m_speed * m_vel1Dir;
1546
1547 f32 maxSpeedY = status.onBit(eStatus::OverZipper) ? KartHalfPipe::TerminalVelocity() :
1548 TERMINAL_VELOCITY;
1549 nextSpeed.y = std::min(nextSpeed.y, maxSpeedY);
1550
1551 dynamics()->setIntVel(dynamics()->intVel() + nextSpeed);
1552
1553 if (status.onBit(eStatus::TouchingGround) &&
1555 if (status.onBit(eStatus::Brake)) {
1556 if (m_drivingDirection == DrivingDirection::Forwards) {
1557 m_drivingDirection = m_processedSpeed > 5.0f ? DrivingDirection::Braking :
1558 DrivingDirection::Backwards;
1559 }
1560 } else {
1561 if (m_processedSpeed >= 0.0f) {
1562 m_drivingDirection = DrivingDirection::Forwards;
1563 }
1564 }
1565 } else {
1566 m_drivingDirection = DrivingDirection::Forwards;
1567 }
1568}
1569
1574 auto &status = KartObjectProxy::status();
1575
1577 return 1.0f;
1578 }
1579
1580 onWallCollision();
1581
1583 return 1.0f;
1584 }
1585
1586 EGG::Vector3f wallNrm = collisionData().wallNrm;
1587 if (wallNrm.y > 0.0f) {
1588 wallNrm.y = 0.0f;
1589 wallNrm.normalise();
1590 }
1591
1592 f32 dot = m_lastDir.dot(wallNrm);
1593
1594 if (dot < 0.0f) {
1595 f1 = std::max(0.0f, dot + 1.0f);
1596
1597 return std::min(1.0f, f1 * (status.onBit(eStatus::WallCollision) ? 0.4f : 0.7f));
1598 }
1599
1600 return 1.0f;
1601}
1602
1608
1609 auto &status = KartObjectProxy::status();
1610
1611 if (status.offBit(eStatus::WallCollisionStart)) {
1612 return;
1613 }
1614
1615 m_outsideDriftAngle = 0.0f;
1616 if (status.offBit(eStatus::InAction)) {
1617 m_dir = bodyFront();
1618 m_vel1Dir = m_dir;
1619 m_landingDir = m_dir;
1620 }
1621
1622 if (status.offBit(eStatus::ZipperInvisibleWall, eStatus::OverZipper) && param_2 < 0.9f) {
1623 f32 speedDiff = m_lastSpeed - m_speed;
1624 const CollisionData &colData = collisionData();
1625
1626 if (speedDiff > 30.0f) {
1627 m_flags.setBit(eFlags::WallBounce);
1628 EGG::Vector3f newPos = colData.relPos + pos();
1629 f32 dot = -bodyUp().dot(colData.relPos) * 0.5f;
1630 EGG::Vector3f scaledUp = dot * bodyUp();
1631 newPos -= scaledUp;
1632
1633 speedDiff = std::min(60.0f, speedDiff);
1634 EGG::Vector3f scaledWallNrm = speedDiff * colData.wallNrm;
1635
1636 auto [proj, rej] = scaledWallNrm.projAndRej(m_vel1Dir);
1637 proj *= 0.3f;
1638 rej *= 0.9f;
1639
1640 if (status.onBit(eStatus::Boost)) {
1641 proj = EGG::Vector3f::zero;
1642 rej = EGG::Vector3f::zero;
1643 }
1644
1645 if (bodyFront().dot(colData.wallNrm) > 0.0f) {
1646 proj = EGG::Vector3f::zero;
1647 }
1648 rej *= 0.9f;
1649
1650 EGG::Vector3f projRejSum = proj + rej;
1651 f32 bumpDeviation = 0.0f;
1652 if (m_flags.offBit(eFlags::DriftReset) && status.onBit(eStatus::TouchingGround)) {
1653 bumpDeviation = param()->stats().bumpDeviationLevel;
1654 }
1655
1656 dynamics()->applyWrenchScaled(newPos, projRejSum, bumpDeviation);
1657 } else if (wallKclType() == COL_TYPE_SPECIAL_WALL && wallKclVariant() == 2) {
1658 dynamics()->addForce(colData.wallNrm * 15.0f);
1659 collide()->startFloorMomentRate();
1660 }
1661
1662 if (wallKclType() == COL_TYPE_SPECIAL_WALL && wallKclVariant() == 0) {
1663 dynamics()->addForce(colData.wallNrm * 15.0f);
1664 collide()->startFloorMomentRate();
1665 }
1666 }
1667}
1668
1673 f32 next = 0.0f;
1674 f32 scalar = 1.0f;
1675
1676 auto &status = KartObjectProxy::status();
1677
1678 if (status.onBit(eStatus::TouchingGround)) {
1679 if (System::RaceManager::Instance()->stage() == System::RaceManager::Stage::Countdown) {
1680 next = 0.015f * -state()->startBoostCharge();
1681 } else if (status.offBit(eStatus::ChargingSSMT)) {
1682 if (status.offBit(eStatus::JumpPad, eStatus::RampBoost, eStatus::SoftWallDrift)) {
1683 f32 speedDiff = m_lastSpeed - m_speed;
1684 scalar = std::min(3.0f, std::max(speedDiff, -3.0f));
1685
1686 if (status.onBit(eStatus::MushroomBoost)) {
1687 next = (scalar * 0.15f) * 0.25f;
1688 if (status.onBit(eStatus::Wheelie)) {
1689 next *= 0.5f;
1690 }
1691 } else {
1692 next = (scalar * 0.15f) * 0.08f;
1693 }
1694 scalar = m_driftingParams->boostRotFactor;
1695 }
1696 } else {
1697 constexpr s16 MAX_SSMT_CHARGE = 75;
1698 next = 0.015f * (-static_cast<f32>(m_ssmtCharge) / static_cast<f32>(MAX_SSMT_CHARGE));
1699 }
1700 }
1701
1702 if (m_flags.onBit(eFlags::WallBounce)) {
1703 m_standStillBoostRot = isBike() ? next * 3.0f : next * 10.0f;
1704 } else {
1705 m_standStillBoostRot += scalar * (next * m_invScale - m_standStillBoostRot);
1706 }
1707}
1708
1713 constexpr f32 DIVE_LIMIT = 0.8f;
1714
1715 m_divingRot *= 0.96f;
1716
1717 auto &status = KartObjectProxy::status();
1718
1719 if (status.onBit(eStatus::TouchingGround, eStatus::CannonStart, eStatus::InCannon,
1720 eStatus::InAction, eStatus::OverZipper)) {
1721 return;
1722 }
1723
1724 f32 stickY = state()->stickY();
1725
1726 if (status.onBit(eStatus::InATrick) && m_jump->type() == TrickType::BikeSideStuntTrick) {
1727 stickY = std::min(1.0f, stickY + 0.4f);
1728 }
1729
1730 u32 airtime = state()->airtime();
1731
1732 if (airtime > 50) {
1733 if (EGG::Mathf::abs(stickY) < 0.1f) {
1734 m_divingRot += 0.05f * (-0.025f - m_divingRot);
1735 }
1736 } else {
1737 stickY *= (airtime / 50.0f);
1738 }
1739
1740 m_divingRot = std::max(-DIVE_LIMIT, std::min(DIVE_LIMIT, m_divingRot + stickY * 0.005f));
1741
1742 EGG::Vector3f angVel2 = dynamics()->angVel2();
1743 angVel2.x += m_divingRot;
1744 dynamics()->setAngVel2(angVel2);
1745
1746 if (state()->airtime() < 50) {
1747 return;
1748 }
1749
1750 EGG::Vector3f topRotated = dynamics()->mainRot().rotateVector(EGG::Vector3f::ey);
1751 EGG::Vector3f forwardRotated = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1752 f32 upDotTop = m_up.dot(topRotated);
1753 EGG::Vector3f upCrossTop = m_up.cross(topRotated);
1754 f32 crossNorm = upCrossTop.length();
1755 f32 angle = EGG::Mathf::abs(EGG::Mathf::atan2(crossNorm, upDotTop));
1756
1757 f32 fVar1 = angle * RAD2DEG - 20.0f;
1758 if (fVar1 <= 0.0f) {
1759 return;
1760 }
1761
1762 f32 mult = std::min(1.0f, fVar1 / 20.0f);
1763 if (forwardRotated.y > 0.0f) {
1764 dynamics()->setGravity((1.0f - 0.2f * mult) * dynamics()->gravity());
1765 } else {
1766 dynamics()->setGravity((0.2f * mult + 1.0f) * dynamics()->gravity());
1767 }
1768}
1769
1774 auto &status = KartObjectProxy::status();
1775
1776 if (EGG::Mathf::abs(m_speed) >= 10.0f || status.onBit(eStatus::Boost, eStatus::RampBoost) ||
1779 return;
1780 }
1781
1783}
1784
1786void KartMove::calcHopPhysics() {
1787 m_hopVelY = m_hopVelY * 0.998f + m_hopGravity;
1789
1790 if (m_hopPosY < 0.0f) {
1791 m_hopPosY = 0.0f;
1792 m_hopVelY = 0.0f;
1793 }
1794}
1795
1797void KartMove::calcRejectRoad() {
1798 m_reject.calcRejectRoad();
1799}
1800
1802bool KartMove::calcZipperCollision(f32 radius, f32 scale, EGG::Vector3f &pos,
1803 EGG::Vector3f &upLocal, const EGG::Vector3f &prevPos, Field::CollisionInfo *colInfo,
1804 Field::KCLTypeMask *maskOut, Field::KCLTypeMask flags) const {
1805 upLocal = mainRot().rotateVector(EGG::Vector3f::ey);
1806 pos = dynamics()->pos() + (-scale * m_scale.y) * upLocal;
1807
1808 auto *colDir = Field::CollisionDirector::Instance();
1809 return colDir->checkSphereFullPush(radius, pos, prevPos, flags, colInfo, maskOut, 0);
1810}
1811
1813f32 KartMove::calcSlerpRate(f32 scale, const EGG::Quatf &from, const EGG::Quatf &to) const {
1814 f32 dotNorm = std::max(-1.0f, std::min(1.0f, from.dot(to)));
1815 f32 acos = EGG::Mathf::acos(dotNorm);
1816 return acos > 0.0f ? std::min(0.1f, scale / acos) : 0.1f;
1817}
1818
1820void KartMove::applyForce(f32 force, const EGG::Vector3f &hitDir, bool stop) {
1821 constexpr s16 BUMP_COOLDOWN = 5;
1822
1823 if (m_bumpTimer >= 1) {
1824 return;
1825 }
1826
1827 dynamics()->addForce(force * hitDir.perpInPlane(m_up, true));
1828 collide()->startFloorMomentRate();
1829
1830 m_bumpTimer = BUMP_COOLDOWN;
1831
1832 if (stop) {
1833 m_speed = 0.0f;
1834 }
1835}
1836
1840 f32 tiltMagnitude = 0.0f;
1841 auto &status = KartObjectProxy::status();
1842
1843 if (status.offBit(eStatus::InAction, eStatus::SoftWallDrift) &&
1845 EGG::Vector3f front = componentZAxis();
1846 front = front.perpInPlane(m_up, true);
1847 EGG::Vector3f frontSpeed = velocity().rej(front).perpInPlane(m_up, false);
1848 f32 magnitude = tiltMagnitude;
1849
1850 if (frontSpeed.squaredLength() > std::numeric_limits<f32>::epsilon()) {
1851 magnitude = frontSpeed.length();
1852
1853 if (front.z * frontSpeed.x - front.x * frontSpeed.z > 0.0f) {
1854 magnitude = -magnitude;
1855 }
1856
1857 tiltMagnitude = -1.0f;
1858 if (-1.0f <= magnitude) {
1859 tiltMagnitude = std::min(1.0f, magnitude);
1860 }
1861 }
1862 } else if (status.offBit(eStatus::Hop) || m_hopPosY <= 0.0f) {
1863 EGG::Vector3f angVel0 = dynamics()->angVel0();
1864 angVel0.z *= 0.98f;
1865 dynamics()->setAngVel0(angVel0);
1866 }
1867
1868 f32 lean =
1869 m_invScale * (tiltMagnitude * param()->stats().tilt * EGG::Mathf::abs(m_weightedTurn));
1870
1872
1873 EGG::Vector3f angVel0 = dynamics()->angVel0();
1874 angVel0.x += m_standStillBoostRot;
1875 angVel0.z += lean;
1876 dynamics()->setAngVel0(angVel0);
1877
1878 EGG::Vector3f angVel2 = dynamics()->angVel2();
1879 angVel2.y += turn;
1880 dynamics()->setAngVel2(angVel2);
1881
1882 calcDive();
1883}
1884
1889 // TODO: Some of these are shared between the base and derived class implementations.
1890 constexpr u16 MAX_MT_CHARGE = 270;
1891 constexpr u16 MAX_SMT_CHARGE = 300;
1892 constexpr u16 BASE_MT_CHARGE = 2;
1893 constexpr u16 BASE_SMT_CHARGE = 2;
1894 constexpr f32 BONUS_CHARGE_STICK_THRESHOLD = 0.4f;
1895 constexpr u16 EXTRA_MT_CHARGE = 3;
1896
1897 if (m_driftState == DriftState::ChargedSmt) {
1898 return;
1899 }
1900
1901 f32 stickX = state()->stickX();
1902
1903 if (m_driftState == DriftState::ChargingMt) {
1904 m_mtCharge += BASE_MT_CHARGE;
1905
1906 if (-BONUS_CHARGE_STICK_THRESHOLD <= stickX) {
1907 if (BONUS_CHARGE_STICK_THRESHOLD < stickX && m_hopStickX == -1) {
1908 m_mtCharge += EXTRA_MT_CHARGE;
1909 }
1910 } else if (m_hopStickX != -1) {
1911 m_mtCharge += EXTRA_MT_CHARGE;
1912 }
1913
1914 if (m_mtCharge > MAX_MT_CHARGE) {
1915 m_mtCharge = MAX_MT_CHARGE;
1916 m_driftState = DriftState::ChargingSmt;
1917 }
1918 }
1919
1920 if (m_driftState != DriftState::ChargingSmt) {
1921 return;
1922 }
1923
1924 m_smtCharge += BASE_SMT_CHARGE;
1925
1926 if (-BONUS_CHARGE_STICK_THRESHOLD <= stickX) {
1927 if (BONUS_CHARGE_STICK_THRESHOLD < stickX && m_hopStickX == -1) {
1928 m_smtCharge += EXTRA_MT_CHARGE;
1929 }
1930 } else if (m_hopStickX != -1) {
1931 m_smtCharge += EXTRA_MT_CHARGE;
1932 }
1933
1934 if (m_smtCharge > MAX_SMT_CHARGE) {
1935 m_smtCharge = MAX_SMT_CHARGE;
1936 m_driftState = DriftState::ChargedSmt;
1937 }
1938}
1939
1941void KartMove::initOob() {
1942 clearBoost();
1943 clearJumpPad();
1944 clearRampBoost();
1945 clearZipperBoost();
1946 clearSsmt();
1947 clearOffroadInvincibility();
1948}
1949
1954 status().setBit(eStatus::Hop).resetBit(eStatus::DriftManual);
1955 onHop();
1956
1957 m_hopUp = dynamics()->mainRot().rotateVector(EGG::Vector3f::ey);
1958 m_hopDir = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1959 m_driftState = DriftState::NotDrifting;
1960 m_smtCharge = 0;
1961 m_mtCharge = 0;
1962 m_hopStickX = 0;
1963 m_hopFrame = 0;
1964 m_hopPosY = 0.0f;
1965 m_hopGravity = dynamics()->gravity();
1966 m_hopVelY = m_driftingParams->hopVelY;
1967 m_outsideDriftBonus = 0.0f;
1968
1969 EGG::Vector3f extVel = dynamics()->extVel();
1970 extVel.y = 0.0f + m_hopVelY;
1971 dynamics()->setExtVel(extVel);
1972
1973 EGG::Vector3f totalForce = dynamics()->totalForce();
1974 totalForce.y = 0.0f;
1975 dynamics()->setTotalForce(totalForce);
1976}
1977
1979void KartMove::tryStartBoostPanel() {
1980 constexpr s16 BOOST_PANEL_DURATION = 60;
1981
1982 if (status().onBit(eStatus::BeforeRespawn, eStatus::InAction)) {
1983 return;
1984 }
1985
1986 activateBoost(KartBoost::Type::MushroomAndBoostPanel, BOOST_PANEL_DURATION);
1987 setOffroadInvincibility(BOOST_PANEL_DURATION);
1988}
1989
1994 constexpr s16 BOOST_RAMP_DURATION = 60;
1995
1996 auto &status = KartObjectProxy::status();
1997
1998 if (status.onBit(eStatus::BeforeRespawn, eStatus::InAction)) {
1999 return;
2000 }
2001
2002 status.setBit(eStatus::RampBoost);
2003 m_rampBoost = BOOST_RAMP_DURATION;
2004 setOffroadInvincibility(BOOST_RAMP_DURATION);
2005}
2006
2012 static constexpr std::array<JumpPadProperties, 8> JUMP_PAD_PROPERTIES = {{
2013 {50.0f, 50.0f, 35.0f},
2014 {50.0f, 50.0f, 47.0f},
2015 {59.0f, 59.0f, 30.0f},
2016 {73.0f, 73.0f, 45.0f},
2017 {73.0f, 73.0f, 53.0f},
2018 {56.0f, 56.0f, 50.0f},
2019 {55.0f, 55.0f, 35.0f},
2020 {56.0f, 56.0f, 50.0f},
2021 }};
2022
2023 auto &status = KartObjectProxy::status();
2024
2025 if (status.onBit(eStatus::BeforeRespawn, eStatus::InAction, eStatus::HalfPipeRamp)) {
2026 return;
2027 }
2028
2029 status.setBit(eStatus::JumpPad);
2030 s32 jumpPadVariant = state()->jumpPadVariant();
2031 m_jumpPadProperties = &JUMP_PAD_PROPERTIES[jumpPadVariant];
2032
2033 if (jumpPadVariant == 3 || jumpPadVariant == 4) {
2034 if (m_jumpPadBoostMultiplier > 1.3f || m_jumpPadSoftSpeedLimit > 110.0f) {
2035 // Set speed to 100 if the player has boost from a boost panel or mushroom(item) before
2036 // hitting the jump pad
2037 static constexpr std::array<JumpPadProperties, 2> JUMP_PAD_PROPERTIES_SHROOM_BOOST = {{
2038 {100.0f, 100.0f, 70.0f},
2039 {100.0f, 100.0f, 65.0f},
2040 }};
2041 m_jumpPadProperties = &JUMP_PAD_PROPERTIES_SHROOM_BOOST[jumpPadVariant != 3];
2042 }
2043
2044 status.setBit(eStatus::JumpPadFixedSpeed);
2045 }
2046
2047 if (jumpPadVariant == 4) {
2048 status.setBit(eStatus::JumpPadMushroomTrigger, eStatus::JumpPadMushroomVelYInc,
2049 eStatus::JumpPadMushroomCollision);
2050 } else {
2051 EGG::Vector3f extVel = dynamics()->extVel();
2052 EGG::Vector3f totalForce = dynamics()->totalForce();
2053
2054 extVel.y = m_jumpPadProperties->velY;
2055 totalForce.y = 0.0f;
2056
2057 dynamics()->setExtVel(extVel);
2058 dynamics()->setTotalForce(totalForce);
2059
2060 if (jumpPadVariant != 3) {
2061 EGG::Vector3f dir = m_dir;
2062 dir.y = 0.0f;
2063 dir.normalise();
2064 m_speed *= m_dir.dot(dir);
2065 m_dir = dir;
2066 m_vel1Dir = dir;
2067 status.setBit(eStatus::JumpPadDisableYsusForce);
2068 }
2069 }
2070
2071 m_jumpPadMinSpeed = m_jumpPadProperties->minSpeed;
2072 m_jumpPadMaxSpeed = m_jumpPadProperties->maxSpeed;
2073 m_speed = std::max(m_speed, m_jumpPadMinSpeed);
2074}
2075
2077void KartMove::tryEndJumpPad() {
2078 auto &status = KartObjectProxy::status();
2079 if (status.onBit(eStatus::JumpPadMushroomTrigger)) {
2080 if (status.onBit(eStatus::GroundStart)) {
2081 status.resetBit(eStatus::JumpPadMushroomTrigger, eStatus::JumpPadFixedSpeed,
2082 eStatus::JumpPadMushroomVelYInc);
2083 }
2084
2085 if (status.onBit(eStatus::JumpPadMushroomVelYInc)) {
2086 EGG::Vector3f newExtVel = dynamics()->extVel();
2087 newExtVel.y += 20.0f;
2088 if (m_jumpPadProperties->velY < newExtVel.y) {
2089 newExtVel.y = m_jumpPadProperties->velY;
2090 status.resetBit(eStatus::JumpPadMushroomVelYInc);
2091 }
2092 dynamics()->setExtVel(newExtVel);
2093 }
2094 }
2095
2096 if (status.onBit(eStatus::GroundStart) && status.offBit(eStatus::JumpPadMushroomTrigger)) {
2097 cancelJumpPad();
2098 }
2099}
2100
2102void KartMove::cancelJumpPad() {
2103 m_jumpPadMinSpeed = 0.0f;
2104 status().resetBit(eStatus::JumpPad);
2105}
2106
2108void KartMove::activateBoost(KartBoost::Type type, s16 frames) {
2109 if (m_boost.activate(type, frames)) {
2110 status().setBit(eStatus::Boost);
2111 }
2112}
2113
2115void KartMove::applyStartBoost(s16 frames) {
2116 activateBoost(KartBoost::Type::AllMt, frames);
2117}
2118
2120void KartMove::activateMushroom() {
2121 constexpr s16 MUSHROOM_DURATION = 90;
2122
2123 auto &status = KartObjectProxy::status();
2124
2125 if (status.onBit(eStatus::BeforeRespawn, eStatus::InAction)) {
2126 return;
2127 }
2128
2129 activateBoost(KartBoost::Type::MushroomAndBoostPanel, MUSHROOM_DURATION);
2130
2131 m_mushroomBoostTimer = MUSHROOM_DURATION;
2133 setOffroadInvincibility(MUSHROOM_DURATION);
2134}
2135
2137void KartMove::activateZipperBoost() {
2138 constexpr s16 BASE_DURATION = 50;
2139 constexpr s16 TRICK_DURATION = 100;
2140
2141 auto &status = KartObjectProxy::status();
2142
2143 if (status.onBit(eStatus::BeforeRespawn, eStatus::InAction)) {
2144 return;
2145 }
2146
2147 s16 boostDuration = status.onBit(eStatus::ZipperTrick) ? TRICK_DURATION : BASE_DURATION;
2148 activateBoost(KartBoost::Type::TrickAndZipper, boostDuration);
2149
2150 setOffroadInvincibility(boostDuration);
2151 m_zipperBoostTimer = 0;
2152 m_zipperBoostMax = boostDuration;
2154}
2155
2161 if (timer > m_offroadInvincibility) {
2162 m_offroadInvincibility = timer;
2163 }
2164
2166}
2167
2172 auto &status = KartObjectProxy::status();
2173
2175 return;
2176 }
2177
2178 if (--m_offroadInvincibility > 0) {
2179 return;
2180 }
2181
2183}
2184
2188 auto &status = KartObjectProxy::status();
2189
2190 if (status.offBit(eStatus::MushroomBoost)) {
2191 return;
2192 }
2193
2194 if (--m_mushroomBoostTimer > 0) {
2195 return;
2196 }
2197
2199}
2200
2202void KartMove::calcZipperBoost() {
2203 auto &status = KartObjectProxy::status();
2204
2205 if (status.offBit(eStatus::ZipperBoost)) {
2206 return;
2207 }
2208
2210
2211 if (status.offBit(eStatus::OverZipper) && ++m_zipperBoostTimer >= m_zipperBoostMax) {
2212 m_zipperBoostTimer = 0;
2214 }
2215
2216 if (m_zipperBoostTimer < 10) {
2217 EGG::Vector3f angVel = dynamics()->angVel0();
2218 angVel.y = 0.0f;
2219 dynamics()->setAngVel0(angVel);
2220 }
2221}
2222
2224void KartMove::landTrick() {
2225 static constexpr std::array<s16, 3> KART_TRICK_BOOST_DURATION = {{
2226 40,
2227 70,
2228 85,
2229 }};
2230 static constexpr std::array<s16, 3> BIKE_TRICK_BOOST_DURATION = {{
2231 45,
2232 80,
2233 95,
2234 }};
2235
2236 if (status().onBit(eStatus::BeforeRespawn, eStatus::InAction)) {
2237 return;
2238 }
2239
2240 s16 duration;
2241 if (isBike()) {
2242 duration = BIKE_TRICK_BOOST_DURATION[static_cast<u32>(m_jump->variant())];
2243 } else {
2244 duration = KART_TRICK_BOOST_DURATION[static_cast<u32>(m_jump->variant())];
2245 }
2246
2247 activateBoost(KartBoost::Type::TrickAndZipper, duration);
2248}
2249
2251void KartMove::activateCrush(u16 timer) {
2252 status().setBit(eStatus::Crushed);
2253 m_crushTimer = timer;
2254 m_kartScale->startCrush();
2255}
2256
2258void KartMove::calcCrushed() {
2259 if (status().offBit(eStatus::Crushed)) {
2260 return;
2261 }
2262
2263 if (--m_crushTimer == 0) {
2264 status().resetBit(eStatus::Crushed);
2265 m_kartScale->endCrush();
2266 }
2267}
2268
2270void KartMove::calcScale() {
2271 m_kartScale->calc();
2272
2273 const EGG::Vector3f sizeScale = m_kartScale->sizeScale();
2274 setScale(m_kartScale->pressScale() * sizeScale);
2275 m_totalScale = m_shockSpeedMultiplier;
2276 m_hitboxScale = std::max(sizeScale.z, m_totalScale);
2277
2278 if (sizeScale.z != 1.0f) {
2279 setInertiaScale(m_scale);
2280 }
2281
2282 m_invScale = m_scale.z > 1.0f ? 1.0f / m_scale.z : 1.0f;
2283}
2284
2286void KartMove::applyShrink(u16 timer) {
2287 auto &status = state()->status();
2288
2289 if (status.onBit(eStatus::InRespawn, eStatus::AfterRespawn, eStatus::CannonStart)) {
2290 return;
2291 }
2292
2293 action()->start(Action::UNK_15);
2294 Item::ItemDirector::Instance()->kartItem(0).clear();
2295 status.setBit(eStatus::Shocked);
2296
2297 if (timer > m_shockTimer) {
2298 m_shockTimer = timer;
2299 m_kartScale->startShrink(0);
2300 }
2301}
2302
2304void KartMove::calcShock() {
2305 auto &status = state()->status();
2306
2307 if (status.onBit(eStatus::Shocked)) {
2308 if (--m_shockTimer == 0) {
2309 deactivateShock(false);
2310 }
2311
2312 m_shockSpeedMultiplier = std::max(0.7f, m_shockSpeedMultiplier - 0.03f);
2313 } else {
2314 m_shockSpeedMultiplier = std::min(1.0f, m_shockSpeedMultiplier + 0.05f);
2315 }
2316}
2317
2319void KartMove::deactivateShock(bool resetSpeed) {
2320 status().resetBit(eStatus::Shocked);
2321 m_shockTimer = 0;
2322 m_kartScale->endShrink(0);
2323
2324 if (resetSpeed) {
2325 m_shockSpeedMultiplier = 1.0f;
2326 }
2327}
2328
2330void KartMove::enterCannon() {
2331 init(true, true);
2332 physics()->clearDecayingRot();
2333 m_boost.resetActive();
2334
2335 auto &status = KartObjectProxy::status();
2336
2337 status.resetBit(eStatus::Boost);
2338
2339 cancelJumpPad();
2340 clearRampBoost();
2341 clearZipperBoost();
2342 clearSsmt();
2343 clearOffroadInvincibility();
2344
2345 dynamics()->reset();
2346
2347 clearDrift();
2348
2349 status.resetBit(eStatus::Hop, eStatus::CannonStart)
2350 .setBit(eStatus::InCannon, eStatus::SkipWheelCalc);
2351
2352 const auto [cannonPos, cannonDir] = getCannonPosRot();
2353 m_cannonEntryPos = pos();
2354 m_cannonEntryOfs = cannonPos - pos();
2355 m_cannonEntryOfsLength = m_cannonEntryOfs.normalise();
2356 m_cannonEntryOfs.normalise();
2357 m_dir = m_cannonEntryOfs;
2358 m_vel1Dir = m_cannonEntryOfs;
2359 m_cannonOrthog = EGG::Vector3f::ey.perpInPlane(m_cannonEntryOfs, true);
2360 m_cannonProgress.setZero();
2361}
2362
2364void KartMove::calcCannon() {
2365 auto [cannonPos, cannonDir] = getCannonPosRot();
2366 EGG::Vector3f forwardXZ = cannonPos - m_cannonEntryPos - m_cannonProgress;
2367 EGG::Vector3f forward = forwardXZ;
2368 f32 forwardLength = forward.normalise();
2369 forwardXZ.y = 0;
2370 forwardXZ.normalise();
2371 EGG::Vector3f local94 = m_cannonEntryOfs;
2372 local94.y = 0;
2373 local94.normalise();
2374 m_speedRatioCapped = 1.0f;
2375 m_speedRatio = 1.5f;
2376 EGG::Matrix34f cannonOrientation;
2377 cannonOrientation.makeOrthonormalBasis(forward, EGG::Vector3f::ey);
2378 EGG::Vector3f up = cannonOrientation.multVector33(EGG::Vector3f::ey);
2379 m_smoothedUp = up;
2380 m_up = up;
2381
2382 if (forwardLength < 30.0f || local94.dot(forwardXZ) <= 0.0f) {
2383 exitCannon();
2384 return;
2385 }
2387 const auto *cannonPoint =
2388 System::CourseMap::Instance()->getCannonPoint(state()->cannonPointId());
2389 size_t cannonParameterIdx = std::max<s16>(0, cannonPoint->parameterIdx());
2390 ASSERT(cannonParameterIdx < CANNON_PARAMETERS.size());
2391 const auto &cannonParams = CANNON_PARAMETERS[cannonParameterIdx];
2392 f32 newSpeed = cannonParams.speed;
2393 if (forwardLength < cannonParams.decelFactor) {
2394 f32 factor = std::max(0.0f, forwardLength / cannonParams.decelFactor);
2395
2396 newSpeed = cannonParams.endDecel;
2397 if (newSpeed <= 0.0f) {
2398 newSpeed = m_baseSpeed;
2399 }
2400
2401 newSpeed += factor * (cannonParams.speed - newSpeed);
2402 if (cannonParams.endDecel > 0.0f) {
2403 m_speed = std::min(newSpeed, m_speed);
2404 }
2405 }
2406
2407 m_cannonProgress += m_cannonEntryOfs * newSpeed;
2408
2409 EGG::Vector3f newPos = EGG::Vector3f::zero;
2410 if (cannonParams.height > 0.0f) {
2411 f32 fVar9 = EGG::Mathf::SinFIdx(
2412 (1.0f - (forwardLength / m_cannonEntryOfsLength)) * 180.0f * DEG2FIDX);
2413 newPos = fVar9 * cannonParams.height * m_cannonOrthog;
2414 }
2415
2416 dynamics()->setPos(m_cannonEntryPos + m_cannonProgress + newPos);
2417 m_dir = m_cannonEntryOfs;
2418 m_vel1Dir = m_cannonEntryOfs;
2419
2420 calcRotCannon(forward);
2421
2422 dynamics()->setExtVel(EGG::Vector3f::zero);
2423}
2424
2426void KartMove::calcRotCannon(const EGG::Vector3f &forward) {
2427 EGG::Vector3f local48 = forward;
2428 local48.normalise();
2429 EGG::Vector3f local54 = bodyFront();
2430 EGG::Vector3f local60 = local54 + ((local48 - local54) * 0.3f);
2431 local54.normalise();
2432 local60.normalise();
2433 // also local70, localA8
2434 EGG::Quatf local80;
2435 local80.makeVectorRotation(local54, local60);
2436 local80 *= dynamics()->fullRot();
2437 local80.normalise();
2438 EGG::Quatf localB8;
2439 localB8.makeVectorRotation(local80.rotateVector(EGG::Vector3f::ey), smoothedUp());
2440 EGG::Quatf newRot = local80.slerpTo(localB8.multSwap(local80), 0.3f);
2441 dynamics()->setFullRot(newRot);
2442 dynamics()->setMainRot(newRot);
2443}
2444
2446void KartMove::exitCannon() {
2447 auto &status = KartObjectProxy::status();
2448
2449 if (status.offBit(eStatus::InCannon)) {
2450 return;
2451 }
2452
2453 status.resetBit(eStatus::InCannon, eStatus::SkipWheelCalc).setBit(eStatus::AfterCannon);
2454 dynamics()->setIntVel(m_cannonEntryOfs * m_speed);
2455}
2456
2458void KartMove::triggerRespawn() {
2459 m_timeInRespawn = 0;
2460 status().setBit(eStatus::TriggerRespawn);
2461}
2462
2464KartMoveBike::KartMoveBike() : m_leanRot(0.0f) {}
2465
2467KartMoveBike::~KartMoveBike() = default;
2468
2472 constexpr f32 MAX_WHEELIE_ROTATION = 0.07f;
2473 constexpr u16 WHEELIE_COOLDOWN = 20;
2474
2475 status().setBit(eStatus::Wheelie);
2476 m_wheelieFrames = 0;
2477 m_maxWheelieRot = MAX_WHEELIE_ROTATION;
2478 m_wheelieCooldown = WHEELIE_COOLDOWN;
2479 m_wheelieRotDec = 0.0f;
2480 m_autoHardStickXFrames = 0;
2481}
2482
2487 status().resetBit(eStatus::Wheelie);
2488 m_wheelieRotDec = 0.0f;
2489 m_autoHardStickXFrames = 0;
2490}
2491
2493void KartMoveBike::createSubsystems(const KartParam::Stats &stats) {
2494 m_jump = new KartJumpBike(this);
2496 m_kartScale = new KartScale(stats);
2497}
2498
2503 f32 leanRotInc = m_turningParams->leanRotIncRace;
2504 f32 leanRotCap = m_turningParams->leanRotCapRace;
2505 const auto *raceManager = System::RaceManager::Instance();
2506
2507 auto &status = KartObjectProxy::status();
2508
2509 if (status.offBit(eStatus::ChargingSSMT)) {
2510 if (!raceManager->isStageReached(System::RaceManager::Stage::Race) ||
2511 EGG::Mathf::abs(m_speed) < 5.0f) {
2512 leanRotInc = m_turningParams->leanRotIncCountdown;
2513 leanRotCap = m_turningParams->leanRotCapCountdown;
2514 }
2515 } else {
2516 leanRotInc = m_turningParams->leanRotIncSSMT;
2517 leanRotCap = m_turningParams->leanRotCapSSMT;
2518 }
2519
2520 m_leanRotCap += 0.3f * (leanRotCap - m_leanRotCap);
2521 m_leanRotInc += 0.3f * (leanRotInc - m_leanRotInc);
2522
2523 f32 stickX = state()->stickX();
2524 f32 extVelXFactor = 0.0f;
2525 f32 leanRotMin = -m_leanRotCap;
2526 f32 leanRotMax = m_leanRotCap;
2527
2528 if (status.onBit(eStatus::BeforeRespawn, eStatus::InAction, eStatus::Wheelie,
2530 eStatus::SoftWallDrift, eStatus::SomethingWallCollision, eStatus::HWG,
2531 eStatus::CannonStart, eStatus::InCannon)) {
2532 m_leanRot *= m_turningParams->leanRotDecayFactor;
2533 } else if (!state()->isDrifting()) {
2534 if (stickX <= 0.2f) {
2535 if (stickX >= -0.2f) {
2536 m_leanRot *= m_turningParams->leanRotDecayFactor;
2537 } else {
2539 extVelXFactor = m_turningParams->leanRotShallowFactor;
2540 }
2541 } else {
2543 extVelXFactor = -m_turningParams->leanRotShallowFactor;
2544 }
2545 } else {
2546 leanRotMax = m_turningParams->leanRotMaxDrift;
2547 leanRotMin = m_turningParams->leanRotMinDrift;
2548
2549 if (m_hopStickX == 1) {
2550 leanRotMin = -leanRotMax;
2551 leanRotMax = -m_turningParams->leanRotMinDrift;
2552 }
2553 if (m_hopStickX == -1) {
2554 if (stickX == 0.0f) {
2555 m_leanRot += (0.5f - m_leanRot) * 0.05f;
2556 } else {
2557 m_leanRot += m_turningParams->driftStickXFactor * stickX;
2558 extVelXFactor = -m_turningParams->leanRotShallowFactor * stickX;
2559 }
2560 } else if (stickX == 0.0f) {
2561 m_leanRot += (-0.5f - m_leanRot) * 0.05f;
2562 } else {
2563 m_leanRot += m_turningParams->driftStickXFactor * stickX;
2564 extVelXFactor = -m_turningParams->leanRotShallowFactor * stickX;
2565 }
2566 }
2567
2568 bool capped = false;
2569 if (leanRotMin <= m_leanRot) {
2570 if (leanRotMax < m_leanRot) {
2571 m_leanRot = leanRotMax;
2572 capped = true;
2573 }
2574 } else {
2575 m_leanRot = leanRotMin;
2576 capped = true;
2577 }
2578
2579 if (!capped) {
2580 dynamics()->setExtVel(dynamics()->extVel() + componentXAxis() * extVelXFactor);
2581 }
2582
2583 f32 leanRotScalar = state()->isDrifting() ? 0.065f : 0.05f;
2584
2586
2587 dynamics()->setAngVel2(dynamics()->angVel2() +
2588 EGG::Vector3f(m_standStillBoostRot, turn * wheelieRotFactor(),
2589 m_leanRot * leanRotScalar));
2590
2591 calcDive();
2592
2593 EGG::Vector3f top = m_up;
2594
2596 f32 scalar = (m_speed >= 0.0f) ? m_speedRatioCapped * 2.0f : 0.0f;
2597 scalar = std::min(1.0f, scalar);
2598 top = scalar * m_up + (1.0f - scalar) * EGG::Vector3f::ey;
2599
2600 if (std::numeric_limits<f32>::epsilon() < top.squaredLength()) {
2601 top.normalise();
2602 }
2603 }
2604
2605 dynamics()->setTop_(top);
2606}
2607
2613 static constexpr std::array<TurningParameters, 2> TURNING_PARAMS_ARRAY = {{
2614 {0.8f, 0.08f, 1.0f, 0.1f, 1.2f, 0.8f, 0.08f, 0.6f, 0.15f, 1.6f, 0.9f, 180},
2615 {1.0f, 0.1f, 1.0f, 0.05f, 1.5f, 0.7f, 0.08f, 0.6f, 0.15f, 1.3f, 0.9f, 180},
2616 }};
2617
2618 KartMove::setTurnParams();
2619
2620 if (param()->stats().driftType == KartParam::Stats::DriftType::Outside_Drift_Bike) {
2621 m_turningParams = &TURNING_PARAMS_ARRAY[0];
2622 } else if (param()->stats().driftType == KartParam::Stats::DriftType::Inside_Drift_Bike) {
2623 m_turningParams = &TURNING_PARAMS_ARRAY[1];
2624 }
2625
2626 if (System::RaceManager::Instance()->isStageReached(System::RaceManager::Stage::Race)) {
2627 m_leanRotInc = m_turningParams->leanRotIncRace;
2628 m_leanRotCap = m_turningParams->leanRotCapRace;
2629 } else {
2630 m_leanRotInc = m_turningParams->leanRotIncCountdown;
2631 m_leanRotCap = m_turningParams->leanRotCapCountdown;
2632 }
2633}
2634
2636void KartMoveBike::init(bool b1, bool b2) {
2637 KartMove::init(b1, b2);
2638
2639 m_leanRot = 0.0f;
2640 m_leanRotCap = 0.0f;
2641 m_leanRotInc = 0.0f;
2642 m_wheelieRot = 0.0f;
2643 m_maxWheelieRot = 0.0f;
2644 m_wheelieFrames = 0;
2646 m_autoHardStickXFrames = 0;
2647}
2648
2650void KartMoveBike::clear() {
2651 KartMove::clear();
2652 cancelWheelie();
2653}
2654
2658 constexpr u32 FAILED_WHEELIE_FRAMES = 15;
2659 constexpr f32 AUTO_WHEELIE_CANCEL_STICK_THRESHOLD = 0.85f;
2660
2662 m_wheelieCooldown = std::max(0, m_wheelieCooldown - 1);
2663
2664 auto &status = KartObjectProxy::status();
2665
2666 if (status.onBit(eStatus::Wheelie)) {
2667 bool cancelAutoWheelie = false;
2668
2669 if (status.offBit(eStatus::AutoDrift) ||
2670 EGG::Mathf::abs(state()->stickX()) <= AUTO_WHEELIE_CANCEL_STICK_THRESHOLD) {
2671 m_autoHardStickXFrames = 0;
2672 } else {
2673 if (++m_autoHardStickXFrames > 15) {
2674 cancelAutoWheelie = true;
2675 }
2676 }
2677
2679 if (m_turningParams->maxWheelieFrames < m_wheelieFrames || cancelAutoWheelie ||
2680 (!canWheelie() && FAILED_WHEELIE_FRAMES <= m_wheelieFrames)) {
2681 cancelWheelie();
2682 } else {
2683 m_wheelieRot += 0.01f;
2684 EGG::Vector3f angVel0 = dynamics()->angVel0();
2685 angVel0.x *= 0.9f;
2686 dynamics()->setAngVel0(angVel0);
2687 }
2688 } else if (0.0f < m_wheelieRot) {
2689 m_wheelieRotDec -= 0.001f;
2690 m_wheelieRotDec = std::max(-0.03f, m_wheelieRotDec);
2692 }
2693
2694 m_wheelieRot = std::max(0.0f, std::min(m_wheelieRot, m_maxWheelieRot));
2695
2696 f32 vel1DirUp = m_vel1Dir.dot(EGG::Vector3f::ey);
2697
2698 if (m_wheelieRot > 0.0f) {
2699 if (vel1DirUp <= 0.5f || m_wheelieFrames < FAILED_WHEELIE_FRAMES) {
2700 EGG::Vector3f angVel2 = dynamics()->angVel2();
2701 angVel2.x -= m_wheelieRot * (1.0f - EGG::Mathf::abs(vel1DirUp));
2702 dynamics()->setAngVel2(angVel2);
2703 } else {
2704 cancelWheelie();
2705 }
2706
2707 status.setBit(eStatus::WheelieRot);
2708 } else {
2709 status.resetBit(eStatus::WheelieRot);
2710 }
2711}
2712
2719 if (status().onBit(eStatus::AutoDrift)) {
2720 return;
2721 }
2722
2723 cancelWheelie();
2724}
2725
2731
2736 constexpr u16 MAX_MT_CHARGE = 270;
2737 constexpr u16 BASE_MT_CHARGE = 2;
2738 constexpr f32 BONUS_CHARGE_STICK_THRESHOLD = 0.4f;
2739 constexpr u16 EXTRA_MT_CHARGE = 3;
2740
2741 if (m_driftState != DriftState::ChargingMt) {
2742 return;
2743 }
2744
2745 m_mtCharge += BASE_MT_CHARGE;
2746
2747 f32 stickX = state()->stickX();
2748 if (-BONUS_CHARGE_STICK_THRESHOLD <= stickX) {
2749 if (BONUS_CHARGE_STICK_THRESHOLD < stickX && m_hopStickX == -1) {
2750 m_mtCharge += EXTRA_MT_CHARGE;
2751 }
2752 } else if (m_hopStickX != -1) {
2753 m_mtCharge += EXTRA_MT_CHARGE;
2754 }
2755
2756 if (m_mtCharge > MAX_MT_CHARGE) {
2757 m_mtCharge = MAX_MT_CHARGE;
2758 m_driftState = DriftState::ChargedMt;
2759 }
2760}
2761
2763void KartMoveBike::initOob() {
2764 KartMove::initOob();
2765 cancelWheelie();
2766}
2767
2771 constexpr s16 COOLDOWN_FRAMES = 20;
2772 bool dpadUp = inputs()->currentState().trickUp();
2773 auto &status = KartObjectProxy::status();
2774
2775 if (status.offBit(eStatus::Wheelie)) {
2776 if (dpadUp && status.onBit(eStatus::TouchingGround)) {
2778 eStatus::Hop, eStatus::DriftAuto, eStatus::InAction)) {
2779 return;
2780 }
2781
2782 if (m_wheelieCooldown > 0) {
2783 return;
2784 }
2785
2786 startWheelie();
2787 }
2788 } else if (inputs()->currentState().trickDown() && m_wheelieCooldown <= 0) {
2789 cancelWheelie();
2790 m_wheelieCooldown = COOLDOWN_FRAMES;
2791 }
2792}
2793
2794} // namespace Kart
@ COL_TYPE_MOVING_WATER
Koopa Cape and DS Yoshi Falls.
@ COL_TYPE_STICKY_ROAD
Player sticks if within 200 units (rBC stairs).
@ COL_TYPE_SPECIAL_WALL
Various other wall types, determined by variant.
#define KCL_TYPE_BIT(x)
#define KCL_TYPE_FLOOR
0x20E80FFF - Any KCL that the player or items can drive/land on.
A 3 x 4 matrix.
Definition Matrix.hh:8
void makeOrthonormalBasis(const Vector3f &v0, const Vector3f &v1)
Sets a 3x3 orthonormal basis for a local coordinate system.
Definition Matrix.cc:154
void setAxisRotation(f32 angle, const Vector3f &axis)
Rotates the matrix about an axis.
Definition Matrix.cc:180
Vector3f multVector33(const Vector3f &vec) const
Multiplies a 3x3 matrix by a vector.
Definition Matrix.cc:249
Vector3f multVector(const Vector3f &vec) const
Multiplies a vector by a matrix.
Definition Matrix.cc:225
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 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 bool onAllBit(Es... es) const
Checks if all of the corresponding bits for the provided enum values are on.
Definition BitFlag.hh:401
constexpr bool offAnyBit(Es... es) const
Checks if any of the corresponding bits for the provided enum values are off.
Definition BitFlag.hh:434
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
bool activate(Type type, s16 frames)
Starts/restarts a boost of the given type.
Definition KartBoost.cc:21
bool calc()
Computes the current frame's boost multiplier, acceleration, and speed limit.
Definition KartBoost.cc:38
void applyWrenchScaled(const EGG::Vector3f &p, const EGG::Vector3f &f, f32 scale)
Applies a force linearly and rotationally to the kart.
Handles the physics and boosts associated with zippers.
f32 m_leanRot
Z-axis rotation of the bike from leaning.
Definition KartMove.hh:530
f32 m_leanRotCap
The maximum leaning rotation.
Definition KartMove.hh:531
void calcWheelie() override
STAGE 1+ - Every frame, checks player input for wheelies and computes wheelie rotation.
Definition KartMove.cc:2657
void calcMtCharge() override
Every frame during a drift, calculates MT charge based on player input.
Definition KartMove.cc:2735
virtual void startWheelie()
STAGE 1+ - Sets the wheelie bit flag and some wheelie-related variables.
Definition KartMove.cc:2471
f32 m_wheelieRotDec
The wheelie rotation decrementor, used after a wheelie has ended.
Definition KartMove.hh:537
u32 m_wheelieFrames
Tracks wheelie duration and cancels the wheelie after 180 frames.
Definition KartMove.hh:535
f32 m_wheelieRot
X-axis rotation from wheeling.
Definition KartMove.hh:533
const TurningParameters * m_turningParams
Inside/outside drifting bike turn info.
Definition KartMove.hh:539
void setTurnParams() override
On init, sets the bike's lean rotation cap and increment.In addition to setting the lean rotation cap...
Definition KartMove.cc:2612
f32 m_maxWheelieRot
The maximum wheelie rotation.
Definition KartMove.hh:534
void tryStartWheelie()
STAGE 1+ - Every frame, checks player input to see if we should start or stop a wheelie.
Definition KartMove.cc:2770
void onWallCollision() override
Called when you collide with a wall. All it does for bikes is cancel wheelies.
Definition KartMove.cc:2728
void onHop() override
Virtual function that just cancels wheelies when you hop.
Definition KartMove.cc:2718
bool canWheelie() const override
Checks if the kart is going fast enough to wheelie.
Definition KartMove.hh:521
void calcVehicleRotation(f32) override
Every frame, calculates rotation, EV, and angular velocity for the bike.
Definition KartMove.cc:2502
s16 m_wheelieCooldown
The number of frames before another wheelie can start.
Definition KartMove.hh:536
f32 m_leanRotInc
The incrementor for leaning rotation.
Definition KartMove.hh:532
virtual void cancelWheelie()
Clears the wheelie bit flag and resets the rotation decrement.
Definition KartMove.cc:2486
f32 m_baseSpeed
The speed associated with the current character/vehicle stats.
Definition KartMove.hh:367
void calcRotation()
Every frame, calculates kart rotation based on player input.
Definition KartMove.cc:1184
s16 m_ssmtLeewayTimer
Frames to forgive letting go of A before clearing SSMT charge.
Definition KartMove.hh:409
s32 m_hopFrame
A timer that can prevent subsequent hops until reset.
Definition KartMove.hh:394
void calcDisableBackwardsAccel()
Computes the current cooldown duration between braking and reversing.
Definition KartMove.cc:758
EGG::Vector3f m_hopUp
The up vector when hopping.
Definition KartMove.hh:395
u16 m_mushroomBoostTimer
Number of frames until the mushroom boost runs out.
Definition KartMove.hh:417
void calcSpecialFloor()
Every frame, calculates any boost resulting from a boost panel.
Definition KartMove.cc:513
void calcWallCollisionStart(f32 param_2)
If we started to collide with a wall this frame, applies rotation.
Definition KartMove.cc:1606
KartHalfPipe * m_halfPipe
Pertains to zipper physics.
Definition KartMove.hh:448
f32 m_kclRotFactor
Float between 0-1 that scales the player's turning radius on offroad.
Definition KartMove.hh:389
f32 m_outsideDriftBonus
Added to angular velocity when outside drifting.
Definition KartMove.hh:402
void tryStartBoostRamp()
Sets offroad invincibility and enables the ramp boost bitfield flag.
Definition KartMove.cc:1993
void calcDeceleration()
Definition KartMove.cc:1373
u16 m_smtCharge
A value between 0 and 300 representing current SMT charge.
Definition KartMove.hh:401
f32 m_speedRatio
The ratio between current speed and the player's base speed stat.
Definition KartMove.hh:387
@ DriftReset
Set when a wall bonk should cancel your drift.
@ SsmtCharged
Set after holding a stand-still mini-turbo for 75 frames.
@ TrickableSurface
Set when driving on a trickable surface.
@ SsmtLeeway
If set, activates SSMT when not pressing A or B.
@ WallBounce
Set when our speed loss from wall collision is > 30.0f.
@ Respawned
Set when Lakitu lets go of the player, cleared when landing.
void tryStartJumpPad()
Applies calculations to start interacting with a jump pad.
Definition KartMove.cc:2011
f32 m_jumpPadMinSpeed
Snaps the player to a minimum speed when first touching a jump pad.
Definition KartMove.hh:422
f32 m_hopPosY
Relative position as the result of a hop. Starts at 0.
Definition KartMove.hh:436
DrivingDirection m_drivingDirection
Current state of driver's direction.
Definition KartMove.hh:443
u16 m_crushTimer
Number of frames until player will be uncrushed.
Definition KartMove.hh:420
bool calcPreDrift()
Each frame, checks for hop or slipdrift. Computes drift direction based on player input.
Definition KartMove.cc:832
f32 m_speed
Current speed, restricted to the soft speed limit.
Definition KartMove.hh:369
s16 m_offroadInvincibility
How many frames until the player is affected by offroad.
Definition KartMove.hh:407
void calcAirtimeTop()
Calculates rotation of the bike due to excessive airtime.
Definition KartMove.cc:488
void startManualDrift()
Called when the player lands from a drift hop, or to start a slipdrift.
Definition KartMove.cc:1083
void controlOutsideDriftAngle()
Every frame, handles mini-turbo charging and outside drifting bike rotation.
Definition KartMove.cc:1151
f32 m_softSpeedLimit
Base speed + boosts + wheelies, restricted to the hard speed limit.
Definition KartMove.hh:368
virtual void hop()
Initializes hop information, resets upwards EV and clears upwards force.
Definition KartMove.cc:1953
void calcStandstillBoostRot()
STAGE Computes the x-component of angular velocity based on the kart's speed.
Definition KartMove.cc:1672
EGG::Vector3f m_outsideDriftLastDir
Used to compute the next m_outsideDriftAngle.
Definition KartMove.hh:385
void calcManualDrift()
Each frame, handles hopping, drifting, and mini-turbos.
Definition KartMove.cc:1001
virtual void calcMtCharge()
Every frame during a drift, calculates MT/SMT charge based on player input.
Definition KartMove.cc:1888
void calcSsmt()
Calculates standstill mini-turbo components, if applicable.
Definition KartMove.cc:775
void calcAcceleration()
Every frame, applies acceleration to the kart's internal velocity.
Definition KartMove.cc:1421
f32 m_processedSpeed
Offset 0x28. It's only ever just a copy of m_speed.
Definition KartMove.hh:371
void releaseMt()
Stops charging a mini-turbo, and applies boost if charged.
Definition KartMove.cc:1125
f32 m_kclSpeedFactor
Float between 0-1 that scales the player's speed on offroad.
Definition KartMove.hh:388
f32 m_weightedTurn
Magnitude+direction of stick input, factoring in the kart's stats.
Definition KartMove.hh:412
void calcVehicleSpeed()
Every frame, computes speed based on acceleration and any active boosts.
Definition KartMove.cc:1281
f32 m_lastSpeed
Last frame's speed, cached to calculate angular velocity.
Definition KartMove.hh:370
s16 m_ssmtDisableAccelTimer
Counter that tracks delay before starting to reverse.
Definition KartMove.hh:410
void calcOffroadInvincibility()
Checks a timer to see if we are still ignoring offroad slowdown.
Definition KartMove.cc:2171
KartBurnout m_burnout
Manages the state of start boost burnout.
Definition KartMove.hh:450
f32 calcVehicleAcceleration() const
Every frame, computes acceleration based off the character/vehicle stats.
Definition KartMove.cc:1387
void calcAutoDrift()
Each frame, handles automatic transmission drifting.
Definition KartMove.cc:946
f32 m_realTurn
The "true" turn magnitude. Equal to m_weightedTurn unless drifting.
Definition KartMove.hh:411
const DriftingParameters * m_driftingParams
Drift-type-specific parameters.
Definition KartMove.hh:451
void clearDrift()
Definition KartMove.cc:885
void calc()
Each frame, calculates the kart's movement.
Definition KartMove.cc:287
EGG::Vector3f m_up
Vector perpendicular to the floor, pointing upwards.
Definition KartMove.hh:376
s16 m_ssmtCharge
Increments every frame up to 75 when charging stand-still MT.
Definition KartMove.hh:408
f32 m_speedDragMultiplier
After 5 frames of airtime, this causes speed to slowly decay.
Definition KartMove.hh:374
u16 m_mtCharge
A value between 0 and 270 representing current MT charge.
Definition KartMove.hh:400
void calcSsmtStart()
Calculates whether we are starting a standstill mini-turbo.
Definition KartMove.cc:1773
s16 m_respawnPostLandTimer
Counts up to 4 if not accelerating after respawn landing.
Definition KartMove.hh:440
virtual f32 getWheelieSoftSpeedLimitBonus() const
Returns the % speed boost from wheelies. For karts, this is always 0.
Definition KartMove.hh:103
f32 m_kclWheelRotFactor
The slowest rotation multiplier of each wheel's floor collision.
Definition KartMove.hh:391
void resetDriftManual()
Clears drift state. Called when touching ground and drift is canceled.
Definition KartMove.cc:874
f32 m_totalScale
[Unused] Always 1.0f
Definition KartMove.hh:414
f32 m_acceleration
Captures the acceleration from player input and boosts.
Definition KartMove.hh:373
virtual void calcVehicleRotation(f32 turn)
Every frame, calculates rotation, EV, and angular velocity for the kart.
Definition KartMove.cc:1839
s16 m_respawnPreLandTimer
Counts down from 4 when pressing A before landing from respawn.
Definition KartMove.hh:439
virtual void calcTurn()
Each frame, looks at player input and kart stats. Saves turn-related info.
Definition KartMove.cc:70
f32 m_divingRot
Induces x-axis angular velocity based on up/down stick input.
Definition KartMove.hh:397
f32 m_outsideDriftAngle
The facing angle of an outward-drifting vehicle.
Definition KartMove.hh:383
EGG::Vector3f m_lastDir
m_speed from the previous frame but with signed magnitude.
Definition KartMove.hh:379
EGG::Vector3f m_smoothedUp
A smoothed up vector, mostly used after significant airtime.
Definition KartMove.hh:375
bool canStartDrift() const
Definition KartMove.hh:131
s32 getAppliedHopStickX() const
Factors in vehicle speed to retrieve our hop direction and magnitude.
Definition KartMove.hh:224
u16 m_floorCollisionCount
The number of tires colliding with the floor.
Definition KartMove.hh:392
void calcDive()
Responds to player input to handle up/down kart tilt mid-air.
Definition KartMove.cc:1712
void calcOffroad()
Each frame, computes rotation and speed scalars from the floor KCL.
Definition KartMove.cc:667
s16 m_bumpTimer
Set when a Reaction::SmallBump collision occurs.
Definition KartMove.hh:442
KartScale * m_kartScale
Manages scaling due to TF stompers and MH cars.
Definition KartMove.hh:449
@ WaitingForBackwards
Holding reverse but waiting on a 15 frame delay.
s16 m_timeInRespawn
The number of frames elapsed after position snap from respawn.
Definition KartMove.hh:438
s32 m_hopStickX
A ternary for the direction of our hop, 0 if still neutral hopping.
Definition KartMove.hh:393
s16 m_backwardsAllowCounter
Tracks the 15f delay before reversing.
Definition KartMove.hh:444
f32 calcWallCollisionSpeedFactor(f32 &f1)
Every frame, computes a speed scalar if we are colliding with a wall.
Definition KartMove.cc:1573
void calcMushroomBoost()
Checks a timer to see if we are still boosting from a mushroom.
Definition KartMove.cc:2187
f32 m_hopGravity
Always main gravity (-1.3f).
Definition KartMove.hh:437
f32 m_hopVelY
Relative velocity due to a hop. Starts at 10 and decreases with gravity.
Definition KartMove.hh:435
f32 m_hardSpeedLimit
Absolute speed cap. It's 120, unless you're in a bullet (140).
Definition KartMove.hh:372
void setInitialPhysicsValues(const EGG::Vector3f &position, const EGG::Vector3f &angles)
Initializes the kart's position and rotation. Calls tire suspension initializers.
Definition KartMove.cc:250
f32 m_rawTurn
Float in range [-1, 1]. Represents stick magnitude + direction.
Definition KartMove.hh:452
void setOffroadInvincibility(s16 timer)
Ignores offroad KCL collision for a set amount of time.
Definition KartMove.cc:2160
f32 m_speedRatioCapped
m_speedRatio but capped at 1.0f.
Definition KartMove.hh:386
EGG::Vector3f m_scale
Normally the unit vector, but may vary due to crush animations.
Definition KartMove.hh:413
f32 m_kclWheelSpeedFactor
The slowest speed multiplier of each wheel's floor collision.
Definition KartMove.hh:390
EGG::Vector3f m_hopDir
Used for outward drift. Tracks the forward vector of our rotation.
Definition KartMove.hh:396
EGG::Vector3f bodyUp() const
Returns the second column of the rotation matrix, which is the "up" direction.
EGG::Vector3f bodyFront() const
Returns the third column of the rotation matrix, which is the facing vector.
Mainly responsible for calculating scaling for the squish/unsquish animation.
Definition KartScale.hh:9
EGG core library.
Definition Archive.cc:6
Pertains to kart-related functionality.
@ BikeSideStuntTrick
A side StuntTrick with a bike.
@ Wheelie
Set while we are in a wheelie (even during the countdown).
@ HopStart
Set if m_bDriftInput was toggled on this frame.
@ RejectRoad
Collision which causes a change in the player's pos and rot.
@ DisableBackwardsAccel
Enforces a 20f delay when reversing after charging SSMT.
@ HalfPipeRamp
Set while colliding with zipper KCL.
@ Boost
Set while in a boost.
@ BoostOffroadInvincibility
Set if we should ignore offroad slowdown this frame.
@ HWG
Set when "Horizontal Wall Glitch" is active.
@ ZipperTrick
Set while tricking mid-air from a zipper.
@ SlipdriftCharge
Currently in a drift w/ automatic.
@ Hop
Set while we are in a drift hop. Clears when we land.
@ MushroomBoost
Set while we are in a mushroom boost.
@ StickyRoad
Like the rBC stairs.
@ StickLeft
Set on left stick input. Mutually exclusive to m_bStickRight.
@ RespawnKillY
Set while respawning to cap external velocity at 0.
@ VehicleBodyFloorCollision
Set if the vehicle body is colliding with the floor.
@ RejectRoadTrigger
e.g. DK Summit ending, and Maple Treeway side walls.
@ GroundStart
Set first frame landing from airtime.
@ 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.
@ ZipperBoost
Set when boosting after landing from a zipper.
@ DriftManual
Currently in a drift w/ manual.
@ OverZipper
Set while mid-air from a zipper.
@ ChargingSSMT
Tracks whether we are charging a stand-still mini-turbo.
@ 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 quaternion, used to represent 3D rotation.
Definition Quat.hh:12
void normalise()
Scales the quaternion to a unit length.
Definition Quat.cc:28
Vector3f rotateVector(const Vector3f &vec) const
Rotates a vector based on the quat.
Definition Quat.cc:55
Quatf slerpTo(const Quatf &q2, f32 t) const
Performs spherical linear interpolation.
Definition Quat.cc:84
void setAxisRotation(f32 angle, const Vector3f &axis)
Set the quat given angle and axis.
Definition Quat.cc:111
f32 dot(const Quatf &q) const
Computes .
Definition Quat.hh:107
void makeVectorRotation(const Vector3f &from, const Vector3f &to)
Captures rotation between two vectors.
Definition Quat.cc:40
constexpr TBitFlag< T, E > & resetBit(Es... es)
Resets the corresponding bits for the provided enum values.
Definition BitFlag.hh:73
constexpr bool onBit(Es... es) const
Checks if any of the corresponding bits for the provided enum values are on.
Definition BitFlag.hh:108
constexpr TBitFlag< T, E > & changeBit(bool on, Es... es)
Changes the state of the corresponding bits for the provided enum values.
Definition BitFlag.hh:85
constexpr bool offBit(Es... es) const
Checks if all of the corresponding bits for the provided enum values are off.
Definition BitFlag.hh:141
constexpr void makeAllZero()
Resets all the bits to zero.
Definition BitFlag.hh:234
constexpr TBitFlag< T, E > & setBit(Es... es)
Sets the corresponding bits for the provided enum values.
Definition BitFlag.hh:62
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
f32 squaredLength() const
The dot product between the vector and itself.
Definition Vector.hh:182
Vector3f proj(const Vector3f &rhs) const
The projection of this vector onto rhs.
Definition Vector.hh:204
Vector3f perpInPlane(const EGG::Vector3f &rhs, bool normalise) const
Calculates the orthogonal vector, based on the plane defined by this vector and rhs.
Definition Vector.cc:114
Vector3f rej(const Vector3f &rhs) const
The rejection of this vector onto rhs.
Definition Vector.hh:210
Information about the current collision and its properties.
Various character/vehicle-related handling and speed stats.
Definition KartParam.hh:67
f32 driftManualTightness
Affects turn radius when manual drifting.
Definition KartParam.hh:106
std::array< f32, 32 > kclRot
Rotation scalars, indexed using KCL attributes.
Definition KartParam.hh:113
f32 driftAutomaticTightness
Affects turn radius when automatic drifting.
Definition KartParam.hh:107
std::array< f32, 32 > kclSpeed
Speed multipliers, indexed using KCL attributes.
Definition KartParam.hh:112
f32 driftReactivity
A weight applied to turn radius when drifting.
Definition KartParam.hh:108
f32 speed
Base full speed of the character/vehicle combo.
Definition KartParam.hh:96
f32 handlingReactivity
A weight applied to turn radius when not drifting.
Definition KartParam.hh:105
u32 miniTurbo
The framecount duration of a charged mini-turbo.
Definition KartParam.hh:111