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