1#include "ObjectWanwan.hh"
3#include "game/field/CollisionDirector.hh"
4#include "game/field/ObjectDirector.hh"
6#include "game/kart/KartObject.hh"
7#include "game/kart/Reaction.hh"
9#include "game/system/RaceConfig.hh"
11#include "game/system/RaceManager.hh"
16ObjectWanwan::ObjectWanwan(
const System::MapdataGeoObj ¶ms)
18 m_chainLength(static_cast<f32>(params.setting(0))),
19 m_attackDistance(4800.0f + static_cast<f32>(params.setting(2))),
20 m_attackArcTargetX(10.0f * static_cast<f32>(static_cast<s16>(params.setting(3)))),
21 m_attackArcTargetZ(10.0f * static_cast<f32>(static_cast<s16>(params.setting(4)))),
22 m_chainAttachMat(EGG::Matrix34f::ident) {
23 constexpr EGG::Vector3f ANCHOR_OFFSET = EGG::Vector3f(0.0f, 20.0f, 0.0f);
24 constexpr EGG::Vector3f POS_OFFSET_MCWII = EGG::Vector3f(14500.0f, 1300.0f, 44850.0f);
25 constexpr EGG::Vector3f POS_OFFSET_RMC = EGG::Vector3f(8012.0f, 1668.0f, -30150.0f);
27 m_idleDuration =
static_cast<u32
>(params.setting(5));
28 m_attackArc =
static_cast<f32
>(params.setting(6));
30 if (m_idleDuration == 0) {
34 if (m_attackArc == 0.0f) {
38 auto *pile = EGG::egg_new<ObjectWanwanPile>(pos(), rot(), scale());
41 m_anchor = pos() + ANCHOR_OFFSET;
45 f32 sqLen = m_chainLength * m_chainLength + 300.0f * (300.0f * scale().y) * scale().y;
46 m_chainCount =
static_cast<u32
>(EGG::Mathf::sqrt(sqLen) / (CHAIN_LENGTH * scale().y));
47 if (m_chainCount > 0) {
51 m_initPos = m_anchor + EGG::Vector3f(0.5f * -m_chainLength, 600.0f, 0.5f * -m_chainLength);
53 auto course = System::RaceConfig::Instance()->raceScenario().course;
54 if (course == Course::Mario_Circuit) {
55 EGG::Vector3f pos = POS_OFFSET_MCWII - m_anchor;
58 m_attackArcCenter = pos * m_chainLength + m_anchor;
59 m_attackArcCenter.y = POS_OFFSET_MCWII.y;
60 }
else if (course == Course::GCN_Mario_Circuit) {
61 EGG::Vector3f pos = POS_OFFSET_RMC - m_anchor;
64 m_attackArcCenter = pos * m_chainLength + m_anchor;
65 m_attackArcCenter.y = POS_OFFSET_RMC.y;
67 m_attackArcCenter = m_anchor;
68 m_attackArcCenter.z += m_chainLength;
71 initTransformKeyframes();
75ObjectWanwan::~ObjectWanwan() =
default;
78void ObjectWanwan::init() {
80 m_chainAttachPos = m_initPos;
84 m_tangent = EGG::Vector3f::ex;
85 m_up = EGG::Vector3f::ey;
86 m_targetUp = EGG::Vector3f::ey;
87 m_touchingFloor =
false;
91 m_targetDir = EGG::Vector3f::ez;
94 m_attackStill =
false;
95 m_backDir = EGG::Vector3f::ex;
100void ObjectWanwan::calc() {
101 StateManager::calc();
107 calcChainAttachMat();
112 if (pos().y < m_anchor.y - 1000.0f) {
113 setPos(EGG::Vector3f(pos().x, m_anchor.y + 1000.0f, pos().z));
119Kart::Reaction ObjectWanwan::onCollision(Kart::KartObject *kartObj, Kart::Reaction reactionOnKart,
120 Kart::Reaction , EGG::Vector3f & ) {
121 if (m_currentStateId == 1 && !m_attackStill) {
122 return reactionOnKart;
125 return kartObj->speedRatioCapped() < 0.5f ? Kart::Reaction::WallAllSpeed : reactionOnKart;
129void ObjectWanwan::enterWait() {
130 constexpr f32 ANGLE_RANGE = 0.33f * 60.0f;
131 constexpr f32 ANGLE_NORMALIZATION = 0.66f * 60.0f;
140 System::RaceManager::Instance()->random().getF32(ANGLE_RANGE) + ANGLE_NORMALIZATION;
142 if (CrossXZ(pos() + m_tangent, pos(), m_anchor) >= 0.0f) {
146 EGG::Vector3f vStack_40 = m_anchor - EGG::Vector3f(pos().x, m_anchor.y, pos().z);
147 vStack_40.normalise2();
149 EGG::Vector3f vStack_4c = RotateXZByYaw(DEG2RAD * randAngle, vStack_40);
150 f32 fVar2 = m_chainLength < 3000.0f ? 0.5f : 0.7f;
151 m_target = vStack_4c * m_chainLength * fVar2 + m_anchor;
155void ObjectWanwan::enterAttack() {
163 m_attackStill =
false;
171void ObjectWanwan::enterBack() {
177 m_pitch = -ObjectDirector::Instance()->WanwanMaxPitch();
178 m_backDir = m_anchor - pos();
180 m_backDir.normalise2();
181 m_attackStill =
false;
187void ObjectWanwan::calcWait() {
188 constexpr f32 ANGLE_RANGE = 0.33f * 0.5f * 60.0f;
189 constexpr f32 ANGLE_NORMALIZATION = 0.66f * 0.5f * 60.0f;
191 EGG::Vector3f targetDir = m_target - pos();
195 f32 distFromTarget = F_PI;
196 if (targetDir.squaredLength() > std::numeric_limits<f32>::epsilon()) {
197 distFromTarget = targetDir.normalise();
200 if (!m_retarget && distFromTarget < 0.55f * m_chainLength) {
201 EGG::Vector3f relTarget = m_target - m_anchor;
202 auto &rand = System::RaceManager::Instance()->random();
203 f32 angle = rand.getF32(ANGLE_RANGE) + ANGLE_NORMALIZATION;
204 if (CrossXZ(m_target, m_attackArcCenter, m_anchor) >= 0.0f) {
208 m_target = RotateXZByYaw(angle * DEG2RAD, relTarget) + m_anchor;
212 m_targetDir = targetDir;
224void ObjectWanwan::calcAttack() {
225 constexpr u32 ATTACK_DURATION = 120;
226 constexpr f32 PITCH_STEP = 25.0f;
228 if (m_currentFrame > ATTACK_DURATION) {
232 f32 maxPitch = ObjectDirector::Instance()->WanwanMaxPitch();
233 if (EGG::Mathf::abs(m_pitch) < EGG::Mathf::abs(maxPitch)) {
234 m_pitch -= maxPitch / PITCH_STEP;
237 calcTangent(10.0f * 0.04f);
240 if (m_chainTaut || m_attackStill) {
241 if (!m_attackStill) {
242 m_target = m_anchor + (m_chainAttachPos - m_anchor) * 2.0f;
243 m_targetDir = m_target - pos();
244 m_targetDir.y = 0.0f;
245 m_targetDir.normalise2();
246 EGG::Vector3f posOffset = pos() - m_chainAttachPos;
248 f32 radius = posOffset.length();
249 EGG::Vector3f chainDir = m_chainAttachPos - m_anchor;
251 chainDir.normalise2();
252 EGG::Vector3f nextPos = m_chainAttachPos + chainDir * radius;
253 setPos(EGG::Vector3f(nextPos.x, pos().y, nextPos.z));
254 EGG::Vector3f tangent = m_tangent + chainDir;
256 if (tangent.squaredLength() > std::numeric_limits<f32>::epsilon()) {
257 tangent.normalise2();
263 m_attackStill =
true;
267 m_vel.x = m_tangent.x * 120.0f;
268 m_vel.z = m_tangent.z * 120.0f;
273void ObjectWanwan::calcBack() {
274 if (EGG::Mathf::abs(m_pitch) > 2.0f) {
275 m_pitch += ObjectDirector::Instance()->WanwanMaxPitch() / 15.0f;
278 m_vel.x = m_backDir.x * m_speed * 1.5f;
279 m_vel.z = m_backDir.z * m_speed * 1.5f;
281 if (m_touchingFloor) {
283 m_accel += EGG::Vector3f::ey * 12.0f;
288 if (m_currentFrame > 90) {
294void ObjectWanwan::calcPos() {
295 m_vel += m_accel - GRAVITY;
301void ObjectWanwan::calcCollision() {
302 constexpr EGG::Vector3f POS_OFFSET = EGG::Vector3f(0.0f, -570.0f, 0.0f);
304 m_touchingFloor =
false;
307 EGG::Vector3f colPos = pos() + POS_OFFSET;
308 auto *colDir = CollisionDirector::Instance();
310 bool hasCol = colDir->checkSphereFullPush(30.0f, colPos, EGG::Vector3f::inf,
KCL_TYPE_FLOOR,
317 m_touchingFloor =
true;
318 EGG::Vector3f local_84 = info.tangentOff;
319 f32 scale = local_84.normalise();
320 addPos(EGG::Vector3f::ey * scale);
322 if (info.floorDist > -std::numeric_limits<f32>::min()) {
323 m_targetUp = info.floorNrm;
330void ObjectWanwan::calcMat() {
332 if (m_currentStateId == 1) {
333 SetRotTangentHorizontal(mat, EGG::Vector3f::ey, m_tangent);
335 SetRotTangentHorizontal(mat, m_up, m_tangent);
337 mat.setBase(3, EGG::Vector3f::zero);
339 EGG::Vector3f rot = EGG::Vector3f(m_pitch * DEG2RAD, 0.0f, 0.0f);
340 EGG::Matrix34f rtMat;
341 rtMat.makeRT(rot, EGG::Vector3f::zero);
342 mat = mat.multiplyTo(rtMat);
343 mat.setBase(3, pos());
348void ObjectWanwan::calcChainAttachMat() {
349 u32 idx = m_currentStateId == 1 ? 0 : m_frame % 15;
353 EGG::Matrix34f mat = transform();
354 mat.setBase(3, EGG::Vector3f::zero);
355 EGG::Vector3f keyFramePos = m_transformKeyframes[idx].base(3);
356 EGG::Vector3f posOffset = mat.ps_multVector(keyFramePos) * 2.0f;
357 mat.setBase(3, posOffset + pos());
359 m_chainAttachMat = mat;
363void ObjectWanwan::calcSpeed() {
364 if (m_speed < 8.0f) {
366 m_accel.x = m_tangent.x * 0.5f;
367 m_accel.z = m_tangent.z * 0.5f;
372 m_vel.x = m_tangent.x * 8.0f;
373 m_vel.z = m_tangent.z * 8.0f;
378void ObjectWanwan::calcBounce() {
379 if (m_touchingFloor) {
381 m_accel += EGG::Vector3f::ey * 12.0f;
388void ObjectWanwan::calcTangent(f32 t) {
389 m_tangent = Interpolate(t, m_tangent, m_targetDir);
390 if (m_tangent.squaredLength() > std::numeric_limits<f32>::epsilon()) {
391 m_tangent.normalise2();
393 m_tangent = m_targetDir;
398void ObjectWanwan::calcUp(f32 t) {
399 m_up = Interpolate(t, m_up, m_targetUp);
400 if (m_up.squaredLength() > std::numeric_limits<f32>::epsilon()) {
403 m_up = EGG::Vector3f::ey;
408void ObjectWanwan::calcRandomTarget() {
409 f32 angle = System::RaceManager::Instance()->random().getF32(m_attackArc * 2.0f);
410 EGG::Vector3f attackArcTarget = EGG::Vector3f(m_attackArcTargetX, 0.0f, m_attackArcTargetZ);
411 EGG::Vector3f attackArcDir = attackArcTarget - m_anchor;
412 attackArcDir.y = 0.0f;
413 attackArcDir.normalise2();
414 EGG::Vector3f attackTargetDir = RotateXZByYaw((angle - m_attackArc) * DEG2RAD, attackArcDir);
415 m_target = m_anchor + attackTargetDir * m_attackDistance;
416 m_targetDir = m_target - pos();
417 m_targetDir.y = 0.0f;
418 m_targetDir.normalise2();
422void ObjectWanwan::initTransformKeyframes() {
423 std::array<f32, 15> xRot;
424 SampleHermiteInterp(0.0f, 10.0f, 2.0f, -1.111111f, std::span(xRot.begin(), 6));
425 SampleHermiteInterp(10.0f, -10.0f, -1.111111f, -1.111111f, std::span(xRot.begin() + 5, 5));
426 SampleHermiteInterp(-10.0f, 0.0f, -1.111111f, 2.0f, std::span(xRot.begin() + 9, 6));
428 std::array<f32, 15> yPos;
429 SampleHermiteInterp(0.0f, -27.35f, -9.1166658f, 3.75f, std::span(yPos.begin(), 4));
430 SampleHermiteInterp(-27.35f, 33.75f, 3.75f, 2.4863639f, std::span(yPos.begin() + 3, 7));
431 SampleHermiteInterp(33.75f, 0.0f, 2.4863639f, -6.75f, std::span(yPos.begin() + 9, 6));
433 std::array<f32, 15> zPos;
434 SampleHermiteInterp(0.0f, -33.75f, -6.75f, -4.8214278f, std::span(zPos.begin(), 6));
435 SampleHermiteInterp(-33.75f, -33.75f, -4.8214278f, 8.4375f, std::span(zPos.begin() + 5, 3));
436 SampleHermiteInterp(-33.75f, 0.0f, 8.4375f, 14.464999f, std::span(zPos.begin() + 7, 3));
437 SampleHermiteInterp(0.0f, 24.11f, 14.464999f, 0.0f, std::span(zPos.begin() + 9, 3));
438 SampleHermiteInterp(24.11f, 0.0f, 0.0f, -8.0366669f, std::span(zPos.begin() + 11, 4));
440 for (u8 i = 0; i < m_transformKeyframes.size(); ++i) {
442 mat.makeR(EGG::Vector3f(xRot[i] * DEG2RAD, 0.0f, 0.0f));
443 mat.setBase(3, EGG::Vector3f(0.0f, yPos[i], zPos[i]));
444 m_transformKeyframes[i] = mat;
449void ObjectWanwan::calcAttackPos() {
450 constexpr f32 SCALED_CHAIN_LENGTH = CHAIN_LENGTH * SCALE;
452 if (m_currentStateId != 1) {
458 calcChainAttachPos(transform());
460 EGG::Vector3f dir = m_chainAttachPos - m_anchor;
461 f32 dist = dir.normalise();
462 dist -= SCALED_CHAIN_LENGTH *
static_cast<f32
>(m_chainCount);
464 if (dist > 0.0f || m_attackStill) {
471void ObjectWanwan::calcChainAttachPos(EGG::Matrix34f mat) {
472 EGG::Vector3f pos = mat.base(3);
473 pos -= mat.base(2) * 250.0f * scale().x;
476 EGG::Vector3f backOffset = mat.base(2) * 140.0f * scale().x;
477 EGG::Vector3f verticalOffset = mat.base(1) * 20.0f * scale().x;
478 m_chainAttachPos = pos - backOffset + verticalOffset;
482void ObjectWanwan::SampleHermiteInterp(f32 start, f32 end, f32 startTangent, f32 endTangent,
483 std::span<f32> dst) {
487 f32 scalar = 1.0f /
static_cast<f32
>(dst.size() - 1);
489 for (u8 i = 1; i < dst.size() - 1; ++i) {
490 dst[i] = EGG::Mathf::Hermite(start, startTangent, end, endTangent,
491 scalar *
static_cast<f32
>(i));
#define KCL_TYPE_FLOOR
0x20E80FFF - Any KCL that the player or items can drive/land on.
Base class that represents different "states" for an object.