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