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
54KartMove::~KartMove() {
55 delete m_jump;
56 delete m_halfPipe;
57 delete m_kartScale;
58}
59
61void KartMove::createSubsystems(const KartParam::Stats &stats) {
62 m_jump = new KartJump(this);
63 m_halfPipe = new KartHalfPipe;
64 m_kartScale = new KartScale(stats);
65}
66
71 m_realTurn = 0.0f;
72 m_rawTurn = 0.0f;
73
74 auto &status = KartObjectProxy::status();
75
76 if (status.onBit(eStatus::InAction, eStatus::CannonStart, eStatus::InCannon,
78 return;
79 }
80
81 if (status.onBit(eStatus::BeforeRespawn)) {
82 return;
83 }
84
85 if (status.offBit(eStatus::Hop) || m_hopStickX == 0) {
86 m_rawTurn = -state()->stickX();
87 if (status.onBit(eStatus::JumpPadMushroomCollision)) {
88 m_rawTurn *= 0.35f;
89 } else if (status.onBit(eStatus::AirtimeOver20)) {
90 m_rawTurn *= 0.01f;
91 }
92 } else {
93 m_rawTurn = static_cast<f32>(m_hopStickX);
94 }
95
96 f32 reactivity;
97 if (state()->isDrifting()) {
98 reactivity = param()->stats().driftReactivity;
99 } else {
100 reactivity = param()->stats().handlingReactivity;
101 }
102
103 m_weightedTurn = m_rawTurn * reactivity + m_weightedTurn * (1.0f - reactivity);
104 m_weightedTurn = std::max(-1.0f, std::min(1.0f, m_weightedTurn));
105
107
108 if (!state()->isDrifting()) {
109 return;
110 }
111
112 m_realTurn = (m_weightedTurn + static_cast<f32>(m_hopStickX)) * 0.5f;
113 m_realTurn = m_realTurn * 0.8f + 0.2f * static_cast<f32>(m_hopStickX);
114 m_realTurn = std::max(-1.0f, std::min(1.0f, m_realTurn));
115}
116
118void KartMove::setTurnParams() {
119 static constexpr std::array<DriftingParameters, 3> DRIFTING_PARAMS_ARRAY = {{
120 {10.0f, 0.5f, 0.5f, 1.0f},
121 {10.0f, 0.5f, 0.5f, 0.2f},
122 {10.0f, 0.22f, 0.5f, 0.2f},
123 }};
124
125 init(false, false);
126 m_dir = bodyFront();
127 m_lastDir = m_dir;
128 m_vel1Dir = m_dir;
129 m_landingDir = m_dir;
130 m_smoothedForward = m_dir;
131 m_outsideDriftLastDir = m_dir;
132 m_driftingParams = &DRIFTING_PARAMS_ARRAY[static_cast<u32>(param()->stats().driftType)];
133 m_kartScale->reset();
134}
135
137void KartMove::init(bool b1, bool b2) {
138 m_lastSpeed = 0.0f;
139 m_baseSpeed = param()->stats().speed;
140 m_jumpPadSoftSpeedLimit = m_softSpeedLimit = param()->stats().speed;
141 m_speed = 0.0f;
142 setKartSpeedLimit();
143 m_acceleration = 0.0f;
145 m_up = EGG::Vector3f::ey;
146 m_smoothedUp = EGG::Vector3f::ey;
147 m_smoothedForward = EGG::Vector3f::ez;
148 m_vel1Dir = EGG::Vector3f::ez;
149 m_lastDir = EGG::Vector3f::ez;
150 m_dir = EGG::Vector3f::ez;
151 m_landingDir = EGG::Vector3f::ez;
152 m_dirDiff = EGG::Vector3f::zero;
153 m_hasLandingDir = false;
154 m_outsideDriftAngle = 0.0f;
155 m_landingAngle = 0.0f;
156 m_outsideDriftLastDir = EGG::Vector3f::ez;
157 m_speedRatio = 0.0f;
158 m_speedRatioCapped = 0.0f;
159 m_kclSpeedFactor = 1.0f;
160 m_kclRotFactor = 1.0f;
162 m_kclWheelRotFactor = 1.0f;
163
164 if (!b2) {
166 }
167
168 m_hopStickX = 0;
169 m_hopFrame = 0;
170 m_hopUp = EGG::Vector3f::ey;
171 m_hopDir = EGG::Vector3f::ez;
172 m_divingRot = 0.0f;
173 m_standStillBoostRot = 0.0f;
174 m_driftState = DriftState::NotDrifting;
175 m_smtCharge = 0;
176 m_mtCharge = 0;
177 m_outsideDriftBonus = 0.0f;
178 m_boost.reset();
179 m_zipperBoostTimer = 0;
180 m_zipperBoostMax = 0;
181 m_reject.reset();
183 m_ssmtCharge = 0;
186 m_nonZipperAirtime = 0;
187 m_realTurn = 0.0f;
188 m_weightedTurn = 0.0f;
189
190 if (!b1) {
191 m_scale.set(1.0f);
192 m_totalScale = 1.0f;
193 m_hitboxScale = 1.0f;
194 m_shockSpeedMultiplier = 1.0f;
195 m_invScale = 1.0f;
197 m_shockTimer = 0;
198 m_crushTimer = 0;
199 }
200
201 m_jumpPadMinSpeed = 0.0f;
202 m_jumpPadMaxSpeed = 0.0f;
203 m_jumpPadBoostMultiplier = 0.0f;
204 m_jumpPadProperties = nullptr;
205 m_rampBoost = 0;
206 m_autoDriftAngle = 0.0f;
207 m_autoDriftStartFrameCounter = 0;
208
209 m_cannonEntryOfsLength = 0.0f;
210 m_cannonEntryPos.setZero();
211 m_cannonEntryOfs.setZero();
212 m_cannonOrthog.setZero();
213 m_cannonProgress.setZero();
214
215 m_hopVelY = 0.0f;
216 m_hopPosY = 0.0f;
217 m_hopGravity = 0.0f;
218 m_timeInRespawn = 0;
221 m_respawnTimer = 0;
222 m_bumpTimer = 0;
223 m_drivingDirection = DrivingDirection::Forwards;
224 m_padType.makeAllZero();
225 m_flags.makeAllZero();
226 m_jump->reset();
227 m_halfPipe->reset();
228 m_rawTurn = 0.0f;
229}
230
232void KartMove::clear() {
233 auto &status = KartObjectProxy::status();
234
235 if (status.onBit(eStatus::OverZipper)) {
237 }
238
239 clearBoost();
240 clearJumpPad();
241 clearRampBoost();
242 clearZipperBoost();
243 clearSsmt();
244 clearOffroadInvincibility();
245 m_halfPipe->end(false);
246 m_jump->end();
247 clearRejectRoad();
248}
249
253 EGG::Quatf quaternion = EGG::Quatf::FromRPY(angles * DEG2RAD);
254 EGG::Vector3f newPos = position;
256 Field::KCLTypeMask kcl_flags = KCL_NONE;
257
258 bool bColliding = Field::CollisionDirector::Instance()->checkSphereFullPush(100.0f, newPos,
259 EGG::Vector3f::inf, KCL_ANY, &info, &kcl_flags, 0);
260
261 if (bColliding && (kcl_flags & KCL_TYPE_FLOOR)) {
262 newPos = newPos + info.tangentOff + (info.floorNrm * -100.0f);
263 newPos += info.floorNrm * bsp().initialYPos;
264 }
265
266 setPos(newPos);
267 setRot(quaternion);
268
269 sub()->initPhysicsValues();
270
271 physics()->setPos(pos());
272 physics()->setVelocity(dynamics()->velocity());
273
274 m_landingDir = bodyFront();
275 m_dir = bodyFront();
276 m_smoothedForward = bodyFront();
277 m_up = bodyUp();
278 dynamics()->setTop(m_up);
279
280 for (u16 tireIdx = 0; tireIdx < suspCount(); ++tireIdx) {
281 suspension(tireIdx)->setInitialState();
282 }
283}
284
291 auto &status = KartObjectProxy::status();
292
293 if (status.onBit(eStatus::InRespawn)) {
294 calcInRespawn();
295 return;
296 }
297
298 dynamics()->resetInternalVelocity();
299 m_burnout.calc();
301 m_halfPipe->calc();
302 calcTop();
303 tryEndJumpPad();
304 calcRespawnBoost();
306 m_jump->calc();
307
308 m_bumpTimer = std::max(m_bumpTimer - 1, 0);
309
311 calcDirs();
312 calcStickyRoad();
313 calcOffroad();
314 calcTurn();
315
316 if (status.offBit(eStatus::AutoDrift)) {
318 }
319
320 calcWheelie();
321 calcSsmt();
322 calcBoost();
324 calcZipperBoost();
325 calcShock();
326 calcCrushed();
327 calcScale();
328
329 if (status.onBit(eStatus::InCannon)) {
330 calcCannon();
331 }
332
336 calcRotation();
337}
338
340void KartMove::calcRespawnStart() {
341 constexpr float RESPAWN_HEIGHT = 700.0f;
342
343 const auto *jugemPoint = System::RaceManager::Instance()->jugemPoint();
344 const EGG::Vector3f &jugemPos = jugemPoint->pos();
345 const EGG::Vector3f &jugemRot = jugemPoint->rot();
346
347 EGG::Vector3f respawnPos = jugemPos;
348 respawnPos.y += RESPAWN_HEIGHT;
349 EGG::Vector3f respawnRot = EGG::Vector3f(0.0f, jugemRot.y, 0.0f);
350
351 setInitialPhysicsValues(respawnPos, respawnRot);
352
353 Item::ItemDirector::Instance()->kartItem(0).clear();
354
355 status().resetBit(eStatus::TriggerRespawn).setBit(eStatus::InRespawn);
356}
357
359void KartMove::calcInRespawn() {
360 constexpr f32 LAKITU_VELOCITY = 1.5f;
361 constexpr u16 RESPAWN_DURATION = 110;
362
363 auto &status = KartObjectProxy::status();
364
365 if (status.offBit(eStatus::InRespawn)) {
366 return;
367 }
368
369 EGG::Vector3f newPos = pos();
370 newPos.y -= LAKITU_VELOCITY;
371 dynamics()->setPos(newPos);
372 dynamics()->setNoGravity(true);
373
374 if (++m_timeInRespawn > RESPAWN_DURATION) {
375 status.resetBit(eStatus::InRespawn).setBit(eStatus::AfterRespawn, eStatus::RespawnKillY);
376 m_timeInRespawn = 0;
377 m_flags.setBit(eFlags::Respawned);
378 dynamics()->setNoGravity(false);
379 }
380}
381
383void KartMove::calcRespawnBoost() {
384 constexpr s16 RESPAWN_BOOST_DURATION = 30;
385 constexpr s16 RESPAWN_BOOST_INPUT_LENIENCY = 4;
386
387 auto &status = KartObjectProxy::status();
388
389 if (status.onBit(eStatus::AfterRespawn)) {
390 if (status.onBit(eStatus::TouchingGround)) {
391 if (m_respawnPreLandTimer > 0) {
392 if (status.offBit(eStatus::BeforeRespawn, eStatus::InAction)) {
393 activateBoost(KartBoost::Type::AllMt, RESPAWN_BOOST_DURATION);
394 m_respawnTimer = RESPAWN_BOOST_DURATION;
395 }
396 } else {
397 m_respawnPostLandTimer = RESPAWN_BOOST_INPUT_LENIENCY;
398 }
399
400 status.resetBit(eStatus::AfterRespawn);
402 }
403
405
406 if (m_flags.onBit(eFlags::Respawned) && status.onBit(eStatus::AccelerateStart)) {
407 m_respawnPreLandTimer = RESPAWN_BOOST_INPUT_LENIENCY;
409 }
410 } else {
411 if (m_respawnPostLandTimer > 0) {
412 if (status.onBit(eStatus::AccelerateStart)) {
413 if (status.offBit(eStatus::BeforeRespawn, eStatus::InAction)) {
414 activateBoost(KartBoost::Type::AllMt, RESPAWN_BOOST_DURATION);
415 m_respawnTimer = RESPAWN_BOOST_DURATION;
416 }
417
419 }
420
422 } else {
424 }
425 }
426
427 m_respawnTimer = std::max(0, m_respawnTimer - 1);
428}
429
431void KartMove::calcTop() {
432 f32 stabilizationFactor = 0.1f;
433 m_hasLandingDir = false;
434 EGG::Vector3f inputTop = state()->top();
435 auto &status = KartObjectProxy::status();
436
437 if (status.onBit(eStatus::GroundStart) && m_nonZipperAirtime >= 3) {
438 m_smoothedUp = inputTop;
439 m_up = inputTop;
440 m_landingDir = m_dir.perpInPlane(m_smoothedUp, true);
441 m_dirDiff = m_landingDir.proj(m_landingDir);
442 m_hasLandingDir = true;
443 } else {
444 if (status.onBit(eStatus::Hop) && m_hopPosY > 0.0f) {
445 stabilizationFactor = m_driftingParams->stabilizationFactor;
446 } else if (status.onBit(eStatus::TouchingGround)) {
447 if ((m_flags.onBit(eFlags::TrickableSurface) || state()->trickableTimer() > 0) &&
448 inputTop.dot(m_dir) > 0.0f && m_speed > 50.0f &&
449 collide()->surfaceFlags().onBit(KartCollide::eSurfaceFlags::NotTrickable)) {
450 inputTop = m_up;
451 } else {
452 m_up = inputTop;
453 }
454
455 f32 scalar = 0.8f;
456
457 if (status.onBit(eStatus::HalfPipeRamp) ||
458 (status.offBit(eStatus::Boost, eStatus::RampBoost, eStatus::Wheelie,
460 (status.offBit(eStatus::ZipperBoost) || m_zipperBoostTimer > 15))) {
461 f32 topDotZ = 0.8f - 6.0f * (EGG::Mathf::abs(inputTop.dot(componentZAxis())));
462 scalar = std::min(0.8f, std::max(0.3f, topDotZ));
463 }
464
465 m_smoothedUp += (inputTop - m_smoothedUp) * scalar;
467
468 f32 bodyDotFront = bodyFront().dot(m_smoothedUp);
469
470 if (bodyDotFront < -0.1f) {
471 stabilizationFactor += std::min(0.2f, EGG::Mathf::abs(bodyDotFront) * 0.5f);
472 }
473
474 if (collide()->surfaceFlags().onBit(KartCollide::eSurfaceFlags::BoostRamp)) {
475 stabilizationFactor = 0.4f;
476 }
477 } else {
479 }
480 }
481
482 dynamics()->setStabilizationFactor(stabilizationFactor);
483
484 m_nonZipperAirtime = status.onBit(eStatus::OverZipper) ? 0 : state()->airtime();
485 m_flags.changeBit(collide()->surfaceFlags().onBit(KartCollide::eSurfaceFlags::Trickable),
487}
488
492 auto &status = KartObjectProxy::status();
493
495 return;
496 }
497
498 if (m_smoothedUp.y <= 0.99f) {
499 m_smoothedUp += (EGG::Vector3f::ey - m_smoothedUp) * 0.03f;
501 } else {
502 m_smoothedUp = EGG::Vector3f::ey;
503 }
504
505 if (m_up.y <= 0.99f) {
506 m_up += (EGG::Vector3f::ey - m_up) * 0.03f;
507 m_up.normalise();
508 } else {
509 m_up = EGG::Vector3f::ey;
510 }
511}
512
517 const auto *raceMgr = System::RaceManager::Instance();
518 if (!raceMgr->isStageReached(System::RaceManager::Stage::Race)) {
519 return;
520 }
521
522 if (m_padType.onBit(ePadType::BoostPanel)) {
523 tryStartBoostPanel();
524 }
525
526 if (m_padType.onBit(ePadType::BoostRamp)) {
528 }
529
530 if (m_padType.onBit(ePadType::JumpPad)) {
532 }
533
534 m_padType.makeAllZero();
535}
536
538void KartMove::calcDirs() {
539 EGG::Vector3f right = dynamics()->mainRot().rotateVector(EGG::Vector3f::ex);
540 EGG::Vector3f local_88 = right.cross(m_smoothedUp);
541 local_88.normalise();
542 m_flags.setBit(eFlags::LaunchBoost);
543 auto &status = KartObjectProxy::status();
544
545 if (status.offBit(eStatus::InATrick, eStatus::OverZipper) &&
546 (((status.onBit(eStatus::TouchingGround) || status.offBit(eStatus::RampBoost) ||
547 !m_jump->isBoostRampEnabled()) &&
548 status.offBit(eStatus::JumpPad) && state()->airtime() <= 5) ||
549 status.onBit(eStatus::JumpPadMushroomCollision,
550 eStatus::NoSparkInvisibleWall))) {
551 EGG::Vector3f local_94 = local_88;
552 if (status.onBit(eStatus::Hop)) {
553 local_94 = m_hopDir;
554 }
555
556 EGG::Matrix34f mat;
557 mat.setAxisRotation(DEG2RAD * (m_autoDriftAngle + m_outsideDriftAngle + m_landingAngle),
559 EGG::Vector3f local_b8 = mat.multVector(local_94);
560 local_b8 = local_b8.perpInPlane(m_smoothedUp, true);
561
562 EGG::Vector3f dirDiff = local_b8 - m_dir;
563
564 if (dirDiff.squaredLength() <= std::numeric_limits<f32>::epsilon()) {
565 m_dir = local_b8;
566 m_dirDiff.setZero();
567 } else {
568 EGG::Vector3f origDirCross = m_dir.cross(local_b8);
569 m_dirDiff += m_kclRotFactor * dirDiff;
570 m_dir += m_dirDiff;
571 m_dir.normalise();
572 m_dirDiff *= 0.1f;
573 EGG::Vector3f newDirCross = m_dir.cross(local_b8);
574
575 if (origDirCross.dot(newDirCross) < 0.0f) {
576 m_dir = local_b8;
577 m_dirDiff.setZero();
578 }
579 }
580
581 m_vel1Dir = m_dir.perpInPlane(m_smoothedUp, true);
582 m_flags.resetBit(eFlags::LaunchBoost);
583 } else {
584 m_vel1Dir = m_dir;
585 }
586
587 if (status.offBit(eStatus::OverZipper)) {
588 m_jump->tryStart(m_smoothedUp.cross(m_dir));
589 }
590
591 EGG::Vector3f nextDir = m_up.cross(local_88);
592 m_smoothedForward = nextDir.cross(m_up);
593 m_smoothedForward.normalise();
594
595 if (m_hasLandingDir) {
596 f32 dot = m_dir.dot(m_landingDir);
597 EGG::Vector3f cross = m_dir.cross(m_landingDir);
598 f32 crossDot = cross.length();
599 f32 angle = EGG::Mathf::atan2(crossDot, dot);
600 angle = EGG::Mathf::abs(angle);
601
602 f32 fVar4 = 1.0f;
603 if (cross.dot(m_smoothedUp) < 0.0f) {
604 fVar4 = -1.0f;
605 }
606
607 m_landingAngle += (angle * RAD2DEG) * fVar4;
608 }
609
610 if (m_landingAngle <= 0.0f) {
611 if (m_landingAngle < 0.0f) {
612 m_landingAngle = std::min(0.0f, m_landingAngle + 2.0f);
613 }
614 } else {
615 m_landingAngle = std::max(0.0f, m_landingAngle - 2.0f);
616 }
617}
618
620void KartMove::calcStickyRoad() {
621 constexpr f32 STICKY_RADIUS = 200.0f;
622 constexpr Field::KCLTypeMask STICKY_MASK =
624
625 auto &status = KartObjectProxy::status();
626
627 if (status.onBit(eStatus::OverZipper)) {
629 return;
630 }
631
632 if ((status.offBit(eStatus::StickyRoad) &&
633 collide()->surfaceFlags().offBit(KartCollide::eSurfaceFlags::Trickable)) ||
634 EGG::Mathf::abs(m_speed) <= 20.0f) {
635 return;
636 }
637
638 EGG::Vector3f pos = dynamics()->pos();
639 EGG::Vector3f vel = dynamics()->movingObjVel() + m_speed * m_vel1Dir;
640 Field::CollisionInfo colInfo;
641 colInfo.bbox.setZero();
642 Field::KCLTypeMask kcl_flags = KCL_NONE;
643 bool stickyRoad = false;
644
645 for (size_t i = 0; i < 3; ++i) {
646 EGG::Vector3f newPos = pos + vel;
647 if (Field::CollisionDirector::Instance()->checkSphereFull(STICKY_RADIUS, newPos,
648 EGG::Vector3f::inf, STICKY_MASK, &colInfo, &kcl_flags, 0)) {
649 m_vel1Dir = m_vel1Dir.perpInPlane(colInfo.floorNrm, true);
650 dynamics()->setMovingObjVel(dynamics()->movingObjVel().rej(colInfo.floorNrm));
651 dynamics()->setMovingRoadVel(dynamics()->movingRoadVel().rej(colInfo.floorNrm));
652
653 if (status.onBit(eStatus::MovingWaterStickyRoad)) {
654 m_up = colInfo.floorNrm;
655 m_smoothedUp = colInfo.floorNrm;
656 }
657
658 stickyRoad = true;
659
660 break;
661 }
662 vel *= 0.5f;
663 pos += -STICKY_RADIUS * componentYAxis();
664 }
665
666 if (!stickyRoad) {
668 }
669}
670
675 auto &status = KartObjectProxy::status();
676
678 m_kclSpeedFactor = 1.0f;
679 m_kclRotFactor = param()->stats().kclRot[0];
680 } else {
681 bool anyWheel = status.onBit(eStatus::AnyWheelCollision);
682 if (anyWheel) {
686 }
687
689 const CollisionData &colData = collisionData();
690 if (anyWheel) {
691 if (colData.speedFactor < m_kclWheelSpeedFactor) {
692 m_kclSpeedFactor = colData.speedFactor;
693 }
694 m_kclRotFactor = (m_kclWheelRotFactor + colData.rotFactor) /
695 static_cast<f32>(m_floorCollisionCount + 1);
696 } else {
697 m_kclSpeedFactor = colData.speedFactor;
698 m_kclRotFactor = colData.rotFactor;
699 }
700 }
701 }
702
703 calcRisingWater();
704}
705
707void KartMove::calcRisingWater() {
708 auto *objDir = Field::ObjectDirector::Instance();
709 auto *psea = objDir->psea();
710 if (!psea) {
711 return;
712 }
713
714 f32 pos = wheelPos(0).y;
715 u16 count = tireCount();
716 for (u16 wheelIdx = 0; wheelIdx < count; ++wheelIdx) {
717 f32 tmp = wheelEdgePos(wheelIdx).y;
718 if (wheelIdx == 0 || tmp < pos) {
719 pos = tmp;
720 }
721 }
722
723 if (objDir->risingWaterKillPlaneHeight() > pos) {
724 collide()->activateOob(true, nullptr, false, false);
725 }
726
727 f32 dist = -objDir->distAboveRisingWater(pos);
728 if (dist > 0.0f && status().offBit(Kart::eStatus::BoostOffroadInvincibility)) {
729 f32 speedScale = std::min(1.0f, dist / 100.0f);
730 m_kclSpeedFactor = 1.0f - (1.0f - param()->stats().kclSpeed[3]) * speedScale;
731 }
732}
733
735void KartMove::calcBoost() {
736 auto &status = KartObjectProxy::status();
737
738 if (m_boost.calc()) {
740 } else {
741 status.resetBit(eStatus::Boost);
742 }
743
744 calcRampBoost();
745}
746
748void KartMove::calcRampBoost() {
749 auto &status = KartObjectProxy::status();
750
751 if (status.offBit(eStatus::RampBoost)) {
752 return;
753 }
754
756 if (--m_rampBoost < 1) {
757 m_rampBoost = 0;
758 status.resetBit(eStatus::RampBoost);
759 }
760}
761
766 auto &status = KartObjectProxy::status();
767
769 return;
770 }
771
772 if (--m_ssmtDisableAccelTimer < 0 ||
773 (m_flags.offBit(eFlags::SsmtLeeway) && status.offBit(eStatus::Brake))) {
776 }
777}
778
783 constexpr s16 MAX_SSMT_CHARGE = 75;
784 constexpr s16 SSMT_BOOST_FRAMES = 30;
785 constexpr s16 LEEWAY_FRAMES = 1;
786 constexpr s16 DISABLE_ACCEL_FRAMES = 20;
787
789
790 auto &status = KartObjectProxy::status();
791
792 if (status.onBit(eStatus::ChargingSSMT)) {
793 if (++m_ssmtCharge > MAX_SSMT_CHARGE) {
794 m_ssmtCharge = MAX_SSMT_CHARGE;
797 }
798
799 return;
800 }
801
802 m_ssmtCharge = 0;
803
804 if (m_flags.offBit(eFlags::SsmtCharged)) {
805 return;
806 }
807
808 if (m_flags.onBit(eFlags::SsmtLeeway)) {
809 if (--m_ssmtLeewayTimer < 0) {
812 m_ssmtDisableAccelTimer = DISABLE_ACCEL_FRAMES;
814 } else {
816 activateBoost(KartBoost::Type::AllMt, SSMT_BOOST_FRAMES);
819 }
820 }
821 } else {
822 if (status.onBit(eStatus::Accelerate) && status.offBit(eStatus::Brake)) {
823 activateBoost(KartBoost::Type::AllMt, SSMT_BOOST_FRAMES);
826 } else {
827 m_ssmtLeewayTimer = LEEWAY_FRAMES;
828 m_flags.setBit(eFlags::SsmtLeeway);
830 m_ssmtDisableAccelTimer = LEEWAY_FRAMES;
831 }
832 }
833}
834
840 auto &status = KartObjectProxy::status();
841
844 if (status.offBit(eStatus::DriftInput)) {
846 } else if (status.offBit(eStatus::SlipdriftCharge)) {
847 if (m_hopStickX == 0) {
848 if (status.onBit(eStatus::StickRight)) {
849 m_hopStickX = -1;
850 } else if (status.onBit(eStatus::StickLeft)) {
851 m_hopStickX = 1;
852 }
854 onHop();
855 }
856 }
857 }
858 }
859
860 if (status.onBit(eStatus::Hop)) {
861 if (m_hopStickX == 0) {
862 if (status.onBit(eStatus::StickRight)) {
863 m_hopStickX = -1;
864 } else if (status.onBit(eStatus::StickLeft)) {
865 m_hopStickX = 1;
866 }
867 }
868 if (m_hopFrame < 3) {
869 ++m_hopFrame;
870 }
871 } else if (status.onBit(eStatus::SlipdriftCharge)) {
872 m_hopFrame = 0;
873 }
874
876}
877
882 m_hopStickX = 0;
883 m_hopFrame = 0;
885 m_driftState = DriftState::NotDrifting;
886 m_smtCharge = 0;
887 m_mtCharge = 0;
888}
889
894 m_outsideDriftAngle = 0.0f;
895 m_hopStickX = 0;
896 m_hopFrame = 0;
897 m_driftState = DriftState::NotDrifting;
898 m_smtCharge = 0;
899 m_mtCharge = 0;
900 m_outsideDriftBonus = 0.0f;
902 eStatus::DriftAuto);
903 m_autoDriftAngle = 0.0f;
904 m_hopStickX = 0;
905 m_autoDriftStartFrameCounter = 0;
906}
907
909void KartMove::clearJumpPad() {
910 m_jumpPadMinSpeed = 0.0f;
911 status().resetBit(eStatus::JumpPad);
912}
913
915void KartMove::clearRampBoost() {
916 m_rampBoost = 0;
917 status().resetBit(eStatus::RampBoost);
918}
919
921void KartMove::clearZipperBoost() {
922 m_zipperBoostTimer = 0;
924}
925
927void KartMove::clearBoost() {
928 m_boost.resetActive();
929 status().resetBit(eStatus::Boost);
930}
931
933void KartMove::clearSsmt() {
934 m_ssmtCharge = 0;
938}
939
941void KartMove::clearOffroadInvincibility() {
944}
945
946void KartMove::clearRejectRoad() {
947 status().resetBit(eStatus::RejectRoadTrigger, eStatus::NoSparkInvisibleWall);
948}
949
954 constexpr s16 AUTO_DRIFT_START_DELAY = 12;
955
956 auto &status = KartObjectProxy::status();
957
958 if (status.offBit(eStatus::AutoDrift)) {
959 return;
960 }
961
962 if (canStartDrift() &&
964 EGG::Mathf::abs(state()->stickX()) > 0.85f) {
965 m_autoDriftStartFrameCounter =
966 std::min<s16>(AUTO_DRIFT_START_DELAY, m_autoDriftStartFrameCounter + 1);
967 } else {
968 m_autoDriftStartFrameCounter = 0;
969 }
970
971 if (m_autoDriftStartFrameCounter >= AUTO_DRIFT_START_DELAY) {
972 status.setBit(eStatus::DriftAuto);
973
974 if (status.onBit(eStatus::TouchingGround)) {
975 if (state()->stickX() < 0.0f) {
976 m_hopStickX = 1;
977 m_autoDriftAngle -= 30.0f * param()->stats().driftAutomaticTightness;
978
979 } else {
980 m_hopStickX = -1;
981 m_autoDriftAngle += 30.0f * param()->stats().driftAutomaticTightness;
982 }
983 }
984
985 f32 halfTarget = 0.5f * param()->stats().driftOutsideTargetAngle;
986 m_autoDriftAngle = std::min(halfTarget, std::max(-halfTarget, m_autoDriftAngle));
987 } else {
988 status.resetBit(eStatus::DriftAuto);
989 m_hopStickX = 0;
990
991 if (m_autoDriftAngle > 0.0f) {
992 m_autoDriftAngle =
993 std::max(0.0f, m_autoDriftAngle - param()->stats().driftOutsideDecrement);
994 } else {
995 m_autoDriftAngle =
996 std::min(0.0f, m_autoDriftAngle + param()->stats().driftOutsideDecrement);
997 }
998 }
999
1000 EGG::Quatf angleAxis;
1001 angleAxis.setAxisRotation(-m_autoDriftAngle * DEG2RAD, m_up);
1002 physics()->composeExtraRot(angleAxis);
1003}
1004
1009 bool isHopping = calcPreDrift();
1010 auto &status = KartObjectProxy::status();
1011
1012 if (status.offBit(eStatus::OverZipper)) {
1013 const EGG::Vector3f rotZ = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1014
1015 if (status.offBit(eStatus::TouchingGround) &&
1016 param()->stats().driftType != KartParam::Stats::DriftType::Inside_Drift_Bike &&
1017 status.offBit(eStatus::JumpPadMushroomCollision) &&
1019 m_flags.onBit(eFlags::LaunchBoost)) {
1020 const EGG::Vector3f up = dynamics()->mainRot().rotateVector(EGG::Vector3f::ey);
1022
1023 if (driftRej.normalise() != 0.0f) {
1024 f32 rejCrossDirMag = driftRej.cross(rotZ).length();
1025 f32 angle = EGG::Mathf::atan2(rejCrossDirMag, driftRej.dot(rotZ));
1026 f32 sign = 1.0f;
1027 if ((rotZ.z * (rotZ.x - driftRej.x)) - (rotZ.x * (rotZ.z - driftRej.z)) > 0.0f) {
1028 sign = -1.0f;
1029 }
1030
1031 m_outsideDriftAngle += angle * RAD2DEG * sign;
1032 }
1033 }
1034
1035 m_outsideDriftLastDir = rotZ;
1036 }
1037
1038 // TODO: Is this backwards/inverted?
1039 if (((status.offBit(eStatus::Hop) || m_hopFrame < 3) &&
1041 (status.onBit(eStatus::InAction) || status.offBit(eStatus::TouchingGround))) {
1042 if (canHop()) {
1043 hop();
1044 isHopping = true;
1045 }
1046 } else {
1048 isHopping = false;
1049 }
1050
1052
1053 if (status.offBit(eStatus::DriftManual)) {
1054 if (!isHopping && status.onBit(eStatus::TouchingGround)) {
1056
1057 if (action()->flags().offBit(KartAction::eFlags::Rotating) || m_speed <= 20.0f) {
1058 f32 driftAngleDecr = param()->stats().driftOutsideDecrement;
1059 if (m_outsideDriftAngle > 0.0f) {
1060 m_outsideDriftAngle = std::max(0.0f, m_outsideDriftAngle - driftAngleDecr);
1061 } else if (m_outsideDriftAngle < 0.0f) {
1062 m_outsideDriftAngle = std::min(0.0f, m_outsideDriftAngle + driftAngleDecr);
1063 }
1064 }
1065 }
1066 } else {
1067 // This is a different comparison than @ref KartMove::canStartDrift().
1068 bool canStartDrift = m_speed > MINIMUM_DRIFT_THRESOLD * m_baseSpeed;
1069
1070 if (status.offBit(eStatus::OverZipper) &&
1072 status.onBit(eStatus::InAction, eStatus::RejectRoadTrigger,
1074 !canStartDrift)) {
1075 if (canStartDrift) {
1076 releaseMt();
1077 }
1078
1080 m_flags.setBit(eFlags::DriftReset);
1081 } else {
1083 }
1084 }
1085}
1086
1091 constexpr f32 OUTSIDE_DRIFT_BONUS = 0.5f;
1092
1093 const auto &stats = param()->stats();
1094 auto &status = KartObjectProxy::status();
1095
1096 if (stats.driftType != KartParam::Stats::DriftType::Inside_Drift_Bike) {
1097 f32 driftAngle = 0.0f;
1098
1099 if (status.onBit(eStatus::Hop)) {
1100 const EGG::Vector3f rotZ = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1101 EGG::Vector3f rotRej = rotZ.rej(m_hopUp);
1102
1103 if (rotRej.normalise() != 0.0f) {
1104 const EGG::Vector3f hopCrossRot = m_hopDir.cross(rotRej);
1105 driftAngle =
1106 EGG::Mathf::atan2(hopCrossRot.length(), m_hopDir.dot(rotRej)) * RAD2DEG;
1107 }
1108 }
1109
1110 m_outsideDriftAngle += driftAngle * static_cast<f32>(-m_hopStickX);
1111 m_outsideDriftAngle = std::max(-60.0f, std::min(60.0f, m_outsideDriftAngle));
1112 }
1113
1115
1116 if (status.offBit(eStatus::DriftInput)) {
1117 return;
1118 }
1119
1120 if (getAppliedHopStickX() == 0) {
1121 return;
1122 }
1123
1124 status.setBit(eStatus::DriftManual).resetBit(eStatus::Hop);
1125 m_driftState = DriftState::ChargingMt;
1126 m_outsideDriftBonus = OUTSIDE_DRIFT_BONUS * (m_speedRatioCapped * stats.driftManualTightness);
1127}
1128
1133 constexpr f32 SMT_LENGTH_FACTOR = 3.0f;
1134
1135 auto &status = KartObjectProxy::status();
1136
1137 if (m_driftState < DriftState::ChargedMt || status.onBit(eStatus::Brake)) {
1138 m_driftState = DriftState::NotDrifting;
1139 return;
1140 }
1141
1142 u16 mtLength = param()->stats().miniTurbo;
1143
1144 if (m_driftState == DriftState::ChargedSmt) {
1145 mtLength *= SMT_LENGTH_FACTOR;
1146 }
1147
1148 if (status.offBit(eStatus::BeforeRespawn, eStatus::InAction)) {
1149 activateBoost(KartBoost::Type::AllMt, mtLength);
1150 }
1151
1152 m_driftState = DriftState::NotDrifting;
1153}
1154
1159 if (state()->airtime() > 5) {
1160 return;
1161 }
1162
1163 if (param()->stats().driftType != KartParam::Stats::DriftType::Inside_Drift_Bike) {
1164 if (m_hopStickX == -1) {
1165 f32 angle = m_outsideDriftAngle;
1166 f32 targetAngle = param()->stats().driftOutsideTargetAngle;
1167 if (angle > targetAngle) {
1168 m_outsideDriftAngle = std::max(m_outsideDriftAngle - 2.0f, targetAngle);
1169 } else if (angle < targetAngle) {
1170 m_outsideDriftAngle += 150.0f * param()->stats().driftManualTightness;
1171 m_outsideDriftAngle = std::min(m_outsideDriftAngle, targetAngle);
1172 }
1173 } else if (m_hopStickX == 1) {
1174 f32 angle = m_outsideDriftAngle;
1175 f32 targetAngle = -param()->stats().driftOutsideTargetAngle;
1176 if (targetAngle > angle) {
1177 m_outsideDriftAngle = std::min(m_outsideDriftAngle + 2.0f, targetAngle);
1178 } else if (targetAngle < angle) {
1179 m_outsideDriftAngle -= 150.0f * param()->stats().driftManualTightness;
1180 m_outsideDriftAngle = std::max(m_outsideDriftAngle, targetAngle);
1181 }
1182 }
1183 }
1184
1185 calcMtCharge();
1186}
1187
1192 f32 turn;
1193 auto &status = KartObjectProxy::status();
1194 bool drifting = state()->isDrifting() && status.offBit(eStatus::JumpPadMushroomCollision);
1195 bool autoDrift = status.onBit(eStatus::AutoDrift);
1196 const auto &stats = param()->stats();
1197
1198 if (drifting) {
1199 turn = autoDrift ? stats.driftAutomaticTightness : stats.driftManualTightness;
1200 } else {
1201 turn = autoDrift ? stats.handlingAutomaticTightness : stats.handlingManualTightness;
1202 }
1203
1204 if (drifting && stats.driftType != KartParam::Stats::DriftType::Inside_Drift_Bike) {
1205 m_outsideDriftBonus *= 0.99f;
1206 turn += m_outsideDriftBonus;
1207 }
1208
1209 bool forwards = true;
1210 if (status.onBit(eStatus::Brake) && m_speed <= 0.0f) {
1211 forwards = false;
1212 }
1213
1214 turn *= m_realTurn;
1215 if (status.onBit(eStatus::ChargingSSMT)) {
1216 turn = m_realTurn * 0.04f;
1217 } else {
1218 if (status.onBit(eStatus::Hop) && m_hopPosY > 0.0f) {
1219 turn *= 1.4f;
1220 }
1221
1222 if (!drifting) {
1223 bool noTurn = false;
1225 EGG::Mathf::abs(m_speed) < 1.0f) {
1226 if (!(status.onBit(eStatus::Hop) && m_hopPosY > 0.0f)) {
1227 turn = 0.0f;
1228 noTurn = true;
1229 }
1230 }
1231 if (forwards && !noTurn) {
1232 if (m_speed >= 20.0f) {
1233 turn *= 0.5f;
1234 if (m_speed < 70.0f) {
1235 turn += (1.0f - (m_speed - 20.0f) / 50.0f) * turn;
1236 }
1237 } else {
1238 turn = (turn * 0.4f) + (m_speed / 20.0f) * (turn * 0.6f);
1239 }
1240 }
1241 }
1242
1243 if (!forwards) {
1244 turn = -turn;
1245 }
1246
1247 if (status.onBit(eStatus::ZipperBoost) && status.offBit(eStatus::DriftManual)) {
1248 turn *= 2.0f;
1249 }
1250
1251 f32 stickX = EGG::Mathf::abs(state()->stickX());
1252 if (autoDrift && stickX > 0.3f) {
1253 f32 stickScalar = (stickX - 0.3f) / 0.7f;
1254 stickX = drifting ? 0.2f : 0.5f;
1255 turn += stickScalar * (turn * stickX * m_speedRatioCapped);
1256 }
1257 }
1258
1259 if (status.offBit(eStatus::InAction, eStatus::ZipperTrick)) {
1260 if (status.offBit(eStatus::TouchingGround)) {
1261 if (status.onBit(eStatus::RampBoost) && m_jump->isBoostRampEnabled()) {
1262 turn = 0.0f;
1263 } else if (status.offBit(eStatus::JumpPadMushroomCollision)) {
1264 u32 airtime = state()->airtime();
1265 if (airtime >= 70) {
1266 turn = 0.0f;
1267 } else if (airtime >= 30) {
1268 turn = std::max(0.0f, turn * (1.0f - (airtime - 30) * 0.025f));
1269 }
1270 }
1271 }
1272
1273 const EGG::Vector3f forward = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1274 f32 angle = EGG::Mathf::atan2(forward.cross(m_dir).length(), forward.dot(m_dir));
1275 angle = EGG::Mathf::abs(angle) * RAD2DEG;
1276
1277 if (angle > 60.0f) {
1278 turn *= std::max(0.0f, 1.0f - (angle - 60.0f) / 40.0f);
1279 }
1280 }
1281
1282 calcVehicleRotation(turn);
1283}
1284
1289 const auto *raceMgr = System::RaceManager::Instance();
1290 auto &status = KartObjectProxy::status();
1291
1292 if (raceMgr->isStageReached(System::RaceManager::Stage::Race)) {
1293 f32 speedFix = dynamics()->speedFix();
1294 if (status.onBit(eStatus::InAction) ||
1295 ((status.onBit(eStatus::WallCollisionStart) || state()->wallBonkTimer() == 0 ||
1296 EGG::Mathf::abs(speedFix) >= 3.0f) &&
1297 status.offBit(eStatus::DriftManual))) {
1298 m_speed += speedFix;
1299 }
1300 }
1301
1302 if (m_speed < -20.0f) {
1303 m_speed += 0.5f;
1304 }
1305
1306 bool water = false;
1307
1308 if (status.onBit(eStatus::MovingWaterVertical) ||
1309 (status.onBit(eStatus::MovingWaterDecaySpeed) &&
1310 status.offBit(eStatus::MushroomBoost) && EGG::Mathf::abs(m_speed) > 5.0f)) {
1311 water = true;
1312 m_speed *= collide()->pullPath().roadSpeedDecay();
1313 }
1314
1315 m_acceleration = 0.0f;
1316 m_speedDragMultiplier = 1.0f;
1317
1318 if (status.onBit(eStatus::InAction)) {
1319 action()->calcVehicleSpeed();
1320 return;
1321 }
1322
1323 if ((status.onAllBit(eStatus::SomethingWallCollision, eStatus::TouchingGround) &&
1326 status.onBit(eStatus::DisableAcceleration, eStatus::ChargingSSMT)) {
1327 if (status.onBit(eStatus::RampBoost) && state()->airtime() < 4) {
1328 m_acceleration = 7.0f;
1329 } else {
1330 if (status.onBit(eStatus::JumpPad) && status.offBit(eStatus::Accelerate)) {
1331 m_speedDragMultiplier = 0.99f;
1332 } else {
1333 if (status.onBit(eStatus::OverZipper)) {
1334 m_speedDragMultiplier = 0.999f;
1335 } else {
1336 if (state()->airtime() > 5) {
1337 m_speedDragMultiplier = 0.999f;
1338 }
1339 }
1340 }
1342 }
1343 } else if (status.offBit(eStatus::Boost)) {
1344 if (status.offBit(eStatus::JumpPad, eStatus::RampBoost)) {
1345 if (status.onBit(eStatus::Accelerate)) {
1348 } else {
1349 if (status.offBit(eStatus::Brake) ||
1351 eStatus::SomethingWallCollision)) {
1352 m_speed *= m_speed > 0.0f ? 0.98f : 0.95f;
1353 } else if (m_drivingDirection == DrivingDirection::Braking) {
1354 m_acceleration = -1.5f;
1356 if (++m_backwardsAllowCounter > 15) {
1357 m_drivingDirection = DrivingDirection::Backwards;
1358 }
1359 } else if (m_drivingDirection == DrivingDirection::Backwards) {
1360 m_acceleration = -2.0f;
1361 }
1362 }
1363
1365 const auto &stats = param()->stats();
1366
1367 f32 x = 1.0f - EGG::Mathf::abs(m_weightedTurn) * m_speedRatioCapped;
1368 m_speed *= stats.turningSpeed + (1.0f - stats.turningSpeed) * x;
1369 }
1370 } else {
1371 m_acceleration = water ? calcVehicleAcceleration() : 7.0f;
1372 }
1373 } else {
1374 m_acceleration = water ? calcVehicleAcceleration() : m_boost.acceleration();
1375 }
1376}
1377
1381 f32 vel = 0.0f;
1382 f32 initialVel = 1.0f - m_smoothedUp.y;
1383 if (EGG::Mathf::abs(m_speed) < 30.0f && m_smoothedUp.y > 0.0f && initialVel > 0.0f) {
1384 initialVel = std::min(initialVel * 2.0f, 2.0f);
1385 vel += initialVel;
1386 vel *= std::min(0.5f, std::max(-0.5f, -bodyFront().y));
1387 }
1388 m_speed += vel;
1389}
1390
1395 f32 ratio = m_speed / m_softSpeedLimit;
1396 if (ratio < 0.0f) {
1397 return 1.0f;
1398 }
1399
1400 std::span<const f32> as;
1401 std::span<const f32> ts;
1402 if (state()->isDrifting()) {
1403 as = param()->stats().accelerationDriftA;
1404 ts = param()->stats().accelerationDriftT;
1405 } else {
1406 as = param()->stats().accelerationStandardA;
1407 ts = param()->stats().accelerationStandardT;
1408 }
1409
1410 size_t i = 0;
1411 f32 acceleration = 0.0f;
1412 f32 t_curr = 0.0f;
1413 for (; i < ts.size(); ++i) {
1414 if (ratio < ts[i]) {
1415 acceleration = as[i] + ((as[i + 1] - as[i]) / (ts[i] - t_curr)) * (ratio - t_curr);
1416 break;
1417 }
1418
1419 t_curr = ts[i];
1420 }
1421
1422 return i < ts.size() ? acceleration : as.back();
1423}
1424
1429 constexpr f32 ROTATION_SCALAR_NORMAL = 0.5f;
1430 constexpr f32 ROTATION_SCALAR_MIDAIR = 0.2f;
1431 constexpr f32 ROTATION_SCALAR_BOOST_RAMP = 4.0f;
1432 constexpr f32 OOB_SLOWDOWN_RATE = 0.95f;
1433 constexpr f32 TERMINAL_VELOCITY = 90.0f;
1434
1436 auto &status = KartObjectProxy::status();
1437
1438 if (status.offBit(eStatus::InAction)) {
1439 dynamics()->setKillExtVelY(status.onBit(eStatus::RespawnKillY));
1440 }
1441
1442 if (status.onBit(eStatus::Burnout)) {
1443 m_speed = 0.0f;
1444 } else {
1445 if (m_acceleration < 0.0f) {
1446 if (m_speed < -20.0f) {
1447 m_acceleration = 0.0f;
1448 } else {
1449 if (m_speed + m_acceleration <= -20.0f) {
1450 m_acceleration = -20.0f - m_speed;
1451 }
1452 }
1453 }
1454
1456 }
1457
1458 if (status.onBit(eStatus::BeforeRespawn)) {
1459 m_speed *= OOB_SLOWDOWN_RATE;
1460 } else {
1461 if (status.onBit(eStatus::ChargingSSMT)) {
1462 m_speed *= 0.8f;
1463 } else {
1464 if (m_drivingDirection == DrivingDirection::Braking && m_speed < 0.0f) {
1465 m_speed = 0.0f;
1468 }
1469 }
1470 }
1471
1472 f32 speedLimit = status.onBit(eStatus::JumpPad) ? m_jumpPadMaxSpeed : m_baseSpeed;
1473 const f32 boostMultiplier = m_boost.multiplier();
1474 const f32 boostSpdLimit = m_boost.speedLimit();
1475 m_jumpPadBoostMultiplier = boostMultiplier;
1476
1477 f32 scaleMultiplier = m_shockSpeedMultiplier;
1478 if (status.onBit(eStatus::Crushed)) {
1479 scaleMultiplier *= 0.7f;
1480 }
1481
1482 f32 wheelieBonus = boostMultiplier + getWheelieSoftSpeedLimitBonus();
1483 speedLimit *= status.onBit(eStatus::JumpPadFixedSpeed) ?
1484 1.0f :
1485 scaleMultiplier * (wheelieBonus * m_kclSpeedFactor);
1486
1487 bool ignoreScale = status.onBit(eStatus::RampBoost, eStatus::ZipperInvisibleWall,
1489 f32 boostSpeed = ignoreScale ? 1.0f : scaleMultiplier;
1490 boostSpeed *= boostSpdLimit * m_kclSpeedFactor;
1491
1492 if (status.offBit(eStatus::JumpPad) && boostSpeed > 0.0f && boostSpeed > speedLimit) {
1493 speedLimit = boostSpeed;
1494 }
1495
1496 m_jumpPadSoftSpeedLimit = boostSpdLimit * m_kclSpeedFactor;
1497
1498 if (status.onBit(eStatus::RampBoost)) {
1499 speedLimit = std::max(speedLimit, 100.0f);
1500 }
1501
1502 m_lastDir = (m_speed > 0.0f) ? 1.0f * m_dir : -1.0f * m_dir;
1503
1504 f32 local_c8 = 1.0f;
1505 speedLimit *= calcWallCollisionSpeedFactor(local_c8);
1506
1507 if (m_softSpeedLimit <= speedLimit) {
1508 m_softSpeedLimit = speedLimit;
1510 m_softSpeedLimit = std::max(m_softSpeedLimit - 3.0f, speedLimit);
1511 } else {
1512 m_softSpeedLimit = speedLimit;
1513 }
1514
1516
1517 m_speed = std::min(m_softSpeedLimit, std::max(-m_softSpeedLimit, m_speed));
1518
1519 if (status.onBit(eStatus::JumpPad)) {
1520 m_speed = std::max(m_speed, m_jumpPadMinSpeed);
1521 }
1522
1523 calcWallCollisionStart(local_c8);
1524
1525 m_speedRatio = EGG::Mathf::abs(m_speed / m_baseSpeed);
1526 m_speedRatioCapped = std::min(1.0f, m_speedRatio);
1527
1528 EGG::Vector3f crossVec = m_smoothedUp.cross(m_dir);
1529 if (m_speed < 0.0f) {
1530 crossVec = -crossVec;
1531 }
1532
1533 f32 rotationScalar = ROTATION_SCALAR_NORMAL;
1534 if (collide()->surfaceFlags().onBit(KartCollide::eSurfaceFlags::BoostRamp)) {
1535 rotationScalar = ROTATION_SCALAR_BOOST_RAMP;
1536 } else if (status.offBit(eStatus::TouchingGround)) {
1537 rotationScalar = ROTATION_SCALAR_MIDAIR;
1538 }
1539
1540 EGG::Matrix34f local_90;
1541 local_90.setAxisRotation(DEG2RAD * rotationScalar, crossVec);
1542 m_vel1Dir = local_90.multVector33(m_vel1Dir);
1543
1544 const auto *raceMgr = System::RaceManager::Instance();
1545 if (status.offBit(eStatus::InAction, eStatus::DisableBackwardsAccel, eStatus::Accelerate) &&
1547 raceMgr->isStageReached(System::RaceManager::Stage::Race)) {
1549 }
1550
1552 EGG::Vector3f nextSpeed = m_speed * m_vel1Dir;
1553
1554 f32 maxSpeedY = status.onBit(eStatus::OverZipper) ? KartHalfPipe::TerminalVelocity() :
1555 TERMINAL_VELOCITY;
1556 nextSpeed.y = std::min(nextSpeed.y, maxSpeedY);
1557
1558 dynamics()->setIntVel(dynamics()->intVel() + nextSpeed);
1559
1560 if (status.onBit(eStatus::TouchingGround) &&
1562 if (status.onBit(eStatus::Brake)) {
1563 if (m_drivingDirection == DrivingDirection::Forwards) {
1564 m_drivingDirection = m_processedSpeed > 5.0f ? DrivingDirection::Braking :
1565 DrivingDirection::Backwards;
1566 }
1567 } else {
1568 if (m_processedSpeed >= 0.0f) {
1569 m_drivingDirection = DrivingDirection::Forwards;
1570 }
1571 }
1572 } else {
1573 m_drivingDirection = DrivingDirection::Forwards;
1574 }
1575}
1576
1581 auto &status = KartObjectProxy::status();
1582
1584 return 1.0f;
1585 }
1586
1587 onWallCollision();
1588
1590 return 1.0f;
1591 }
1592
1593 EGG::Vector3f wallNrm = collisionData().wallNrm;
1594 if (wallNrm.y > 0.0f) {
1595 wallNrm.y = 0.0f;
1596 wallNrm.normalise();
1597 }
1598
1599 f32 dot = m_lastDir.dot(wallNrm);
1600
1601 if (dot < 0.0f) {
1602 f1 = std::max(0.0f, dot + 1.0f);
1603
1604 return std::min(1.0f, f1 * (status.onBit(eStatus::WallCollision) ? 0.4f : 0.7f));
1605 }
1606
1607 return 1.0f;
1608}
1609
1615
1616 auto &status = KartObjectProxy::status();
1617
1618 if (status.offBit(eStatus::WallCollisionStart)) {
1619 return;
1620 }
1621
1622 m_outsideDriftAngle = 0.0f;
1623 if (status.offBit(eStatus::InAction)) {
1624 m_dir = bodyFront();
1625 m_vel1Dir = m_dir;
1626 m_landingDir = m_dir;
1627 m_smoothedForward = m_dir;
1628 }
1629
1630 if (status.offBit(eStatus::ZipperInvisibleWall, eStatus::OverZipper) && param_2 < 0.9f) {
1631 f32 speedDiff = m_lastSpeed - m_speed;
1632 const CollisionData &colData = collisionData();
1633
1634 if (speedDiff > 30.0f) {
1635 m_flags.setBit(eFlags::WallBounce);
1636 EGG::Vector3f newPos = colData.relPos + pos();
1637 f32 dot = -bodyUp().dot(colData.relPos) * 0.5f;
1638 EGG::Vector3f scaledUp = dot * bodyUp();
1639 newPos -= scaledUp;
1640
1641 speedDiff = std::min(60.0f, speedDiff);
1642 EGG::Vector3f scaledWallNrm = speedDiff * colData.wallNrm;
1643
1644 auto [proj, rej] = scaledWallNrm.projAndRej(m_vel1Dir);
1645 proj *= 0.3f;
1646 rej *= 0.9f;
1647
1648 if (status.onBit(eStatus::Boost)) {
1649 proj = EGG::Vector3f::zero;
1650 rej = EGG::Vector3f::zero;
1651 }
1652
1653 if (bodyFront().dot(colData.wallNrm) > 0.0f) {
1654 proj = EGG::Vector3f::zero;
1655 }
1656 rej *= 0.9f;
1657
1658 EGG::Vector3f projRejSum = proj + rej;
1659 f32 bumpDeviation = 0.0f;
1660 if (m_flags.offBit(eFlags::DriftReset) && status.onBit(eStatus::TouchingGround)) {
1661 bumpDeviation = param()->stats().bumpDeviationLevel;
1662 }
1663
1664 dynamics()->applyWrenchScaled(newPos, projRejSum, bumpDeviation);
1665 } else if (wallKclType() == COL_TYPE_SPECIAL_WALL && wallKclVariant() == 2) {
1666 dynamics()->addForce(colData.wallNrm * 15.0f);
1667 collide()->startFloorMomentRate();
1668 }
1669
1670 if (wallKclType() == COL_TYPE_SPECIAL_WALL && wallKclVariant() == 0) {
1671 dynamics()->addForce(colData.wallNrm * 15.0f);
1672 collide()->startFloorMomentRate();
1673 }
1674 }
1675}
1676
1681 f32 next = 0.0f;
1682 f32 scalar = 1.0f;
1683
1684 auto &status = KartObjectProxy::status();
1685
1686 if (status.onBit(eStatus::TouchingGround)) {
1687 if (System::RaceManager::Instance()->stage() == System::RaceManager::Stage::Countdown) {
1688 next = 0.015f * -state()->startBoostCharge();
1689 } else if (status.offBit(eStatus::ChargingSSMT)) {
1690 if (status.offBit(eStatus::JumpPad, eStatus::RampBoost, eStatus::SoftWallDrift)) {
1691 f32 speedDiff = m_lastSpeed - m_speed;
1692 scalar = std::min(3.0f, std::max(speedDiff, -3.0f));
1693
1694 if (status.onBit(eStatus::MushroomBoost)) {
1695 next = (scalar * 0.15f) * 0.25f;
1696 if (status.onBit(eStatus::Wheelie)) {
1697 next *= 0.5f;
1698 }
1699 } else {
1700 next = (scalar * 0.15f) * 0.08f;
1701 }
1702 scalar = m_driftingParams->boostRotFactor;
1703 }
1704 } else {
1705 constexpr s16 MAX_SSMT_CHARGE = 75;
1706 next = 0.015f * (-static_cast<f32>(m_ssmtCharge) / static_cast<f32>(MAX_SSMT_CHARGE));
1707 }
1708 }
1709
1710 if (m_flags.onBit(eFlags::WallBounce)) {
1711 m_standStillBoostRot = isBike() ? next * 3.0f : next * 10.0f;
1712 } else {
1713 m_standStillBoostRot += scalar * (next * m_invScale - m_standStillBoostRot);
1714 }
1715}
1716
1721 constexpr f32 DIVE_LIMIT = 0.8f;
1722
1723 m_divingRot *= 0.96f;
1724
1725 auto &status = KartObjectProxy::status();
1726
1727 if (status.onBit(eStatus::TouchingGround, eStatus::CannonStart, eStatus::InCannon,
1728 eStatus::InAction, eStatus::OverZipper)) {
1729 return;
1730 }
1731
1732 f32 stickY = state()->stickY();
1733
1734 if (status.onBit(eStatus::InATrick) && m_jump->type() == TrickType::BikeSideStuntTrick) {
1735 stickY = std::min(1.0f, stickY + 0.4f);
1736 }
1737
1738 u32 airtime = state()->airtime();
1739
1740 if (airtime > 50) {
1741 if (EGG::Mathf::abs(stickY) < 0.1f) {
1742 m_divingRot += 0.05f * (-0.025f - m_divingRot);
1743 }
1744 } else {
1745 stickY *= (airtime / 50.0f);
1746 }
1747
1748 m_divingRot = std::max(-DIVE_LIMIT, std::min(DIVE_LIMIT, m_divingRot + stickY * 0.005f));
1749
1750 EGG::Vector3f angVel2 = dynamics()->angVel2();
1751 angVel2.x += m_divingRot;
1752 dynamics()->setAngVel2(angVel2);
1753
1754 if (state()->airtime() < 50) {
1755 return;
1756 }
1757
1758 EGG::Vector3f topRotated = dynamics()->mainRot().rotateVector(EGG::Vector3f::ey);
1759 EGG::Vector3f forwardRotated = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1760 f32 upDotTop = m_up.dot(topRotated);
1761 EGG::Vector3f upCrossTop = m_up.cross(topRotated);
1762 f32 crossNorm = upCrossTop.length();
1763 f32 angle = EGG::Mathf::abs(EGG::Mathf::atan2(crossNorm, upDotTop));
1764
1765 f32 fVar1 = angle * RAD2DEG - 20.0f;
1766 if (fVar1 <= 0.0f) {
1767 return;
1768 }
1769
1770 f32 mult = std::min(1.0f, fVar1 / 20.0f);
1771 if (forwardRotated.y > 0.0f) {
1772 dynamics()->setGravity((1.0f - 0.2f * mult) * dynamics()->gravity());
1773 } else {
1774 dynamics()->setGravity((0.2f * mult + 1.0f) * dynamics()->gravity());
1775 }
1776}
1777
1782 auto &status = KartObjectProxy::status();
1783
1784 if (EGG::Mathf::abs(m_speed) >= 10.0f || status.onBit(eStatus::Boost, eStatus::RampBoost) ||
1787 return;
1788 }
1789
1791}
1792
1794void KartMove::calcHopPhysics() {
1795 m_hopVelY = m_hopVelY * 0.998f + m_hopGravity;
1797
1798 if (m_hopPosY < 0.0f) {
1799 m_hopPosY = 0.0f;
1800 m_hopVelY = 0.0f;
1801 }
1802}
1803
1805void KartMove::calcRejectRoad() {
1806 m_reject.calcRejectRoad();
1807}
1808
1810bool KartMove::calcZipperCollision(f32 radius, f32 scale, EGG::Vector3f &pos,
1811 EGG::Vector3f &upLocal, const EGG::Vector3f &prevPos, Field::CollisionInfo *colInfo,
1812 Field::KCLTypeMask *maskOut, Field::KCLTypeMask flags) const {
1813 upLocal = mainRot().rotateVector(EGG::Vector3f::ey);
1814 pos = dynamics()->pos() + (-scale * m_scale.y) * upLocal;
1815
1816 auto *colDir = Field::CollisionDirector::Instance();
1817 return colDir->checkSphereFullPush(radius, pos, prevPos, flags, colInfo, maskOut, 0);
1818}
1819
1821f32 KartMove::calcSlerpRate(f32 scale, const EGG::Quatf &from, const EGG::Quatf &to) const {
1822 f32 dotNorm = std::max(-1.0f, std::min(1.0f, from.dot(to)));
1823 f32 acos = EGG::Mathf::acos(dotNorm);
1824 return acos > 0.0f ? std::min(0.1f, scale / acos) : 0.1f;
1825}
1826
1828void KartMove::applyForce(f32 force, const EGG::Vector3f &hitDir, bool stop) {
1829 constexpr s16 BUMP_COOLDOWN = 5;
1830
1831 if (m_bumpTimer >= 1) {
1832 return;
1833 }
1834
1835 dynamics()->addForce(force * hitDir.perpInPlane(m_up, true));
1836 collide()->startFloorMomentRate();
1837
1838 m_bumpTimer = BUMP_COOLDOWN;
1839
1840 if (stop) {
1841 m_speed = 0.0f;
1842 }
1843}
1844
1848 f32 tiltMagnitude = 0.0f;
1849 auto &status = KartObjectProxy::status();
1850
1851 if (status.offBit(eStatus::InAction, eStatus::SoftWallDrift) &&
1853 EGG::Vector3f front = componentZAxis();
1854 front = front.perpInPlane(m_up, true);
1855 EGG::Vector3f frontSpeed = velocity().rej(front).perpInPlane(m_up, false);
1856 f32 magnitude = tiltMagnitude;
1857
1858 if (frontSpeed.squaredLength() > std::numeric_limits<f32>::epsilon()) {
1859 magnitude = frontSpeed.length();
1860
1861 if (front.z * frontSpeed.x - front.x * frontSpeed.z > 0.0f) {
1862 magnitude = -magnitude;
1863 }
1864
1865 tiltMagnitude = -1.0f;
1866 if (-1.0f <= magnitude) {
1867 tiltMagnitude = std::min(1.0f, magnitude);
1868 }
1869 }
1870 } else if (status.offBit(eStatus::Hop) || m_hopPosY <= 0.0f) {
1871 EGG::Vector3f angVel0 = dynamics()->angVel0();
1872 angVel0.z *= 0.98f;
1873 dynamics()->setAngVel0(angVel0);
1874 }
1875
1876 f32 lean =
1877 m_invScale * (tiltMagnitude * param()->stats().tilt * EGG::Mathf::abs(m_weightedTurn));
1878
1880
1881 EGG::Vector3f angVel0 = dynamics()->angVel0();
1882 angVel0.x += m_standStillBoostRot;
1883 angVel0.z += lean;
1884 dynamics()->setAngVel0(angVel0);
1885
1886 EGG::Vector3f angVel2 = dynamics()->angVel2();
1887 angVel2.y += turn;
1888 dynamics()->setAngVel2(angVel2);
1889
1890 calcDive();
1891}
1892
1897 // TODO: Some of these are shared between the base and derived class implementations.
1898 constexpr u16 MAX_MT_CHARGE = 270;
1899 constexpr u16 MAX_SMT_CHARGE = 300;
1900 constexpr u16 BASE_MT_CHARGE = 2;
1901 constexpr u16 BASE_SMT_CHARGE = 2;
1902 constexpr f32 BONUS_CHARGE_STICK_THRESHOLD = 0.4f;
1903 constexpr u16 EXTRA_MT_CHARGE = 3;
1904
1905 if (m_driftState == DriftState::ChargedSmt) {
1906 return;
1907 }
1908
1909 f32 stickX = state()->stickX();
1910
1911 if (m_driftState == DriftState::ChargingMt) {
1912 m_mtCharge += BASE_MT_CHARGE;
1913
1914 if (-BONUS_CHARGE_STICK_THRESHOLD <= stickX) {
1915 if (BONUS_CHARGE_STICK_THRESHOLD < stickX && m_hopStickX == -1) {
1916 m_mtCharge += EXTRA_MT_CHARGE;
1917 }
1918 } else if (m_hopStickX != -1) {
1919 m_mtCharge += EXTRA_MT_CHARGE;
1920 }
1921
1922 if (m_mtCharge > MAX_MT_CHARGE) {
1923 m_mtCharge = MAX_MT_CHARGE;
1924 m_driftState = DriftState::ChargingSmt;
1925 }
1926 }
1927
1928 if (m_driftState != DriftState::ChargingSmt) {
1929 return;
1930 }
1931
1932 m_smtCharge += BASE_SMT_CHARGE;
1933
1934 if (-BONUS_CHARGE_STICK_THRESHOLD <= stickX) {
1935 if (BONUS_CHARGE_STICK_THRESHOLD < stickX && m_hopStickX == -1) {
1936 m_smtCharge += EXTRA_MT_CHARGE;
1937 }
1938 } else if (m_hopStickX != -1) {
1939 m_smtCharge += EXTRA_MT_CHARGE;
1940 }
1941
1942 if (m_smtCharge > MAX_SMT_CHARGE) {
1943 m_smtCharge = MAX_SMT_CHARGE;
1944 m_driftState = DriftState::ChargedSmt;
1945 }
1946}
1947
1949void KartMove::initOob() {
1950 clearBoost();
1951 clearJumpPad();
1952 clearRampBoost();
1953 clearZipperBoost();
1954 clearSsmt();
1955 clearOffroadInvincibility();
1956}
1957
1962 status().setBit(eStatus::Hop).resetBit(eStatus::DriftManual);
1963 onHop();
1964
1965 m_hopUp = dynamics()->mainRot().rotateVector(EGG::Vector3f::ey);
1966 m_hopDir = dynamics()->mainRot().rotateVector(EGG::Vector3f::ez);
1967 m_driftState = DriftState::NotDrifting;
1968 m_smtCharge = 0;
1969 m_mtCharge = 0;
1970 m_hopStickX = 0;
1971 m_hopFrame = 0;
1972 m_hopPosY = 0.0f;
1973 m_hopGravity = dynamics()->gravity();
1974 m_hopVelY = m_driftingParams->hopVelY;
1975 m_outsideDriftBonus = 0.0f;
1976
1977 EGG::Vector3f extVel = dynamics()->extVel();
1978 extVel.y = 0.0f + m_hopVelY;
1979 dynamics()->setExtVel(extVel);
1980
1981 EGG::Vector3f totalForce = dynamics()->totalForce();
1982 totalForce.y = 0.0f;
1983 dynamics()->setTotalForce(totalForce);
1984}
1985
1987void KartMove::tryStartBoostPanel() {
1988 constexpr s16 BOOST_PANEL_DURATION = 60;
1989
1990 if (status().onBit(eStatus::BeforeRespawn, eStatus::InAction)) {
1991 return;
1992 }
1993
1994 activateBoost(KartBoost::Type::MushroomAndBoostPanel, BOOST_PANEL_DURATION);
1995 setOffroadInvincibility(BOOST_PANEL_DURATION);
1996}
1997
2002 constexpr s16 BOOST_RAMP_DURATION = 60;
2003
2004 auto &status = KartObjectProxy::status();
2005
2006 if (status.onBit(eStatus::BeforeRespawn, eStatus::InAction)) {
2007 return;
2008 }
2009
2010 status.setBit(eStatus::RampBoost);
2011 m_rampBoost = BOOST_RAMP_DURATION;
2012 setOffroadInvincibility(BOOST_RAMP_DURATION);
2013}
2014
2020 static constexpr std::array<JumpPadProperties, 8> JUMP_PAD_PROPERTIES = {{
2021 {50.0f, 50.0f, 35.0f},
2022 {50.0f, 50.0f, 47.0f},
2023 {59.0f, 59.0f, 30.0f},
2024 {73.0f, 73.0f, 45.0f},
2025 {73.0f, 73.0f, 53.0f},
2026 {56.0f, 56.0f, 50.0f},
2027 {55.0f, 55.0f, 35.0f},
2028 {56.0f, 56.0f, 50.0f},
2029 }};
2030
2031 auto &status = KartObjectProxy::status();
2032
2033 if (status.onBit(eStatus::BeforeRespawn, eStatus::InAction, eStatus::HalfPipeRamp)) {
2034 return;
2035 }
2036
2037 status.setBit(eStatus::JumpPad);
2038 s32 jumpPadVariant = state()->jumpPadVariant();
2039 m_jumpPadProperties = &JUMP_PAD_PROPERTIES[jumpPadVariant];
2040
2041 if (jumpPadVariant == 3 || jumpPadVariant == 4) {
2042 if (m_jumpPadBoostMultiplier > 1.3f || m_jumpPadSoftSpeedLimit > 110.0f) {
2043 // Set speed to 100 if the player has boost from a boost panel or mushroom(item) before
2044 // hitting the jump pad
2045 static constexpr std::array<JumpPadProperties, 2> JUMP_PAD_PROPERTIES_SHROOM_BOOST = {{
2046 {100.0f, 100.0f, 70.0f},
2047 {100.0f, 100.0f, 65.0f},
2048 }};
2049 m_jumpPadProperties = &JUMP_PAD_PROPERTIES_SHROOM_BOOST[jumpPadVariant != 3];
2050 }
2051
2052 status.setBit(eStatus::JumpPadFixedSpeed);
2053 }
2054
2055 if (jumpPadVariant == 4) {
2056 status.setBit(eStatus::JumpPadMushroomTrigger, eStatus::JumpPadMushroomVelYInc,
2057 eStatus::JumpPadMushroomCollision);
2058 } else {
2059 EGG::Vector3f extVel = dynamics()->extVel();
2060 EGG::Vector3f totalForce = dynamics()->totalForce();
2061
2062 extVel.y = m_jumpPadProperties->velY;
2063 totalForce.y = 0.0f;
2064
2065 dynamics()->setExtVel(extVel);
2066 dynamics()->setTotalForce(totalForce);
2067
2068 if (jumpPadVariant != 3) {
2069 EGG::Vector3f dir = m_dir;
2070 dir.y = 0.0f;
2071 dir.normalise();
2072 m_speed *= m_dir.dot(dir);
2073 m_dir = dir;
2074 m_vel1Dir = dir;
2075 status.setBit(eStatus::JumpPadDisableYsusForce);
2076 }
2077 }
2078
2079 m_jumpPadMinSpeed = m_jumpPadProperties->minSpeed;
2080 m_jumpPadMaxSpeed = m_jumpPadProperties->maxSpeed;
2081 m_speed = std::max(m_speed, m_jumpPadMinSpeed);
2082}
2083
2085void KartMove::tryEndJumpPad() {
2086 auto &status = KartObjectProxy::status();
2087 if (status.onBit(eStatus::JumpPadMushroomTrigger)) {
2088 if (status.onBit(eStatus::GroundStart)) {
2089 status.resetBit(eStatus::JumpPadMushroomTrigger, eStatus::JumpPadFixedSpeed,
2090 eStatus::JumpPadMushroomVelYInc);
2091 }
2092
2093 if (status.onBit(eStatus::JumpPadMushroomVelYInc)) {
2094 EGG::Vector3f newExtVel = dynamics()->extVel();
2095 newExtVel.y += 20.0f;
2096 if (m_jumpPadProperties->velY < newExtVel.y) {
2097 newExtVel.y = m_jumpPadProperties->velY;
2098 status.resetBit(eStatus::JumpPadMushroomVelYInc);
2099 }
2100 dynamics()->setExtVel(newExtVel);
2101 }
2102 }
2103
2104 if (status.onBit(eStatus::GroundStart) && status.offBit(eStatus::JumpPadMushroomTrigger)) {
2105 cancelJumpPad();
2106 }
2107}
2108
2110void KartMove::cancelJumpPad() {
2111 m_jumpPadMinSpeed = 0.0f;
2112 status().resetBit(eStatus::JumpPad);
2113}
2114
2116void KartMove::activateBoost(KartBoost::Type type, s16 frames) {
2117 if (m_boost.activate(type, frames)) {
2118 status().setBit(eStatus::Boost);
2119 }
2120}
2121
2123void KartMove::applyStartBoost(s16 frames) {
2124 activateBoost(KartBoost::Type::AllMt, frames);
2125}
2126
2128void KartMove::activateMushroom() {
2129 constexpr s16 MUSHROOM_DURATION = 90;
2130
2131 auto &status = KartObjectProxy::status();
2132
2133 if (status.onBit(eStatus::BeforeRespawn, eStatus::InAction)) {
2134 return;
2135 }
2136
2137 activateBoost(KartBoost::Type::MushroomAndBoostPanel, MUSHROOM_DURATION);
2138
2139 m_mushroomBoostTimer = MUSHROOM_DURATION;
2141 setOffroadInvincibility(MUSHROOM_DURATION);
2142}
2143
2145void KartMove::activateZipperBoost() {
2146 constexpr s16 BASE_DURATION = 50;
2147 constexpr s16 TRICK_DURATION = 100;
2148
2149 auto &status = KartObjectProxy::status();
2150
2151 if (status.onBit(eStatus::BeforeRespawn, eStatus::InAction)) {
2152 return;
2153 }
2154
2155 s16 boostDuration = status.onBit(eStatus::ZipperTrick) ? TRICK_DURATION : BASE_DURATION;
2156 activateBoost(KartBoost::Type::TrickAndZipper, boostDuration);
2157
2158 setOffroadInvincibility(boostDuration);
2159 m_zipperBoostTimer = 0;
2160 m_zipperBoostMax = boostDuration;
2162}
2163
2169 if (timer > m_offroadInvincibility) {
2170 m_offroadInvincibility = timer;
2171 }
2172
2174}
2175
2180 auto &status = KartObjectProxy::status();
2181
2183 return;
2184 }
2185
2186 if (--m_offroadInvincibility > 0) {
2187 return;
2188 }
2189
2191}
2192
2196 auto &status = KartObjectProxy::status();
2197
2198 if (status.offBit(eStatus::MushroomBoost)) {
2199 return;
2200 }
2201
2202 if (--m_mushroomBoostTimer > 0) {
2203 return;
2204 }
2205
2207}
2208
2210void KartMove::calcZipperBoost() {
2211 auto &status = KartObjectProxy::status();
2212
2213 if (status.offBit(eStatus::ZipperBoost)) {
2214 return;
2215 }
2216
2218
2219 if (status.offBit(eStatus::OverZipper) && ++m_zipperBoostTimer >= m_zipperBoostMax) {
2220 m_zipperBoostTimer = 0;
2222 }
2223
2224 if (m_zipperBoostTimer < 10) {
2225 EGG::Vector3f angVel = dynamics()->angVel0();
2226 angVel.y = 0.0f;
2227 dynamics()->setAngVel0(angVel);
2228 }
2229}
2230
2232void KartMove::landTrick() {
2233 static constexpr std::array<s16, 3> KART_TRICK_BOOST_DURATION = {{
2234 40,
2235 70,
2236 85,
2237 }};
2238 static constexpr std::array<s16, 3> BIKE_TRICK_BOOST_DURATION = {{
2239 45,
2240 80,
2241 95,
2242 }};
2243
2244 if (status().onBit(eStatus::BeforeRespawn, eStatus::InAction)) {
2245 return;
2246 }
2247
2248 s16 duration;
2249 if (isBike()) {
2250 duration = BIKE_TRICK_BOOST_DURATION[static_cast<u32>(m_jump->variant())];
2251 } else {
2252 duration = KART_TRICK_BOOST_DURATION[static_cast<u32>(m_jump->variant())];
2253 }
2254
2255 activateBoost(KartBoost::Type::TrickAndZipper, duration);
2256}
2257
2259void KartMove::activateCrush(u16 timer) {
2260 status().setBit(eStatus::Crushed);
2261 m_crushTimer = timer;
2262 m_kartScale->startCrush();
2263}
2264
2266void KartMove::calcCrushed() {
2267 if (status().offBit(eStatus::Crushed)) {
2268 return;
2269 }
2270
2271 if (--m_crushTimer == 0) {
2272 status().resetBit(eStatus::Crushed);
2273 m_kartScale->endCrush();
2274 }
2275}
2276
2278void KartMove::calcScale() {
2279 m_kartScale->calc();
2280
2281 const EGG::Vector3f sizeScale = m_kartScale->sizeScale();
2282 setScale(m_kartScale->pressScale() * sizeScale);
2283 m_totalScale = m_shockSpeedMultiplier;
2284 m_hitboxScale = std::max(sizeScale.z, m_totalScale);
2285
2286 if (sizeScale.z != 1.0f) {
2287 setInertiaScale(m_scale);
2288 }
2289
2290 m_invScale = m_scale.z > 1.0f ? 1.0f / m_scale.z : 1.0f;
2291}
2292
2294void KartMove::applyShrink(u16 timer) {
2295 auto &status = state()->status();
2296
2297 if (status.onBit(eStatus::InRespawn, eStatus::AfterRespawn, eStatus::CannonStart)) {
2298 return;
2299 }
2300
2301 action()->start(Action::UNK_15);
2302 Item::ItemDirector::Instance()->kartItem(0).clear();
2303 status.setBit(eStatus::Shocked);
2304
2305 if (timer > m_shockTimer) {
2306 m_shockTimer = timer;
2307 m_kartScale->startShrink(0);
2308 }
2309}
2310
2312void KartMove::calcShock() {
2313 auto &status = state()->status();
2314
2315 if (status.onBit(eStatus::Shocked)) {
2316 if (--m_shockTimer == 0) {
2317 deactivateShock(false);
2318 }
2319
2320 m_shockSpeedMultiplier = std::max(0.7f, m_shockSpeedMultiplier - 0.03f);
2321 } else {
2322 m_shockSpeedMultiplier = std::min(1.0f, m_shockSpeedMultiplier + 0.05f);
2323 }
2324}
2325
2327void KartMove::deactivateShock(bool resetSpeed) {
2328 status().resetBit(eStatus::Shocked);
2329 m_shockTimer = 0;
2330 m_kartScale->endShrink(0);
2331
2332 if (resetSpeed) {
2333 m_shockSpeedMultiplier = 1.0f;
2334 }
2335}
2336
2338void KartMove::enterCannon() {
2339 init(true, true);
2340 physics()->clearDecayingRot();
2341 m_boost.resetActive();
2342
2343 auto &status = KartObjectProxy::status();
2344
2345 status.resetBit(eStatus::Boost);
2346
2347 cancelJumpPad();
2348 clearRampBoost();
2349 clearZipperBoost();
2350 clearSsmt();
2351 clearOffroadInvincibility();
2352
2353 dynamics()->reset();
2354
2355 clearDrift();
2356
2357 status.resetBit(eStatus::Hop, eStatus::CannonStart)
2358 .setBit(eStatus::InCannon, eStatus::SkipWheelCalc);
2359
2360 const auto [cannonPos, cannonDir] = getCannonPosRot();
2361 m_cannonEntryPos = pos();
2362 m_cannonEntryOfs = cannonPos - pos();
2363 m_cannonEntryOfsLength = m_cannonEntryOfs.normalise();
2364 m_cannonEntryOfs.normalise();
2365 m_dir = m_cannonEntryOfs;
2366 m_vel1Dir = m_cannonEntryOfs;
2367 m_cannonOrthog = EGG::Vector3f::ey.perpInPlane(m_cannonEntryOfs, true);
2368 m_cannonProgress.setZero();
2369}
2370
2372void KartMove::calcCannon() {
2373 auto [cannonPos, cannonDir] = getCannonPosRot();
2374 EGG::Vector3f forwardXZ = cannonPos - m_cannonEntryPos - m_cannonProgress;
2375 EGG::Vector3f forward = forwardXZ;
2376 f32 forwardLength = forward.normalise();
2377 forwardXZ.y = 0;
2378 forwardXZ.normalise();
2379 EGG::Vector3f local94 = m_cannonEntryOfs;
2380 local94.y = 0;
2381 local94.normalise();
2382 m_speedRatioCapped = 1.0f;
2383 m_speedRatio = 1.5f;
2384 EGG::Matrix34f cannonOrientation;
2385 cannonOrientation.makeOrthonormalBasis(forward, EGG::Vector3f::ey);
2386 EGG::Vector3f up = cannonOrientation.multVector33(EGG::Vector3f::ey);
2387 m_smoothedUp = up;
2388 m_up = up;
2389
2390 if (forwardLength < 30.0f || local94.dot(forwardXZ) <= 0.0f) {
2391 exitCannon();
2392 return;
2393 }
2394
2395 m_smoothedForward = cannonOrientation.multVector33(EGG::Vector3f::ez);
2397 const auto *cannonPoint =
2398 System::CourseMap::Instance()->getCannonPoint(state()->cannonPointId());
2399 size_t cannonParameterIdx = std::max<s16>(0, cannonPoint->parameterIdx());
2400 ASSERT(cannonParameterIdx < CANNON_PARAMETERS.size());
2401 const auto &cannonParams = CANNON_PARAMETERS[cannonParameterIdx];
2402 f32 newSpeed = cannonParams.speed;
2403 if (forwardLength < cannonParams.decelFactor) {
2404 f32 factor = std::max(0.0f, forwardLength / cannonParams.decelFactor);
2405
2406 newSpeed = cannonParams.endDecel;
2407 if (newSpeed <= 0.0f) {
2408 newSpeed = m_baseSpeed;
2409 }
2410
2411 newSpeed += factor * (cannonParams.speed - newSpeed);
2412 if (cannonParams.endDecel > 0.0f) {
2413 m_speed = std::min(newSpeed, m_speed);
2414 }
2415 }
2416
2417 m_cannonProgress += m_cannonEntryOfs * newSpeed;
2418
2419 EGG::Vector3f newPos = EGG::Vector3f::zero;
2420 if (cannonParams.height > 0.0f) {
2421 f32 fVar9 = EGG::Mathf::SinFIdx(
2422 (1.0f - (forwardLength / m_cannonEntryOfsLength)) * 180.0f * DEG2FIDX);
2423 newPos = fVar9 * cannonParams.height * m_cannonOrthog;
2424 }
2425
2426 dynamics()->setPos(m_cannonEntryPos + m_cannonProgress + newPos);
2427 m_dir = m_cannonEntryOfs;
2428 m_vel1Dir = m_cannonEntryOfs;
2429
2430 calcRotCannon(forward);
2431
2432 dynamics()->setExtVel(EGG::Vector3f::zero);
2433}
2434
2436void KartMove::calcRotCannon(const EGG::Vector3f &forward) {
2437 EGG::Vector3f local48 = forward;
2438 local48.normalise();
2439 EGG::Vector3f local54 = bodyFront();
2440 EGG::Vector3f local60 = local54 + ((local48 - local54) * 0.3f);
2441 local54.normalise();
2442 local60.normalise();
2443 // also local70, localA8
2444 EGG::Quatf local80;
2445 local80.makeVectorRotation(local54, local60);
2446 local80 *= dynamics()->fullRot();
2447 local80.normalise();
2448 EGG::Quatf localB8;
2449 localB8.makeVectorRotation(local80.rotateVector(EGG::Vector3f::ey), smoothedUp());
2450 EGG::Quatf newRot = local80.slerpTo(localB8.multSwap(local80), 0.3f);
2451 dynamics()->setFullRot(newRot);
2452 dynamics()->setMainRot(newRot);
2453}
2454
2456void KartMove::exitCannon() {
2457 auto &status = KartObjectProxy::status();
2458
2459 if (status.offBit(eStatus::InCannon)) {
2460 return;
2461 }
2462
2463 status.resetBit(eStatus::InCannon, eStatus::SkipWheelCalc).setBit(eStatus::AfterCannon);
2464 dynamics()->setIntVel(m_cannonEntryOfs * m_speed);
2465}
2466
2468void KartMove::triggerRespawn() {
2469 m_timeInRespawn = 0;
2470 status().setBit(eStatus::TriggerRespawn);
2471}
2472
2474KartMoveBike::KartMoveBike() : m_leanRot(0.0f) {}
2475
2477KartMoveBike::~KartMoveBike() = default;
2478
2482 constexpr f32 MAX_WHEELIE_ROTATION = 0.07f;
2483 constexpr u16 WHEELIE_COOLDOWN = 20;
2484
2485 status().setBit(eStatus::Wheelie);
2486 m_wheelieFrames = 0;
2487 m_maxWheelieRot = MAX_WHEELIE_ROTATION;
2488 m_wheelieCooldown = WHEELIE_COOLDOWN;
2489 m_wheelieRotDec = 0.0f;
2490 m_autoHardStickXFrames = 0;
2491}
2492
2497 status().resetBit(eStatus::Wheelie);
2498 m_wheelieRotDec = 0.0f;
2499 m_autoHardStickXFrames = 0;
2500}
2501
2503void KartMoveBike::createSubsystems(const KartParam::Stats &stats) {
2504 m_jump = new KartJumpBike(this);
2506 m_kartScale = new KartScale(stats);
2507}
2508
2513 f32 leanRotInc = m_turningParams->leanRotIncRace;
2514 f32 leanRotCap = m_turningParams->leanRotCapRace;
2515 const auto *raceManager = System::RaceManager::Instance();
2516
2517 auto &status = KartObjectProxy::status();
2518
2519 if (status.offBit(eStatus::ChargingSSMT)) {
2520 if (!raceManager->isStageReached(System::RaceManager::Stage::Race) ||
2521 EGG::Mathf::abs(m_speed) < 5.0f) {
2522 leanRotInc = m_turningParams->leanRotIncCountdown;
2523 leanRotCap = m_turningParams->leanRotCapCountdown;
2524 }
2525 } else {
2526 leanRotInc = m_turningParams->leanRotIncSSMT;
2527 leanRotCap = m_turningParams->leanRotCapSSMT;
2528 }
2529
2530 m_leanRotCap += 0.3f * (leanRotCap - m_leanRotCap);
2531 m_leanRotInc += 0.3f * (leanRotInc - m_leanRotInc);
2532
2533 f32 stickX = state()->stickX();
2534 f32 extVelXFactor = 0.0f;
2535 f32 leanRotMin = -m_leanRotCap;
2536 f32 leanRotMax = m_leanRotCap;
2537
2538 if (status.onBit(eStatus::BeforeRespawn, eStatus::InAction, eStatus::Wheelie,
2540 eStatus::SoftWallDrift, eStatus::SomethingWallCollision, eStatus::HWG,
2541 eStatus::CannonStart, eStatus::InCannon)) {
2542 m_leanRot *= m_turningParams->leanRotDecayFactor;
2543 } else if (!state()->isDrifting()) {
2544 if (stickX <= 0.2f) {
2545 if (stickX >= -0.2f) {
2546 m_leanRot *= m_turningParams->leanRotDecayFactor;
2547 } else {
2549 extVelXFactor = m_turningParams->leanRotShallowFactor;
2550 }
2551 } else {
2553 extVelXFactor = -m_turningParams->leanRotShallowFactor;
2554 }
2555 } else {
2556 leanRotMax = m_turningParams->leanRotMaxDrift;
2557 leanRotMin = m_turningParams->leanRotMinDrift;
2558
2559 if (m_hopStickX == 1) {
2560 leanRotMin = -leanRotMax;
2561 leanRotMax = -m_turningParams->leanRotMinDrift;
2562 }
2563 if (m_hopStickX == -1) {
2564 if (stickX == 0.0f) {
2565 m_leanRot += (0.5f - m_leanRot) * 0.05f;
2566 } else {
2567 m_leanRot += m_turningParams->driftStickXFactor * stickX;
2568 extVelXFactor = -m_turningParams->leanRotShallowFactor * stickX;
2569 }
2570 } else 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 }
2577
2578 bool capped = false;
2579 if (leanRotMin <= m_leanRot) {
2580 if (leanRotMax < m_leanRot) {
2581 m_leanRot = leanRotMax;
2582 capped = true;
2583 }
2584 } else {
2585 m_leanRot = leanRotMin;
2586 capped = true;
2587 }
2588
2589 if (!capped) {
2590 dynamics()->setExtVel(dynamics()->extVel() + componentXAxis() * extVelXFactor);
2591 }
2592
2593 f32 leanRotScalar = state()->isDrifting() ? 0.065f : 0.05f;
2594
2596
2597 dynamics()->setAngVel2(dynamics()->angVel2() +
2598 EGG::Vector3f(m_standStillBoostRot, turn * wheelieRotFactor(),
2599 m_leanRot * leanRotScalar));
2600
2601 calcDive();
2602
2603 EGG::Vector3f top = m_up;
2604
2606 f32 scalar = (m_speed >= 0.0f) ? m_speedRatioCapped * 2.0f : 0.0f;
2607 scalar = std::min(1.0f, scalar);
2608 top = scalar * m_up + (1.0f - scalar) * EGG::Vector3f::ey;
2609
2610 if (std::numeric_limits<f32>::epsilon() < top.squaredLength()) {
2611 top.normalise();
2612 }
2613 }
2614
2615 dynamics()->setTop_(top);
2616}
2617
2623 static constexpr std::array<TurningParameters, 2> TURNING_PARAMS_ARRAY = {{
2624 {0.8f, 0.08f, 1.0f, 0.1f, 1.2f, 0.8f, 0.08f, 0.6f, 0.15f, 1.6f, 0.9f, 180},
2625 {1.0f, 0.1f, 1.0f, 0.05f, 1.5f, 0.7f, 0.08f, 0.6f, 0.15f, 1.3f, 0.9f, 180},
2626 }};
2627
2628 KartMove::setTurnParams();
2629
2630 if (param()->stats().driftType == KartParam::Stats::DriftType::Outside_Drift_Bike) {
2631 m_turningParams = &TURNING_PARAMS_ARRAY[0];
2632 } else if (param()->stats().driftType == KartParam::Stats::DriftType::Inside_Drift_Bike) {
2633 m_turningParams = &TURNING_PARAMS_ARRAY[1];
2634 }
2635
2636 if (System::RaceManager::Instance()->isStageReached(System::RaceManager::Stage::Race)) {
2637 m_leanRotInc = m_turningParams->leanRotIncRace;
2638 m_leanRotCap = m_turningParams->leanRotCapRace;
2639 } else {
2640 m_leanRotInc = m_turningParams->leanRotIncCountdown;
2641 m_leanRotCap = m_turningParams->leanRotCapCountdown;
2642 }
2643}
2644
2646void KartMoveBike::init(bool b1, bool b2) {
2647 KartMove::init(b1, b2);
2648
2649 m_leanRot = 0.0f;
2650 m_leanRotCap = 0.0f;
2651 m_leanRotInc = 0.0f;
2652 m_wheelieRot = 0.0f;
2653 m_maxWheelieRot = 0.0f;
2654 m_wheelieFrames = 0;
2656 m_autoHardStickXFrames = 0;
2657}
2658
2660void KartMoveBike::clear() {
2661 KartMove::clear();
2662 cancelWheelie();
2663}
2664
2668 constexpr u32 FAILED_WHEELIE_FRAMES = 15;
2669 constexpr f32 AUTO_WHEELIE_CANCEL_STICK_THRESHOLD = 0.85f;
2670
2672 m_wheelieCooldown = std::max(0, m_wheelieCooldown - 1);
2673
2674 auto &status = KartObjectProxy::status();
2675
2676 if (status.onBit(eStatus::Wheelie)) {
2677 bool cancelAutoWheelie = false;
2678
2679 if (status.offBit(eStatus::AutoDrift) ||
2680 EGG::Mathf::abs(state()->stickX()) <= AUTO_WHEELIE_CANCEL_STICK_THRESHOLD) {
2681 m_autoHardStickXFrames = 0;
2682 } else {
2683 if (++m_autoHardStickXFrames > 15) {
2684 cancelAutoWheelie = true;
2685 }
2686 }
2687
2689 if (m_turningParams->maxWheelieFrames < m_wheelieFrames || cancelAutoWheelie ||
2690 (!canWheelie() && FAILED_WHEELIE_FRAMES <= m_wheelieFrames)) {
2691 cancelWheelie();
2692 } else {
2693 m_wheelieRot += 0.01f;
2694 EGG::Vector3f angVel0 = dynamics()->angVel0();
2695 angVel0.x *= 0.9f;
2696 dynamics()->setAngVel0(angVel0);
2697 }
2698 } else if (0.0f < m_wheelieRot) {
2699 m_wheelieRotDec -= 0.001f;
2700 m_wheelieRotDec = std::max(-0.03f, m_wheelieRotDec);
2702 }
2703
2704 m_wheelieRot = std::max(0.0f, std::min(m_wheelieRot, m_maxWheelieRot));
2705
2706 f32 vel1DirUp = m_vel1Dir.dot(EGG::Vector3f::ey);
2707
2708 if (m_wheelieRot > 0.0f) {
2709 if (vel1DirUp <= 0.5f || m_wheelieFrames < FAILED_WHEELIE_FRAMES) {
2710 EGG::Vector3f angVel2 = dynamics()->angVel2();
2711 angVel2.x -= m_wheelieRot * (1.0f - EGG::Mathf::abs(vel1DirUp));
2712 dynamics()->setAngVel2(angVel2);
2713 } else {
2714 cancelWheelie();
2715 }
2716
2717 status.setBit(eStatus::WheelieRot);
2718 } else {
2719 status.resetBit(eStatus::WheelieRot);
2720 }
2721}
2722
2729 if (status().onBit(eStatus::AutoDrift)) {
2730 return;
2731 }
2732
2733 cancelWheelie();
2734}
2735
2741
2746 constexpr u16 MAX_MT_CHARGE = 270;
2747 constexpr u16 BASE_MT_CHARGE = 2;
2748 constexpr f32 BONUS_CHARGE_STICK_THRESHOLD = 0.4f;
2749 constexpr u16 EXTRA_MT_CHARGE = 3;
2750
2751 if (m_driftState != DriftState::ChargingMt) {
2752 return;
2753 }
2754
2755 m_mtCharge += BASE_MT_CHARGE;
2756
2757 f32 stickX = state()->stickX();
2758 if (-BONUS_CHARGE_STICK_THRESHOLD <= stickX) {
2759 if (BONUS_CHARGE_STICK_THRESHOLD < stickX && m_hopStickX == -1) {
2760 m_mtCharge += EXTRA_MT_CHARGE;
2761 }
2762 } else if (m_hopStickX != -1) {
2763 m_mtCharge += EXTRA_MT_CHARGE;
2764 }
2765
2766 if (m_mtCharge > MAX_MT_CHARGE) {
2767 m_mtCharge = MAX_MT_CHARGE;
2768 m_driftState = DriftState::ChargedMt;
2769 }
2770}
2771
2773void KartMoveBike::initOob() {
2774 KartMove::initOob();
2775 cancelWheelie();
2776}
2777
2781 constexpr s16 COOLDOWN_FRAMES = 20;
2782 bool dpadUp = inputs()->currentState().trickUp();
2783 auto &status = KartObjectProxy::status();
2784
2785 if (status.offBit(eStatus::Wheelie)) {
2786 if (dpadUp && status.onBit(eStatus::TouchingGround)) {
2788 eStatus::Hop, eStatus::DriftAuto, eStatus::InAction)) {
2789 return;
2790 }
2791
2792 if (m_wheelieCooldown > 0) {
2793 return;
2794 }
2795
2796 startWheelie();
2797 }
2798 } else if (inputs()->currentState().trickDown() && m_wheelieCooldown <= 0) {
2799 cancelWheelie();
2800 m_wheelieCooldown = COOLDOWN_FRAMES;
2801 }
2802}
2803
2804} // 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 bool offAnyBit(Es... es) const
Checks if any of the corresponding bits for the provided enum values are off.
Definition BitFlag.hh:436
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 bool onAllBit(Es... es) const
Checks if all of the corresponding bits for the provided enum values are on.
Definition BitFlag.hh:403
constexpr TBitFlagExt< N, E > & resetBit(Es... es)
Resets the corresponding bits for the provided enum values.
Definition BitFlag.hh:357
bool start(Action action)
Starts an action.
Definition KartAction.cc:49
bool activate(Type type, s16 frames)
Starts/restarts a boost of the given type.
Definition KartBoost.cc:21
bool calc()
Computes the current frame's boost multiplier, acceleration, and speed limit.
Definition KartBoost.cc:38
void applyWrenchScaled(const EGG::Vector3f &p, const EGG::Vector3f &f, f32 scale)
Applies a force linearly and rotationally to the kart.
Handles the physics and boosts associated with zippers.
f32 m_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:2512
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:2622
virtual void cancelWheelie()
Clears the wheelie bit flag and resets the rotation decrement.
Definition KartMove.cc:2496
void tryStartWheelie()
STAGE 1+ - Every frame, checks player input to see if we should start or stop a wheelie.
Definition KartMove.cc:2780
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:2738
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:2667
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:2481
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:2745
void onHop() override
Virtual function that just cancels wheelies when you hop.
Definition KartMove.cc:2728
void releaseMt()
Stops charging a mini-turbo, and applies boost if charged.
Definition KartMove.cc:1132
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:674
void controlOutsideDriftAngle()
Every frame, handles mini-turbo charging and outside drifting bike rotation.
Definition KartMove.cc:1158
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:70
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:1720
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:1580
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:881
void calcSsmt()
Calculates standstill mini-turbo components, if applicable.
Definition KartMove.cc:782
void calcAcceleration()
Every frame, applies acceleration to the kart's internal velocity.
Definition KartMove.cc:1428
void startManualDrift()
Called when the player lands from a drift hop, or to start a slipdrift.
Definition KartMove.cc:1090
void setOffroadInvincibility(s16 timer)
Ignores offroad KCL collision for a set amount of time.
Definition KartMove.cc:2168
f32 calcVehicleAcceleration() const
Every frame, computes acceleration based off the character/vehicle stats.
Definition KartMove.cc:1394
void calcRotation()
Every frame, calculates kart rotation based on player input.
Definition KartMove.cc:1191
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:1288
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:765
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:839
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:953
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:2179
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:1680
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:2019
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:290
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:516
void calcAirtimeTop()
Calculates rotation of the bike due to excessive airtime.
Definition KartMove.cc:491
void calcMushroomBoost()
Checks a timer to see if we are still boosting from a mushroom.
Definition KartMove.cc:2195
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:252
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.
void calcSsmtStart()
Calculates whether we are starting a standstill mini-turbo.
Definition KartMove.cc:1781
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:1896
virtual void calcVehicleRotation(f32 turn)
Every frame, calculates rotation, EV, and angular velocity for the kart.
Definition KartMove.cc:1847
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:1613
virtual void hop()
Initializes hop information, resets upwards EV and clears upwards force.
Definition KartMove.cc:1961
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:2001
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.
@ SsmtCharged
Set after holding a stand-still mini-turbo for 75 frames.
@ TrickableSurface
Set when driving on a trickable surface.
@ SsmtLeeway
If set, activates SSMT when not pressing A or B.
@ WallBounce
Set when our speed loss from wall collision is > 30.0f.
@ Respawned
Set when Lakitu lets go of the player, cleared when landing.
void calcManualDrift()
Each frame, handles hopping, drifting, and mini-turbos.
Definition KartMove.cc:1008
EGG::Vector3f bodyUp() const
Returns the second column of the rotation matrix, which is the "up" direction.
EGG::Vector3f bodyFront() const
Returns the third column of the rotation matrix, which is the facing vector.
Mainly responsible for calculating scaling for the squish/unsquish animation.
Definition KartScale.hh:9
Pertains to kart-related functionality.
@ Wheelie
Set while we are in a wheelie (even during the countdown).
@ HopStart
Set if m_bDriftInput was toggled on this frame.
@ RejectRoad
Collision which causes a change in the player's pos and rot.
@ DisableBackwardsAccel
Enforces a 20f delay when reversing after charging SSMT.
@ HalfPipeRamp
Set while colliding with zipper KCL.
@ Boost
Set while in a boost.
@ BoostOffroadInvincibility
Set if we should ignore offroad slowdown this frame.
@ HWG
Set when "Horizontal Wall Glitch" is active.
@ ZipperTrick
Set while tricking mid-air from a zipper.
@ SlipdriftCharge
Currently in a drift w/ automatic.
@ Hop
Set while we are in a drift hop. Clears when we land.
@ MushroomBoost
Set while we are in a mushroom boost.
@ StickyRoad
Like the rBC stairs.
@ StickLeft
Set on left stick input. Mutually exclusive to m_bStickRight.
@ RespawnKillY
Set while respawning to cap external velocity at 0.
@ VehicleBodyFloorCollision
Set if the vehicle body is colliding with the floor.
@ RejectRoadTrigger
e.g. DK Summit ending, and Maple Treeway side walls.
@ GroundStart
Set first frame landing from airtime.
@ Accelerate
Accel button is pressed.
@ StickRight
Set on right stick input. Mutually exclusive to m_bStickLeft.
@ ActionMidZipper
Set when we enter an action while mid-air from a zipper.
@ DriftInput
A "fake" button, normally set if you meet the speed requirement to hop.
@ AirtimeOver20
Set after 20 frames of airtime, resets on landing.
@ ZipperBoost
Set when boosting after landing from a zipper.
@ DriftManual
Currently in a drift w/ manual.
@ OverZipper
Set while mid-air from a zipper.
@ ChargingSSMT
Tracks whether we are charging a stand-still mini-turbo.
@ AccelerateStart
Set if m_bAccelerate was toggled on this frame.
@ Burnout
Set during a burnout on race start.
@ WallCollisionStart
Set if we have just started colliding with a wall.
@ AnyWheelCollision
Set when any wheel is touching floor collision.
@ AutoDrift
True if auto transmission, false if manual.
@ Wall3Collision
Set when colliding with wall KCL COL_TYPE_WALL_2.
@ TouchingGround
Set when any part of the vehicle is colliding with floor KCL.
@ ZipperInvisibleWall
Set when colliding with invisible wall above a zipper.
@ BeforeRespawn
Set on respawn collision, cleared on position snap.
@ WallCollision
Set if we are colliding with a wall.
@ BikeSideStuntTrick
A side StuntTrick with a bike.
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 normalise()
Scales the quaternion to a unit length.
Definition Quat.hh:104
constexpr void setAxisRotation(f32 angle, const EGG::Vector3f &axis)
Set the quat given angle and axis.
Definition Quat.hh:208
constexpr void makeAllZero()
Resets all the bits to zero.
Definition BitFlag.hh:236
constexpr TBitFlag< T, E > & resetBit(Es... es)
Resets the corresponding bits for the provided enum values.
Definition BitFlag.hh:75
constexpr bool offBit(Es... es) const
Checks if all of the corresponding bits for the provided enum values are off.
Definition BitFlag.hh:143
constexpr TBitFlag< T, E > & changeBit(bool on, Es... es)
Changes the state of the corresponding bits for the provided enum values.
Definition BitFlag.hh:87
constexpr TBitFlag< T, E > & setBit(Es... es)
Sets the corresponding bits for the provided enum values.
Definition BitFlag.hh:64
constexpr bool onBit(Es... es) const
Checks if any of the corresponding bits for the provided enum values are on.
Definition BitFlag.hh:110
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 Vector3f proj(const Vector3f &rhs) const
The projection of this vector onto rhs.
Definition Vector.hh:223
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
std::array< f32, 32 > kclSpeed
Speed multipliers, indexed using KCL attributes.
Definition KartParam.hh:124
f32 speed
Base full speed of the character/vehicle combo.
Definition KartParam.hh:108
f32 driftReactivity
A weight applied to turn radius when drifting.
Definition KartParam.hh:120
std::array< f32, 32 > kclRot
Rotation scalars, indexed using KCL attributes.
Definition KartParam.hh:125
f32 driftManualTightness
Affects turn radius when manual drifting.
Definition KartParam.hh:118
u32 miniTurbo
The framecount duration of a charged mini-turbo.
Definition KartParam.hh:123
f32 driftAutomaticTightness
Affects turn radius when automatic drifting.
Definition KartParam.hh:119
f32 handlingReactivity
A weight applied to turn radius when not drifting.
Definition KartParam.hh:117