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 auto &status = KartObjectProxy::status();
72
73 if (status.onBit(eStatus::InAction, eStatus::CannonStart, eStatus::InCannon,
75 return;
76 }
77
78 if (status.onBit(eStatus::BeforeRespawn)) {
79 return;
80 }
81
82 if (status.offBit(eStatus::Hop) || m_hopStickX == 0) {
83 m_rawTurn = -state()->stickX();
84 if (status.onBit(eStatus::JumpPadMushroomCollision)) {
85 m_rawTurn *= 0.35f;
86 } else if (status.onBit(eStatus::AirtimeOver20)) {
87 m_rawTurn *= 0.01f;
88 }
89 } else {
90 m_rawTurn = static_cast<f32>(m_hopStickX);
91 }
92
93 f32 reactivity;
94 if (state()->isDrifting()) {
95 reactivity = param()->stats().driftReactivity;
96 } else {
97 reactivity = param()->stats().handlingReactivity;
98 }
99
100 m_weightedTurn = m_rawTurn * reactivity + m_weightedTurn * (1.0f - reactivity);
101 m_weightedTurn = std::max(-1.0f, std::min(1.0f, m_weightedTurn));
102
104
105 if (!state()->isDrifting()) {
106 return;
107 }
108
109 m_realTurn = (m_weightedTurn + static_cast<f32>(m_hopStickX)) * 0.5f;
110 m_realTurn = m_realTurn * 0.8f + 0.2f * static_cast<f32>(m_hopStickX);
111 m_realTurn = std::max(-1.0f, std::min(1.0f, m_realTurn));
112}
113
115void KartMove::setTurnParams() {
116 static constexpr std::array<DriftingParameters, 3> DRIFTING_PARAMS_ARRAY = {{
117 {10.0f, 0.5f, 0.5f, 1.0f},
118 {10.0f, 0.5f, 0.5f, 0.2f},
119 {10.0f, 0.22f, 0.5f, 0.2f},
120 }};
121
122 init(false, false);
123 m_dir = bodyFront();
124 m_lastDir = m_dir;
125 m_vel1Dir = m_dir;
126 m_landingDir = m_dir;
127 m_outsideDriftLastDir = m_dir;
128 m_driftingParams = &DRIFTING_PARAMS_ARRAY[static_cast<u32>(param()->stats().driftType)];
129 m_kartScale->reset();
130}
131
133void KartMove::init(bool b1, bool b2) {
134 m_lastSpeed = 0.0f;
135 m_baseSpeed = param()->stats().speed;
136 m_jumpPadSoftSpeedLimit = m_softSpeedLimit = param()->stats().speed;
137 m_speed = 0.0f;
138 setKartSpeedLimit();
139 m_acceleration = 0.0f;
141 m_up = EGG::Vector3f::ey;
142 m_smoothedUp = EGG::Vector3f::ey;
143 m_vel1Dir = EGG::Vector3f::ez;
144 m_lastDir = EGG::Vector3f::ez;
145 m_dir = EGG::Vector3f::ez;
146 m_landingDir = EGG::Vector3f::ez;
147 m_dirDiff = EGG::Vector3f::zero;
148 m_hasLandingDir = false;
149 m_outsideDriftAngle = 0.0f;
150 m_landingAngle = 0.0f;
151 m_outsideDriftLastDir = EGG::Vector3f::ez;
152 m_speedRatio = 0.0f;
153 m_speedRatioCapped = 0.0f;
154 m_kclSpeedFactor = 1.0f;
155 m_kclRotFactor = 1.0f;
157 m_kclWheelRotFactor = 1.0f;
158
159 if (!b2) {
161 }
162
163 m_hopStickX = 0;
164 m_hopFrame = 0;
165 m_hopUp = EGG::Vector3f::ey;
166 m_hopDir = EGG::Vector3f::ez;
167 m_divingRot = 0.0f;
168 m_standStillBoostRot = 0.0f;
169 m_driftState = DriftState::NotDrifting;
170 m_smtCharge = 0;
171 m_mtCharge = 0;
172 m_outsideDriftBonus = 0.0f;
173 m_boost.reset();
174 m_zipperBoostTimer = 0;
175 m_zipperBoostMax = 0;
176 m_reject.reset();
178 m_ssmtCharge = 0;
181 m_nonZipperAirtime = 0;
182 m_realTurn = 0.0f;
183 m_weightedTurn = 0.0f;
184
185 if (!b1) {
186 m_scale.set(1.0f);
187 m_totalScale = 1.0f;
188 m_hitboxScale = 1.0f;
190 m_crushTimer = 0;
191 }
192
193 m_jumpPadMinSpeed = 0.0f;
194 m_jumpPadMaxSpeed = 0.0f;
195 m_jumpPadBoostMultiplier = 0.0f;
196 m_jumpPadProperties = nullptr;
197 m_rampBoost = 0;
198 m_autoDriftAngle = 0.0f;
199 m_autoDriftStartFrameCounter = 0;
200
201 m_cannonEntryOfsLength = 0.0f;
202 m_cannonEntryPos.setZero();
203 m_cannonEntryOfs.setZero();
204 m_cannonOrthog.setZero();
205 m_cannonProgress.setZero();
206
207 m_hopVelY = 0.0f;
208 m_hopPosY = 0.0f;
209 m_hopGravity = 0.0f;
210 m_timeInRespawn = 0;
213 m_respawnTimer = 0;
214 m_bumpTimer = 0;
215 m_drivingDirection = DrivingDirection::Forwards;
216 m_padType.makeAllZero();
217 m_flags.makeAllZero();
218 m_jump->reset();
219 m_halfPipe->reset();
220 m_rawTurn = 0.0f;
221}
222
224void KartMove::clear() {
225 auto &status = KartObjectProxy::status();
226
227 if (status.onBit(eStatus::OverZipper)) {
229 }
230
231 clearBoost();
232 clearJumpPad();
233 clearRampBoost();
234 clearZipperBoost();
235 clearSsmt();
236 clearOffroadInvincibility();
237 m_halfPipe->end(false);
238 m_jump->end();
239 clearRejectRoad();
240}
241
245 EGG::Quatf quaternion = EGG::Quatf::FromRPY(angles * DEG2RAD);
246 EGG::Vector3f newPos = position;
248 Field::KCLTypeMask kcl_flags = KCL_NONE;
249
250 bool bColliding = Field::CollisionDirector::Instance()->checkSphereFullPush(100.0f, newPos,
251 EGG::Vector3f::inf, KCL_ANY, &info, &kcl_flags, 0);
252
253 if (bColliding && (kcl_flags & KCL_TYPE_FLOOR)) {
254 newPos = newPos + info.tangentOff + (info.floorNrm * -100.0f);
255 newPos += info.floorNrm * bsp().initialYPos;
256 }
257
258 setPos(newPos);
259 setRot(quaternion);
260
261 sub()->initPhysicsValues();
262
263 physics()->setPos(pos());
264 physics()->setVelocity(dynamics()->velocity());
265
266 m_landingDir = bodyFront();
267 m_dir = bodyFront();
268 m_up = bodyUp();
269 dynamics()->setTop(m_up);
270
271 for (u16 tireIdx = 0; tireIdx < suspCount(); ++tireIdx) {
272 suspension(tireIdx)->setInitialState();
273 }
274}
275
282 auto &status = KartObjectProxy::status();
283
284 if (status.onBit(eStatus::InRespawn)) {
285 calcInRespawn();
286 return;
287 }
288
289 dynamics()->resetInternalVelocity();
290 m_burnout.calc();
292 m_halfPipe->calc();
293 calcTop();
294 tryEndJumpPad();
295 calcRespawnBoost();
297 m_jump->calc();
298
299 m_bumpTimer = std::max(m_bumpTimer - 1, 0);
300
302 calcDirs();
303 calcStickyRoad();
304 calcOffroad();
305 calcTurn();
306
307 if (status.offBit(eStatus::AutoDrift)) {
309 }
310
311 calcWheelie();
312 calcSsmt();
313 calcBoost();
315 calcZipperBoost();
316 calcCrushed();
317 calcScale();
318
319 if (status.onBit(eStatus::InCannon)) {
320 calcCannon();
321 }
322
326 calcRotation();
327}
328
330void KartMove::calcRespawnStart() {
331 constexpr float RESPAWN_HEIGHT = 700.0f;
332
333 const auto *jugemPoint = System::RaceManager::Instance()->jugemPoint();
334 const EGG::Vector3f &jugemPos = jugemPoint->pos();
335 const EGG::Vector3f &jugemRot = jugemPoint->rot();
336
337 EGG::Vector3f respawnPos = jugemPos;
338 respawnPos.y += RESPAWN_HEIGHT;
339 EGG::Vector3f respawnRot = EGG::Vector3f(0.0f, jugemRot.y, 0.0f);
340
341 setInitialPhysicsValues(respawnPos, respawnRot);
342
343 Item::ItemDirector::Instance()->kartItem(0).clear();
344
345 status().resetBit(eStatus::TriggerRespawn).setBit(eStatus::InRespawn);
346}
347
349void KartMove::calcInRespawn() {
350 constexpr f32 LAKITU_VELOCITY = 1.5f;
351 constexpr u16 RESPAWN_DURATION = 110;
352
353 auto &status = KartObjectProxy::status();
354
355 if (status.offBit(eStatus::InRespawn)) {
356 return;
357 }
358
359 EGG::Vector3f newPos = pos();
360 newPos.y -= LAKITU_VELOCITY;
361 dynamics()->setPos(newPos);
362 dynamics()->setNoGravity(true);
363
364 if (++m_timeInRespawn > RESPAWN_DURATION) {
365 status.resetBit(eStatus::InRespawn).setBit(eStatus::AfterRespawn, eStatus::RespawnKillY);
366 m_timeInRespawn = 0;
367 m_flags.setBit(eFlags::Respawned);
368 dynamics()->setNoGravity(false);
369 }
370}
371
373void KartMove::calcRespawnBoost() {
374 constexpr s16 RESPAWN_BOOST_DURATION = 30;
375 constexpr s16 RESPAWN_BOOST_INPUT_LENIENCY = 4;
376
377 auto &status = KartObjectProxy::status();
378
379 if (status.onBit(eStatus::AfterRespawn)) {
380 if (status.onBit(eStatus::TouchingGround)) {
381 if (m_respawnPreLandTimer > 0) {
382 if (status.offBit(eStatus::BeforeRespawn, eStatus::InAction)) {
383 activateBoost(KartBoost::Type::AllMt, RESPAWN_BOOST_DURATION);
384 m_respawnTimer = RESPAWN_BOOST_DURATION;
385 }
386 } else {
387 m_respawnPostLandTimer = RESPAWN_BOOST_INPUT_LENIENCY;
388 }
389
390 status.resetBit(eStatus::AfterRespawn);
392 }
393
395
396 if (m_flags.onBit(eFlags::Respawned) && status.onBit(eStatus::AccelerateStart)) {
397 m_respawnPreLandTimer = RESPAWN_BOOST_INPUT_LENIENCY;
399 }
400 } else {
401 if (m_respawnPostLandTimer > 0) {
402 if (status.onBit(eStatus::AccelerateStart)) {
403 if (status.offBit(eStatus::BeforeRespawn, eStatus::InAction)) {
404 activateBoost(KartBoost::Type::AllMt, RESPAWN_BOOST_DURATION);
405 m_respawnTimer = RESPAWN_BOOST_DURATION;
406 }
407
409 }
410
412 } else {
414 }
415 }
416
417 m_respawnTimer = std::max(0, m_respawnTimer - 1);
418}
419
421void KartMove::calcTop() {
422 f32 stabilizationFactor = 0.1f;
423 m_hasLandingDir = false;
424 EGG::Vector3f inputTop = state()->top();
425 auto &status = KartObjectProxy::status();
426
427 if (status.onBit(eStatus::GroundStart) && m_nonZipperAirtime >= 3) {
428 m_smoothedUp = inputTop;
429 m_up = inputTop;
430 m_landingDir = m_dir.perpInPlane(m_smoothedUp, true);
431 m_dirDiff = m_landingDir.proj(m_landingDir);
432 m_hasLandingDir = true;
433 } else {
434 if (status.onBit(eStatus::Hop) && m_hopPosY > 0.0f) {
435 stabilizationFactor = m_driftingParams->stabilizationFactor;
436 } else if (status.onBit(eStatus::TouchingGround)) {
437 if ((m_flags.onBit(eFlags::TrickableSurface) || state()->trickableTimer() > 0) &&
438 inputTop.dot(m_dir) > 0.0f && m_speed > 50.0f &&
439 collide()->surfaceFlags().onBit(KartCollide::eSurfaceFlags::NotTrickable)) {
440 inputTop = m_up;
441 } else {
442 m_up = inputTop;
443 }
444
445 f32 scalar = 0.8f;
446
447 if (status.onBit(eStatus::HalfPipeRamp) ||
448 (status.offBit(eStatus::Boost, eStatus::RampBoost, eStatus::Wheelie,
450 (status.offBit(eStatus::ZipperBoost) || m_zipperBoostTimer > 15))) {
451 f32 topDotZ = 0.8f - 6.0f * (EGG::Mathf::abs(inputTop.dot(componentZAxis())));
452 scalar = std::min(0.8f, std::max(0.3f, topDotZ));
453 }
454
455 m_smoothedUp += (inputTop - m_smoothedUp) * scalar;
457
458 f32 bodyDotFront = bodyFront().dot(m_smoothedUp);
459
460 if (bodyDotFront < -0.1f) {
461 stabilizationFactor += std::min(0.2f, EGG::Mathf::abs(bodyDotFront) * 0.5f);
462 }
463
464 if (collide()->surfaceFlags().onBit(KartCollide::eSurfaceFlags::BoostRamp)) {
465 stabilizationFactor = 0.4f;
466 }
467 } else {
469 }
470 }
471
472 dynamics()->setStabilizationFactor(stabilizationFactor);
473
474 m_nonZipperAirtime = status.onBit(eStatus::OverZipper) ? 0 : state()->airtime();
475 m_flags.changeBit(collide()->surfaceFlags().onBit(KartCollide::eSurfaceFlags::Trickable),
477}
478
482 auto &status = KartObjectProxy::status();
483
485 return;
486 }
487
488 if (m_smoothedUp.y <= 0.99f) {
489 m_smoothedUp += (EGG::Vector3f::ey - m_smoothedUp) * 0.03f;
491 } else {
492 m_smoothedUp = EGG::Vector3f::ey;
493 }
494
495 if (m_up.y <= 0.99f) {
496 m_up += (EGG::Vector3f::ey - m_up) * 0.03f;
497 m_up.normalise();
498 } else {
499 m_up = EGG::Vector3f::ey;
500 }
501}
502
507 const auto *raceMgr = System::RaceManager::Instance();
508 if (!raceMgr->isStageReached(System::RaceManager::Stage::Race)) {
509 return;
510 }
511
512 if (m_padType.onBit(ePadType::BoostPanel)) {
513 tryStartBoostPanel();
514 }
515
516 if (m_padType.onBit(ePadType::BoostRamp)) {
518 }
519
520 if (m_padType.onBit(ePadType::JumpPad)) {
522 }
523
524 m_padType.makeAllZero();
525}
526
528void KartMove::calcDirs() {
529 EGG::Vector3f right = dynamics()->mainRot().rotateVector(EGG::Vector3f::ex);
530 EGG::Vector3f local_88 = right.cross(m_smoothedUp);
531 local_88.normalise();
532 m_flags.setBit(eFlags::LaunchBoost);
533 auto &status = KartObjectProxy::status();
534
535 if (status.offBit(eStatus::InATrick, eStatus::OverZipper) &&
536 (((status.onBit(eStatus::TouchingGround) || status.offBit(eStatus::RampBoost) ||
537 !m_jump->isBoostRampEnabled()) &&
538 status.offBit(eStatus::JumpPad) && state()->airtime() <= 5) ||
539 status.onBit(eStatus::JumpPadMushroomCollision,
540 eStatus::NoSparkInvisibleWall))) {
541 if (status.onBit(eStatus::Hop)) {
542 local_88 = m_hopDir;
543 }
544
545 EGG::Matrix34f mat;
546 mat.setAxisRotation(DEG2RAD * (m_autoDriftAngle + m_outsideDriftAngle + m_landingAngle),
548 EGG::Vector3f local_b8 = mat.multVector(local_88);
549 local_b8 = local_b8.perpInPlane(m_smoothedUp, true);
550
551 EGG::Vector3f dirDiff = local_b8 - m_dir;
552
553 if (dirDiff.squaredLength() <= std::numeric_limits<f32>::epsilon()) {
554 m_dir = local_b8;
555 m_dirDiff.setZero();
556 } else {
557 EGG::Vector3f origDirCross = m_dir.cross(local_b8);
558 m_dirDiff += m_kclRotFactor * dirDiff;
559 m_dir += m_dirDiff;
560 m_dir.normalise();
561 m_dirDiff *= 0.1f;
562 EGG::Vector3f newDirCross = m_dir.cross(local_b8);
563
564 if (origDirCross.dot(newDirCross) < 0.0f) {
565 m_dir = local_b8;
566 m_dirDiff.setZero();
567 }
568 }
569
570 m_vel1Dir = m_dir.perpInPlane(m_smoothedUp, true);
571 m_flags.resetBit(eFlags::LaunchBoost);
572 } else {
573 m_vel1Dir = m_dir;
574 }
575
576 if (status.offBit(eStatus::OverZipper)) {
577 m_jump->tryStart(m_smoothedUp.cross(m_dir));
578 }
579
580 if (m_hasLandingDir) {
581 f32 dot = m_dir.dot(m_landingDir);
582 EGG::Vector3f cross = m_dir.cross(m_landingDir);
583 f32 crossDot = cross.length();
584 f32 angle = EGG::Mathf::atan2(crossDot, dot);
585 angle = EGG::Mathf::abs(angle);
586
587 f32 fVar4 = 1.0f;
588 if (cross.dot(m_smoothedUp) < 0.0f) {
589 fVar4 = -1.0f;
590 }
591
592 m_landingAngle += (angle * RAD2DEG) * fVar4;
593 }
594
595 if (m_landingAngle <= 0.0f) {
596 if (m_landingAngle < 0.0f) {
597 m_landingAngle = std::min(0.0f, m_landingAngle + 2.0f);
598 }
599 } else {
600 m_landingAngle = std::max(0.0f, m_landingAngle - 2.0f);
601 }
602}
603
605void KartMove::calcStickyRoad() {
606 constexpr f32 STICKY_RADIUS = 200.0f;
607 constexpr Field::KCLTypeMask STICKY_MASK =
609
610 auto &status = KartObjectProxy::status();
611
612 if (status.onBit(eStatus::OverZipper)) {
614 return;
615 }
616
617 if ((status.offBit(eStatus::StickyRoad) &&
618 collide()->surfaceFlags().offBit(KartCollide::eSurfaceFlags::Trickable)) ||
619 EGG::Mathf::abs(m_speed) <= 20.0f) {
620 return;
621 }
622
623 EGG::Vector3f pos = dynamics()->pos();
624 EGG::Vector3f vel = dynamics()->movingObjVel() + m_speed * m_vel1Dir;
625 EGG::Vector3f down = -STICKY_RADIUS * componentYAxis();
626 Field::CollisionInfo colInfo;
627 colInfo.bbox.setZero();
628 Field::KCLTypeMask kcl_flags = KCL_NONE;
629 bool stickyRoad = false;
630
631 for (size_t i = 0; i < 3; ++i) {
632 EGG::Vector3f newPos = pos + vel;
633 if (Field::CollisionDirector::Instance()->checkSphereFull(STICKY_RADIUS, newPos,
634 EGG::Vector3f::inf, STICKY_MASK, &colInfo, &kcl_flags, 0)) {
635 m_vel1Dir = m_vel1Dir.perpInPlane(colInfo.floorNrm, true);
636 dynamics()->setMovingObjVel(dynamics()->movingObjVel().rej(colInfo.floorNrm));
637 stickyRoad = true;
638
639 break;
640 }
641 vel *= 0.5f;
642 pos += -STICKY_RADIUS * componentYAxis();
643 }
644
645 if (!stickyRoad) {
647 }
648}
649
654 auto &status = KartObjectProxy::status();
655
657 m_kclSpeedFactor = 1.0f;
658 m_kclRotFactor = param()->stats().kclRot[0];
659 } else {
660 bool anyWheel = status.onBit(eStatus::AnyWheelCollision);
661 if (anyWheel) {
665 }
666
668 const CollisionData &colData = collisionData();
669 if (anyWheel) {
670 if (colData.speedFactor < m_kclWheelSpeedFactor) {
671 m_kclSpeedFactor = colData.speedFactor;
672 }
673 m_kclRotFactor = (m_kclWheelRotFactor + colData.rotFactor) /
674 static_cast<f32>(m_floorCollisionCount + 1);
675 } else {
676 m_kclSpeedFactor = colData.speedFactor;
677 m_kclRotFactor = colData.rotFactor;
678 }
679 }
680 }
681}
682
684void KartMove::calcBoost() {
685 auto &status = KartObjectProxy::status();
686
687 if (m_boost.calc()) {
689 } else {
690 status.resetBit(eStatus::Boost);
691 }
692
693 calcRampBoost();
694}
695
697void KartMove::calcRampBoost() {
698 auto &status = KartObjectProxy::status();
699
700 if (status.offBit(eStatus::RampBoost)) {
701 return;
702 }
703
705 if (--m_rampBoost < 1) {
706 m_rampBoost = 0;
707 status.resetBit(eStatus::RampBoost);
708 }
709}
710
715 auto &status = KartObjectProxy::status();
716
718 return;
719 }
720
721 if (--m_ssmtDisableAccelTimer < 0 ||
722 (m_flags.offBit(eFlags::SsmtLeeway) && status.offBit(eStatus::Brake))) {
725 }
726}
727
732 constexpr s16 MAX_SSMT_CHARGE = 75;
733 constexpr s16 SSMT_BOOST_FRAMES = 30;
734 constexpr s16 LEEWAY_FRAMES = 1;
735 constexpr s16 DISABLE_ACCEL_FRAMES = 20;
736
738
739 auto &status = KartObjectProxy::status();
740
741 if (status.onBit(eStatus::ChargingSSMT)) {
742 if (++m_ssmtCharge > MAX_SSMT_CHARGE) {
743 m_ssmtCharge = MAX_SSMT_CHARGE;
746 }
747
748 return;
749 }
750
751 m_ssmtCharge = 0;
752
753 if (m_flags.offBit(eFlags::SsmtCharged)) {
754 return;
755 }
756
757 if (m_flags.onBit(eFlags::SsmtLeeway)) {
758 if (--m_ssmtLeewayTimer < 0) {
761 m_ssmtDisableAccelTimer = DISABLE_ACCEL_FRAMES;
763 } else {
765 activateBoost(KartBoost::Type::AllMt, SSMT_BOOST_FRAMES);
768 }
769 }
770 } else {
771 if (status.onBit(eStatus::Accelerate) && status.offBit(eStatus::Brake)) {
772 activateBoost(KartBoost::Type::AllMt, SSMT_BOOST_FRAMES);
775 } else {
776 m_ssmtLeewayTimer = LEEWAY_FRAMES;
777 m_flags.setBit(eFlags::SsmtLeeway);
779 m_ssmtDisableAccelTimer = LEEWAY_FRAMES;
780 }
781 }
782}
783
789 auto &status = KartObjectProxy::status();
790
793 if (status.offBit(eStatus::DriftInput)) {
795 } else if (status.offBit(eStatus::SlipdriftCharge)) {
796 if (m_hopStickX == 0) {
797 if (status.onBit(eStatus::StickRight)) {
798 m_hopStickX = -1;
799 } else if (status.onBit(eStatus::StickLeft)) {
800 m_hopStickX = 1;
801 }
803 onHop();
804 }
805 }
806 }
807 }
808
809 if (status.onBit(eStatus::Hop)) {
810 if (m_hopStickX == 0) {
811 if (status.onBit(eStatus::StickRight)) {
812 m_hopStickX = -1;
813 } else if (status.onBit(eStatus::StickLeft)) {
814 m_hopStickX = 1;
815 }
816 }
817 if (m_hopFrame < 3) {
818 ++m_hopFrame;
819 }
820 } else if (status.onBit(eStatus::SlipdriftCharge)) {
821 m_hopFrame = 0;
822 }
823
825}
826
831 m_hopStickX = 0;
832 m_hopFrame = 0;
834 m_driftState = DriftState::NotDrifting;
835 m_smtCharge = 0;
836 m_mtCharge = 0;
837}
838
843 m_outsideDriftAngle = 0.0f;
844 m_hopStickX = 0;
845 m_hopFrame = 0;
846 m_driftState = DriftState::NotDrifting;
847 m_smtCharge = 0;
848 m_mtCharge = 0;
849 m_outsideDriftBonus = 0.0f;
851 eStatus::DriftAuto);
852 m_autoDriftAngle = 0.0f;
853 m_hopStickX = 0;
854 m_autoDriftStartFrameCounter = 0;
855}
856
858void KartMove::clearJumpPad() {
859 m_jumpPadMinSpeed = 0.0f;
860 status().resetBit(eStatus::JumpPad);
861}
862
864void KartMove::clearRampBoost() {
865 m_rampBoost = 0;
866 status().resetBit(eStatus::RampBoost);
867}
868
870void KartMove::clearZipperBoost() {
871 m_zipperBoostTimer = 0;
873}
874
876void KartMove::clearBoost() {
877 m_boost.resetActive();
878 status().resetBit(eStatus::Boost);
879}
880
882void KartMove::clearSsmt() {
883 m_ssmtCharge = 0;
887}
888
890void KartMove::clearOffroadInvincibility() {
893}
894
895void KartMove::clearRejectRoad() {
896 status().resetBit(eStatus::RejectRoadTrigger, eStatus::NoSparkInvisibleWall);
897}
898
903 constexpr s16 AUTO_DRIFT_START_DELAY = 12;
904
905 auto &status = KartObjectProxy::status();
906
907 if (status.offBit(eStatus::AutoDrift)) {
908 return;
909 }
910
911 if (canStartDrift() &&
913 EGG::Mathf::abs(state()->stickX()) > 0.85f) {
914 m_autoDriftStartFrameCounter =
915 std::min<s16>(AUTO_DRIFT_START_DELAY, m_autoDriftStartFrameCounter + 1);
916 } else {
917 m_autoDriftStartFrameCounter = 0;
918 }
919
920 if (m_autoDriftStartFrameCounter >= AUTO_DRIFT_START_DELAY) {
921 status.setBit(eStatus::DriftAuto);
922
923 if (status.onBit(eStatus::TouchingGround)) {
924 if (state()->stickX() < 0.0f) {
925 m_hopStickX = 1;
926 m_autoDriftAngle -= 30.0f * param()->stats().driftAutomaticTightness;
927
928 } else {
929 m_hopStickX = -1;
930 m_autoDriftAngle += 30.0f * param()->stats().driftAutomaticTightness;
931 }
932 }
933
934 f32 halfTarget = 0.5f * param()->stats().driftOutsideTargetAngle;
935 m_autoDriftAngle = std::min(halfTarget, std::max(-halfTarget, m_autoDriftAngle));
936 } else {
937 status.resetBit(eStatus::DriftAuto);
938 m_hopStickX = 0;
939
940 if (m_autoDriftAngle > 0.0f) {
941 m_autoDriftAngle =
942 std::max(0.0f, m_autoDriftAngle - param()->stats().driftOutsideDecrement);
943 } else {
944 m_autoDriftAngle =
945 std::min(0.0f, m_autoDriftAngle + param()->stats().driftOutsideDecrement);
946 }
947 }
948
949 EGG::Quatf angleAxis;
950 angleAxis.setAxisRotation(-m_autoDriftAngle * DEG2RAD, m_up);
951 physics()->composeExtraRot(angleAxis);
952}
953
958 bool isHopping = calcPreDrift();
959 auto &status = KartObjectProxy::status();
960
961 if (status.offBit(eStatus::OverZipper)) {
962 const EGG::Vector3f rotZ = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
963
964 if (status.offBit(eStatus::TouchingGround) &&
965 param()->stats().driftType != KartParam::Stats::DriftType::Inside_Drift_Bike &&
966 status.offBit(eStatus::JumpPadMushroomCollision) &&
968 m_flags.onBit(eFlags::LaunchBoost)) {
969 const EGG::Vector3f up = dynamics()->mainRot().rotateVector(EGG::Vector3f::ey);
971
972 if (driftRej.normalise() != 0.0f) {
973 f32 rejCrossDirMag = driftRej.cross(rotZ).length();
974 f32 angle = EGG::Mathf::atan2(rejCrossDirMag, driftRej.dot(rotZ));
975 f32 sign = 1.0f;
976 if ((rotZ.z * (rotZ.x - driftRej.x)) - (rotZ.x * (rotZ.z - driftRej.z)) > 0.0f) {
977 sign = -1.0f;
978 }
979
980 m_outsideDriftAngle += angle * RAD2DEG * sign;
981 }
982 }
983
985 }
986
987 // TODO: Is this backwards/inverted?
988 if (((status.offBit(eStatus::Hop) || m_hopFrame < 3) &&
990 (status.onBit(eStatus::InAction) || status.offBit(eStatus::TouchingGround))) {
991 if (canHop()) {
992 hop();
993 isHopping = true;
994 }
995 } else {
997 isHopping = false;
998 }
999
1001
1002 if (status.offBit(eStatus::DriftManual)) {
1003 if (!isHopping && status.onBit(eStatus::TouchingGround)) {
1005
1006 if (action()->flags().offBit(KartAction::eFlags::Rotating) || m_speed <= 20.0f) {
1007 f32 driftAngleDecr = param()->stats().driftOutsideDecrement;
1008 if (m_outsideDriftAngle > 0.0f) {
1009 m_outsideDriftAngle = std::max(0.0f, m_outsideDriftAngle - driftAngleDecr);
1010 } else if (m_outsideDriftAngle < 0.0f) {
1011 m_outsideDriftAngle = std::min(0.0f, m_outsideDriftAngle + driftAngleDecr);
1012 }
1013 }
1014 }
1015 } else {
1016 // This is a different comparison than @ref KartMove::canStartDrift().
1017 bool canStartDrift = m_speed > MINIMUM_DRIFT_THRESOLD * m_baseSpeed;
1018
1019 if (status.offBit(eStatus::OverZipper) &&
1021 status.onBit(eStatus::InAction, eStatus::RejectRoadTrigger,
1023 !canStartDrift)) {
1024 if (canStartDrift) {
1025 releaseMt();
1026 }
1027
1029 m_flags.setBit(eFlags::DriftReset);
1030 } else {
1032 }
1033 }
1034}
1035
1040 constexpr f32 OUTSIDE_DRIFT_BONUS = 0.5f;
1041
1042 const auto &stats = param()->stats();
1043 auto &status = KartObjectProxy::status();
1044
1045 if (stats.driftType != KartParam::Stats::DriftType::Inside_Drift_Bike) {
1046 f32 driftAngle = 0.0f;
1047
1048 if (status.onBit(eStatus::Hop)) {
1049 const EGG::Vector3f rotZ = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1050 EGG::Vector3f rotRej = rotZ.rej(m_hopUp);
1051
1052 if (rotRej.normalise() != 0.0f) {
1053 const EGG::Vector3f hopCrossRot = m_hopDir.cross(rotRej);
1054 driftAngle =
1055 EGG::Mathf::atan2(hopCrossRot.length(), m_hopDir.dot(rotRej)) * RAD2DEG;
1056 }
1057 }
1058
1059 m_outsideDriftAngle += driftAngle * static_cast<f32>(-m_hopStickX);
1060 m_outsideDriftAngle = std::max(-60.0f, std::min(60.0f, m_outsideDriftAngle));
1061 }
1062
1064
1065 if (status.offBit(eStatus::DriftInput)) {
1066 return;
1067 }
1068
1069 if (getAppliedHopStickX() == 0) {
1070 return;
1071 }
1072
1073 status.setBit(eStatus::DriftManual).resetBit(eStatus::Hop);
1074 m_driftState = DriftState::ChargingMt;
1075 m_outsideDriftBonus = OUTSIDE_DRIFT_BONUS * (m_speedRatioCapped * stats.driftManualTightness);
1076}
1077
1082 constexpr f32 SMT_LENGTH_FACTOR = 3.0f;
1083
1084 auto &status = KartObjectProxy::status();
1085
1086 if (m_driftState < DriftState::ChargedMt || status.onBit(eStatus::Brake)) {
1087 m_driftState = DriftState::NotDrifting;
1088 return;
1089 }
1090
1091 u16 mtLength = param()->stats().miniTurbo;
1092
1093 if (m_driftState == DriftState::ChargedSmt) {
1094 mtLength *= SMT_LENGTH_FACTOR;
1095 }
1096
1097 if (status.offBit(eStatus::BeforeRespawn, eStatus::InAction)) {
1098 activateBoost(KartBoost::Type::AllMt, mtLength);
1099 }
1100
1101 m_driftState = DriftState::NotDrifting;
1102}
1103
1108 if (state()->airtime() > 5) {
1109 return;
1110 }
1111
1112 if (param()->stats().driftType != KartParam::Stats::DriftType::Inside_Drift_Bike) {
1113 if (m_hopStickX == -1) {
1114 f32 angle = m_outsideDriftAngle;
1115 f32 targetAngle = param()->stats().driftOutsideTargetAngle;
1116 if (angle > targetAngle) {
1117 m_outsideDriftAngle = std::max(m_outsideDriftAngle - 2.0f, targetAngle);
1118 } else if (angle < targetAngle) {
1119 m_outsideDriftAngle += 150.0f * param()->stats().driftManualTightness;
1120 m_outsideDriftAngle = std::min(m_outsideDriftAngle, targetAngle);
1121 }
1122 } else if (m_hopStickX == 1) {
1123 f32 angle = m_outsideDriftAngle;
1124 f32 targetAngle = -param()->stats().driftOutsideTargetAngle;
1125 if (targetAngle > angle) {
1126 m_outsideDriftAngle = std::min(m_outsideDriftAngle + 2.0f, targetAngle);
1127 } else if (targetAngle < angle) {
1128 m_outsideDriftAngle -= 150.0f * param()->stats().driftManualTightness;
1129 m_outsideDriftAngle = std::max(m_outsideDriftAngle, targetAngle);
1130 }
1131 }
1132 }
1133
1134 calcMtCharge();
1135}
1136
1141 f32 turn;
1142 auto &status = KartObjectProxy::status();
1143 bool drifting = state()->isDrifting() && status.offBit(eStatus::JumpPadMushroomCollision);
1144 bool autoDrift = status.onBit(eStatus::AutoDrift);
1145 const auto &stats = param()->stats();
1146
1147 if (drifting) {
1148 turn = autoDrift ? stats.driftAutomaticTightness : stats.driftManualTightness;
1149 } else {
1150 turn = autoDrift ? stats.handlingAutomaticTightness : stats.handlingManualTightness;
1151 }
1152
1153 if (drifting && stats.driftType != KartParam::Stats::DriftType::Inside_Drift_Bike) {
1154 m_outsideDriftBonus *= 0.99f;
1155 turn += m_outsideDriftBonus;
1156 }
1157
1158 bool forwards = true;
1159 if (status.onBit(eStatus::Brake) && m_speed <= 0.0f) {
1160 forwards = false;
1161 }
1162
1163 turn *= m_realTurn;
1164 if (status.onBit(eStatus::ChargingSSMT)) {
1165 turn = m_realTurn * 0.04f;
1166 } else {
1167 if (status.onBit(eStatus::Hop) && m_hopPosY > 0.0f) {
1168 turn *= 1.4f;
1169 }
1170
1171 if (!drifting) {
1172 bool noTurn = false;
1174 EGG::Mathf::abs(m_speed) < 1.0f) {
1175 if (!(status.onBit(eStatus::Hop) && m_hopPosY > 0.0f)) {
1176 turn = 0.0f;
1177 noTurn = true;
1178 }
1179 }
1180 if (forwards && !noTurn) {
1181 if (m_speed >= 20.0f) {
1182 turn *= 0.5f;
1183 if (m_speed < 70.0f) {
1184 turn += (1.0f - (m_speed - 20.0f) / 50.0f) * turn;
1185 }
1186 } else {
1187 turn = (turn * 0.4f) + (m_speed / 20.0f) * (turn * 0.6f);
1188 }
1189 }
1190 }
1191
1192 if (!forwards) {
1193 turn = -turn;
1194 }
1195
1196 if (status.onBit(eStatus::ZipperBoost) && status.offBit(eStatus::DriftManual)) {
1197 turn *= 2.0f;
1198 }
1199
1200 f32 stickX = EGG::Mathf::abs(state()->stickX());
1201 if (autoDrift && stickX > 0.3f) {
1202 f32 stickScalar = (stickX - 0.3f) / 0.7f;
1203 stickX = drifting ? 0.2f : 0.5f;
1204 turn += stickScalar * (turn * stickX * m_speedRatioCapped);
1205 }
1206 }
1207
1208 if (status.offBit(eStatus::InAction, eStatus::ZipperTrick)) {
1209 if (status.offBit(eStatus::TouchingGround)) {
1210 if (status.onBit(eStatus::RampBoost) && m_jump->isBoostRampEnabled()) {
1211 turn = 0.0f;
1212 } else if (status.offBit(eStatus::JumpPadMushroomCollision)) {
1213 u32 airtime = state()->airtime();
1214 if (airtime >= 70) {
1215 turn = 0.0f;
1216 } else if (airtime >= 30) {
1217 turn = std::max(0.0f, turn * (1.0f - (airtime - 30) * 0.025f));
1218 }
1219 }
1220 }
1221
1222 const EGG::Vector3f forward = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1223 f32 angle = EGG::Mathf::atan2(forward.cross(m_dir).length(), forward.dot(m_dir));
1224 angle = EGG::Mathf::abs(angle) * RAD2DEG;
1225
1226 if (angle > 60.0f) {
1227 turn *= std::max(0.0f, 1.0f - (angle - 60.0f) / 40.0f);
1228 }
1229 }
1230
1231 calcVehicleRotation(turn);
1232}
1233
1238 const auto *raceMgr = System::RaceManager::Instance();
1239 auto &status = KartObjectProxy::status();
1240
1241 if (raceMgr->isStageReached(System::RaceManager::Stage::Race)) {
1242 f32 speedFix = dynamics()->speedFix();
1243 if (status.onBit(eStatus::InAction) ||
1244 ((status.onBit(eStatus::WallCollisionStart) || state()->wallBonkTimer() == 0 ||
1245 EGG::Mathf::abs(speedFix) >= 3.0f) &&
1246 status.offBit(eStatus::DriftManual))) {
1247 m_speed += speedFix;
1248 }
1249 }
1250
1251 if (m_speed < -20.0f) {
1252 m_speed += 0.5f;
1253 }
1254
1255 m_acceleration = 0.0f;
1256 m_speedDragMultiplier = 1.0f;
1257
1258 if (status.onBit(eStatus::InAction)) {
1259 action()->calcVehicleSpeed();
1260 return;
1261 }
1262
1263 if ((status.onAllBit(eStatus::SomethingWallCollision, eStatus::TouchingGround) &&
1266 if (status.onBit(eStatus::RampBoost) && state()->airtime() < 4) {
1267 m_acceleration = 7.0f;
1268 } else {
1269 if (status.onBit(eStatus::JumpPad) && status.offBit(eStatus::Accelerate)) {
1270 m_speedDragMultiplier = 0.99f;
1271 } else {
1272 if (status.onBit(eStatus::OverZipper)) {
1273 m_speedDragMultiplier = 0.999f;
1274 } else {
1275 if (state()->airtime() > 5) {
1276 m_speedDragMultiplier = 0.999f;
1277 }
1278 }
1279 }
1281 }
1282
1283 } else if (status.onBit(eStatus::Boost)) {
1284 m_acceleration = m_boost.acceleration();
1285 } else {
1286 if (status.offBit(eStatus::JumpPad, eStatus::RampBoost)) {
1287 if (status.onBit(eStatus::Accelerate)) {
1290 } else {
1291 if (status.offBit(eStatus::Brake) ||
1293 eStatus::SomethingWallCollision)) {
1294 m_speed *= m_speed > 0.0f ? 0.98f : 0.95f;
1295 } else if (m_drivingDirection == DrivingDirection::Braking) {
1296 m_acceleration = -1.5f;
1298 if (++m_backwardsAllowCounter > 15) {
1299 m_drivingDirection = DrivingDirection::Backwards;
1300 }
1301 } else if (m_drivingDirection == DrivingDirection::Backwards) {
1302 m_acceleration = -2.0f;
1303 }
1304 }
1305
1307 const auto &stats = param()->stats();
1308
1309 f32 x = 1.0f - EGG::Mathf::abs(m_weightedTurn) * m_speedRatioCapped;
1310 m_speed *= stats.turningSpeed + (1.0f - stats.turningSpeed) * x;
1311 }
1312 } else {
1313 m_acceleration = 7.0f;
1314 }
1315 }
1316}
1317
1321 f32 vel = 0.0f;
1322 f32 initialVel = 1.0f - m_smoothedUp.y;
1323 if (EGG::Mathf::abs(m_speed) < 30.0f && m_smoothedUp.y > 0.0f && initialVel > 0.0f) {
1324 initialVel = std::min(initialVel * 2.0f, 2.0f);
1325 vel += initialVel;
1326 vel *= std::min(0.5f, std::max(-0.5f, -bodyFront().y));
1327 }
1328 m_speed += vel;
1329}
1330
1335 f32 ratio = m_speed / m_softSpeedLimit;
1336 if (ratio < 0.0f) {
1337 return 1.0f;
1338 }
1339
1340 std::span<const f32> as;
1341 std::span<const f32> ts;
1342 if (state()->isDrifting()) {
1343 as = param()->stats().accelerationDriftA;
1344 ts = param()->stats().accelerationDriftT;
1345 } else {
1346 as = param()->stats().accelerationStandardA;
1347 ts = param()->stats().accelerationStandardT;
1348 }
1349
1350 size_t i = 0;
1351 f32 acceleration = 0.0f;
1352 f32 t_curr = 0.0f;
1353 for (; i < ts.size(); ++i) {
1354 if (ratio < ts[i]) {
1355 acceleration = as[i] + ((as[i + 1] - as[i]) / (ts[i] - t_curr)) * (ratio - t_curr);
1356 break;
1357 }
1358
1359 t_curr = ts[i];
1360 }
1361
1362 return i < ts.size() ? acceleration : as.back();
1363}
1364
1369 constexpr f32 ROTATION_SCALAR_NORMAL = 0.5f;
1370 constexpr f32 ROTATION_SCALAR_MIDAIR = 0.2f;
1371 constexpr f32 ROTATION_SCALAR_BOOST_RAMP = 4.0f;
1372 constexpr f32 OOB_SLOWDOWN_RATE = 0.95f;
1373 constexpr f32 TERMINAL_VELOCITY = 90.0f;
1374
1376 auto &status = KartObjectProxy::status();
1377
1378 dynamics()->setKillExtVelY(status.onBit(eStatus::RespawnKillY));
1379
1380 if (status.onBit(eStatus::Burnout)) {
1381 m_speed = 0.0f;
1382 } else {
1383 if (m_acceleration < 0.0f) {
1384 if (m_speed < -20.0f) {
1385 m_acceleration = 0.0f;
1386 } else {
1387 if (m_speed + m_acceleration <= -20.0f) {
1388 m_acceleration = -20.0f - m_speed;
1389 }
1390 }
1391 }
1392
1394 }
1395
1396 if (status.onBit(eStatus::BeforeRespawn)) {
1397 m_speed *= OOB_SLOWDOWN_RATE;
1398 } else {
1399 if (status.onBit(eStatus::ChargingSSMT)) {
1400 m_speed *= 0.8f;
1401 } else {
1402 if (m_drivingDirection == DrivingDirection::Braking && m_speed < 0.0f) {
1403 m_speed = 0.0f;
1406 }
1407 }
1408 }
1409
1410 f32 speedLimit = status.onBit(eStatus::JumpPad) ? m_jumpPadMaxSpeed : m_baseSpeed;
1411 const f32 boostMultiplier = m_boost.multiplier();
1412 const f32 boostSpdLimit = m_boost.speedLimit();
1413 m_jumpPadBoostMultiplier = boostMultiplier;
1414
1415 f32 crushMultiplier = status.onBit(eStatus::Crushed) ? 0.7f : 1.0f;
1416 f32 wheelieBonus = boostMultiplier + getWheelieSoftSpeedLimitBonus();
1417 speedLimit *= status.onBit(eStatus::JumpPadFixedSpeed) ?
1418 1.0f :
1419 crushMultiplier * (wheelieBonus * m_kclSpeedFactor);
1420
1421 bool ignoreCrushSpeed = status.onBit(eStatus::RampBoost, eStatus::ZipperInvisibleWall,
1423 f32 boostSpeed = ignoreCrushSpeed ? 1.0f : crushMultiplier;
1424 boostSpeed *= boostSpdLimit * m_kclSpeedFactor;
1425
1426 if (status.offBit(eStatus::JumpPad) && boostSpeed > 0.0f && boostSpeed > speedLimit) {
1427 speedLimit = boostSpeed;
1428 }
1429
1430 m_jumpPadSoftSpeedLimit = boostSpdLimit * m_kclSpeedFactor;
1431
1432 if (status.onBit(eStatus::RampBoost)) {
1433 speedLimit = std::max(speedLimit, 100.0f);
1434 }
1435
1436 m_lastDir = (m_speed > 0.0f) ? 1.0f * m_dir : -1.0f * m_dir;
1437
1438 f32 local_c8 = 1.0f;
1439 speedLimit *= calcWallCollisionSpeedFactor(local_c8);
1440
1441 if (m_softSpeedLimit <= speedLimit) {
1442 m_softSpeedLimit = speedLimit;
1444 m_softSpeedLimit = std::max(m_softSpeedLimit - 3.0f, speedLimit);
1445 } else {
1446 m_softSpeedLimit = speedLimit;
1447 }
1448
1450
1451 m_speed = std::min(m_softSpeedLimit, std::max(-m_softSpeedLimit, m_speed));
1452
1453 if (status.onBit(eStatus::JumpPad)) {
1454 m_speed = std::max(m_speed, m_jumpPadMinSpeed);
1455 }
1456
1457 calcWallCollisionStart(local_c8);
1458
1459 m_speedRatio = EGG::Mathf::abs(m_speed / m_baseSpeed);
1460 m_speedRatioCapped = std::min(1.0f, m_speedRatio);
1461
1462 EGG::Vector3f crossVec = m_smoothedUp.cross(m_dir);
1463 if (m_speed < 0.0f) {
1464 crossVec = -crossVec;
1465 }
1466
1467 f32 rotationScalar = ROTATION_SCALAR_NORMAL;
1468 if (collide()->surfaceFlags().onBit(KartCollide::eSurfaceFlags::BoostRamp)) {
1469 rotationScalar = ROTATION_SCALAR_BOOST_RAMP;
1470 } else if (status.offBit(eStatus::TouchingGround)) {
1471 rotationScalar = ROTATION_SCALAR_MIDAIR;
1472 }
1473
1474 EGG::Matrix34f local_90;
1475 local_90.setAxisRotation(rotationScalar * DEG2RAD, crossVec);
1476 m_vel1Dir = local_90.multVector33(m_vel1Dir);
1477
1478 const auto *raceMgr = System::RaceManager::Instance();
1479 if (status.offBit(eStatus::InAction, eStatus::DisableBackwardsAccel, eStatus::Accelerate) &&
1481 raceMgr->isStageReached(System::RaceManager::Stage::Race)) {
1483 }
1484
1486 EGG::Vector3f nextSpeed = m_speed * m_vel1Dir;
1487
1488 f32 maxSpeedY = status.onBit(eStatus::OverZipper) ? KartHalfPipe::TerminalVelocity() :
1489 TERMINAL_VELOCITY;
1490 nextSpeed.y = std::min(nextSpeed.y, maxSpeedY);
1491
1492 dynamics()->setIntVel(dynamics()->intVel() + nextSpeed);
1493
1494 if (status.onBit(eStatus::TouchingGround) &&
1496 if (status.onBit(eStatus::Brake)) {
1497 if (m_drivingDirection == DrivingDirection::Forwards) {
1498 m_drivingDirection = m_processedSpeed > 5.0f ? DrivingDirection::Braking :
1499 DrivingDirection::Backwards;
1500 }
1501 } else {
1502 if (m_processedSpeed >= 0.0f) {
1503 m_drivingDirection = DrivingDirection::Forwards;
1504 }
1505 }
1506 } else {
1507 m_drivingDirection = DrivingDirection::Forwards;
1508 }
1509}
1510
1515 auto &status = KartObjectProxy::status();
1516
1518 return 1.0f;
1519 }
1520
1521 onWallCollision();
1522
1524 return 1.0f;
1525 }
1526
1527 EGG::Vector3f wallNrm = collisionData().wallNrm;
1528 if (wallNrm.y > 0.0f) {
1529 wallNrm.y = 0.0f;
1530 wallNrm.normalise();
1531 }
1532
1533 f32 dot = m_lastDir.dot(wallNrm);
1534
1535 if (dot < 0.0f) {
1536 f1 = std::max(0.0f, dot + 1.0f);
1537
1538 return std::min(1.0f, f1 * (status.onBit(eStatus::WallCollision) ? 0.4f : 0.7f));
1539 }
1540
1541 return 1.0f;
1542}
1543
1549
1550 auto &status = KartObjectProxy::status();
1551
1552 if (status.offBit(eStatus::WallCollisionStart)) {
1553 return;
1554 }
1555
1556 m_outsideDriftAngle = 0.0f;
1557 if (status.offBit(eStatus::InAction)) {
1558 m_dir = bodyFront();
1559 m_vel1Dir = m_dir;
1560 m_landingDir = m_dir;
1561 }
1562
1563 if (status.offBit(eStatus::ZipperInvisibleWall, eStatus::OverZipper) && param_2 < 0.9f) {
1564 f32 speedDiff = m_lastSpeed - m_speed;
1565 const CollisionData &colData = collisionData();
1566
1567 if (speedDiff > 30.0f) {
1568 m_flags.setBit(eFlags::WallBounce);
1569 EGG::Vector3f newPos = colData.relPos + pos();
1570 f32 dot = -bodyUp().dot(colData.relPos) * 0.5f;
1571 EGG::Vector3f scaledUp = dot * bodyUp();
1572 newPos -= scaledUp;
1573
1574 speedDiff = std::min(60.0f, speedDiff);
1575 EGG::Vector3f scaledWallNrm = speedDiff * colData.wallNrm;
1576
1577 auto [proj, rej] = scaledWallNrm.projAndRej(m_vel1Dir);
1578 proj *= 0.3f;
1579 rej *= 0.9f;
1580
1581 if (status.onBit(eStatus::Boost)) {
1582 proj = EGG::Vector3f::zero;
1583 rej = EGG::Vector3f::zero;
1584 }
1585
1586 if (bodyFront().dot(colData.wallNrm) > 0.0f) {
1587 proj = EGG::Vector3f::zero;
1588 }
1589 rej *= 0.9f;
1590
1591 EGG::Vector3f projRejSum = proj + rej;
1592 f32 bumpDeviation = 0.0f;
1593 if (m_flags.offBit(eFlags::DriftReset) && status.onBit(eStatus::TouchingGround)) {
1594 bumpDeviation = param()->stats().bumpDeviationLevel;
1595 }
1596
1597 dynamics()->applyWrenchScaled(newPos, projRejSum, bumpDeviation);
1598 } else if (wallKclType() == COL_TYPE_SPECIAL_WALL && wallKclVariant() == 2) {
1599 dynamics()->addForce(colData.wallNrm * 15.0f);
1600 collide()->startFloorMomentRate();
1601 }
1602
1603 if (wallKclType() == COL_TYPE_SPECIAL_WALL && wallKclVariant() == 0) {
1604 dynamics()->addForce(colData.wallNrm * 15.0f);
1605 collide()->startFloorMomentRate();
1606 }
1607 }
1608}
1609
1614 f32 next = 0.0f;
1615 f32 scalar = 1.0f;
1616
1617 auto &status = KartObjectProxy::status();
1618
1619 if (status.onBit(eStatus::TouchingGround)) {
1620 if (System::RaceManager::Instance()->stage() == System::RaceManager::Stage::Countdown) {
1621 next = 0.015f * -state()->startBoostCharge();
1622 } else if (status.offBit(eStatus::ChargingSSMT)) {
1623 if (status.offBit(eStatus::JumpPad, eStatus::RampBoost, eStatus::SoftWallDrift)) {
1624 f32 speedDiff = m_lastSpeed - m_speed;
1625 scalar = std::min(3.0f, std::max(speedDiff, -3.0f));
1626
1627 if (status.onBit(eStatus::MushroomBoost)) {
1628 next = (scalar * 0.15f) * 0.25f;
1629 if (status.onBit(eStatus::Wheelie)) {
1630 next *= 0.5f;
1631 }
1632 } else {
1633 next = (scalar * 0.15f) * 0.08f;
1634 }
1635 scalar = m_driftingParams->boostRotFactor;
1636 }
1637 } else {
1638 constexpr s16 MAX_SSMT_CHARGE = 75;
1639 next = 0.015f * (-static_cast<f32>(m_ssmtCharge) / static_cast<f32>(MAX_SSMT_CHARGE));
1640 }
1641 }
1642
1643 if (m_flags.onBit(eFlags::WallBounce)) {
1644 m_standStillBoostRot = isBike() ? next * 3.0f : next * 10.0f;
1645 } else {
1646 m_standStillBoostRot += scalar * (next - m_standStillBoostRot);
1647 }
1648}
1649
1654 constexpr f32 DIVE_LIMIT = 0.8f;
1655
1656 m_divingRot *= 0.96f;
1657
1658 auto &status = KartObjectProxy::status();
1659
1660 if (status.onBit(eStatus::TouchingGround, eStatus::CannonStart, eStatus::InCannon,
1661 eStatus::InAction, eStatus::OverZipper)) {
1662 return;
1663 }
1664
1665 f32 stickY = state()->stickY();
1666
1667 if (status.onBit(eStatus::InATrick) && m_jump->type() == TrickType::BikeSideStuntTrick) {
1668 stickY = std::min(1.0f, stickY + 0.4f);
1669 }
1670
1671 u32 airtime = state()->airtime();
1672
1673 if (airtime > 50) {
1674 if (EGG::Mathf::abs(stickY) < 0.1f) {
1675 m_divingRot += 0.05f * (-0.025f - m_divingRot);
1676 }
1677 } else {
1678 stickY *= (airtime / 50.0f);
1679 }
1680
1681 m_divingRot = std::max(-DIVE_LIMIT, std::min(DIVE_LIMIT, m_divingRot + stickY * 0.005f));
1682
1683 EGG::Vector3f angVel2 = dynamics()->angVel2();
1684 angVel2.x += m_divingRot;
1685 dynamics()->setAngVel2(angVel2);
1686
1687 if (state()->airtime() < 50) {
1688 return;
1689 }
1690
1691 EGG::Vector3f topRotated = dynamics()->mainRot().rotateVector(EGG::Vector3f::ey);
1692 EGG::Vector3f forwardRotated = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1693 f32 upDotTop = m_up.dot(topRotated);
1694 EGG::Vector3f upCrossTop = m_up.cross(topRotated);
1695 f32 crossNorm = upCrossTop.length();
1696 f32 angle = EGG::Mathf::abs(EGG::Mathf::atan2(crossNorm, upDotTop));
1697
1698 f32 fVar1 = angle * RAD2DEG - 20.0f;
1699 if (fVar1 <= 0.0f) {
1700 return;
1701 }
1702
1703 f32 mult = std::min(1.0f, fVar1 / 20.0f);
1704 if (forwardRotated.y > 0.0f) {
1705 dynamics()->setGravity((1.0f - 0.2f * mult) * dynamics()->gravity());
1706 } else {
1707 dynamics()->setGravity((0.2f * mult + 1.0f) * dynamics()->gravity());
1708 }
1709}
1710
1715 auto &status = KartObjectProxy::status();
1716
1717 if (EGG::Mathf::abs(m_speed) >= 10.0f || status.onBit(eStatus::Boost, eStatus::RampBoost) ||
1720 return;
1721 }
1722
1724}
1725
1727void KartMove::calcHopPhysics() {
1728 m_hopVelY = m_hopVelY * 0.998f + m_hopGravity;
1730
1731 if (m_hopPosY < 0.0f) {
1732 m_hopPosY = 0.0f;
1733 m_hopVelY = 0.0f;
1734 }
1735}
1736
1738void KartMove::calcRejectRoad() {
1739 m_reject.calcRejectRoad();
1740}
1741
1743bool KartMove::calcZipperCollision(f32 radius, f32 scale, EGG::Vector3f &pos,
1744 EGG::Vector3f &upLocal, const EGG::Vector3f &prevPos, Field::CollisionInfo *colInfo,
1745 Field::KCLTypeMask *maskOut, Field::KCLTypeMask flags) const {
1746 upLocal = mainRot().rotateVector(EGG::Vector3f::ey);
1747 pos = dynamics()->pos() + (-scale * m_scale.y) * upLocal;
1748
1749 auto *colDir = Field::CollisionDirector::Instance();
1750 return colDir->checkSphereFullPush(radius, pos, prevPos, flags, colInfo, maskOut, 0);
1751}
1752
1754f32 KartMove::calcSlerpRate(f32 scale, const EGG::Quatf &from, const EGG::Quatf &to) const {
1755 f32 dotNorm = std::max(-1.0f, std::min(1.0f, from.dot(to)));
1756 f32 acos = EGG::Mathf::acos(dotNorm);
1757 return acos > 0.0f ? std::min(0.1f, scale / acos) : 0.1f;
1758}
1759
1761void KartMove::applyForce(f32 force, const EGG::Vector3f &hitDir, bool stop) {
1762 if (m_bumpTimer >= 1) {
1763 return;
1764 }
1765
1766 dynamics()->addForce(force * hitDir.perpInPlane(m_up, true));
1767 collide()->startFloorMomentRate();
1768
1769 m_bumpTimer = 5;
1770
1771 if (stop) {
1772 m_speed = 0.0f;
1773 }
1774}
1775
1779 f32 tiltMagnitude = 0.0f;
1780 auto &status = KartObjectProxy::status();
1781
1782 if (status.offBit(eStatus::InAction, eStatus::SoftWallDrift) &&
1784 EGG::Vector3f front = componentZAxis();
1785 front = front.perpInPlane(m_up, true);
1786 EGG::Vector3f frontSpeed = velocity().rej(front).perpInPlane(m_up, false);
1787 f32 magnitude = tiltMagnitude;
1788
1789 if (frontSpeed.squaredLength() > std::numeric_limits<f32>::epsilon()) {
1790 magnitude = frontSpeed.length();
1791
1792 if (front.z * frontSpeed.x - front.x * frontSpeed.z > 0.0f) {
1793 magnitude = -magnitude;
1794 }
1795
1796 tiltMagnitude = -1.0f;
1797 if (-1.0f <= magnitude) {
1798 tiltMagnitude = std::min(1.0f, magnitude);
1799 }
1800 }
1801 } else if (status.offBit(eStatus::Hop) || m_hopPosY <= 0.0f) {
1802 EGG::Vector3f angVel0 = dynamics()->angVel0();
1803 angVel0.z *= 0.98f;
1804 dynamics()->setAngVel0(angVel0);
1805 }
1806
1807 f32 lean = EGG::Mathf::abs(m_weightedTurn) * (tiltMagnitude * param()->stats().tilt);
1808
1810
1811 EGG::Vector3f angVel0 = dynamics()->angVel0();
1812 angVel0.x += m_standStillBoostRot;
1813 angVel0.z += lean;
1814 dynamics()->setAngVel0(angVel0);
1815
1816 EGG::Vector3f angVel2 = dynamics()->angVel2();
1817 angVel2.y += turn;
1818 dynamics()->setAngVel2(angVel2);
1819
1820 calcDive();
1821}
1822
1827 // TODO: Some of these are shared between the base and derived class implementations.
1828 constexpr u16 MAX_MT_CHARGE = 270;
1829 constexpr u16 MAX_SMT_CHARGE = 300;
1830 constexpr u16 BASE_MT_CHARGE = 2;
1831 constexpr u16 BASE_SMT_CHARGE = 2;
1832 constexpr f32 BONUS_CHARGE_STICK_THRESHOLD = 0.4f;
1833 constexpr u16 EXTRA_MT_CHARGE = 3;
1834
1835 if (m_driftState == DriftState::ChargedSmt) {
1836 return;
1837 }
1838
1839 f32 stickX = state()->stickX();
1840
1841 if (m_driftState == DriftState::ChargingMt) {
1842 m_mtCharge += BASE_MT_CHARGE;
1843
1844 if (-BONUS_CHARGE_STICK_THRESHOLD <= stickX) {
1845 if (BONUS_CHARGE_STICK_THRESHOLD < stickX && m_hopStickX == -1) {
1846 m_mtCharge += EXTRA_MT_CHARGE;
1847 }
1848 } else if (m_hopStickX != -1) {
1849 m_mtCharge += EXTRA_MT_CHARGE;
1850 }
1851
1852 if (m_mtCharge > MAX_MT_CHARGE) {
1853 m_mtCharge = MAX_MT_CHARGE;
1854 m_driftState = DriftState::ChargingSmt;
1855 }
1856 }
1857
1858 if (m_driftState != DriftState::ChargingSmt) {
1859 return;
1860 }
1861
1862 m_smtCharge += BASE_SMT_CHARGE;
1863
1864 if (-BONUS_CHARGE_STICK_THRESHOLD <= stickX) {
1865 if (BONUS_CHARGE_STICK_THRESHOLD < stickX && m_hopStickX == -1) {
1866 m_smtCharge += EXTRA_MT_CHARGE;
1867 }
1868 } else if (m_hopStickX != -1) {
1869 m_smtCharge += EXTRA_MT_CHARGE;
1870 }
1871
1872 if (m_smtCharge > MAX_SMT_CHARGE) {
1873 m_smtCharge = MAX_SMT_CHARGE;
1874 m_driftState = DriftState::ChargedSmt;
1875 }
1876}
1877
1879void KartMove::initOob() {
1880 clearBoost();
1881 clearJumpPad();
1882 clearRampBoost();
1883 clearZipperBoost();
1884 clearSsmt();
1885 clearOffroadInvincibility();
1886}
1887
1892 status().setBit(eStatus::Hop).resetBit(eStatus::DriftManual);
1893 onHop();
1894
1895 m_hopUp = dynamics()->mainRot().rotateVector(EGG::Vector3f::ey);
1896 m_hopDir = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1897 m_driftState = DriftState::NotDrifting;
1898 m_smtCharge = 0;
1899 m_mtCharge = 0;
1900 m_hopStickX = 0;
1901 m_hopFrame = 0;
1902 m_hopPosY = 0.0f;
1903 m_hopGravity = dynamics()->gravity();
1904 m_hopVelY = m_driftingParams->hopVelY;
1905 m_outsideDriftBonus = 0.0f;
1906
1907 EGG::Vector3f extVel = dynamics()->extVel();
1908 extVel.y = 0.0f + m_hopVelY;
1909 dynamics()->setExtVel(extVel);
1910
1911 EGG::Vector3f totalForce = dynamics()->totalForce();
1912 totalForce.y = 0.0f;
1913 dynamics()->setTotalForce(totalForce);
1914}
1915
1917void KartMove::tryStartBoostPanel() {
1918 constexpr s16 BOOST_PANEL_DURATION = 60;
1919
1920 if (status().onBit(eStatus::BeforeRespawn, eStatus::InAction)) {
1921 return;
1922 }
1923
1924 activateBoost(KartBoost::Type::MushroomAndBoostPanel, BOOST_PANEL_DURATION);
1925 setOffroadInvincibility(BOOST_PANEL_DURATION);
1926}
1927
1932 constexpr s16 BOOST_RAMP_DURATION = 60;
1933
1934 auto &status = KartObjectProxy::status();
1935
1936 if (status.onBit(eStatus::BeforeRespawn, eStatus::InAction)) {
1937 return;
1938 }
1939
1940 status.setBit(eStatus::RampBoost);
1941 m_rampBoost = BOOST_RAMP_DURATION;
1942 setOffroadInvincibility(BOOST_RAMP_DURATION);
1943}
1944
1950 static constexpr std::array<JumpPadProperties, 8> JUMP_PAD_PROPERTIES = {{
1951 {50.0f, 50.0f, 35.0f},
1952 {50.0f, 50.0f, 47.0f},
1953 {59.0f, 59.0f, 30.0f},
1954 {73.0f, 73.0f, 45.0f},
1955 {73.0f, 73.0f, 53.0f},
1956 {55.0f, 55.0f, 35.0f},
1957 {56.0f, 56.0f, 50.0f},
1958 }};
1959
1960 auto &status = KartObjectProxy::status();
1961
1962 if (status.onBit(eStatus::BeforeRespawn, eStatus::InAction, eStatus::HalfPipeRamp)) {
1963 return;
1964 }
1965
1966 status.setBit(eStatus::JumpPad);
1967 s32 jumpPadVariant = state()->jumpPadVariant();
1968 m_jumpPadProperties = &JUMP_PAD_PROPERTIES[jumpPadVariant];
1969
1970 if (jumpPadVariant == 3 || jumpPadVariant == 4) {
1971 if (m_jumpPadBoostMultiplier > 1.3f || m_jumpPadSoftSpeedLimit > 110.0f) {
1972 // Set speed to 100 if the player has boost from a boost panel or mushroom(item) before
1973 // hitting the jump pad
1974 static constexpr std::array<JumpPadProperties, 2> JUMP_PAD_PROPERTIES_SHROOM_BOOST = {{
1975 {100.0f, 100.0f, 70.0f},
1976 {100.0f, 100.0f, 65.0f},
1977 }};
1978 m_jumpPadProperties = &JUMP_PAD_PROPERTIES_SHROOM_BOOST[jumpPadVariant != 3];
1979 }
1980
1981 status.setBit(eStatus::JumpPadFixedSpeed);
1982 }
1983
1984 if (jumpPadVariant == 4) {
1985 status.setBit(eStatus::JumpPadMushroomTrigger, eStatus::JumpPadMushroomVelYInc,
1986 eStatus::JumpPadMushroomCollision);
1987 } else {
1988 EGG::Vector3f extVel = dynamics()->extVel();
1989 EGG::Vector3f totalForce = dynamics()->totalForce();
1990
1991 extVel.y = m_jumpPadProperties->velY;
1992 totalForce.y = 0.0f;
1993
1994 dynamics()->setExtVel(extVel);
1995 dynamics()->setTotalForce(totalForce);
1996
1997 if (jumpPadVariant != 3) {
1998 EGG::Vector3f dir = m_dir;
1999 dir.y = 0.0f;
2000 dir.normalise();
2001 m_speed *= m_dir.dot(dir);
2002 m_dir = dir;
2003 m_vel1Dir = dir;
2004 status.setBit(eStatus::JumpPadDisableYsusForce);
2005 }
2006 }
2007
2008 m_jumpPadMinSpeed = m_jumpPadProperties->minSpeed;
2009 m_jumpPadMaxSpeed = m_jumpPadProperties->maxSpeed;
2010 m_speed = std::max(m_speed, m_jumpPadMinSpeed);
2011}
2012
2014void KartMove::tryEndJumpPad() {
2015 auto &status = KartObjectProxy::status();
2016 if (status.onBit(eStatus::JumpPadMushroomTrigger)) {
2017 if (status.onBit(eStatus::GroundStart)) {
2018 status.resetBit(eStatus::JumpPadMushroomTrigger, eStatus::JumpPadFixedSpeed,
2019 eStatus::JumpPadMushroomVelYInc);
2020 }
2021
2022 if (status.onBit(eStatus::JumpPadMushroomVelYInc)) {
2023 EGG::Vector3f newExtVel = dynamics()->extVel();
2024 newExtVel.y += 20.0f;
2025 if (m_jumpPadProperties->velY < newExtVel.y) {
2026 newExtVel.y = m_jumpPadProperties->velY;
2027 status.resetBit(eStatus::JumpPadMushroomVelYInc);
2028 }
2029 dynamics()->setExtVel(newExtVel);
2030 }
2031 }
2032
2033 if (status.onBit(eStatus::GroundStart) && status.offBit(eStatus::JumpPadMushroomTrigger)) {
2034 cancelJumpPad();
2035 }
2036}
2037
2039void KartMove::cancelJumpPad() {
2040 m_jumpPadMinSpeed = 0.0f;
2041 status().resetBit(eStatus::JumpPad);
2042}
2043
2045void KartMove::activateBoost(KartBoost::Type type, s16 frames) {
2046 if (m_boost.activate(type, frames)) {
2047 status().setBit(eStatus::Boost);
2048 }
2049}
2050
2052void KartMove::applyStartBoost(s16 frames) {
2053 activateBoost(KartBoost::Type::AllMt, frames);
2054}
2055
2057void KartMove::activateMushroom() {
2058 constexpr s16 MUSHROOM_DURATION = 90;
2059
2060 auto &status = KartObjectProxy::status();
2061
2062 if (status.onBit(eStatus::BeforeRespawn, eStatus::InAction)) {
2063 return;
2064 }
2065
2066 activateBoost(KartBoost::Type::MushroomAndBoostPanel, MUSHROOM_DURATION);
2067
2068 m_mushroomBoostTimer = MUSHROOM_DURATION;
2070 setOffroadInvincibility(MUSHROOM_DURATION);
2071}
2072
2074void KartMove::activateZipperBoost() {
2075 constexpr s16 BASE_DURATION = 50;
2076 constexpr s16 TRICK_DURATION = 100;
2077
2078 auto &status = KartObjectProxy::status();
2079
2080 if (status.onBit(eStatus::BeforeRespawn, eStatus::InAction)) {
2081 return;
2082 }
2083
2084 s16 boostDuration = status.onBit(eStatus::ZipperTrick) ? TRICK_DURATION : BASE_DURATION;
2085 activateBoost(KartBoost::Type::TrickAndZipper, boostDuration);
2086
2087 setOffroadInvincibility(boostDuration);
2088 m_zipperBoostTimer = 0;
2089 m_zipperBoostMax = boostDuration;
2091}
2092
2098 if (timer > m_offroadInvincibility) {
2099 m_offroadInvincibility = timer;
2100 }
2101
2103}
2104
2109 auto &status = KartObjectProxy::status();
2110
2112 return;
2113 }
2114
2115 if (--m_offroadInvincibility > 0) {
2116 return;
2117 }
2118
2120}
2121
2125 auto &status = KartObjectProxy::status();
2126
2127 if (status.offBit(eStatus::MushroomBoost)) {
2128 return;
2129 }
2130
2131 if (--m_mushroomBoostTimer > 0) {
2132 return;
2133 }
2134
2136}
2137
2139void KartMove::calcZipperBoost() {
2140 auto &status = KartObjectProxy::status();
2141
2142 if (status.offBit(eStatus::ZipperBoost)) {
2143 return;
2144 }
2145
2147
2148 if (status.offBit(eStatus::OverZipper) && ++m_zipperBoostTimer >= m_zipperBoostMax) {
2149 m_zipperBoostTimer = 0;
2151 }
2152
2153 if (m_zipperBoostTimer < 10) {
2154 EGG::Vector3f angVel = dynamics()->angVel0();
2155 angVel.y = 0.0f;
2156 dynamics()->setAngVel0(angVel);
2157 }
2158}
2159
2161void KartMove::landTrick() {
2162 static constexpr std::array<s16, 3> KART_TRICK_BOOST_DURATION = {{
2163 40,
2164 70,
2165 85,
2166 }};
2167 static constexpr std::array<s16, 3> BIKE_TRICK_BOOST_DURATION = {{
2168 45,
2169 80,
2170 95,
2171 }};
2172
2173 if (status().onBit(eStatus::BeforeRespawn, eStatus::InAction)) {
2174 return;
2175 }
2176
2177 s16 duration;
2178 if (isBike()) {
2179 duration = BIKE_TRICK_BOOST_DURATION[static_cast<u32>(m_jump->variant())];
2180 } else {
2181 duration = KART_TRICK_BOOST_DURATION[static_cast<u32>(m_jump->variant())];
2182 }
2183
2184 activateBoost(KartBoost::Type::TrickAndZipper, duration);
2185}
2186
2188void KartMove::activateCrush(u16 timer) {
2189 status().setBit(eStatus::Crushed);
2190 m_crushTimer = timer;
2191 m_kartScale->startCrush();
2192}
2193
2195void KartMove::calcCrushed() {
2196 if (status().offBit(eStatus::Crushed)) {
2197 return;
2198 }
2199
2200 if (--m_crushTimer == 0) {
2201 status().resetBit(eStatus::Crushed);
2202 m_kartScale->startUncrush();
2203 }
2204}
2205
2207void KartMove::calcScale() {
2208 m_kartScale->calc();
2209 setScale(m_kartScale->currScale());
2210}
2211
2213void KartMove::applyBumpForce(f32 speed, const EGG::Vector3f &hitDir, bool resetSpeed) {
2214 constexpr s16 BUMP_COOLDOWN = 5;
2215
2216 if (m_bumpTimer >= 1) {
2217 return;
2218 }
2219
2220 dynamics()->addForce(speed * hitDir.perpInPlane(move()->up(), true));
2221 collide()->startFloorMomentRate();
2222
2223 m_bumpTimer = BUMP_COOLDOWN;
2224
2225 if (resetSpeed) {
2226 m_speed = 0.0f;
2227 }
2228}
2229
2231void KartMove::enterCannon() {
2232 init(true, true);
2233 physics()->clearDecayingRot();
2234 m_boost.resetActive();
2235
2236 auto &status = KartObjectProxy::status();
2237
2238 status.resetBit(eStatus::Boost);
2239
2240 cancelJumpPad();
2241 clearRampBoost();
2242 clearZipperBoost();
2243 clearSsmt();
2244 clearOffroadInvincibility();
2245
2246 dynamics()->reset();
2247
2248 clearDrift();
2249
2250 status.resetBit(eStatus::Hop, eStatus::CannonStart)
2251 .setBit(eStatus::InCannon, eStatus::SkipWheelCalc);
2252
2253 const auto [cannonPos, cannonDir] = getCannonPosRot();
2254 m_cannonEntryPos = pos();
2255 m_cannonEntryOfs = cannonPos - pos();
2256 m_cannonEntryOfsLength = m_cannonEntryOfs.normalise();
2257 m_cannonEntryOfs.normalise();
2258 m_dir = m_cannonEntryOfs;
2259 m_vel1Dir = m_cannonEntryOfs;
2260 m_cannonOrthog = EGG::Vector3f::ey.perpInPlane(m_cannonEntryOfs, true);
2261 m_cannonProgress.setZero();
2262}
2263
2265void KartMove::calcCannon() {
2266 auto [cannonPos, cannonDir] = getCannonPosRot();
2267 EGG::Vector3f forwardXZ = cannonPos - m_cannonEntryPos - m_cannonProgress;
2268 EGG::Vector3f forward = forwardXZ;
2269 f32 forwardLength = forward.normalise();
2270 forwardXZ.y = 0;
2271 forwardXZ.normalise();
2272 EGG::Vector3f local94 = m_cannonEntryOfs;
2273 local94.y = 0;
2274 local94.normalise();
2275 m_speedRatioCapped = 1.0f;
2276 m_speedRatio = 1.5f;
2277 EGG::Matrix34f cannonOrientation;
2278 cannonOrientation.makeOrthonormalBasis(forward, EGG::Vector3f::ey);
2279 EGG::Vector3f up = cannonOrientation.multVector33(EGG::Vector3f::ey);
2280 m_smoothedUp = up;
2281 m_up = up;
2282
2283 if (forwardLength < 30.0f || local94.dot(forwardXZ) <= 0.0f) {
2284 exitCannon();
2285 return;
2286 }
2288 const auto *cannonPoint =
2289 System::CourseMap::Instance()->getCannonPoint(state()->cannonPointId());
2290 size_t cannonParameterIdx = std::max<s16>(0, cannonPoint->parameterIdx());
2291 ASSERT(cannonParameterIdx < CANNON_PARAMETERS.size());
2292 const auto &cannonParams = CANNON_PARAMETERS[cannonParameterIdx];
2293 f32 newSpeed = cannonParams.speed;
2294 if (forwardLength < cannonParams.decelFactor) {
2295 f32 factor = std::max(0.0f, forwardLength / cannonParams.decelFactor);
2296
2297 newSpeed = cannonParams.endDecel;
2298 if (newSpeed <= 0.0f) {
2299 newSpeed = m_baseSpeed;
2300 }
2301
2302 newSpeed += factor * (cannonParams.speed - newSpeed);
2303 if (cannonParams.endDecel > 0.0f) {
2304 m_speed = std::min(newSpeed, m_speed);
2305 }
2306 }
2307
2308 m_cannonProgress += m_cannonEntryOfs * newSpeed;
2309
2310 EGG::Vector3f newPos = EGG::Vector3f::zero;
2311 if (cannonParams.height > 0.0f) {
2312 f32 fVar9 = EGG::Mathf::SinFIdx(
2313 (1.0f - (forwardLength / m_cannonEntryOfsLength)) * 180.0f * DEG2FIDX);
2314 newPos = fVar9 * cannonParams.height * m_cannonOrthog;
2315 }
2316
2317 dynamics()->setPos(m_cannonEntryPos + m_cannonProgress + newPos);
2318 m_dir = m_cannonEntryOfs;
2319 m_vel1Dir = m_cannonEntryOfs;
2320
2321 calcRotCannon(forward);
2322
2323 dynamics()->setExtVel(EGG::Vector3f::zero);
2324}
2325
2327void KartMove::calcRotCannon(const EGG::Vector3f &forward) {
2328 EGG::Vector3f local48 = forward;
2329 local48.normalise();
2330 EGG::Vector3f local54 = bodyFront();
2331 EGG::Vector3f local60 = local54 + ((local48 - local54) * 0.3f);
2332 local54.normalise();
2333 local60.normalise();
2334 // also local70, localA8
2335 EGG::Quatf local80;
2336 local80.makeVectorRotation(local54, local60);
2337 local80 *= dynamics()->fullRot();
2338 local80.normalise();
2339 EGG::Quatf localB8;
2340 localB8.makeVectorRotation(local80.rotateVector(EGG::Vector3f::ey), smoothedUp());
2341 EGG::Quatf newRot = local80.slerpTo(localB8.multSwap(local80), 0.3f);
2342 dynamics()->setFullRot(newRot);
2343 dynamics()->setMainRot(newRot);
2344}
2345
2347void KartMove::exitCannon() {
2348 auto &status = KartObjectProxy::status();
2349
2350 if (status.offBit(eStatus::InCannon)) {
2351 return;
2352 }
2353
2354 status.resetBit(eStatus::InCannon, eStatus::SkipWheelCalc).setBit(eStatus::AfterCannon);
2355 dynamics()->setIntVel(m_cannonEntryOfs * m_speed);
2356}
2357
2359void KartMove::triggerRespawn() {
2360 m_timeInRespawn = 0;
2361 status().setBit(eStatus::TriggerRespawn);
2362}
2363
2365KartMoveBike::KartMoveBike() : m_leanRot(0.0f) {}
2366
2368KartMoveBike::~KartMoveBike() = default;
2369
2373 constexpr f32 MAX_WHEELIE_ROTATION = 0.07f;
2374 constexpr u16 WHEELIE_COOLDOWN = 20;
2375
2376 status().setBit(eStatus::Wheelie);
2377 m_wheelieFrames = 0;
2378 m_maxWheelieRot = MAX_WHEELIE_ROTATION;
2379 m_wheelieCooldown = WHEELIE_COOLDOWN;
2380 m_wheelieRotDec = 0.0f;
2381 m_autoHardStickXFrames = 0;
2382}
2383
2388 status().resetBit(eStatus::Wheelie);
2389 m_wheelieRotDec = 0.0f;
2390 m_autoHardStickXFrames = 0;
2391}
2392
2394void KartMoveBike::createSubsystems() {
2395 m_jump = new KartJumpBike(this);
2396 m_halfPipe = new KartHalfPipe();
2397 m_kartScale = new KartScale();
2398}
2399
2404 f32 leanRotInc = m_turningParams->leanRotIncRace;
2405 f32 leanRotCap = m_turningParams->leanRotCapRace;
2406 const auto *raceManager = System::RaceManager::Instance();
2407
2408 auto &status = KartObjectProxy::status();
2409
2410 if (status.offBit(eStatus::ChargingSSMT)) {
2411 if (!raceManager->isStageReached(System::RaceManager::Stage::Race) ||
2412 EGG::Mathf::abs(m_speed) < 5.0f) {
2413 leanRotInc = m_turningParams->leanRotIncCountdown;
2414 leanRotCap = m_turningParams->leanRotCapCountdown;
2415 }
2416 } else {
2417 leanRotInc = m_turningParams->leanRotIncSSMT;
2418 leanRotCap = m_turningParams->leanRotCapSSMT;
2419 }
2420
2421 m_leanRotCap += 0.3f * (leanRotCap - m_leanRotCap);
2422 m_leanRotInc += 0.3f * (leanRotInc - m_leanRotInc);
2423
2424 f32 stickX = state()->stickX();
2425 f32 extVelXFactor = 0.0f;
2426 f32 leanRotMin = -m_leanRotCap;
2427 f32 leanRotMax = m_leanRotCap;
2428
2429 if (status.onBit(eStatus::BeforeRespawn, eStatus::InAction, eStatus::Wheelie,
2431 eStatus::SoftWallDrift, eStatus::SomethingWallCollision, eStatus::HWG,
2432 eStatus::CannonStart, eStatus::InCannon)) {
2433 m_leanRot *= m_turningParams->leanRotDecayFactor;
2434 } else if (!state()->isDrifting()) {
2435 if (stickX <= 0.2f) {
2436 if (stickX >= -0.2f) {
2437 m_leanRot *= m_turningParams->leanRotDecayFactor;
2438 } else {
2440 extVelXFactor = m_turningParams->leanRotShallowFactor;
2441 }
2442 } else {
2444 extVelXFactor = -m_turningParams->leanRotShallowFactor;
2445 }
2446 } else {
2447 leanRotMax = m_turningParams->leanRotMaxDrift;
2448 leanRotMin = m_turningParams->leanRotMinDrift;
2449
2450 if (m_hopStickX == 1) {
2451 leanRotMin = -leanRotMax;
2452 leanRotMax = -m_turningParams->leanRotMinDrift;
2453 }
2454 if (m_hopStickX == -1) {
2455 if (stickX == 0.0f) {
2456 m_leanRot += (0.5f - m_leanRot) * 0.05f;
2457 } else {
2458 m_leanRot += m_turningParams->driftStickXFactor * stickX;
2459 extVelXFactor = -m_turningParams->leanRotShallowFactor * stickX;
2460 }
2461 } else if (stickX == 0.0f) {
2462 m_leanRot += (-0.5f - m_leanRot) * 0.05f;
2463 } else {
2464 m_leanRot += m_turningParams->driftStickXFactor * stickX;
2465 extVelXFactor = -m_turningParams->leanRotShallowFactor * stickX;
2466 }
2467 }
2468
2469 bool capped = false;
2470 if (leanRotMin <= m_leanRot) {
2471 if (leanRotMax < m_leanRot) {
2472 m_leanRot = leanRotMax;
2473 capped = true;
2474 }
2475 } else {
2476 m_leanRot = leanRotMin;
2477 capped = true;
2478 }
2479
2480 if (!capped) {
2481 dynamics()->setExtVel(dynamics()->extVel() + componentXAxis() * extVelXFactor);
2482 }
2483
2484 f32 leanRotScalar = state()->isDrifting() ? 0.065f : 0.05f;
2485
2487
2488 dynamics()->setAngVel2(dynamics()->angVel2() +
2489 EGG::Vector3f(m_standStillBoostRot, turn * wheelieRotFactor(),
2490 m_leanRot * leanRotScalar));
2491
2492 calcDive();
2493
2494 EGG::Vector3f top = m_up;
2495
2497 f32 scalar = (m_speed >= 0.0f) ? m_speedRatioCapped * 2.0f : 0.0f;
2498 scalar = std::min(1.0f, scalar);
2499 top = scalar * m_up + (1.0f - scalar) * EGG::Vector3f::ey;
2500
2501 if (std::numeric_limits<f32>::epsilon() < top.squaredLength()) {
2502 top.normalise();
2503 }
2504 }
2505
2506 dynamics()->setTop_(top);
2507}
2508
2514 static constexpr std::array<TurningParameters, 2> TURNING_PARAMS_ARRAY = {{
2515 {0.8f, 0.08f, 1.0f, 0.1f, 1.2f, 0.8f, 0.08f, 0.6f, 0.15f, 1.6f, 0.9f, 180},
2516 {1.0f, 0.1f, 1.0f, 0.05f, 1.5f, 0.7f, 0.08f, 0.6f, 0.15f, 1.3f, 0.9f, 180},
2517 }};
2518
2519 KartMove::setTurnParams();
2520
2521 if (param()->stats().driftType == KartParam::Stats::DriftType::Outside_Drift_Bike) {
2522 m_turningParams = &TURNING_PARAMS_ARRAY[0];
2523 } else if (param()->stats().driftType == KartParam::Stats::DriftType::Inside_Drift_Bike) {
2524 m_turningParams = &TURNING_PARAMS_ARRAY[1];
2525 }
2526
2527 if (System::RaceManager::Instance()->isStageReached(System::RaceManager::Stage::Race)) {
2528 m_leanRotInc = m_turningParams->leanRotIncRace;
2529 m_leanRotCap = m_turningParams->leanRotCapRace;
2530 } else {
2531 m_leanRotInc = m_turningParams->leanRotIncCountdown;
2532 m_leanRotCap = m_turningParams->leanRotCapCountdown;
2533 }
2534}
2535
2537void KartMoveBike::init(bool b1, bool b2) {
2538 KartMove::init(b1, b2);
2539
2540 m_leanRot = 0.0f;
2541 m_leanRotCap = 0.0f;
2542 m_leanRotInc = 0.0f;
2543 m_wheelieRot = 0.0f;
2544 m_maxWheelieRot = 0.0f;
2545 m_wheelieFrames = 0;
2547 m_autoHardStickXFrames = 0;
2548}
2549
2551void KartMoveBike::clear() {
2552 KartMove::clear();
2553 cancelWheelie();
2554}
2555
2559 constexpr u32 FAILED_WHEELIE_FRAMES = 15;
2560 constexpr f32 AUTO_WHEELIE_CANCEL_STICK_THRESHOLD = 0.85f;
2561
2563 m_wheelieCooldown = std::max(0, m_wheelieCooldown - 1);
2564
2565 auto &status = KartObjectProxy::status();
2566
2567 if (status.onBit(eStatus::Wheelie)) {
2568 bool cancelAutoWheelie = false;
2569
2570 if (status.offBit(eStatus::AutoDrift) ||
2571 EGG::Mathf::abs(state()->stickX()) <= AUTO_WHEELIE_CANCEL_STICK_THRESHOLD) {
2572 m_autoHardStickXFrames = 0;
2573 } else {
2574 if (++m_autoHardStickXFrames > 15) {
2575 cancelAutoWheelie = true;
2576 }
2577 }
2578
2580 if (m_turningParams->maxWheelieFrames < m_wheelieFrames || cancelAutoWheelie ||
2581 (!canWheelie() && FAILED_WHEELIE_FRAMES <= m_wheelieFrames)) {
2582 cancelWheelie();
2583 } else {
2584 m_wheelieRot += 0.01f;
2585 EGG::Vector3f angVel0 = dynamics()->angVel0();
2586 angVel0.x *= 0.9f;
2587 dynamics()->setAngVel0(angVel0);
2588 }
2589 } else if (0.0f < m_wheelieRot) {
2590 m_wheelieRotDec -= 0.001f;
2591 m_wheelieRotDec = std::max(-0.03f, m_wheelieRotDec);
2593 }
2594
2595 m_wheelieRot = std::max(0.0f, std::min(m_wheelieRot, m_maxWheelieRot));
2596
2597 f32 vel1DirUp = m_vel1Dir.dot(EGG::Vector3f::ey);
2598
2599 if (m_wheelieRot > 0.0f) {
2600 if (vel1DirUp <= 0.5f || m_wheelieFrames < FAILED_WHEELIE_FRAMES) {
2601 EGG::Vector3f angVel2 = dynamics()->angVel2();
2602 angVel2.x -= m_wheelieRot * (1.0f - EGG::Mathf::abs(vel1DirUp));
2603 dynamics()->setAngVel2(angVel2);
2604 } else {
2605 cancelWheelie();
2606 }
2607
2608 status.setBit(eStatus::WheelieRot);
2609 } else {
2610 status.resetBit(eStatus::WheelieRot);
2611 }
2612}
2613
2620 if (status().onBit(eStatus::AutoDrift)) {
2621 return;
2622 }
2623
2624 cancelWheelie();
2625}
2626
2632
2637 constexpr u16 MAX_MT_CHARGE = 270;
2638 constexpr u16 BASE_MT_CHARGE = 2;
2639 constexpr f32 BONUS_CHARGE_STICK_THRESHOLD = 0.4f;
2640 constexpr u16 EXTRA_MT_CHARGE = 3;
2641
2642 if (m_driftState != DriftState::ChargingMt) {
2643 return;
2644 }
2645
2646 m_mtCharge += BASE_MT_CHARGE;
2647
2648 f32 stickX = state()->stickX();
2649 if (-BONUS_CHARGE_STICK_THRESHOLD <= stickX) {
2650 if (BONUS_CHARGE_STICK_THRESHOLD < stickX && m_hopStickX == -1) {
2651 m_mtCharge += EXTRA_MT_CHARGE;
2652 }
2653 } else if (m_hopStickX != -1) {
2654 m_mtCharge += EXTRA_MT_CHARGE;
2655 }
2656
2657 if (m_mtCharge > MAX_MT_CHARGE) {
2658 m_mtCharge = MAX_MT_CHARGE;
2659 m_driftState = DriftState::ChargedMt;
2660 }
2661}
2662
2664void KartMoveBike::initOob() {
2665 KartMove::initOob();
2666 cancelWheelie();
2667}
2668
2672 constexpr s16 COOLDOWN_FRAMES = 20;
2673 bool dpadUp = inputs()->currentState().trickUp();
2674 auto &status = KartObjectProxy::status();
2675
2676 if (status.offBit(eStatus::Wheelie)) {
2677 if (dpadUp && status.onBit(eStatus::TouchingGround)) {
2679 eStatus::Hop, eStatus::DriftAuto, eStatus::InAction)) {
2680 return;
2681 }
2682
2683 if (m_wheelieCooldown > 0) {
2684 return;
2685 }
2686
2687 startWheelie();
2688 }
2689 } else if (inputs()->currentState().trickDown() && m_wheelieCooldown <= 0) {
2690 cancelWheelie();
2691 m_wheelieCooldown = COOLDOWN_FRAMES;
2692 }
2693}
2694
2695} // 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
constexpr bool offBit(Es... es) const
Checks if all of the corresponding bits for the provided enum values are off.
Definition BitFlag.hh:412
constexpr bool onBit(Es... es) const
Checks if any of the corresponding bits for the provided enum values are on.
Definition BitFlag.hh:379
constexpr TBitFlagExt< N, E > & resetBit(Es... es)
Resets the corresponding bits for the provided enum values.
Definition BitFlag.hh:355
constexpr bool onAllBit(Es... es) const
Checks if all of the corresponding bits for the provided enum values are on.
Definition BitFlag.hh:401
constexpr bool offAnyBit(Es... es) const
Checks if any of the corresponding bits for the provided enum values are off.
Definition BitFlag.hh:434
constexpr TBitFlagExt< N, E > & setBit(Es... es)
Sets the corresponding bits for the provided enum values.
Definition BitFlag.hh:344
bool 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:517
f32 m_leanRotCap
The maximum leaning rotation.
Definition KartMove.hh:518
void calcWheelie() override
STAGE 1+ - Every frame, checks player input for wheelies and computes wheelie rotation.
Definition KartMove.cc:2558
void calcMtCharge() override
Every frame during a drift, calculates MT charge based on player input.
Definition KartMove.cc:2636
virtual void startWheelie()
STAGE 1+ - Sets the wheelie bit flag and some wheelie-related variables.
Definition KartMove.cc:2372
f32 m_wheelieRotDec
The wheelie rotation decrementor, used after a wheelie has ended.
Definition KartMove.hh:524
u32 m_wheelieFrames
Tracks wheelie duration and cancels the wheelie after 180 frames.
Definition KartMove.hh:522
f32 m_wheelieRot
X-axis rotation from wheeling.
Definition KartMove.hh:520
const TurningParameters * m_turningParams
Inside/outside drifting bike turn info.
Definition KartMove.hh:526
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:2513
f32 m_maxWheelieRot
The maximum wheelie rotation.
Definition KartMove.hh:521
void tryStartWheelie()
STAGE 1+ - Every frame, checks player input to see if we should start or stop a wheelie.
Definition KartMove.cc:2671
void onWallCollision() override
Called when you collide with a wall. All it does for bikes is cancel wheelies.
Definition KartMove.cc:2629
void onHop() override
Virtual function that just cancels wheelies when you hop.
Definition KartMove.cc:2619
bool canWheelie() const override
Checks if the kart is going fast enough to wheelie.
Definition KartMove.hh:508
void calcVehicleRotation(f32) override
Every frame, calculates rotation, EV, and angular velocity for the bike.
Definition KartMove.cc:2403
s16 m_wheelieCooldown
The number of frames before another wheelie can start.
Definition KartMove.hh:523
f32 m_leanRotInc
The incrementor for leaning rotation.
Definition KartMove.hh:519
virtual void cancelWheelie()
Clears the wheelie bit flag and resets the rotation decrement.
Definition KartMove.cc:2387
f32 m_baseSpeed
The speed associated with the current character/vehicle stats.
Definition KartMove.hh:357
void calcRotation()
Every frame, calculates kart rotation based on player input.
Definition KartMove.cc:1140
s16 m_ssmtLeewayTimer
Frames to forgive letting go of A before clearing SSMT charge.
Definition KartMove.hh:399
s32 m_hopFrame
A timer that can prevent subsequent hops until reset.
Definition KartMove.hh:384
void calcDisableBackwardsAccel()
Computes the current cooldown duration between braking and reversing.
Definition KartMove.cc:714
EGG::Vector3f m_hopUp
The up vector when hopping.
Definition KartMove.hh:385
u16 m_mushroomBoostTimer
Number of frames until the mushroom boost runs out.
Definition KartMove.hh:406
void calcSpecialFloor()
Every frame, calculates any boost resulting from a boost panel.
Definition KartMove.cc:506
void calcWallCollisionStart(f32 param_2)
If we started to collide with a wall this frame, applies rotation.
Definition KartMove.cc:1547
KartHalfPipe * m_halfPipe
Pertains to zipper physics.
Definition KartMove.hh:435
f32 m_kclRotFactor
Float between 0-1 that scales the player's turning radius on offroad.
Definition KartMove.hh:379
f32 m_outsideDriftBonus
Added to angular velocity when outside drifting.
Definition KartMove.hh:392
void tryStartBoostRamp()
Sets offroad invincibility and and enables the ramp boost bitfield flag.
Definition KartMove.cc:1931
void calcDeceleration()
Definition KartMove.cc:1320
u16 m_smtCharge
A value between 0 and 300 representing current SMT charge.
Definition KartMove.hh:391
f32 m_speedRatio
The ratio between current speed and the player's base speed stat.
Definition KartMove.hh:377
@ 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:1949
f32 m_jumpPadMinSpeed
Snaps the player to a minimum speed when first touching a jump pad.
Definition KartMove.hh:409
f32 m_hopPosY
Relative position as the result of a hop. Starts at 0.
Definition KartMove.hh:423
DrivingDirection m_drivingDirection
Current state of driver's direction.
Definition KartMove.hh:430
u16 m_crushTimer
Number of frames until player will be uncrushed.
Definition KartMove.hh:407
bool calcPreDrift()
Each frame, checks for hop or slipdrift. Computes drift direction based on player input.
Definition KartMove.cc:788
f32 m_speed
Current speed, restricted to the soft speed limit.
Definition KartMove.hh:359
s16 m_offroadInvincibility
How many frames until the player is affected by offroad.
Definition KartMove.hh:397
void calcAirtimeTop()
Calculates rotation of the bike due to excessive airtime.
Definition KartMove.cc:481
void startManualDrift()
Called when the player lands from a drift hop, or to start a slipdrift.
Definition KartMove.cc:1039
void controlOutsideDriftAngle()
Every frame, handles mini-turbo charging and outside drifting bike rotation.
Definition KartMove.cc:1107
f32 m_softSpeedLimit
Base speed + boosts + wheelies, restricted to the hard speed limit.
Definition KartMove.hh:358
virtual void hop()
Initializes hop information, resets upwards EV and clears upwards force.
Definition KartMove.cc:1891
void calcStandstillBoostRot()
STAGE Computes the x-component of angular velocity based on the kart's speed.
Definition KartMove.cc:1613
EGG::Vector3f m_outsideDriftLastDir
Used to compute the next m_outsideDriftAngle.
Definition KartMove.hh:375
void calcManualDrift()
Each frame, handles hopping, drifting, and mini-turbos.
Definition KartMove.cc:957
virtual void calcMtCharge()
Every frame during a drift, calculates MT/SMT charge based on player input.
Definition KartMove.cc:1826
void calcSsmt()
Calculates standstill mini-turbo components, if applicable.
Definition KartMove.cc:731
void calcAcceleration()
Every frame, applies acceleration to the kart's internal velocity.
Definition KartMove.cc:1368
f32 m_processedSpeed
Offset 0x28. It's only ever just a copy of m_speed.
Definition KartMove.hh:361
void releaseMt()
Stops charging a mini-turbo, and applies boost if charged.
Definition KartMove.cc:1081
f32 m_kclSpeedFactor
Float between 0-1 that scales the player's speed on offroad.
Definition KartMove.hh:378
f32 m_weightedTurn
Magnitude+direction of stick input, factoring in the kart's stats.
Definition KartMove.hh:402
void calcVehicleSpeed()
Every frame, computes speed based on acceleration and any active boosts.
Definition KartMove.cc:1237
f32 m_lastSpeed
Last frame's speed, cached to calculate angular velocity.
Definition KartMove.hh:360
s16 m_ssmtDisableAccelTimer
Counter that tracks delay before starting to reverse.
Definition KartMove.hh:400
void calcOffroadInvincibility()
Checks a timer to see if we are still ignoring offroad slowdown.
Definition KartMove.cc:2108
KartBurnout m_burnout
Manages the state of start boost burnout.
Definition KartMove.hh:437
f32 calcVehicleAcceleration() const
Every frame, computes acceleration based off the character/vehicle stats.
Definition KartMove.cc:1334
void calcAutoDrift()
Each frame, handles automatic transmission drifting.
Definition KartMove.cc:902
f32 m_realTurn
The "true" turn magnitude. Equal to m_weightedTurn unless drifting.
Definition KartMove.hh:401
const DriftingParameters * m_driftingParams
Drift-type-specific parameters.
Definition KartMove.hh:438
void clearDrift()
Definition KartMove.cc:841
void calc()
Each frame, calculates the kart's movement.
Definition KartMove.cc:281
EGG::Vector3f m_up
Vector perpendicular to the floor, pointing upwards.
Definition KartMove.hh:366
s16 m_ssmtCharge
Increments every frame up to 75 when charging stand-still MT.
Definition KartMove.hh:398
f32 m_speedDragMultiplier
After 5 frames of airtime, this causes speed to slowly decay.
Definition KartMove.hh:364
u16 m_mtCharge
A value between 0 and 270 representing current MT charge.
Definition KartMove.hh:390
void calcSsmtStart()
Calculates whether we are starting a standstill mini-turbo.
Definition KartMove.cc:1714
s16 m_respawnPostLandTimer
Counts up to 4 if not accelerating after respawn landing.
Definition KartMove.hh:427
virtual f32 getWheelieSoftSpeedLimitBonus() const
Returns the % speed boost from wheelies. For karts, this is always 0.
Definition KartMove.hh:101
f32 m_kclWheelRotFactor
The slowest rotation multiplier of each wheel's floor collision.
Definition KartMove.hh:381
void resetDriftManual()
Clears drift state. Called when touching ground and drift is canceled.
Definition KartMove.cc:830
f32 m_totalScale
[Unused] Always 1.0f
Definition KartMove.hh:404
f32 m_acceleration
Captures the acceleration from player input and boosts.
Definition KartMove.hh:363
virtual void calcVehicleRotation(f32 turn)
Every frame, calculates rotation, EV, and angular velocity for the kart.
Definition KartMove.cc:1778
s16 m_respawnPreLandTimer
Counts down from 4 when pressing A before landing from respawn.
Definition KartMove.hh:426
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:387
f32 m_outsideDriftAngle
The facing angle of an outward-drifting vehicle.
Definition KartMove.hh:373
EGG::Vector3f m_lastDir
m_speed from the previous frame but with signed magnitude.
Definition KartMove.hh:369
EGG::Vector3f m_smoothedUp
A smoothed up vector, mostly used after significant airtime.
Definition KartMove.hh:365
bool canStartDrift() const
Definition KartMove.hh:129
s32 getAppliedHopStickX() const
Factors in vehicle speed to retrieve our hop direction and magnitude.
Definition KartMove.hh:214
u16 m_floorCollisionCount
The number of tires colliding with the floor.
Definition KartMove.hh:382
void calcDive()
Responds to player input to handle up/down kart tilt mid-air.
Definition KartMove.cc:1653
void calcOffroad()
Each frame, computes rotation and speed scalars from the floor KCL.
Definition KartMove.cc:653
s16 m_bumpTimer
Set when a Reaction::SmallBump collision occurs.
Definition KartMove.hh:429
KartScale * m_kartScale
Manages scaling due to TF stompers and MH cars.
Definition KartMove.hh:436
@ 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:425
s32 m_hopStickX
A ternary for the direction of our hop, 0 if still neutral hopping.
Definition KartMove.hh:383
s16 m_backwardsAllowCounter
Tracks the 15f delay before reversing.
Definition KartMove.hh:431
f32 calcWallCollisionSpeedFactor(f32 &f1)
Every frame, computes a speed scalar if we are colliding with a wall.
Definition KartMove.cc:1514
void calcMushroomBoost()
Checks a timer to see if we are still boosting from a mushroom.
Definition KartMove.cc:2124
f32 m_hopGravity
Always main gravity (-1.3f).
Definition KartMove.hh:424
f32 m_hopVelY
Relative velocity due to a hop. Starts at 10 and decreases with gravity.
Definition KartMove.hh:422
f32 m_hardSpeedLimit
Absolute speed cap. It's 120, unless you're in a bullet (140).
Definition KartMove.hh:362
void setInitialPhysicsValues(const EGG::Vector3f &position, const EGG::Vector3f &angles)
Initializes the kart's position and rotation. Calls tire suspension initializers.
Definition KartMove.cc:244
f32 m_rawTurn
Float in range [-1, 1]. Represents stick magnitude + direction.
Definition KartMove.hh:439
void setOffroadInvincibility(s16 timer)
Ignores offroad KCL collision for a set amount of time.
Definition KartMove.cc:2097
f32 m_speedRatioCapped
m_speedRatio but capped at 1.0f.
Definition KartMove.hh:376
EGG::Vector3f m_scale
Normally the unit vector, but may vary due to crush animations.
Definition KartMove.hh:403
f32 m_kclWheelSpeedFactor
The slowest speed multiplier of each wheel's floor collision.
Definition KartMove.hh:380
EGG::Vector3f m_hopDir
Used for outward drift. Tracks the forward vector of our rotation.
Definition KartMove.hh:386
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.
@ Wheelie
Set while we are in a wheelie (even during the countdown).
@ HopStart
Set if m_bDriftInput was toggled on this frame.
@ RejectRoad
Collision which causes a change in the player's pos and rot.
@ DisableBackwardsAccel
Enforces a 20f delay when reversing after charging SSMT.
@ HalfPipeRamp
Set while colliding with zipper KCL.
@ Boost
Set while in a boost.
@ BoostOffroadInvincibility
Set if we should ignore offroad slowdown this frame.
@ HWG
Set when "Horizontal Wall Glitch" is active.
@ ZipperTrick
Set while tricking mid-air from a zipper.
@ SlipdriftCharge
Currently in a drift w/ automatic.
@ Hop
Set while we are in a drift hop. Clears when we land.
@ MushroomBoost
Set while we are in a mushroom boost.
@ StickyRoad
Like the rBC stairs.
@ StickLeft
Set on left stick input. Mutually exclusive to m_bStickRight.
@ RespawnKillY
Set while respawning to cap external velocity at 0.
@ VehicleBodyFloorCollision
Set if the vehicle body is colliding with the floor.
@ RejectRoadTrigger
e.g. DK Summit ending, and Maple Treeway side walls.
@ GroundStart
Set first frame landing from airtime.
@ Accelerate
Accel button is pressed.
@ StickRight
Set on right stick input. Mutually exclusive to m_bStickLeft.
@ ActionMidZipper
Set when we enter an action while mid-air from a zipper.
@ DriftInput
A "fake" button, normally set if you meet the speed requirement to hop.
@ AirtimeOver20
Set after 20 frames of airtime, resets on landing.
@ ZipperBoost
Set when boosting after landing from a zipper.
@ DriftManual
Currently in a drift w/ manual.
@ OverZipper
Set while mid-air from a zipper.
@ ChargingSSMT
Tracks whether we are charging a stand-still mini-turbo.
@ AccelerateStart
Set if m_bAccelerate was toggled on this frame.
@ Burnout
Set during a burnout on race start.
@ WallCollisionStart
Set if we have just started colliding with a wall.
@ AnyWheelCollision
Set when any wheel is touching floor collision.
@ AutoDrift
True if auto transmission, false if manual.
@ Wall3Collision
Set when colliding with wall KCL COL_TYPE_WALL_2.
@ TouchingGround
Set when any part of the vehicle is colliding with floor KCL.
@ ZipperInvisibleWall
Set when colliding with invisible wall above a zipper.
@ BeforeRespawn
Set on respawn collision, cleared on position snap.
@ WallCollision
Set if we are colliding with a wall.
A quaternion, used to represent 3D rotation.
Definition Quat.hh:12
void normalise()
Scales the quaternion to a unit length.
Definition Quat.cc:28
Vector3f rotateVector(const Vector3f &vec) const
Rotates a vector based on the quat.
Definition Quat.cc:55
Quatf slerpTo(const Quatf &q2, f32 t) const
Performs spherical linear interpolation.
Definition Quat.cc:84
void setAxisRotation(f32 angle, const Vector3f &axis)
Set the quat given angle and axis.
Definition Quat.cc:111
f32 dot(const Quatf &q) const
Computes .
Definition Quat.hh:107
void makeVectorRotation(const Vector3f &from, const Vector3f &to)
Captures rotation between two vectors.
Definition Quat.cc:40
constexpr TBitFlag< T, E > & resetBit(Es... es)
Resets the corresponding bits for the provided enum values.
Definition BitFlag.hh:73
constexpr bool onBit(Es... es) const
Checks if any of the corresponding bits for the provided enum values are on.
Definition BitFlag.hh:108
constexpr TBitFlag< T, E > & changeBit(bool on, Es... es)
Changes the state of the corresponding bits for the provided enum values.
Definition BitFlag.hh:85
constexpr bool offBit(Es... es) const
Checks if all of the corresponding bits for the provided enum values are off.
Definition BitFlag.hh:141
constexpr void makeAllZero()
Resets all the bits to zero.
Definition BitFlag.hh:234
constexpr TBitFlag< T, E > & setBit(Es... es)
Sets the corresponding bits for the provided enum values.
Definition BitFlag.hh:62
A 3D float vector.
Definition Vector.hh:88
f32 normalise()
Normalizes the vector and returns the original length.
Definition Vector.cc:52
f32 dot(const Vector3f &rhs) const
The dot product between two vectors.
Definition Vector.hh:187
f32 length() const
The square root of the vector's dot product.
Definition Vector.hh:192
f32 squaredLength() const
The dot product between the vector and itself.
Definition Vector.hh:182
Vector3f proj(const Vector3f &rhs) const
The projection of this vector onto rhs.
Definition Vector.hh:198
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:104
Vector3f rej(const Vector3f &rhs) const
The rejection of this vector onto rhs.
Definition Vector.hh:204
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