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
15#include "game/item/ItemDirector.hh"
16#include "game/item/KartItem.hh"
17
18#include "game/system/CourseMap.hh"
19#include "game/system/RaceManager.hh"
20#include "game/system/map/MapdataCannonPoint.hh"
21#include "game/system/map/MapdataJugemPoint.hh"
22
23#include <egg/math/Math.hh>
24#include <egg/math/Quat.hh>
25
26namespace Kart {
27
29 f32 speed;
30 f32 height;
31 f32 decelFactor;
32 f32 endDecel;
33};
34
35static constexpr std::array<CannonParameter, 3> CANNON_PARAMETERS = {{
36 {500.0f, 0.0f, 6000.0f, -1.0f},
37 {500.0f, 5000.0f, 6000.0f, -1.0f},
38 {120.0f, 2000.0f, 1000.0f, 45.0f},
39}};
40
42KartMove::KartMove() : m_smoothedUp(EGG::Vector3f::ey), m_scale(1.0f, 1.0f, 1.0f) {
43 m_totalScale = 1.0f;
44 m_hitboxScale = 1.0f;
45 m_padType.makeAllZero();
46 m_flags.makeAllZero();
47 m_jump = nullptr;
48}
49
51KartMove::~KartMove() {
52 delete m_jump;
53 delete m_halfPipe;
54 delete m_kartScale;
55}
56
58void KartMove::createSubsystems() {
59 m_jump = new KartJump(this);
60 m_halfPipe = new KartHalfPipe();
61 m_kartScale = new KartScale();
62}
63
68 m_realTurn = 0.0f;
69 m_rawTurn = 0.0f;
70
71 if (state()->isInAction() || state()->isCannonStart() || state()->isInCannon() ||
72 state()->isOverZipper()) {
73 return;
74 }
75
76 if (state()->isBeforeRespawn()) {
77 return;
78 }
79
80 if (!state()->isHop() || m_hopStickX == 0) {
81 m_rawTurn = -state()->stickX();
82 if (state()->isJumpPadMushroomCollision()) {
83 m_rawTurn *= 0.35f;
84 } else if (state()->isAirtimeOver20()) {
85 m_rawTurn *= 0.01f;
86 }
87 } else {
88 m_rawTurn = static_cast<f32>(m_hopStickX);
89 }
90
91 f32 reactivity;
92 if (state()->isDrifting()) {
93 reactivity = param()->stats().driftReactivity;
94 } else {
95 reactivity = param()->stats().handlingReactivity;
96 }
97
98 m_weightedTurn = m_rawTurn * reactivity + m_weightedTurn * (1.0f - reactivity);
99 m_weightedTurn = std::max(-1.0f, std::min(1.0f, m_weightedTurn));
100
102
103 if (!state()->isDrifting()) {
104 return;
105 }
106
107 m_realTurn = (m_weightedTurn + static_cast<f32>(m_hopStickX)) * 0.5f;
108 m_realTurn = m_realTurn * 0.8f + 0.2f * static_cast<f32>(m_hopStickX);
109 m_realTurn = std::max(-1.0f, std::min(1.0f, m_realTurn));
110}
111
113void KartMove::setTurnParams() {
114 static constexpr std::array<DriftingParameters, 3> DRIFTING_PARAMS_ARRAY = {{
115 {10.0f, 0.5f, 0.5f, 1.0f},
116 {10.0f, 0.5f, 0.5f, 0.2f},
117 {10.0f, 0.22f, 0.5f, 0.2f},
118 }};
119
120 init(false, false);
121 m_dir = bodyFront();
122 m_lastDir = m_dir;
123 m_vel1Dir = m_dir;
124 m_landingDir = m_dir;
125 m_outsideDriftLastDir = m_dir;
126 m_driftingParams = &DRIFTING_PARAMS_ARRAY[static_cast<u32>(param()->stats().driftType)];
127 m_kartScale->reset();
128}
129
131void KartMove::init(bool b1, bool b2) {
132 m_lastSpeed = 0.0f;
133 m_baseSpeed = param()->stats().speed;
134 m_jumpPadSoftSpeedLimit = m_softSpeedLimit = param()->stats().speed;
135 m_speed = 0.0f;
136 setKartSpeedLimit();
137 m_acceleration = 0.0f;
139 m_up = EGG::Vector3f::ey;
140 m_smoothedUp = EGG::Vector3f::ey;
141 m_vel1Dir = EGG::Vector3f::ez;
142 m_lastDir = EGG::Vector3f::ez;
143 m_dir = EGG::Vector3f::ez;
144 m_landingDir = EGG::Vector3f::ez;
145 m_dirDiff = EGG::Vector3f::zero;
146 m_hasLandingDir = false;
147 m_outsideDriftAngle = 0.0f;
148 m_landingAngle = 0.0f;
149 m_outsideDriftLastDir = EGG::Vector3f::ez;
150 m_speedRatio = 0.0f;
151 m_speedRatioCapped = 0.0f;
152 m_kclSpeedFactor = 1.0f;
153 m_kclRotFactor = 1.0f;
155 m_kclWheelRotFactor = 1.0f;
156
157 if (!b2) {
159 }
160
161 m_hopStickX = 0;
162 m_hopFrame = 0;
163 m_hopUp = EGG::Vector3f::ey;
164 m_hopDir = EGG::Vector3f::ez;
165 m_divingRot = 0.0f;
166 m_standStillBoostRot = 0.0f;
167 m_driftState = DriftState::NotDrifting;
168 m_smtCharge = 0;
169 m_mtCharge = 0;
170 m_outsideDriftBonus = 0.0f;
171 m_boost.reset();
172 m_zipperBoostTimer = 0;
173 m_zipperBoostMax = 0;
174 m_reject.reset();
176 m_ssmtCharge = 0;
179 m_nonZipperAirtime = 0;
180 m_realTurn = 0.0f;
181 m_weightedTurn = 0.0f;
182
183 if (!b1) {
184 m_scale.set(1.0f);
185 m_totalScale = 1.0f;
186 m_hitboxScale = 1.0f;
188 m_crushTimer = 0;
189 }
190
191 m_jumpPadMinSpeed = 0.0f;
192 m_jumpPadMaxSpeed = 0.0f;
193 m_jumpPadBoostMultiplier = 0.0f;
194 m_jumpPadProperties = nullptr;
195 m_rampBoost = 0;
196 m_autoDriftAngle = 0.0f;
197 m_autoDriftStartFrameCounter = 0;
198
199 m_cannonEntryOfsLength = 0.0f;
200 m_cannonEntryPos.setZero();
201 m_cannonEntryOfs.setZero();
202 m_cannonOrthog.setZero();
203 m_cannonProgress.setZero();
204
205 m_hopVelY = 0.0f;
206 m_hopPosY = 0.0f;
207 m_hopGravity = 0.0f;
208 m_timeInRespawn = 0;
211 m_respawnTimer = 0;
212 m_bumpTimer = 0;
213 m_drivingDirection = DrivingDirection::Forwards;
214 m_padType.makeAllZero();
215 m_flags.makeAllZero();
216 m_jump->reset();
217 m_halfPipe->reset();
218 m_rawTurn = 0.0f;
219}
220
222void KartMove::clear() {
223 if (state()->isOverZipper()) {
224 state()->setActionMidZipper(true);
225 }
226
227 clearBoost();
228 clearJumpPad();
229 clearRampBoost();
230 clearZipperBoost();
231 clearSsmt();
232 clearOffroadInvincibility();
233 m_halfPipe->end(false);
234 m_jump->end();
235 clearRejectRoad();
236}
237
241 EGG::Quatf quaternion = EGG::Quatf::FromRPY(angles * DEG2RAD);
242 EGG::Vector3f newPos = position;
244 Field::KCLTypeMask kcl_flags = KCL_NONE;
245
246 bool bColliding = Field::CollisionDirector::Instance()->checkSphereFullPush(100.0f, newPos,
247 EGG::Vector3f::inf, KCL_ANY, &info, &kcl_flags, 0);
248
249 if (bColliding && (kcl_flags & KCL_TYPE_FLOOR)) {
250 newPos = newPos + info.tangentOff + (info.floorNrm * -100.0f);
251 newPos += info.floorNrm * bsp().initialYPos;
252 }
253
254 setPos(newPos);
255 setRot(quaternion);
256
257 sub()->initPhysicsValues();
258
259 physics()->setPos(pos());
260 physics()->setVelocity(dynamics()->velocity());
261
262 m_landingDir = bodyFront();
263 m_dir = bodyFront();
264 m_up = bodyUp();
265 dynamics()->setTop(m_up);
266
267 for (u16 tireIdx = 0; tireIdx < suspCount(); ++tireIdx) {
268 suspension(tireIdx)->setInitialState();
269 }
270}
271
278 if (state()->isInRespawn()) {
279 calcInRespawn();
280 return;
281 }
282
283 dynamics()->resetInternalVelocity();
284 m_burnout.calc();
286 m_halfPipe->calc();
287 calcTop();
288 tryEndJumpPad();
289 calcRespawnBoost();
291 m_jump->calc();
292
293 m_bumpTimer = std::max(m_bumpTimer - 1, 0);
294
296 calcDirs();
297 calcStickyRoad();
298 calcOffroad();
299 calcTurn();
300
301 if (!state()->isAutoDrift()) {
303 }
304
305 calcWheelie();
306 calcSsmt();
307 calcBoost();
309 calcZipperBoost();
310 calcCrushed();
311 calcScale();
312
313 if (state()->isInCannon()) {
314 calcCannon();
315 }
316
320 calcRotation();
321}
322
324void KartMove::calcRespawnStart() {
325 constexpr float RESPAWN_HEIGHT = 700.0f;
326
327 const auto *jugemPoint = System::RaceManager::Instance()->jugemPoint();
328 const EGG::Vector3f &jugemPos = jugemPoint->pos();
329 const EGG::Vector3f &jugemRot = jugemPoint->rot();
330
331 EGG::Vector3f respawnPos = jugemPos;
332 respawnPos.y += RESPAWN_HEIGHT;
333 EGG::Vector3f respawnRot = EGG::Vector3f(0.0f, jugemRot.y, 0.0f);
334
335 setInitialPhysicsValues(respawnPos, respawnRot);
336
337 Item::ItemDirector::Instance()->kartItem(0).clear();
338
339 state()->setTriggerRespawn(false);
340 state()->setInRespawn(true);
341}
342
344void KartMove::calcInRespawn() {
345 constexpr f32 LAKITU_VELOCITY = 1.5f;
346 constexpr u16 RESPAWN_DURATION = 110;
347
348 if (!state()->isInRespawn()) {
349 return;
350 }
351
352 EGG::Vector3f newPos = pos();
353 newPos.y -= LAKITU_VELOCITY;
354 dynamics()->setPos(newPos);
355 dynamics()->setNoGravity(true);
356
357 if (++m_timeInRespawn > RESPAWN_DURATION) {
358 state()->setInRespawn(false);
359 state()->setAfterRespawn(true);
360 state()->setRespawnKillY(true);
361 m_timeInRespawn = 0;
362 m_flags.setBit(eFlags::Respawned);
363 dynamics()->setNoGravity(false);
364 }
365}
366
368void KartMove::calcRespawnBoost() {
369 constexpr s16 RESPAWN_BOOST_DURATION = 30;
370 constexpr s16 RESPAWN_BOOST_INPUT_LENIENCY = 4;
371
372 if (state()->isAfterRespawn()) {
373 if (state()->isTouchingGround()) {
374 if (m_respawnPreLandTimer > 0) {
375 if (!state()->isBeforeRespawn() && !state()->isInAction()) {
376 activateBoost(KartBoost::Type::AllMt, RESPAWN_BOOST_DURATION);
377 m_respawnTimer = RESPAWN_BOOST_DURATION;
378 }
379 } else {
380 m_respawnPostLandTimer = RESPAWN_BOOST_INPUT_LENIENCY;
381 }
382
383 state()->setAfterRespawn(false);
385 }
386
388
389 if (m_flags.onBit(eFlags::Respawned) && state()->isAccelerateStart()) {
390 m_respawnPreLandTimer = RESPAWN_BOOST_INPUT_LENIENCY;
392 }
393 } else {
394 if (m_respawnPostLandTimer > 0) {
395 if (state()->isAccelerateStart()) {
396 if (!state()->isBeforeRespawn() && !state()->isInAction()) {
397 activateBoost(KartBoost::Type::AllMt, RESPAWN_BOOST_DURATION);
398 m_respawnTimer = RESPAWN_BOOST_DURATION;
399 }
400
402 }
403
405 } else {
406 state()->setRespawnKillY(false);
407 }
408 }
409
410 m_respawnTimer = std::max(0, m_respawnTimer - 1);
411}
412
414void KartMove::calcTop() {
415 f32 stabilizationFactor = 0.1f;
416 m_hasLandingDir = false;
417 EGG::Vector3f inputTop = state()->top();
418
419 if (state()->isGroundStart() && m_nonZipperAirtime >= 3) {
420 m_smoothedUp = inputTop;
421 m_up = inputTop;
422 m_landingDir = m_dir.perpInPlane(m_smoothedUp, true);
423 m_dirDiff = m_landingDir.proj(m_landingDir);
424 m_hasLandingDir = true;
425 } else {
426 if (state()->isHop() && m_hopPosY > 0.0f) {
427 stabilizationFactor = m_driftingParams->stabilizationFactor;
428 } else if (state()->isTouchingGround()) {
429 if ((m_flags.onBit(eFlags::TrickableSurface) || state()->trickableTimer() > 0) &&
430 inputTop.dot(m_dir) > 0.0f && m_speed > 50.0f &&
431 collide()->surfaceFlags().onBit(KartCollide::eSurfaceFlags::NotTrickable)) {
432 inputTop = m_up;
433 } else {
434 m_up = inputTop;
435 }
436
437 f32 scalar = 0.8f;
438
439 if (state()->isHalfPipeRamp() ||
440 (!state()->isBoost() && !state()->isRampBoost() && !state()->isWheelie() &&
441 !state()->isOverZipper() &&
442 (!state()->isZipperBoost() || m_zipperBoostTimer > 15))) {
443 f32 topDotZ = 0.8f - 6.0f * (EGG::Mathf::abs(inputTop.dot(componentZAxis())));
444 scalar = std::min(0.8f, std::max(0.3f, topDotZ));
445 }
446
447 m_smoothedUp += (inputTop - m_smoothedUp) * scalar;
449
450 f32 bodyDotFront = bodyFront().dot(m_smoothedUp);
451
452 if (bodyDotFront < -0.1f) {
453 stabilizationFactor += std::min(0.2f, EGG::Mathf::abs(bodyDotFront) * 0.5f);
454 }
455
456 if (collide()->surfaceFlags().onBit(KartCollide::eSurfaceFlags::BoostRamp)) {
457 stabilizationFactor = 0.4f;
458 }
459 } else {
461 }
462 }
463
464 dynamics()->setStabilizationFactor(stabilizationFactor);
465
466 m_nonZipperAirtime = state()->isOverZipper() ? 0 : state()->airtime();
467 m_flags.changeBit(collide()->surfaceFlags().onBit(KartCollide::eSurfaceFlags::Trickable),
469}
470
474 if (state()->isOverZipper() || !state()->isAirtimeOver20()) {
475 return;
476 }
477
478 if (m_smoothedUp.y <= 0.99f) {
479 m_smoothedUp += (EGG::Vector3f::ey - m_smoothedUp) * 0.03f;
481 } else {
482 m_smoothedUp = EGG::Vector3f::ey;
483 }
484
485 if (m_up.y <= 0.99f) {
486 m_up += (EGG::Vector3f::ey - m_up) * 0.03f;
487 m_up.normalise();
488 } else {
489 m_up = EGG::Vector3f::ey;
490 }
491}
492
497 const auto *raceMgr = System::RaceManager::Instance();
498 if (!raceMgr->isStageReached(System::RaceManager::Stage::Race)) {
499 return;
500 }
501
502 if (m_padType.onBit(ePadType::BoostPanel)) {
503 tryStartBoostPanel();
504 }
505
506 if (m_padType.onBit(ePadType::BoostRamp)) {
508 }
509
510 if (m_padType.onBit(ePadType::JumpPad)) {
512 }
513
514 m_padType.makeAllZero();
515}
516
518void KartMove::calcDirs() {
519 EGG::Vector3f right = dynamics()->mainRot().rotateVector(EGG::Vector3f::ex);
520 EGG::Vector3f local_88 = right.cross(m_smoothedUp);
521 local_88.normalise();
522 m_flags.setBit(eFlags::LaunchBoost);
523
524 if (!state()->isInATrick() && !state()->isOverZipper() &&
525 (((state()->isTouchingGround() || !state()->isRampBoost() ||
526 !m_jump->isBoostRampEnabled()) &&
527 !state()->isJumpPad() && state()->airtime() <= 5) ||
528 state()->isJumpPadMushroomCollision() || state()->isNoSparkInvisibleWall())) {
529 if (state()->isHop()) {
530 local_88 = m_hopDir;
531 }
532
533 EGG::Matrix34f mat;
534 mat.setAxisRotation(DEG2RAD * (m_autoDriftAngle + m_outsideDriftAngle + m_landingAngle),
536 EGG::Vector3f local_b8 = mat.multVector(local_88);
537 local_b8 = local_b8.perpInPlane(m_smoothedUp, true);
538
539 EGG::Vector3f dirDiff = local_b8 - m_dir;
540
541 if (dirDiff.squaredLength() <= std::numeric_limits<f32>::epsilon()) {
542 m_dir = local_b8;
543 m_dirDiff.setZero();
544 } else {
545 EGG::Vector3f origDirCross = m_dir.cross(local_b8);
546 m_dirDiff += m_kclRotFactor * dirDiff;
547 m_dir += m_dirDiff;
548 m_dir.normalise();
549 m_dirDiff *= 0.1f;
550 EGG::Vector3f newDirCross = m_dir.cross(local_b8);
551
552 if (origDirCross.dot(newDirCross) < 0.0f) {
553 m_dir = local_b8;
554 m_dirDiff.setZero();
555 }
556 }
557
558 m_vel1Dir = m_dir.perpInPlane(m_smoothedUp, true);
559 m_flags.resetBit(eFlags::LaunchBoost);
560 } else {
561 m_vel1Dir = m_dir;
562 }
563
564 if (!state()->isOverZipper()) {
565 m_jump->tryStart(m_smoothedUp.cross(m_dir));
566 }
567
568 if (m_hasLandingDir) {
569 f32 dot = m_dir.dot(m_landingDir);
570 EGG::Vector3f cross = m_dir.cross(m_landingDir);
571 f32 crossDot = cross.length();
572 f32 angle = EGG::Mathf::atan2(crossDot, dot);
573 angle = EGG::Mathf::abs(angle);
574
575 f32 fVar4 = 1.0f;
576 if (cross.dot(m_smoothedUp) < 0.0f) {
577 fVar4 = -1.0f;
578 }
579
580 m_landingAngle += (angle * RAD2DEG) * fVar4;
581 }
582
583 if (m_landingAngle <= 0.0f) {
584 if (m_landingAngle < 0.0f) {
585 m_landingAngle = std::min(0.0f, m_landingAngle + 2.0f);
586 }
587 } else {
588 m_landingAngle = std::max(0.0f, m_landingAngle - 2.0f);
589 }
590}
591
593void KartMove::calcStickyRoad() {
594 constexpr f32 STICKY_RADIUS = 200.0f;
595 constexpr Field::KCLTypeMask STICKY_MASK =
597
598 if (state()->isOverZipper()) {
599 state()->setStickyRoad(false);
600 return;
601 }
602
603 if ((!state()->isStickyRoad() &&
604 collide()->surfaceFlags().offBit(KartCollide::eSurfaceFlags::Trickable)) ||
605 EGG::Mathf::abs(m_speed) <= 20.0f) {
606 return;
607 }
608
609 EGG::Vector3f pos = dynamics()->pos();
610 EGG::Vector3f vel = dynamics()->movingObjVel() + m_speed * m_vel1Dir;
611 EGG::Vector3f down = -STICKY_RADIUS * componentYAxis();
612 Field::CollisionInfo colInfo;
613 colInfo.bbox.setZero();
614 Field::KCLTypeMask kcl_flags = KCL_NONE;
615 bool stickyRoad = false;
616
617 for (size_t i = 0; i < 3; ++i) {
618 EGG::Vector3f newPos = pos + vel;
619 if (Field::CollisionDirector::Instance()->checkSphereFull(STICKY_RADIUS, newPos,
620 EGG::Vector3f::inf, STICKY_MASK, &colInfo, &kcl_flags, 0)) {
621 m_vel1Dir = m_vel1Dir.perpInPlane(colInfo.floorNrm, true);
622 dynamics()->setMovingObjVel(dynamics()->movingObjVel().rej(colInfo.floorNrm));
623 stickyRoad = true;
624
625 break;
626 }
627 vel *= 0.5f;
628 pos += -STICKY_RADIUS * componentYAxis();
629 }
630
631 if (!stickyRoad) {
632 state()->setStickyRoad(false);
633 }
634}
635
640 if (state()->isBoostOffroadInvincibility()) {
641 m_kclSpeedFactor = 1.0f;
642 m_kclRotFactor = param()->stats().kclRot[0];
643 } else {
644 bool anyWheel = state()->isAnyWheelCollision();
645 if (anyWheel) {
649 }
650
651 if (state()->isVehicleBodyFloorCollision()) {
652 const CollisionData &colData = collisionData();
653 if (anyWheel) {
654 if (colData.speedFactor < m_kclWheelSpeedFactor) {
655 m_kclSpeedFactor = colData.speedFactor;
656 }
657 m_kclRotFactor = (m_kclWheelRotFactor + colData.rotFactor) /
658 static_cast<f32>(m_floorCollisionCount + 1);
659 } else {
660 m_kclSpeedFactor = colData.speedFactor;
661 m_kclRotFactor = colData.rotFactor;
662 }
663 }
664 }
665}
666
668void KartMove::calcBoost() {
669 if (m_boost.calc()) {
670 state()->setAccelerate(true);
671 } else {
672 state()->setBoost(false);
673 }
674
675 calcRampBoost();
676}
677
679void KartMove::calcRampBoost() {
680 if (!state()->isRampBoost()) {
681 return;
682 }
683
684 state()->setAccelerate(true);
685 if (--m_rampBoost < 1) {
686 m_rampBoost = 0;
687 state()->setRampBoost(false);
688 }
689}
690
695 if (!state()->isDisableBackwardsAccel()) {
696 return;
697 }
698
699 if (--m_ssmtDisableAccelTimer < 0 ||
700 (m_flags.offBit(eFlags::SsmtLeeway) && !state()->isBrake())) {
701 state()->setDisableBackwardsAccel(false);
703 }
704}
705
710 constexpr s16 MAX_SSMT_CHARGE = 75;
711 constexpr s16 SSMT_BOOST_FRAMES = 30;
712 constexpr s16 LEEWAY_FRAMES = 1;
713 constexpr s16 DISABLE_ACCEL_FRAMES = 20;
714
716
717 if (state()->isChargingSsmt()) {
718 if (++m_ssmtCharge > MAX_SSMT_CHARGE) {
719 m_ssmtCharge = MAX_SSMT_CHARGE;
722 }
723
724 return;
725 }
726
727 m_ssmtCharge = 0;
728
729 if (m_flags.offBit(eFlags::SsmtCharged)) {
730 return;
731 }
732
733 if (m_flags.onBit(eFlags::SsmtLeeway)) {
734 if (--m_ssmtLeewayTimer < 0) {
737 m_ssmtDisableAccelTimer = DISABLE_ACCEL_FRAMES;
738 state()->setDisableBackwardsAccel(true);
739 } else {
740 if (!state()->isAccelerate() && !state()->isBrake()) {
741 activateBoost(KartBoost::Type::AllMt, SSMT_BOOST_FRAMES);
744 }
745 }
746 } else {
747 if (state()->isAccelerate() && !state()->isBrake()) {
748 activateBoost(KartBoost::Type::AllMt, SSMT_BOOST_FRAMES);
751 } else {
752 m_ssmtLeewayTimer = LEEWAY_FRAMES;
753 m_flags.setBit(eFlags::SsmtLeeway);
754 state()->setDisableBackwardsAccel(true);
755 m_ssmtDisableAccelTimer = LEEWAY_FRAMES;
756 }
757 }
758}
759
765 if (!state()->isTouchingGround() && !state()->isHop() && !state()->isDriftManual()) {
766 if (state()->isStickLeft() || state()->isStickRight()) {
767 if (!state()->isDriftInput()) {
768 state()->setSlipdriftCharge(false);
769 } else if (!state()->isSlipdriftCharge()) {
770 if (m_hopStickX == 0) {
771 if (state()->isStickRight()) {
772 m_hopStickX = -1;
773 } else if (state()->isStickLeft()) {
774 m_hopStickX = 1;
775 }
776 state()->setSlipdriftCharge(true);
777 onHop();
778 }
779 }
780 }
781 }
782
783 if (state()->isHop()) {
784 if (m_hopStickX == 0) {
785 if (state()->isStickRight()) {
786 m_hopStickX = -1;
787 } else if (state()->isStickLeft()) {
788 m_hopStickX = 1;
789 }
790 }
791 if (m_hopFrame < 3) {
792 ++m_hopFrame;
793 }
794 } else if (state()->isSlipdriftCharge()) {
795 m_hopFrame = 0;
796 }
797
798 return state()->isHop() || state()->isSlipdriftCharge();
799}
800
805 m_hopStickX = 0;
806 m_hopFrame = 0;
807 state()->setHop(false);
808 state()->setDriftManual(false);
809 m_driftState = DriftState::NotDrifting;
810 m_smtCharge = 0;
811 m_mtCharge = 0;
812}
813
818 m_outsideDriftAngle = 0.0f;
819 m_hopStickX = 0;
820 m_hopFrame = 0;
821 m_driftState = DriftState::NotDrifting;
822 m_smtCharge = 0;
823 m_mtCharge = 0;
824 m_outsideDriftBonus = 0.0f;
825 state()->setHop(false);
826 state()->setSlipdriftCharge(false);
827 state()->setDriftManual(false);
828 state()->setDriftAuto(false);
829 m_autoDriftAngle = 0.0f;
830 m_hopStickX = 0;
831 m_autoDriftStartFrameCounter = 0;
832}
833
835void KartMove::clearJumpPad() {
836 m_jumpPadMinSpeed = 0.0f;
837 state()->setJumpPad(false);
838}
839
841void KartMove::clearRampBoost() {
842 m_rampBoost = 0;
843 state()->setRampBoost(false);
844}
845
847void KartMove::clearZipperBoost() {
848 m_zipperBoostTimer = 0;
849 state()->setZipperBoost(false);
850}
851
853void KartMove::clearBoost() {
854 m_boost.resetActive();
855 state()->setBoost(false);
856}
857
859void KartMove::clearSsmt() {
860 m_ssmtCharge = 0;
864}
865
867void KartMove::clearOffroadInvincibility() {
869 state()->setBoostOffroadInvincibility(false);
870}
871
872void KartMove::clearRejectRoad() {
873 state()->setRejectRoadTrigger(false);
874 state()->setNoSparkInvisibleWall(false);
875}
876
881 constexpr s16 AUTO_DRIFT_START_DELAY = 12;
882
883 if (!state()->isAutoDrift()) {
884 return;
885 }
886
887 if (canStartDrift() && !state()->isOverZipper() && !state()->isRejectRoadTrigger() &&
888 !state()->isWheelie() && EGG::Mathf::abs(state()->stickX()) > 0.85f) {
889 m_autoDriftStartFrameCounter =
890 std::min<s16>(AUTO_DRIFT_START_DELAY, m_autoDriftStartFrameCounter + 1);
891 } else {
892 m_autoDriftStartFrameCounter = 0;
893 }
894
895 if (m_autoDriftStartFrameCounter >= AUTO_DRIFT_START_DELAY) {
896 state()->setDriftAuto(true);
897
898 if (state()->isTouchingGround()) {
899 if (state()->stickX() < 0.0f) {
900 m_hopStickX = 1;
901 m_autoDriftAngle -= 30.0f * param()->stats().driftAutomaticTightness;
902
903 } else {
904 m_hopStickX = -1;
905 m_autoDriftAngle += 30.0f * param()->stats().driftAutomaticTightness;
906 }
907 }
908
909 f32 halfTarget = 0.5f * param()->stats().driftOutsideTargetAngle;
910 m_autoDriftAngle = std::min(halfTarget, std::max(-halfTarget, m_autoDriftAngle));
911 } else {
912 state()->setDriftAuto(false);
913 m_hopStickX = 0;
914
915 if (m_autoDriftAngle > 0.0f) {
916 m_autoDriftAngle =
917 std::max(0.0f, m_autoDriftAngle - param()->stats().driftOutsideDecrement);
918 } else {
919 m_autoDriftAngle =
920 std::min(0.0f, m_autoDriftAngle + param()->stats().driftOutsideDecrement);
921 }
922 }
923
924 EGG::Quatf angleAxis;
925 angleAxis.setAxisRotation(-m_autoDriftAngle * DEG2RAD, m_up);
926 physics()->composeExtraRot(angleAxis);
927}
928
933 bool isHopping = calcPreDrift();
934
935 if (!state()->isOverZipper()) {
936 const EGG::Vector3f rotZ = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
937
938 if (!state()->isTouchingGround() &&
939 param()->stats().driftType != KartParam::Stats::DriftType::Inside_Drift_Bike &&
940 !state()->isJumpPadMushroomCollision() &&
941 (state()->isDriftManual() || state()->isSlipdriftCharge()) &&
942 m_flags.onBit(eFlags::LaunchBoost)) {
943 const EGG::Vector3f up = dynamics()->mainRot().rotateVector(EGG::Vector3f::ey);
945
946 if (driftRej.normalise() != 0.0f) {
947 f32 rejCrossDirMag = driftRej.cross(rotZ).length();
948 f32 angle = EGG::Mathf::atan2(rejCrossDirMag, driftRej.dot(rotZ));
949 f32 sign = 1.0f;
950 if ((rotZ.z * (rotZ.x - driftRej.x)) - (rotZ.x * (rotZ.z - driftRej.z)) > 0.0f) {
951 sign = -1.0f;
952 }
953
954 m_outsideDriftAngle += angle * RAD2DEG * sign;
955 }
956 }
957
959 }
960
961 // TODO: Is this backwards/inverted?
962 if (((!state()->isHop() || m_hopFrame < 3) && !state()->isSlipdriftCharge()) ||
963 (state()->isInAction() || !state()->isTouchingGround())) {
964 if (canHop()) {
965 hop();
966 isHopping = true;
967 }
968 } else {
970 isHopping = false;
971 }
972
974
975 if (!state()->isDriftManual()) {
976 if (!isHopping && state()->isTouchingGround()) {
978
979 if (action()->flags().offBit(KartAction::eFlags::Rotating) || m_speed <= 20.0f) {
980 f32 driftAngleDecr = param()->stats().driftOutsideDecrement;
981 if (m_outsideDriftAngle > 0.0f) {
982 m_outsideDriftAngle = std::max(0.0f, m_outsideDriftAngle - driftAngleDecr);
983 } else if (m_outsideDriftAngle < 0.0f) {
984 m_outsideDriftAngle = std::min(0.0f, m_outsideDriftAngle + driftAngleDecr);
985 }
986 }
987 }
988 } else {
989 if (!state()->isOverZipper() &&
990 (!state()->isDriftInput() || !state()->isAccelerate() || state()->isInAction() ||
991 state()->isRejectRoadTrigger() || state()->isWall3Collision() ||
992 state()->isWallCollision() || !canStartDrift())) {
993 if (canStartDrift()) {
994 releaseMt();
995 }
996
998 m_flags.setBit(eFlags::DriftReset);
999 } else {
1001 }
1002 }
1003}
1004
1009 constexpr f32 OUTSIDE_DRIFT_BONUS = 0.5f;
1010
1011 const auto &stats = param()->stats();
1012
1013 if (stats.driftType != KartParam::Stats::DriftType::Inside_Drift_Bike) {
1014 f32 driftAngle = 0.0f;
1015
1016 if (state()->isHop()) {
1017 const EGG::Vector3f rotZ = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1018 EGG::Vector3f rotRej = rotZ.rej(m_hopUp);
1019
1020 if (rotRej.normalise() != 0.0f) {
1021 const EGG::Vector3f hopCrossRot = m_hopDir.cross(rotRej);
1022 driftAngle =
1023 EGG::Mathf::atan2(hopCrossRot.length(), m_hopDir.dot(rotRej)) * RAD2DEG;
1024 }
1025 }
1026
1027 m_outsideDriftAngle += driftAngle * static_cast<f32>(-m_hopStickX);
1028 m_outsideDriftAngle = std::max(-60.0f, std::min(60.0f, m_outsideDriftAngle));
1029 }
1030
1031 state()->setHop(false);
1032 state()->setSlipdriftCharge(false);
1033
1034 if (!state()->isDriftInput()) {
1035 return;
1036 }
1037
1038 if (getAppliedHopStickX() == 0) {
1039 return;
1040 }
1041
1042 state()->setDriftManual(true);
1043 state()->setHop(false);
1044 m_driftState = DriftState::ChargingMt;
1045 m_outsideDriftBonus = OUTSIDE_DRIFT_BONUS * (m_speedRatioCapped * stats.driftManualTightness);
1046}
1047
1052 constexpr f32 SMT_LENGTH_FACTOR = 3.0f;
1053
1054 if (m_driftState < DriftState::ChargedMt || state()->isBrake()) {
1055 m_driftState = DriftState::NotDrifting;
1056 return;
1057 }
1058
1059 u16 mtLength = param()->stats().miniTurbo;
1060
1061 if (m_driftState == DriftState::ChargedSmt) {
1062 mtLength *= SMT_LENGTH_FACTOR;
1063 }
1064
1065 if (!state()->isBeforeRespawn() && !state()->isInAction()) {
1066 activateBoost(KartBoost::Type::AllMt, mtLength);
1067 }
1068
1069 m_driftState = DriftState::NotDrifting;
1070}
1071
1076 if (state()->airtime() > 5) {
1077 return;
1078 }
1079
1080 if (param()->stats().driftType != KartParam::Stats::DriftType::Inside_Drift_Bike) {
1081 if (m_hopStickX == -1) {
1082 f32 angle = m_outsideDriftAngle;
1083 f32 targetAngle = param()->stats().driftOutsideTargetAngle;
1084 if (angle > targetAngle) {
1085 m_outsideDriftAngle = std::max(m_outsideDriftAngle - 2.0f, targetAngle);
1086 } else if (angle < targetAngle) {
1087 m_outsideDriftAngle += 150.0f * param()->stats().driftManualTightness;
1088 m_outsideDriftAngle = std::min(m_outsideDriftAngle, targetAngle);
1089 }
1090 } else if (m_hopStickX == 1) {
1091 f32 angle = m_outsideDriftAngle;
1092 f32 targetAngle = -param()->stats().driftOutsideTargetAngle;
1093 if (targetAngle > angle) {
1094 m_outsideDriftAngle = std::min(m_outsideDriftAngle + 2.0f, targetAngle);
1095 } else if (targetAngle < angle) {
1096 m_outsideDriftAngle -= 150.0f * param()->stats().driftManualTightness;
1097 m_outsideDriftAngle = std::max(m_outsideDriftAngle, targetAngle);
1098 }
1099 }
1100 }
1101
1102 calcMtCharge();
1103}
1104
1109 f32 turn;
1110 bool drifting = state()->isDrifting() && !state()->isJumpPadMushroomCollision();
1111 bool autoDrift = state()->isAutoDrift();
1112 const auto &stats = param()->stats();
1113
1114 if (drifting) {
1115 turn = autoDrift ? stats.driftAutomaticTightness : stats.driftManualTightness;
1116 } else {
1117 turn = autoDrift ? stats.handlingAutomaticTightness : stats.handlingManualTightness;
1118 }
1119
1120 if (drifting && stats.driftType != KartParam::Stats::DriftType::Inside_Drift_Bike) {
1121 m_outsideDriftBonus *= 0.99f;
1122 turn += m_outsideDriftBonus;
1123 }
1124
1125 bool forwards = true;
1126 if (state()->isBrake() && m_speed <= 0.0f) {
1127 forwards = false;
1128 }
1129
1130 turn *= m_realTurn;
1131 if (state()->isChargingSsmt()) {
1132 turn = m_realTurn * 0.04f;
1133 } else {
1134 if (state()->isHop() && m_hopPosY > 0.0f) {
1135 turn *= 1.4f;
1136 }
1137
1138 if (!drifting) {
1139 bool noTurn = false;
1140 if (!state()->isWallCollision() && !state()->isWall3Collision() &&
1141 EGG::Mathf::abs(m_speed) < 1.0f) {
1142 if (!(state()->isHop() && m_hopPosY > 0.0f)) {
1143 turn = 0.0f;
1144 noTurn = true;
1145 }
1146 }
1147 if (forwards && !noTurn) {
1148 if (m_speed >= 20.0f) {
1149 turn *= 0.5f;
1150 if (m_speed < 70.0f) {
1151 turn += (1.0f - (m_speed - 20.0f) / 50.0f) * turn;
1152 }
1153 } else {
1154 turn = (turn * 0.4f) + (m_speed / 20.0f) * (turn * 0.6f);
1155 }
1156 }
1157 }
1158
1159 if (!forwards) {
1160 turn = -turn;
1161 }
1162
1163 if (state()->isZipperBoost() && !state()->isDriftManual()) {
1164 turn *= 2.0f;
1165 }
1166
1167 f32 stickX = EGG::Mathf::abs(state()->stickX());
1168 if (autoDrift && stickX > 0.3f) {
1169 f32 stickScalar = (stickX - 0.3f) / 0.7f;
1170 stickX = drifting ? 0.2f : 0.5f;
1171 turn += stickScalar * (turn * stickX * m_speedRatioCapped);
1172 }
1173 }
1174
1175 if (!state()->isInAction() && !state()->isZipperTrick()) {
1176 if (!state()->isTouchingGround()) {
1177 if (state()->isRampBoost() && m_jump->isBoostRampEnabled()) {
1178 turn = 0.0f;
1179 } else if (!state()->isJumpPadMushroomCollision()) {
1180 u32 airtime = state()->airtime();
1181 if (airtime >= 70) {
1182 turn = 0.0f;
1183 } else if (airtime >= 30) {
1184 turn = std::max(0.0f, turn * (1.0f - (airtime - 30) * 0.025f));
1185 }
1186 }
1187 }
1188
1189 const EGG::Vector3f forward = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1190 f32 angle = EGG::Mathf::atan2(forward.cross(m_dir).length(), forward.dot(m_dir));
1191 angle = EGG::Mathf::abs(angle) * RAD2DEG;
1192
1193 if (angle > 60.0f) {
1194 turn *= std::max(0.0f, 1.0f - (angle - 60.0f) / 40.0f);
1195 }
1196 }
1197
1198 calcVehicleRotation(turn);
1199}
1200
1205 const auto *raceMgr = System::RaceManager::Instance();
1206 if (raceMgr->isStageReached(System::RaceManager::Stage::Race)) {
1207 f32 speedFix = dynamics()->speedFix();
1208 if (state()->isInAction() ||
1209 ((state()->isWallCollisionStart() || state()->wallBonkTimer() == 0 ||
1210 EGG::Mathf::abs(speedFix) >= 3.0f) &&
1211 !state()->isDriftManual())) {
1212 m_speed += speedFix;
1213 }
1214 }
1215
1216 if (m_speed < -20.0f) {
1217 m_speed += 0.5f;
1218 }
1219
1220 m_acceleration = 0.0f;
1221 m_speedDragMultiplier = 1.0f;
1222
1223 if (state()->isInAction()) {
1224 action()->calcVehicleSpeed();
1225 return;
1226 }
1227
1228 if ((state()->isSomethingWallCollision() && state()->isTouchingGround() &&
1229 !state()->isAnyWheelCollision()) ||
1230 !state()->isTouchingGround() || state()->isChargingSsmt()) {
1231 if (state()->isRampBoost() && state()->airtime() < 4) {
1232 m_acceleration = 7.0f;
1233 } else {
1234 if (state()->isJumpPad() && !state()->isAccelerate()) {
1235 m_speedDragMultiplier = 0.99f;
1236 } else {
1237 if (state()->isOverZipper()) {
1238 m_speedDragMultiplier = 0.999f;
1239 } else {
1240 if (state()->airtime() > 5) {
1241 m_speedDragMultiplier = 0.999f;
1242 }
1243 }
1244 }
1246 }
1247
1248 } else if (state()->isBoost()) {
1249 m_acceleration = m_boost.acceleration();
1250 } else {
1251 if (!state()->isJumpPad() && !state()->isRampBoost()) {
1252 if (state()->isAccelerate()) {
1253 m_acceleration = state()->isHalfPipeRamp() ? 5.0f : calcVehicleAcceleration();
1254 } else {
1255 if (!state()->isBrake() || state()->isDisableBackwardsAccel() ||
1256 state()->isSomethingWallCollision()) {
1257 m_speed *= m_speed > 0.0f ? 0.98f : 0.95f;
1258 } else if (m_drivingDirection == DrivingDirection::Braking) {
1259 m_acceleration = -1.5f;
1261 if (++m_backwardsAllowCounter > 15) {
1262 m_drivingDirection = DrivingDirection::Backwards;
1263 }
1264 } else if (m_drivingDirection == DrivingDirection::Backwards) {
1265 m_acceleration = -2.0f;
1266 }
1267 }
1268
1269 if (!state()->isBoost() && !state()->isDriftManual() && !state()->isAutoDrift()) {
1270 const auto &stats = param()->stats();
1271
1272 f32 x = 1.0f - EGG::Mathf::abs(m_weightedTurn) * m_speedRatioCapped;
1273 m_speed *= stats.turningSpeed + (1.0f - stats.turningSpeed) * x;
1274 }
1275 } else {
1276 m_acceleration = 7.0f;
1277 }
1278 }
1279}
1280
1284 f32 vel = 0.0f;
1285 f32 initialVel = 1.0f - m_smoothedUp.y;
1286 if (EGG::Mathf::abs(m_speed) < 30.0f && m_smoothedUp.y > 0.0f && initialVel > 0.0f) {
1287 initialVel = std::min(initialVel * 2.0f, 2.0f);
1288 vel += initialVel;
1289 vel *= std::min(0.5f, std::max(-0.5f, -bodyFront().y));
1290 }
1291 m_speed += vel;
1292}
1293
1298 f32 ratio = m_speed / m_softSpeedLimit;
1299 if (ratio < 0.0f) {
1300 return 1.0f;
1301 }
1302
1303 std::span<const f32> as;
1304 std::span<const f32> ts;
1305 if (state()->isDrifting()) {
1306 as = param()->stats().accelerationDriftA;
1307 ts = param()->stats().accelerationDriftT;
1308 } else {
1309 as = param()->stats().accelerationStandardA;
1310 ts = param()->stats().accelerationStandardT;
1311 }
1312
1313 size_t i = 0;
1314 f32 acceleration = 0.0f;
1315 f32 t_curr = 0.0f;
1316 for (; i < ts.size(); ++i) {
1317 if (ratio < ts[i]) {
1318 acceleration = as[i] + ((as[i + 1] - as[i]) / (ts[i] - t_curr)) * (ratio - t_curr);
1319 break;
1320 }
1321
1322 t_curr = ts[i];
1323 }
1324
1325 return i < ts.size() ? acceleration : as.back();
1326}
1327
1332 constexpr f32 ROTATION_SCALAR_NORMAL = 0.5f;
1333 constexpr f32 ROTATION_SCALAR_MIDAIR = 0.2f;
1334 constexpr f32 ROTATION_SCALAR_BOOST_RAMP = 4.0f;
1335 constexpr f32 OOB_SLOWDOWN_RATE = 0.95f;
1336 constexpr f32 TERMINAL_VELOCITY = 90.0f;
1337
1339
1340 dynamics()->setKillExtVelY(state()->isRespawnKillY());
1341
1342 if (state()->isBurnout()) {
1343 m_speed = 0.0f;
1344 } else {
1345 if (m_acceleration < 0.0f) {
1346 if (m_speed < -20.0f) {
1347 m_acceleration = 0.0f;
1348 } else {
1349 if (m_speed + m_acceleration <= -20.0f) {
1350 m_acceleration = -20.0f - m_speed;
1351 }
1352 }
1353 }
1354
1356 }
1357
1358 if (state()->isBeforeRespawn()) {
1359 m_speed *= OOB_SLOWDOWN_RATE;
1360 } else {
1361 if (state()->isChargingSsmt()) {
1362 m_speed *= 0.8f;
1363 } else {
1364 if (m_drivingDirection == DrivingDirection::Braking && m_speed < 0.0f) {
1365 m_speed = 0.0f;
1368 }
1369 }
1370 }
1371
1372 f32 speedLimit = state()->isJumpPad() ? m_jumpPadMaxSpeed : m_baseSpeed;
1373 const f32 boostMultiplier = m_boost.multiplier();
1374 const f32 boostSpdLimit = m_boost.speedLimit();
1375 m_jumpPadBoostMultiplier = boostMultiplier;
1376
1377 f32 crushMultiplier = state()->isCrushed() ? 0.7f : 1.0f;
1378 f32 wheelieBonus = boostMultiplier + getWheelieSoftSpeedLimitBonus();
1379 speedLimit *= state()->isJumpPadFixedSpeed() ?
1380 1.0f :
1381 crushMultiplier * (wheelieBonus * m_kclSpeedFactor);
1382
1383 bool ignoreCrushSpeed = state()->isRampBoost() || state()->isZipperInvisibleWall() ||
1384 state()->isOverZipper() || state()->isHalfPipeRamp();
1385 f32 boostSpeed = ignoreCrushSpeed ? 1.0f : crushMultiplier;
1386 boostSpeed *= boostSpdLimit * m_kclSpeedFactor;
1387
1388 if (!state()->isJumpPad() && boostSpeed > 0.0f && boostSpeed > speedLimit) {
1389 speedLimit = boostSpeed;
1390 }
1391
1392 m_jumpPadSoftSpeedLimit = boostSpdLimit * m_kclSpeedFactor;
1393
1394 if (state()->isRampBoost()) {
1395 speedLimit = std::max(speedLimit, 100.0f);
1396 }
1397
1398 m_lastDir = (m_speed > 0.0f) ? 1.0f * m_dir : -1.0f * m_dir;
1399
1400 f32 local_c8 = 1.0f;
1401 speedLimit *= calcWallCollisionSpeedFactor(local_c8);
1402
1403 if (m_softSpeedLimit <= speedLimit) {
1404 m_softSpeedLimit = speedLimit;
1405 } else if (!state()->isWallCollision() && !state()->isWall3Collision()) {
1406 m_softSpeedLimit = std::max(m_softSpeedLimit - 3.0f, speedLimit);
1407 } else {
1408 m_softSpeedLimit = speedLimit;
1409 }
1410
1412
1413 m_speed = std::min(m_softSpeedLimit, std::max(-m_softSpeedLimit, m_speed));
1414
1415 if (state()->isJumpPad()) {
1416 m_speed = std::max(m_speed, m_jumpPadMinSpeed);
1417 }
1418
1419 calcWallCollisionStart(local_c8);
1420
1421 m_speedRatio = EGG::Mathf::abs(m_speed / m_baseSpeed);
1422 m_speedRatioCapped = std::min(1.0f, m_speedRatio);
1423
1424 EGG::Vector3f crossVec = m_smoothedUp.cross(m_dir);
1425 if (m_speed < 0.0f) {
1426 crossVec = -crossVec;
1427 }
1428
1429 f32 rotationScalar = ROTATION_SCALAR_NORMAL;
1430 if (collide()->surfaceFlags().onBit(KartCollide::eSurfaceFlags::BoostRamp)) {
1431 rotationScalar = ROTATION_SCALAR_BOOST_RAMP;
1432 } else if (!state()->isTouchingGround()) {
1433 rotationScalar = ROTATION_SCALAR_MIDAIR;
1434 }
1435
1436 EGG::Matrix34f local_90;
1437 local_90.setAxisRotation(rotationScalar * DEG2RAD, crossVec);
1438 m_vel1Dir = local_90.multVector33(m_vel1Dir);
1439
1440 const auto *raceMgr = System::RaceManager::Instance();
1441 if (!state()->isInAction() && !state()->isDisableBackwardsAccel() &&
1442 state()->isTouchingGround() && !state()->isAccelerate() &&
1443 raceMgr->isStageReached(System::RaceManager::Stage::Race)) {
1445 }
1446
1448 EGG::Vector3f nextSpeed = m_speed * m_vel1Dir;
1449
1450 f32 maxSpeedY = state()->isOverZipper() ? KartHalfPipe::TerminalVelocity() : TERMINAL_VELOCITY;
1451 nextSpeed.y = std::min(nextSpeed.y, maxSpeedY);
1452
1453 dynamics()->setIntVel(dynamics()->intVel() + nextSpeed);
1454
1455 if (state()->isTouchingGround() && !state()->isDriftManual() && !state()->isHop()) {
1456 if (state()->isBrake()) {
1457 if (m_drivingDirection == DrivingDirection::Forwards) {
1458 m_drivingDirection = m_processedSpeed > 5.0f ? DrivingDirection::Braking :
1459 DrivingDirection::Backwards;
1460 }
1461 } else {
1462 if (m_processedSpeed >= 0.0f) {
1463 m_drivingDirection = DrivingDirection::Forwards;
1464 }
1465 }
1466 } else {
1467 m_drivingDirection = DrivingDirection::Forwards;
1468 }
1469}
1470
1475 if (!state()->isWallCollision() && !state()->isWall3Collision()) {
1476 return 1.0f;
1477 }
1478
1479 onWallCollision();
1480
1481 if (state()->isZipperInvisibleWall() || state()->isOverZipper()) {
1482 return 1.0f;
1483 }
1484
1485 EGG::Vector3f wallNrm = collisionData().wallNrm;
1486 if (wallNrm.y > 0.0f) {
1487 wallNrm.y = 0.0f;
1488 wallNrm.normalise();
1489 }
1490
1491 f32 dot = m_lastDir.dot(wallNrm);
1492
1493 if (dot < 0.0f) {
1494 f1 = std::max(0.0f, dot + 1.0f);
1495
1496 return std::min(1.0f, f1 * (state()->isWallCollision() ? 0.4f : 0.7f));
1497 }
1498
1499 return 1.0f;
1500}
1501
1507
1508 if (!state()->isWallCollisionStart()) {
1509 return;
1510 }
1511
1512 m_outsideDriftAngle = 0.0f;
1513 if (!state()->isInAction()) {
1514 m_dir = bodyFront();
1515 m_vel1Dir = m_dir;
1516 m_landingDir = m_dir;
1517 }
1518
1519 if (!state()->isZipperInvisibleWall() && !state()->isOverZipper() && param_2 < 0.9f) {
1520 f32 speedDiff = m_lastSpeed - m_speed;
1521 const CollisionData &colData = collisionData();
1522
1523 if (speedDiff > 30.0f) {
1524 m_flags.setBit(eFlags::WallBounce);
1525 EGG::Vector3f newPos = colData.relPos + pos();
1526 f32 dot = -bodyUp().dot(colData.relPos) * 0.5f;
1527 EGG::Vector3f scaledUp = dot * bodyUp();
1528 newPos -= scaledUp;
1529
1530 speedDiff = std::min(60.0f, speedDiff);
1531 EGG::Vector3f scaledWallNrm = speedDiff * colData.wallNrm;
1532
1533 auto [proj, rej] = scaledWallNrm.projAndRej(m_vel1Dir);
1534 proj *= 0.3f;
1535 rej *= 0.9f;
1536
1537 if (state()->isBoost()) {
1538 proj = EGG::Vector3f::zero;
1539 rej = EGG::Vector3f::zero;
1540 }
1541
1542 if (bodyFront().dot(colData.wallNrm) > 0.0f) {
1543 proj = EGG::Vector3f::zero;
1544 }
1545 rej *= 0.9f;
1546
1547 EGG::Vector3f projRejSum = proj + rej;
1548 f32 bumpDeviation = 0.0f;
1549 if (m_flags.offBit(eFlags::DriftReset) && state()->isTouchingGround()) {
1550 bumpDeviation = param()->stats().bumpDeviationLevel;
1551 }
1552
1553 dynamics()->applyWrenchScaled(newPos, projRejSum, bumpDeviation);
1554 } else if (wallKclType() == COL_TYPE_SPECIAL_WALL && wallKclVariant() == 2) {
1555 dynamics()->addForce(colData.wallNrm * 15.0f);
1556 collide()->startFloorMomentRate();
1557 }
1558 }
1559}
1560
1565 f32 next = 0.0f;
1566 f32 scalar = 1.0f;
1567
1568 if (state()->isTouchingGround()) {
1569 if (System::RaceManager::Instance()->stage() == System::RaceManager::Stage::Countdown) {
1570 next = 0.015f * -state()->startBoostCharge();
1571 } else if (!state()->isChargingSsmt()) {
1572 if (!state()->isJumpPad() && !state()->isRampBoost() && !state()->isSoftWallDrift()) {
1573 f32 speedDiff = m_lastSpeed - m_speed;
1574 scalar = std::min(3.0f, std::max(speedDiff, -3.0f));
1575
1576 if (state()->isMushroomBoost()) {
1577 next = (scalar * 0.15f) * 0.25f;
1578 if (state()->isWheelie()) {
1579 next *= 0.5f;
1580 }
1581 } else {
1582 next = (scalar * 0.15f) * 0.08f;
1583 }
1584 scalar = m_driftingParams->boostRotFactor;
1585 }
1586 } else {
1587 constexpr s16 MAX_SSMT_CHARGE = 75;
1588 next = 0.015f * (-static_cast<f32>(m_ssmtCharge) / static_cast<f32>(MAX_SSMT_CHARGE));
1589 }
1590 }
1591
1592 if (m_flags.onBit(eFlags::WallBounce)) {
1593 m_standStillBoostRot = isBike() ? next * 3.0f : next * 10.0f;
1594 } else {
1595 m_standStillBoostRot += scalar * (next - m_standStillBoostRot);
1596 }
1597}
1598
1603 constexpr f32 DIVE_LIMIT = 0.8f;
1604
1605 m_divingRot *= 0.96f;
1606
1607 if (state()->isTouchingGround() || state()->isCannonStart() || state()->isInCannon() ||
1608 state()->isInAction() || state()->isOverZipper()) {
1609 return;
1610 }
1611
1612 f32 stickY = state()->stickY();
1613
1614 if (state()->isInATrick() && m_jump->type() == TrickType::BikeSideStuntTrick) {
1615 stickY = std::min(1.0f, stickY + 0.4f);
1616 }
1617
1618 u32 airtime = state()->airtime();
1619
1620 if (airtime > 50) {
1621 if (EGG::Mathf::abs(stickY) < 0.1f) {
1622 m_divingRot += 0.05f * (-0.025f - m_divingRot);
1623 }
1624 } else {
1625 stickY *= (airtime / 50.0f);
1626 }
1627
1628 m_divingRot = std::max(-DIVE_LIMIT, std::min(DIVE_LIMIT, m_divingRot + stickY * 0.005f));
1629
1630 EGG::Vector3f angVel2 = dynamics()->angVel2();
1631 angVel2.x += m_divingRot;
1632 dynamics()->setAngVel2(angVel2);
1633
1634 if (state()->airtime() < 50) {
1635 return;
1636 }
1637
1638 EGG::Vector3f topRotated = dynamics()->mainRot().rotateVector(EGG::Vector3f::ey);
1639 EGG::Vector3f forwardRotated = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1640 f32 upDotTop = m_up.dot(topRotated);
1641 EGG::Vector3f upCrossTop = m_up.cross(topRotated);
1642 f32 crossNorm = upCrossTop.length();
1643 f32 angle = EGG::Mathf::abs(EGG::Mathf::atan2(crossNorm, upDotTop));
1644
1645 f32 fVar1 = angle * RAD2DEG - 20.0f;
1646 if (fVar1 <= 0.0f) {
1647 return;
1648 }
1649
1650 f32 mult = std::min(1.0f, fVar1 / 20.0f);
1651 if (forwardRotated.y > 0.0f) {
1652 dynamics()->setGravity((1.0f - 0.2f * mult) * dynamics()->gravity());
1653 } else {
1654 dynamics()->setGravity((0.2f * mult + 1.0f) * dynamics()->gravity());
1655 }
1656}
1657
1662 if (EGG::Mathf::abs(m_speed) >= 10.0f || state()->isBoost() || state()->isRampBoost() ||
1663 !state()->isAccelerate() || !state()->isBrake()) {
1664 state()->setChargingSsmt(false);
1665 return;
1666 }
1667
1668 state()->setChargingSsmt(true);
1669 state()->setHopStart(false);
1670 state()->setDriftInput(false);
1671}
1672
1674void KartMove::calcHopPhysics() {
1675 m_hopVelY = m_hopVelY * 0.998f + m_hopGravity;
1677
1678 if (m_hopPosY < 0.0f) {
1679 m_hopPosY = 0.0f;
1680 m_hopVelY = 0.0f;
1681 }
1682}
1683
1685void KartMove::calcRejectRoad() {
1686 m_reject.calcRejectRoad();
1687}
1688
1690bool KartMove::calcZipperCollision(f32 radius, f32 scale, EGG::Vector3f &pos,
1691 EGG::Vector3f &upLocal, const EGG::Vector3f &prevPos, Field::CollisionInfo *colInfo,
1692 Field::KCLTypeMask *maskOut, Field::KCLTypeMask flags) const {
1693 upLocal = mainRot().rotateVector(EGG::Vector3f::ey);
1694 pos = dynamics()->pos() + (-scale * m_scale.y) * upLocal;
1695
1696 auto *colDir = Field::CollisionDirector::Instance();
1697 return colDir->checkSphereFullPush(radius, pos, prevPos, flags, colInfo, maskOut, 0);
1698}
1699
1701f32 KartMove::calcSlerpRate(f32 scale, const EGG::Quatf &from, const EGG::Quatf &to) const {
1702 f32 dotNorm = std::max(-1.0f, std::min(1.0f, from.dot(to)));
1703 f32 acos = EGG::Mathf::acos(dotNorm);
1704 return acos > 0.0f ? std::min(0.1f, scale / acos) : 0.1f;
1705}
1706
1710 f32 tiltMagnitude = 0.0f;
1711
1712 if (!state()->isInAction() && !state()->isSoftWallDrift() && state()->isAnyWheelCollision()) {
1713 EGG::Vector3f front = componentZAxis();
1714 front = front.perpInPlane(m_up, true);
1715 EGG::Vector3f frontSpeed = velocity().rej(front).perpInPlane(m_up, false);
1716 f32 magnitude = tiltMagnitude;
1717
1718 if (frontSpeed.squaredLength() > std::numeric_limits<f32>::epsilon()) {
1719 magnitude = frontSpeed.length();
1720
1721 if (front.z * frontSpeed.x - front.x * frontSpeed.z > 0.0f) {
1722 magnitude = -magnitude;
1723 }
1724
1725 tiltMagnitude = -1.0f;
1726 if (-1.0f <= magnitude) {
1727 tiltMagnitude = std::min(1.0f, magnitude);
1728 }
1729 }
1730 } else if (!state()->isHop() || m_hopPosY <= 0.0f) {
1731 EGG::Vector3f angVel0 = dynamics()->angVel0();
1732 angVel0.z *= 0.98f;
1733 dynamics()->setAngVel0(angVel0);
1734 }
1735
1736 f32 lean = EGG::Mathf::abs(m_weightedTurn) * (tiltMagnitude * param()->stats().tilt);
1737
1739
1740 EGG::Vector3f angVel0 = dynamics()->angVel0();
1741 angVel0.x += m_standStillBoostRot;
1742 angVel0.z += lean;
1743 dynamics()->setAngVel0(angVel0);
1744
1745 EGG::Vector3f angVel2 = dynamics()->angVel2();
1746 angVel2.y += turn;
1747 dynamics()->setAngVel2(angVel2);
1748
1749 calcDive();
1750}
1751
1756 // TODO: Some of these are shared between the base and derived class implementations.
1757 constexpr u16 MAX_MT_CHARGE = 270;
1758 constexpr u16 MAX_SMT_CHARGE = 300;
1759 constexpr u16 BASE_MT_CHARGE = 2;
1760 constexpr u16 BASE_SMT_CHARGE = 2;
1761 constexpr f32 BONUS_CHARGE_STICK_THRESHOLD = 0.4f;
1762 constexpr u16 EXTRA_MT_CHARGE = 3;
1763
1764 if (m_driftState == DriftState::ChargedSmt) {
1765 return;
1766 }
1767
1768 f32 stickX = state()->stickX();
1769
1770 if (m_driftState == DriftState::ChargingMt) {
1771 m_mtCharge += BASE_MT_CHARGE;
1772
1773 if (-BONUS_CHARGE_STICK_THRESHOLD <= stickX) {
1774 if (BONUS_CHARGE_STICK_THRESHOLD < stickX && m_hopStickX == -1) {
1775 m_mtCharge += EXTRA_MT_CHARGE;
1776 }
1777 } else if (m_hopStickX != -1) {
1778 m_mtCharge += EXTRA_MT_CHARGE;
1779 }
1780
1781 if (m_mtCharge > MAX_MT_CHARGE) {
1782 m_mtCharge = MAX_MT_CHARGE;
1783 m_driftState = DriftState::ChargingSmt;
1784 }
1785 }
1786
1787 if (m_driftState != DriftState::ChargingSmt) {
1788 return;
1789 }
1790
1791 m_smtCharge += BASE_SMT_CHARGE;
1792
1793 if (-BONUS_CHARGE_STICK_THRESHOLD <= stickX) {
1794 if (BONUS_CHARGE_STICK_THRESHOLD < stickX && m_hopStickX == -1) {
1795 m_smtCharge += EXTRA_MT_CHARGE;
1796 }
1797 } else if (m_hopStickX != -1) {
1798 m_smtCharge += EXTRA_MT_CHARGE;
1799 }
1800
1801 if (m_smtCharge > MAX_SMT_CHARGE) {
1802 m_smtCharge = MAX_SMT_CHARGE;
1803 m_driftState = DriftState::ChargedSmt;
1804 }
1805}
1806
1808void KartMove::initOob() {
1809 clearBoost();
1810 clearJumpPad();
1811 clearRampBoost();
1812 clearZipperBoost();
1813 clearSsmt();
1814 clearOffroadInvincibility();
1815}
1816
1821 state()->setHop(true);
1822 state()->setDriftManual(false);
1823 onHop();
1824
1825 m_hopUp = dynamics()->mainRot().rotateVector(EGG::Vector3f::ey);
1826 m_hopDir = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1827 m_driftState = DriftState::NotDrifting;
1828 m_smtCharge = 0;
1829 m_mtCharge = 0;
1830 m_hopStickX = 0;
1831 m_hopFrame = 0;
1832 m_hopPosY = 0.0f;
1833 m_hopGravity = dynamics()->gravity();
1834 m_hopVelY = m_driftingParams->hopVelY;
1835 m_outsideDriftBonus = 0.0f;
1836
1837 EGG::Vector3f extVel = dynamics()->extVel();
1838 extVel.y = 0.0f + m_hopVelY;
1839 dynamics()->setExtVel(extVel);
1840
1841 EGG::Vector3f totalForce = dynamics()->totalForce();
1842 totalForce.y = 0.0f;
1843 dynamics()->setTotalForce(totalForce);
1844}
1845
1847void KartMove::tryStartBoostPanel() {
1848 constexpr s16 BOOST_PANEL_DURATION = 60;
1849
1850 if (state()->isBeforeRespawn() || state()->isInAction()) {
1851 return;
1852 }
1853
1854 activateBoost(KartBoost::Type::MushroomAndBoostPanel, BOOST_PANEL_DURATION);
1855 setOffroadInvincibility(BOOST_PANEL_DURATION);
1856}
1857
1862 constexpr s16 BOOST_RAMP_DURATION = 60;
1863
1864 if (state()->isBeforeRespawn() || state()->isInAction()) {
1865 return;
1866 }
1867
1868 state()->setRampBoost(true);
1869 m_rampBoost = BOOST_RAMP_DURATION;
1870 setOffroadInvincibility(BOOST_RAMP_DURATION);
1871}
1872
1878 static constexpr std::array<JumpPadProperties, 8> JUMP_PAD_PROPERTIES = {{
1879 {50.0f, 50.0f, 35.0f},
1880 {50.0f, 50.0f, 47.0f},
1881 {59.0f, 59.0f, 30.0f},
1882 {73.0f, 73.0f, 45.0f},
1883 {73.0f, 73.0f, 53.0f},
1884 {55.0f, 55.0f, 35.0f},
1885 {56.0f, 56.0f, 50.0f},
1886 }};
1887
1888 if (state()->isBeforeRespawn() || state()->isInAction() || state()->isHalfPipeRamp()) {
1889 return;
1890 }
1891
1892 state()->setJumpPad(true);
1893 s32 jumpPadVariant = state()->jumpPadVariant();
1894 m_jumpPadProperties = &JUMP_PAD_PROPERTIES[jumpPadVariant];
1895
1896 if (jumpPadVariant == 3 || jumpPadVariant == 4) {
1897 if (m_jumpPadBoostMultiplier > 1.3f || m_jumpPadSoftSpeedLimit > 110.0f) {
1898 // Set speed to 100 if the player has boost from a boost panel or mushroom(item) before
1899 // hitting the jump pad
1900 static constexpr std::array<JumpPadProperties, 2> JUMP_PAD_PROPERTIES_SHROOM_BOOST = {{
1901 {100.0f, 100.0f, 70.0f},
1902 {100.0f, 100.0f, 65.0f},
1903 }};
1904 m_jumpPadProperties = &JUMP_PAD_PROPERTIES_SHROOM_BOOST[jumpPadVariant != 3];
1905 }
1906 state()->setJumpPadFixedSpeed(true);
1907 }
1908
1909 if (jumpPadVariant == 4) {
1910 state()->setJumpPadMushroomTrigger(true);
1911 state()->setJumpPadMushroomVelYInc(true);
1912 state()->setJumpPadMushroomCollision(true);
1913 } else {
1914 EGG::Vector3f extVel = dynamics()->extVel();
1915 EGG::Vector3f totalForce = dynamics()->totalForce();
1916
1917 extVel.y = m_jumpPadProperties->velY;
1918 totalForce.y = 0.0f;
1919
1920 dynamics()->setExtVel(extVel);
1921 dynamics()->setTotalForce(totalForce);
1922
1923 if (jumpPadVariant != 3) {
1924 EGG::Vector3f dir = m_dir;
1925 dir.y = 0.0f;
1926 dir.normalise();
1927 m_speed *= m_dir.dot(dir);
1928 m_dir = dir;
1929 m_vel1Dir = dir;
1930 state()->setJumpPadDisableYsusForce(true);
1931 }
1932 }
1933
1934 m_jumpPadMinSpeed = m_jumpPadProperties->minSpeed;
1935 m_jumpPadMaxSpeed = m_jumpPadProperties->maxSpeed;
1936 m_speed = std::max(m_speed, m_jumpPadMinSpeed);
1937}
1938
1940void KartMove::tryEndJumpPad() {
1941 if (state()->isJumpPadMushroomTrigger()) {
1942 if (state()->isGroundStart()) {
1943 state()->setJumpPadMushroomTrigger(false);
1944 state()->setJumpPadFixedSpeed(false);
1945 state()->setJumpPadMushroomVelYInc(false);
1946 }
1947
1948 if (state()->isJumpPadMushroomVelYInc()) {
1949 EGG::Vector3f newExtVel = dynamics()->extVel();
1950 newExtVel.y += 20.0f;
1951 if (m_jumpPadProperties->velY < newExtVel.y) {
1952 newExtVel.y = m_jumpPadProperties->velY;
1953 state()->setJumpPadMushroomVelYInc(false);
1954 }
1955 dynamics()->setExtVel(newExtVel);
1956 }
1957 }
1958
1959 if (state()->isGroundStart() && !state()->isJumpPadMushroomTrigger()) {
1960 cancelJumpPad();
1961 }
1962}
1963
1965void KartMove::cancelJumpPad() {
1966 m_jumpPadMinSpeed = 0.0f;
1967 state()->setJumpPad(false);
1968}
1969
1971void KartMove::activateBoost(KartBoost::Type type, s16 frames) {
1972 if (m_boost.activate(type, frames)) {
1973 state()->setBoost(true);
1974 }
1975}
1976
1978void KartMove::applyStartBoost(s16 frames) {
1979 activateBoost(KartBoost::Type::AllMt, frames);
1980}
1981
1983void KartMove::activateMushroom() {
1984 constexpr s16 MUSHROOM_DURATION = 90;
1985
1986 if (state()->isBeforeRespawn() || state()->isInAction()) {
1987 return;
1988 }
1989
1990 activateBoost(KartBoost::Type::MushroomAndBoostPanel, MUSHROOM_DURATION);
1991
1992 m_mushroomBoostTimer = MUSHROOM_DURATION;
1993 state()->setMushroomBoost(true);
1994 setOffroadInvincibility(MUSHROOM_DURATION);
1995}
1996
1998void KartMove::activateZipperBoost() {
1999 constexpr s16 BASE_DURATION = 50;
2000 constexpr s16 TRICK_DURATION = 100;
2001
2002 if (state()->isBeforeRespawn() || state()->isInAction()) {
2003 return;
2004 }
2005
2006 s16 boostDuration = state()->isZipperTrick() ? TRICK_DURATION : BASE_DURATION;
2007 activateBoost(KartBoost::Type::TrickAndZipper, boostDuration);
2008
2009 setOffroadInvincibility(boostDuration);
2010 m_zipperBoostTimer = 0;
2011 m_zipperBoostMax = boostDuration;
2012 state()->setZipperBoost(true);
2013}
2014
2020 if (timer > m_offroadInvincibility) {
2021 m_offroadInvincibility = timer;
2022 }
2023
2024 state()->setBoostOffroadInvincibility(true);
2025}
2026
2031 if (!state()->isBoostOffroadInvincibility()) {
2032 return;
2033 }
2034
2035 if (--m_offroadInvincibility > 0) {
2036 return;
2037 }
2038
2039 state()->setBoostOffroadInvincibility(false);
2040}
2041
2045 if (!state()->isMushroomBoost()) {
2046 return;
2047 }
2048
2049 if (--m_mushroomBoostTimer > 0) {
2050 return;
2051 }
2052
2053 state()->setMushroomBoost(false);
2054}
2055
2057void KartMove::calcZipperBoost() {
2058 if (!state()->isZipperBoost()) {
2059 return;
2060 }
2061
2062 state()->setAccelerate(true);
2063
2064 if (!state()->isOverZipper() && ++m_zipperBoostTimer >= m_zipperBoostMax) {
2065 m_zipperBoostTimer = 0;
2066 state()->setZipperBoost(false);
2067 }
2068
2069 if (m_zipperBoostTimer < 10) {
2070 EGG::Vector3f angVel = dynamics()->angVel0();
2071 angVel.y = 0.0f;
2072 dynamics()->setAngVel0(angVel);
2073 }
2074}
2075
2077void KartMove::landTrick() {
2078 static constexpr std::array<s16, 3> KART_TRICK_BOOST_DURATION = {{
2079 40,
2080 70,
2081 85,
2082 }};
2083 static constexpr std::array<s16, 3> BIKE_TRICK_BOOST_DURATION = {{
2084 45,
2085 80,
2086 95,
2087 }};
2088
2089 if (state()->isBeforeRespawn() || state()->isInAction()) {
2090 return;
2091 }
2092
2093 s16 duration;
2094 if (isBike()) {
2095 duration = BIKE_TRICK_BOOST_DURATION[static_cast<u32>(m_jump->variant())];
2096 } else {
2097 duration = KART_TRICK_BOOST_DURATION[static_cast<u32>(m_jump->variant())];
2098 }
2099
2100 activateBoost(KartBoost::Type::TrickAndZipper, duration);
2101}
2102
2104void KartMove::activateCrush(u16 timer) {
2105 state()->setCrushed(true);
2106 m_crushTimer = timer;
2107 m_kartScale->startCrush();
2108}
2109
2111void KartMove::calcCrushed() {
2112 if (!state()->isCrushed()) {
2113 return;
2114 }
2115
2116 if (--m_crushTimer == 0) {
2117 state()->setCrushed(false);
2118 m_kartScale->startUncrush();
2119 }
2120}
2121
2123void KartMove::calcScale() {
2124 m_kartScale->calc();
2125 setScale(m_kartScale->currScale());
2126}
2127
2129void KartMove::applyBumpForce(f32 speed, const EGG::Vector3f &hitDir, bool resetSpeed) {
2130 constexpr s16 BUMP_COOLDOWN = 5;
2131
2132 if (m_bumpTimer >= 1) {
2133 return;
2134 }
2135
2136 dynamics()->addForce(speed * hitDir.perpInPlane(move()->up(), true));
2137 collide()->startFloorMomentRate();
2138
2139 m_bumpTimer = BUMP_COOLDOWN;
2140
2141 if (resetSpeed) {
2142 m_speed = 0.0f;
2143 }
2144}
2145
2147void KartMove::enterCannon() {
2148 init(true, true);
2149 physics()->clearDecayingRot();
2150 m_boost.resetActive();
2151 state()->setBoost(false);
2152
2153 cancelJumpPad();
2154 clearRampBoost();
2155 clearZipperBoost();
2156 clearSsmt();
2157 clearOffroadInvincibility();
2158
2159 dynamics()->reset();
2160
2161 clearDrift();
2162 state()->setHop(false);
2163 state()->setInCannon(true);
2164 state()->setSkipWheelCalc(true);
2165 state()->setCannonStart(false);
2166
2167 const auto [cannonPos, cannonDir] = getCannonPosRot();
2168 m_cannonEntryPos = pos();
2169 m_cannonEntryOfs = cannonPos - pos();
2170 m_cannonEntryOfsLength = m_cannonEntryOfs.normalise();
2171 m_cannonEntryOfs.normalise();
2172 m_dir = m_cannonEntryOfs;
2173 m_vel1Dir = m_cannonEntryOfs;
2174 m_cannonOrthog = EGG::Vector3f::ey.perpInPlane(m_cannonEntryOfs, true);
2175 m_cannonProgress.setZero();
2176}
2177
2179void KartMove::calcCannon() {
2180 auto [cannonPos, cannonDir] = getCannonPosRot();
2181 EGG::Vector3f forwardXZ = cannonPos - m_cannonEntryPos - m_cannonProgress;
2182 EGG::Vector3f forward = forwardXZ;
2183 f32 forwardLength = forward.normalise();
2184 forwardXZ.y = 0;
2185 forwardXZ.normalise();
2186 EGG::Vector3f local94 = m_cannonEntryOfs;
2187 local94.y = 0;
2188 local94.normalise();
2189 m_speedRatioCapped = 1.0f;
2190 m_speedRatio = 1.5f;
2191 EGG::Matrix34f cannonOrientation;
2192 cannonOrientation.makeOrthonormalBasis(forward, EGG::Vector3f::ey);
2193 EGG::Vector3f up = cannonOrientation.multVector33(EGG::Vector3f::ey);
2194 m_smoothedUp = up;
2195 m_up = up;
2196
2197 if (forwardLength < 30.0f || local94.dot(forwardXZ) <= 0.0f) {
2198 exitCannon();
2199 return;
2200 }
2202 const auto *cannonPoint =
2203 System::CourseMap::Instance()->getCannonPoint(state()->cannonPointId());
2204 size_t cannonParameterIdx = std::max<s16>(0, cannonPoint->parameterIdx());
2205 ASSERT(cannonParameterIdx < CANNON_PARAMETERS.size());
2206 const auto &cannonParams = CANNON_PARAMETERS[cannonParameterIdx];
2207 f32 newSpeed = cannonParams.speed;
2208 if (forwardLength < cannonParams.decelFactor) {
2209 f32 factor = std::max(0.0f, forwardLength / cannonParams.decelFactor);
2210
2211 newSpeed = cannonParams.endDecel;
2212 if (newSpeed <= 0.0f) {
2213 newSpeed = m_baseSpeed;
2214 }
2215
2216 newSpeed += factor * (cannonParams.speed - newSpeed);
2217 if (cannonParams.endDecel > 0.0f) {
2218 m_speed = std::min(newSpeed, m_speed);
2219 }
2220 }
2221
2222 m_cannonProgress += m_cannonEntryOfs * newSpeed;
2223
2224 EGG::Vector3f newPos = EGG::Vector3f::zero;
2225 if (cannonParams.height > 0.0f) {
2226 f32 fVar9 = EGG::Mathf::SinFIdx(
2227 (1.0f - (forwardLength / m_cannonEntryOfsLength)) * 180.0f * DEG2FIDX);
2228 newPos = fVar9 * cannonParams.height * m_cannonOrthog;
2229 }
2230
2231 dynamics()->setPos(m_cannonEntryPos + m_cannonProgress + newPos);
2232 m_dir = m_cannonEntryOfs;
2233 m_vel1Dir = m_cannonEntryOfs;
2234
2235 calcRotCannon(forward);
2236
2237 dynamics()->setExtVel(EGG::Vector3f::zero);
2238}
2239
2241void KartMove::calcRotCannon(const EGG::Vector3f &forward) {
2242 EGG::Vector3f local48 = forward;
2243 local48.normalise();
2244 EGG::Vector3f local54 = bodyFront();
2245 EGG::Vector3f local60 = local54 + ((local48 - local54) * 0.3f);
2246 local54.normalise();
2247 local60.normalise();
2248 // also local70, localA8
2249 EGG::Quatf local80;
2250 local80.makeVectorRotation(local54, local60);
2251 local80 *= dynamics()->fullRot();
2252 local80.normalise();
2253 EGG::Quatf localB8;
2254 localB8.makeVectorRotation(local80.rotateVector(EGG::Vector3f::ey), smoothedUp());
2255 EGG::Quatf newRot = local80.slerpTo(localB8.multSwap(local80), 0.3f);
2256 dynamics()->setFullRot(newRot);
2257 dynamics()->setMainRot(newRot);
2258}
2259
2261void KartMove::exitCannon() {
2262 if (!state()->isInCannon()) {
2263 return;
2264 }
2265
2266 state()->setInCannon(false);
2267 state()->setSkipWheelCalc(false);
2268 state()->setAfterCannon(true);
2269 dynamics()->setIntVel(m_cannonEntryOfs * m_speed);
2270}
2271
2273void KartMove::triggerRespawn() {
2274 m_timeInRespawn = 0;
2275 state()->setTriggerRespawn(true);
2276}
2277
2279KartMoveBike::KartMoveBike() : m_leanRot(0.0f) {}
2280
2282KartMoveBike::~KartMoveBike() = default;
2283
2287 constexpr f32 MAX_WHEELIE_ROTATION = 0.07f;
2288 constexpr u16 WHEELIE_COOLDOWN = 20;
2289
2290 state()->setWheelie(true);
2291 m_wheelieFrames = 0;
2292 m_maxWheelieRot = MAX_WHEELIE_ROTATION;
2293 m_wheelieCooldown = WHEELIE_COOLDOWN;
2294 m_wheelieRotDec = 0.0f;
2295 m_autoHardStickXFrames = 0;
2296}
2297
2302 state()->setWheelie(false);
2303 m_wheelieRotDec = 0.0f;
2304 m_autoHardStickXFrames = 0;
2305}
2306
2308void KartMoveBike::createSubsystems() {
2309 m_jump = new KartJumpBike(this);
2310 m_halfPipe = new KartHalfPipe();
2311 m_kartScale = new KartScale();
2312}
2313
2318 f32 leanRotInc = m_turningParams->leanRotIncRace;
2319 f32 leanRotCap = m_turningParams->leanRotCapRace;
2320 const auto *raceManager = System::RaceManager::Instance();
2321
2322 if (!state()->isChargingSsmt()) {
2323 if (!raceManager->isStageReached(System::RaceManager::Stage::Race) ||
2324 EGG::Mathf::abs(m_speed) < 5.0f) {
2325 leanRotInc = m_turningParams->leanRotIncCountdown;
2326 leanRotCap = m_turningParams->leanRotCapCountdown;
2327 }
2328 } else {
2329 leanRotInc = m_turningParams->leanRotIncSSMT;
2330 leanRotCap = m_turningParams->leanRotCapSSMT;
2331 }
2332
2333 m_leanRotCap += 0.3f * (leanRotCap - m_leanRotCap);
2334 m_leanRotInc += 0.3f * (leanRotInc - m_leanRotInc);
2335
2336 f32 stickX = state()->stickX();
2337 f32 extVelXFactor = 0.0f;
2338 f32 leanRotMin = -m_leanRotCap;
2339 f32 leanRotMax = m_leanRotCap;
2340
2341 if (state()->isBeforeRespawn() || state()->isInAction() || state()->isWheelie() ||
2342 state()->isOverZipper() || state()->isRejectRoadTrigger() ||
2343 state()->isAirtimeOver20() || state()->isSoftWallDrift() ||
2344 state()->isSomethingWallCollision() || state()->isHWG() || state()->isCannonStart() ||
2345 state()->isInCannon()) {
2346 m_leanRot *= m_turningParams->leanRotDecayFactor;
2347 } else if (!state()->isDrifting()) {
2348 if (stickX <= 0.2f) {
2349 if (stickX >= -0.2f) {
2350 m_leanRot *= m_turningParams->leanRotDecayFactor;
2351 } else {
2353 extVelXFactor = m_turningParams->leanRotShallowFactor;
2354 }
2355 } else {
2357 extVelXFactor = -m_turningParams->leanRotShallowFactor;
2358 }
2359 } else {
2360 leanRotMax = m_turningParams->leanRotMaxDrift;
2361 leanRotMin = m_turningParams->leanRotMinDrift;
2362
2363 if (m_hopStickX == 1) {
2364 leanRotMin = -leanRotMax;
2365 leanRotMax = -m_turningParams->leanRotMinDrift;
2366 }
2367 if (m_hopStickX == -1) {
2368 if (stickX == 0.0f) {
2369 m_leanRot += (0.5f - m_leanRot) * 0.05f;
2370 } else {
2371 m_leanRot += m_turningParams->driftStickXFactor * stickX;
2372 extVelXFactor = -m_turningParams->leanRotShallowFactor * stickX;
2373 }
2374 } else if (stickX == 0.0f) {
2375 m_leanRot += (-0.5f - m_leanRot) * 0.05f;
2376 } else {
2377 m_leanRot += m_turningParams->driftStickXFactor * stickX;
2378 extVelXFactor = -m_turningParams->leanRotShallowFactor * stickX;
2379 }
2380 }
2381
2382 bool capped = false;
2383 if (leanRotMin <= m_leanRot) {
2384 if (leanRotMax < m_leanRot) {
2385 m_leanRot = leanRotMax;
2386 capped = true;
2387 }
2388 } else {
2389 m_leanRot = leanRotMin;
2390 capped = true;
2391 }
2392
2393 if (!capped) {
2394 dynamics()->setExtVel(dynamics()->extVel() + componentXAxis() * extVelXFactor);
2395 }
2396
2397 f32 leanRotScalar = state()->isDrifting() ? 0.065f : 0.05f;
2398
2400
2401 dynamics()->setAngVel2(dynamics()->angVel2() +
2402 EGG::Vector3f(m_standStillBoostRot, turn * wheelieRotFactor(),
2403 m_leanRot * leanRotScalar));
2404
2405 calcDive();
2406
2407 EGG::Vector3f top = m_up;
2408
2409 if (!state()->isRejectRoad() && !state()->isHalfPipeRamp() && !state()->isOverZipper()) {
2410 f32 scalar = (m_speed >= 0.0f) ? m_speedRatioCapped * 2.0f : 0.0f;
2411 scalar = std::min(1.0f, scalar);
2412 top = scalar * m_up + (1.0f - scalar) * EGG::Vector3f::ey;
2413
2414 if (std::numeric_limits<f32>::epsilon() < top.squaredLength()) {
2415 top.normalise();
2416 }
2417 }
2418
2419 dynamics()->setTop_(top);
2420}
2421
2427 static constexpr std::array<TurningParameters, 2> TURNING_PARAMS_ARRAY = {{
2428 {0.8f, 0.08f, 1.0f, 0.1f, 1.2f, 0.8f, 0.08f, 0.6f, 0.15f, 1.6f, 0.9f, 180},
2429 {1.0f, 0.1f, 1.0f, 0.05f, 1.5f, 0.7f, 0.08f, 0.6f, 0.15f, 1.3f, 0.9f, 180},
2430 }};
2431
2432 KartMove::setTurnParams();
2433
2434 if (param()->stats().driftType == KartParam::Stats::DriftType::Outside_Drift_Bike) {
2435 m_turningParams = &TURNING_PARAMS_ARRAY[0];
2436 } else if (param()->stats().driftType == KartParam::Stats::DriftType::Inside_Drift_Bike) {
2437 m_turningParams = &TURNING_PARAMS_ARRAY[1];
2438 }
2439
2440 if (System::RaceManager::Instance()->isStageReached(System::RaceManager::Stage::Race)) {
2441 m_leanRotInc = m_turningParams->leanRotIncRace;
2442 m_leanRotCap = m_turningParams->leanRotCapRace;
2443 } else {
2444 m_leanRotInc = m_turningParams->leanRotIncCountdown;
2445 m_leanRotCap = m_turningParams->leanRotCapCountdown;
2446 }
2447}
2448
2450void KartMoveBike::init(bool b1, bool b2) {
2451 KartMove::init(b1, b2);
2452
2453 m_leanRot = 0.0f;
2454 m_leanRotCap = 0.0f;
2455 m_leanRotInc = 0.0f;
2456 m_wheelieRot = 0.0f;
2457 m_maxWheelieRot = 0.0f;
2458 m_wheelieFrames = 0;
2460 m_autoHardStickXFrames = 0;
2461}
2462
2464void KartMoveBike::clear() {
2465 KartMove::clear();
2466 cancelWheelie();
2467}
2468
2472 constexpr u32 FAILED_WHEELIE_FRAMES = 15;
2473 constexpr f32 AUTO_WHEELIE_CANCEL_STICK_THRESHOLD = 0.85f;
2474
2476 m_wheelieCooldown = std::max(0, m_wheelieCooldown - 1);
2477
2478 if (state()->isWheelie()) {
2479 bool cancelAutoWheelie = false;
2480
2481 if (!state()->isAutoDrift() ||
2482 EGG::Mathf::abs(state()->stickX()) <= AUTO_WHEELIE_CANCEL_STICK_THRESHOLD) {
2483 m_autoHardStickXFrames = 0;
2484 } else {
2485 if (++m_autoHardStickXFrames > 15) {
2486 cancelAutoWheelie = true;
2487 }
2488 }
2489
2491 if (m_turningParams->maxWheelieFrames < m_wheelieFrames || cancelAutoWheelie ||
2492 (!canWheelie() && FAILED_WHEELIE_FRAMES <= m_wheelieFrames)) {
2493 cancelWheelie();
2494 } else {
2495 m_wheelieRot += 0.01f;
2496 EGG::Vector3f angVel0 = dynamics()->angVel0();
2497 angVel0.x *= 0.9f;
2498 dynamics()->setAngVel0(angVel0);
2499 }
2500 } else if (0.0f < m_wheelieRot) {
2501 m_wheelieRotDec -= 0.001f;
2502 m_wheelieRotDec = std::max(-0.03f, m_wheelieRotDec);
2504 }
2505
2506 m_wheelieRot = std::max(0.0f, std::min(m_wheelieRot, m_maxWheelieRot));
2507
2508 f32 vel1DirUp = m_vel1Dir.dot(EGG::Vector3f::ey);
2509
2510 if (m_wheelieRot > 0.0f) {
2511 if (vel1DirUp <= 0.5f || m_wheelieFrames < FAILED_WHEELIE_FRAMES) {
2512 EGG::Vector3f angVel2 = dynamics()->angVel2();
2513 angVel2.x -= m_wheelieRot * (1.0f - EGG::Mathf::abs(vel1DirUp));
2514 dynamics()->setAngVel2(angVel2);
2515 } else {
2516 cancelWheelie();
2517 }
2518
2519 state()->setWheelieRot(true);
2520 } else {
2521 state()->setWheelieRot(false);
2522 }
2523}
2524
2531 if (state()->isAutoDrift()) {
2532 return;
2533 }
2534
2535 cancelWheelie();
2536}
2537
2543
2548 constexpr u16 MAX_MT_CHARGE = 270;
2549 constexpr u16 BASE_MT_CHARGE = 2;
2550 constexpr f32 BONUS_CHARGE_STICK_THRESHOLD = 0.4f;
2551 constexpr u16 EXTRA_MT_CHARGE = 3;
2552
2553 if (m_driftState != DriftState::ChargingMt) {
2554 return;
2555 }
2556
2557 m_mtCharge += BASE_MT_CHARGE;
2558
2559 f32 stickX = state()->stickX();
2560 if (-BONUS_CHARGE_STICK_THRESHOLD <= stickX) {
2561 if (BONUS_CHARGE_STICK_THRESHOLD < stickX && m_hopStickX == -1) {
2562 m_mtCharge += EXTRA_MT_CHARGE;
2563 }
2564 } else if (m_hopStickX != -1) {
2565 m_mtCharge += EXTRA_MT_CHARGE;
2566 }
2567
2568 if (m_mtCharge > MAX_MT_CHARGE) {
2569 m_mtCharge = MAX_MT_CHARGE;
2570 m_driftState = DriftState::ChargedMt;
2571 }
2572}
2573
2575void KartMoveBike::initOob() {
2576 KartMove::initOob();
2577 cancelWheelie();
2578}
2579
2583 constexpr s16 COOLDOWN_FRAMES = 20;
2584 bool dpadUp = inputs()->currentState().trickUp();
2585
2586 if (!state()->isWheelie()) {
2587 if (dpadUp && state()->isTouchingGround()) {
2588 if (state()->isDriftManual() || state()->isWallCollision() ||
2589 state()->isWall3Collision() || state()->isHop() || state()->isDriftAuto() ||
2590 state()->isInAction()) {
2591 return;
2592 }
2593
2594 if (m_wheelieCooldown > 0) {
2595 return;
2596 }
2597
2598 startWheelie();
2599 }
2600 } else if (inputs()->currentState().trickDown() && m_wheelieCooldown <= 0) {
2601 cancelWheelie();
2602 m_wheelieCooldown = COOLDOWN_FRAMES;
2603 }
2604}
2605
2606} // 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:167
Vector3f multVector33(const Vector3f &vec) const
Multiplies a 3x3 matrix by a vector.
Definition Matrix.cc:236
Vector3f multVector(const Vector3f &vec) const
Multiplies a vector by a matrix.
Definition Matrix.cc:212
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:511
f32 m_leanRotCap
The maximum leaning rotation.
Definition KartMove.hh:512
void calcWheelie() override
STAGE 1+ - Every frame, checks player input for wheelies and computes wheelie rotation.
Definition KartMove.cc:2471
void calcMtCharge() override
Every frame during a drift, calculates MT charge based on player input.
Definition KartMove.cc:2547
virtual void startWheelie()
STAGE 1+ - Sets the wheelie bit flag and some wheelie-related variables.
Definition KartMove.cc:2286
f32 m_wheelieRotDec
The wheelie rotation decrementor, used after a wheelie has ended.
Definition KartMove.hh:518
u32 m_wheelieFrames
Tracks wheelie duration and cancels the wheelie after 180 frames.
Definition KartMove.hh:516
f32 m_wheelieRot
X-axis rotation from wheeling.
Definition KartMove.hh:514
const TurningParameters * m_turningParams
Inside/outside drifting bike turn info.
Definition KartMove.hh:520
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:2426
f32 m_maxWheelieRot
The maximum wheelie rotation.
Definition KartMove.hh:515
void tryStartWheelie()
STAGE 1+ - Every frame, checks player input to see if we should start or stop a wheelie.
Definition KartMove.cc:2582
void onWallCollision() override
Called when you collide with a wall. All it does for bikes is cancel wheelies.
Definition KartMove.cc:2540
void onHop() override
Virtual function that just cancels wheelies when you hop.
Definition KartMove.cc:2530
bool canWheelie() const override
Checks if the kart is going fast enough to wheelie.
Definition KartMove.hh:502
void calcVehicleRotation(f32) override
Every frame, calculates rotation, EV, and angular velocity for the bike.
Definition KartMove.cc:2317
s16 m_wheelieCooldown
The number of frames before another wheelie can start.
Definition KartMove.hh:517
f32 m_leanRotInc
The incrementor for leaning rotation.
Definition KartMove.hh:513
virtual void cancelWheelie()
Clears the wheelie bit flag and resets the rotation decrement.
Definition KartMove.cc:2301
f32 m_baseSpeed
The speed associated with the current character/vehicle stats.
Definition KartMove.hh:353
void calcRotation()
Every frame, calculates kart rotation based on player input.
Definition KartMove.cc:1108
s16 m_ssmtLeewayTimer
Frames to forgive letting go of A before clearing SSMT charge.
Definition KartMove.hh:395
s32 m_hopFrame
A timer that can prevent subsequent hops until reset.
Definition KartMove.hh:380
void calcDisableBackwardsAccel()
Computes the current cooldown duration between braking and reversing.
Definition KartMove.cc:694
EGG::Vector3f m_hopUp
The up vector when hopping.
Definition KartMove.hh:381
u16 m_mushroomBoostTimer
Number of frames until the mushroom boost runs out.
Definition KartMove.hh:402
void calcSpecialFloor()
Every frame, calculates any boost resulting from a boost panel.
Definition KartMove.cc:496
void calcWallCollisionStart(f32 param_2)
If we started to collide with a wall this frame, applies rotation.
Definition KartMove.cc:1505
KartHalfPipe * m_halfPipe
Pertains to zipper physics.
Definition KartMove.hh:431
f32 m_kclRotFactor
Float between 0-1 that scales the player's turning radius on offroad.
Definition KartMove.hh:375
f32 m_outsideDriftBonus
Added to angular velocity when outside drifting.
Definition KartMove.hh:388
void tryStartBoostRamp()
Sets offroad invincibility and and enables the ramp boost bitfield flag.
Definition KartMove.cc:1861
void calcDeceleration()
Definition KartMove.cc:1283
u16 m_smtCharge
A value between 0 and 300 representing current SMT charge.
Definition KartMove.hh:387
f32 m_speedRatio
The ratio between current speed and the player's base speed stat.
Definition KartMove.hh:373
@ 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:1877
f32 m_jumpPadMinSpeed
Snaps the player to a minimum speed when first touching a jump pad.
Definition KartMove.hh:405
f32 m_hopPosY
Relative position as the result of a hop. Starts at 0.
Definition KartMove.hh:419
DrivingDirection m_drivingDirection
Current state of driver's direction.
Definition KartMove.hh:426
u16 m_crushTimer
Number of frames until player will be uncrushed.
Definition KartMove.hh:403
bool calcPreDrift()
Each frame, checks for hop or slipdrift. Computes drift direction based on player input.
Definition KartMove.cc:764
f32 m_speed
Current speed, restricted to the soft speed limit.
Definition KartMove.hh:355
s16 m_offroadInvincibility
How many frames until the player is affected by offroad.
Definition KartMove.hh:393
void calcAirtimeTop()
Calculates rotation of the bike due to excessive airtime.
Definition KartMove.cc:473
void startManualDrift()
Called when the player lands from a drift hop, or to start a slipdrift.
Definition KartMove.cc:1008
void controlOutsideDriftAngle()
Every frame, handles mini-turbo charging and outside drifting bike rotation.
Definition KartMove.cc:1075
f32 m_softSpeedLimit
Base speed + boosts + wheelies, restricted to the hard speed limit.
Definition KartMove.hh:354
virtual void hop()
Initializes hop information, resets upwards EV and clears upwards force.
Definition KartMove.cc:1820
void calcStandstillBoostRot()
STAGE Computes the x-component of angular velocity based on the kart's speed.
Definition KartMove.cc:1564
EGG::Vector3f m_outsideDriftLastDir
Used to compute the next m_outsideDriftAngle.
Definition KartMove.hh:371
void calcManualDrift()
Each frame, handles hopping, drifting, and mini-turbos.
Definition KartMove.cc:932
virtual void calcMtCharge()
Every frame during a drift, calculates MT/SMT charge based on player input.
Definition KartMove.cc:1755
void calcSsmt()
Calculates standstill mini-turbo components, if applicable.
Definition KartMove.cc:709
void calcAcceleration()
Every frame, applies acceleration to the kart's internal velocity.
Definition KartMove.cc:1331
f32 m_processedSpeed
Offset 0x28. It's only ever just a copy of m_speed.
Definition KartMove.hh:357
void releaseMt()
Stops charging a mini-turbo, and applies boost if charged.
Definition KartMove.cc:1051
f32 m_kclSpeedFactor
Float between 0-1 that scales the player's speed on offroad.
Definition KartMove.hh:374
f32 m_weightedTurn
Magnitude+direction of stick input, factoring in the kart's stats.
Definition KartMove.hh:398
void calcVehicleSpeed()
Every frame, computes speed based on acceleration and any active boosts.
Definition KartMove.cc:1204
f32 m_lastSpeed
Last frame's speed, cached to calculate angular velocity.
Definition KartMove.hh:356
s16 m_ssmtDisableAccelTimer
Counter that tracks delay before starting to reverse.
Definition KartMove.hh:396
void calcOffroadInvincibility()
Checks a timer to see if we are still ignoring offroad slowdown.
Definition KartMove.cc:2030
KartBurnout m_burnout
Manages the state of start boost burnout.
Definition KartMove.hh:433
f32 calcVehicleAcceleration() const
Every frame, computes acceleration based off the character/vehicle stats.
Definition KartMove.cc:1297
void calcAutoDrift()
Each frame, handles automatic transmission drifting.
Definition KartMove.cc:880
f32 m_realTurn
The "true" turn magnitude. Equal to m_weightedTurn unless drifting.
Definition KartMove.hh:397
const DriftingParameters * m_driftingParams
Drift-type-specific parameters.
Definition KartMove.hh:434
void clearDrift()
Definition KartMove.cc:816
void calc()
Each frame, calculates the kart's movement.
Definition KartMove.cc:277
EGG::Vector3f m_up
Vector perpendicular to the floor, pointing upwards.
Definition KartMove.hh:362
s16 m_ssmtCharge
Increments every frame up to 75 when charging stand-still MT.
Definition KartMove.hh:394
f32 m_speedDragMultiplier
After 5 frames of airtime, this causes speed to slowly decay.
Definition KartMove.hh:360
u16 m_mtCharge
A value between 0 and 270 representing current MT charge.
Definition KartMove.hh:386
void calcSsmtStart()
Calculates whether we are starting a standstill mini-turbo.
Definition KartMove.cc:1661
s16 m_respawnPostLandTimer
Counts up to 4 if not accelerating after respawn landing.
Definition KartMove.hh:423
virtual f32 getWheelieSoftSpeedLimitBonus() const
Returns the % speed boost from wheelies. For karts, this is always 0.
Definition KartMove.hh:100
f32 m_kclWheelRotFactor
The slowest rotation multiplier of each wheel's floor collision.
Definition KartMove.hh:377
void resetDriftManual()
Clears drift state. Called when touching ground and drift is canceled.
Definition KartMove.cc:804
f32 m_totalScale
[Unused] Always 1.0f
Definition KartMove.hh:400
f32 m_acceleration
Captures the acceleration from player input and boosts.
Definition KartMove.hh:359
virtual void calcVehicleRotation(f32 turn)
Every frame, calculates rotation, EV, and angular velocity for the kart.
Definition KartMove.cc:1709
s16 m_respawnPreLandTimer
Counts down from 4 when pressing A before landing from respawn.
Definition KartMove.hh:422
virtual void calcTurn()
Each frame, looks at player input and kart stats. Saves turn-related info.
Definition KartMove.cc:67
f32 m_divingRot
Induces x-axis angular velocity based on up/down stick input.
Definition KartMove.hh:383
f32 m_outsideDriftAngle
The facing angle of an outward-drifting vehicle.
Definition KartMove.hh:369
EGG::Vector3f m_lastDir
m_speed from the previous frame but with signed magnitude.
Definition KartMove.hh:365
EGG::Vector3f m_smoothedUp
A smoothed up vector, mostly used after significant airtime.
Definition KartMove.hh:361
s32 getAppliedHopStickX() const
Factors in vehicle speed to retrieve our hop direction and magnitude.
Definition KartMove.hh:210
u16 m_floorCollisionCount
The number of tires colliding with the floor.
Definition KartMove.hh:378
void calcDive()
Responds to player input to handle up/down kart tilt mid-air.
Definition KartMove.cc:1602
void calcOffroad()
Each frame, computes rotation and speed scalars from the floor KCL.
Definition KartMove.cc:639
KartScale * m_kartScale
Manages scaling due to TF stompers and MH cars.
Definition KartMove.hh:432
@ 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:421
s32 m_hopStickX
A ternary for the direction of our hop, 0 if still neutral hopping.
Definition KartMove.hh:379
s16 m_backwardsAllowCounter
Tracks the 15f delay before reversing.
Definition KartMove.hh:427
f32 calcWallCollisionSpeedFactor(f32 &f1)
Every frame, computes a speed scalar if we are colliding with a wall.
Definition KartMove.cc:1474
void calcMushroomBoost()
Checks a timer to see if we are still boosting from a mushroom.
Definition KartMove.cc:2044
f32 m_hopGravity
Always main gravity (-1.3f).
Definition KartMove.hh:420
f32 m_hopVelY
Relative velocity due to a hop. Starts at 10 and decreases with gravity.
Definition KartMove.hh:418
f32 m_hardSpeedLimit
Absolute speed cap. It's 120, unless you're in a bullet (140).
Definition KartMove.hh:358
void setInitialPhysicsValues(const EGG::Vector3f &position, const EGG::Vector3f &angles)
Initializes the kart's position and rotation. Calls tire suspension initializers.
Definition KartMove.cc:240
f32 m_rawTurn
Float in range [-1, 1]. Represents stick magnitude + direction.
Definition KartMove.hh:435
void setOffroadInvincibility(s16 timer)
Ignores offroad KCL collision for a set amount of time.
Definition KartMove.cc:2019
f32 m_speedRatioCapped
m_speedRatio but capped at 1.0f.
Definition KartMove.hh:372
EGG::Vector3f m_scale
Normally the unit vector, but may vary due to crush animations.
Definition KartMove.hh:399
f32 m_kclWheelSpeedFactor
The slowest speed multiplier of each wheel's floor collision.
Definition KartMove.hh:376
EGG::Vector3f m_hopDir
Used for outward drift. Tracks the forward vector of our rotation.
Definition KartMove.hh:382
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:8
EGG core library.
Definition Archive.cc:6
Pertains to kart-related functionality.
@ BikeSideStuntTrick
A side StuntTrick with a bike.
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:68
constexpr bool onBit(Es... es) const
Checks if any of the corresponding bits for the provided enum values are on.
Definition BitFlag.hh:103
constexpr TBitFlag< T, E > & changeBit(bool on, Es... es)
Changes the state of the corresponding bits for the provided enum values.
Definition BitFlag.hh:80
constexpr bool offBit(Es... es) const
Checks if all of the corresponding bits for the provided enum values are off.
Definition BitFlag.hh:136
constexpr void makeAllZero()
Resets all the bits to zero.
Definition BitFlag.hh:229
constexpr TBitFlag< T, E > & setBit(Es... es)
Sets the corresponding bits for the provided enum values.
Definition BitFlag.hh:57
A 3D float vector.
Definition Vector.hh:87
f32 normalise()
Normalizes the vector and returns the original length.
Definition Vector.cc:44
f32 dot(const Vector3f &rhs) const
The dot product between two vectors.
Definition Vector.hh:186
f32 length() const
The square root of the vector's dot product.
Definition Vector.hh:191
f32 squaredLength() const
The dot product between the vector and itself.
Definition Vector.hh:181
Vector3f proj(const Vector3f &rhs) const
The projection of this vector onto rhs.
Definition Vector.hh:197
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:96
Vector3f rej(const Vector3f &rhs) const
The rejection of this vector onto rhs.
Definition Vector.hh:203
Information about the current collision and its properties.
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
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