#include "GameGlobalObjects.hpp" #include "CDependencyGroup.hpp" #include "CMorphBall.hpp" #include "CPlayer.hpp" #include "CSimplePool.hpp" #include "CGameLight.hpp" #include "World/CWorld.hpp" #include "World/CScriptAreaAttributes.hpp" #include "TCastTo.hpp" namespace urde { static float kSpiderBallCollisionRadius; const u8 CMorphBall::BallTransFlashColors[9][3] = { {0xc2, 0x7e, 0x10}, {0x66, 0xc4, 0xff}, {0x60, 0xff, 0x90}, {0x33, 0x33, 0xff}, {0xff, 0x20, 0x20}, {0x0, 0x9d, 0xb6}, {0xd3, 0xf1, 0x0}, {0xa6, 0x86, 0xd8}, {0xfb, 0x98, 0x21} }; CMorphBall::CMorphBall(CPlayer& player, float radius) : x0_player(player), xc_radius(radius), x38_collisionSphere({{0.f, 0.f, radius}, radius}, {EMaterialTypes::Player, EMaterialTypes::Solid, EMaterialTypes::GroundCollider}), x58_ballModel(GetMorphBallModel("SamusBallANCS", radius)), x60_spiderBallGlassModel(GetMorphBallModel("SamusSpiderBallGlassCMDL", radius)), x68_lowPolyBallModel(GetMorphBallModel("SamusBallLowPolyCMDL", radius)), x70_frozenBallModel(GetMorphBallModel("SamusBallFrozenCMDL", radius)), x1968_slowBlueTailSwoosh(g_SimplePool->GetObj("SlowBlueTailSwoosh")), x1970_slowBlueTailSwoosh2(g_SimplePool->GetObj("SlowBlueTailSwoosh2")), x1978_jaggyTrail(g_SimplePool->GetObj("JaggyTrail")), x1980_wallSpark(g_SimplePool->GetObj("WallSpark")), x1988_ballInnerGlow(g_SimplePool->GetObj("BallInnerGlow")), x1990_spiderBallMagnetEffect(g_SimplePool->GetObj("SpiderBallMagnetEffect")), x1998_boostBallGlow(g_SimplePool->GetObj("BoostBallGlow")), x19a0_spiderElectric(g_SimplePool->GetObj("SpiderElectric")), x19a8_morphBallTransitionFlash(g_SimplePool->GetObj("MorphBallTransitionFlash")), x19b0_effect_morphBallIceBreak(g_SimplePool->GetObj("Effect_MorphBallIceBreak")) { x19b8_slowBlueTailSwooshGen = std::make_unique(x1968_slowBlueTailSwoosh, 0); x19bc_slowBlueTailSwooshGen2 = std::make_unique(x1968_slowBlueTailSwoosh, 0); x19c0_slowBlueTailSwoosh2Gen = std::make_unique(x1970_slowBlueTailSwoosh2, 0); x19c4_slowBlueTailSwoosh2Gen2 = std::make_unique(x1970_slowBlueTailSwoosh2, 0); x19c8_jaggyTrailGen = std::make_unique(x1978_jaggyTrail, 0); x19cc_wallSparkGen = std::make_unique(x1980_wallSpark); x19d0_ballInnerGlowGen = std::make_unique(x1988_ballInnerGlow); x19d4_spiderBallMagnetEffectGen = std::make_unique(x1990_spiderBallMagnetEffect); x19d8_boostBallGlowGen = std::make_unique(x1998_boostBallGlow); x1c14_worldShadow = std::make_unique(16, 16, false); x1c18_actorLights = std::make_unique(8, zeus::CVector3f::skZero, 4, 4, false, false, false, 0.1f); x1c1c_rainSplashGen = std::make_unique(x58_ballModel->GetScale(), 40, 2, 0.15f, 0.5f); x1de4_24 = false; x1de4_25 = true; x1df8_24_inHalfPipeMode = false; x1df8_25_inHalfPipeModeInAir = false; x1df8_26_touchedHalfPipeRecently = false; x1df8_27 = false; x19d4_spiderBallMagnetEffectGen->SetParticleEmission(false); x19d4_spiderBallMagnetEffectGen->Update(1.0 / 60.0); kSpiderBallCollisionRadius = GetBallRadius() + 0.2f; for (int i=0 ; i<32 ; ++i) x19e4_spiderElectricGens.emplace_back(std::make_unique(x19a0_spiderElectric, 0), false); LoadAnimationTokens("SamusBallANCS"); InitializeWakeEffects(); } void CMorphBall::LoadAnimationTokens(const std::string& ancsName) { TToken dgrp = g_SimplePool->GetObj((ancsName + "_DGRP").c_str()); x1958_animationTokens.clear(); x1958_animationTokens.reserve(dgrp->GetObjectTagVector().size()); for (const SObjectTag& tag : dgrp->GetObjectTagVector()) { if (tag.type == FOURCC('CMDL') || tag.type == FOURCC('CSKR') || tag.type == FOURCC('TXTR')) continue; x1958_animationTokens.push_back(g_SimplePool->GetObj(tag)); x1958_animationTokens.back().Lock(); } } /* Maps material index to effect in generator array */ static const s32 skWakeEffectMap[32] = { -1, -1, -1, -1, -1, -1, -1, 0, // Phazon 2, // Dirt 3, // Lava -1, 4, // Snow 5, // MudSlow -1, -1, -1, -1, 6, // Sand -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; void CMorphBall::InitializeWakeEffects() { TToken nullParticle = CToken(TObjOwnerDerivedFromIObj::GetNewDerivedObject(std::make_unique())); for (int i=0 ; i<8 ; ++i) x1b84_wakeEffects.push_back(nullParticle); x1b84_wakeEffects[2] = g_SimplePool->GetObj("DirtWake"); x1b84_wakeEffects[0] = g_SimplePool->GetObj("PhazonWake"); x1b84_wakeEffects[1] = g_SimplePool->GetObj("PhazonWakeOrange"); x1b84_wakeEffects[3] = g_SimplePool->GetObj("LavaWake"); x1b84_wakeEffects[4] = g_SimplePool->GetObj("SnowWake"); x1b84_wakeEffects[5] = g_SimplePool->GetObj("MudWake"); x1b84_wakeEffects[6] = g_SimplePool->GetObj("SandWake"); x1b84_wakeEffects[7] = g_SimplePool->GetObj("RainWake"); x1bc8_wakeEffectGens.resize(8); x1bc8_wakeEffectGens[2] = std::make_unique(x1b84_wakeEffects[2]); x1bc8_wakeEffectGens[0] = std::make_unique(x1b84_wakeEffects[0]); x1bc8_wakeEffectGens[1] = std::make_unique(x1b84_wakeEffects[1]); x1bc8_wakeEffectGens[3] = std::make_unique(x1b84_wakeEffects[3]); x1bc8_wakeEffectGens[4] = std::make_unique(x1b84_wakeEffects[4]); x1bc8_wakeEffectGens[5] = std::make_unique(x1b84_wakeEffects[5]); x1bc8_wakeEffectGens[6] = std::make_unique(x1b84_wakeEffects[6]); x1bc8_wakeEffectGens[7] = std::make_unique(x1b84_wakeEffects[7]); } std::unique_ptr CMorphBall::GetMorphBallModel(const char* name, float radius) { const SObjectTag* tag = g_ResFactory->GetResourceIdByName(name); if (tag->type == FOURCC('CMDL')) return std::make_unique(CStaticRes(tag->id, zeus::CVector3f(radius * 2.f))); else return std::make_unique(CAnimRes(tag->id, 0, zeus::CVector3f(radius * 2.f), 0, false)); } void CMorphBall::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) { switch (msg) { case EScriptObjectMessage::Registered: if (x19d0_ballInnerGlowGen && x19d0_ballInnerGlowGen->SystemHasLight()) { x1c10_ballInnerGlowLight = mgr.AllocateUniqueId(); CGameLight* l = new CGameLight(x1c10_ballInnerGlowLight, kInvalidAreaId, false, "BallLight", GetBallToWorld(), x0_player.GetUniqueId(), x19d0_ballInnerGlowGen->GetLight(), u32(reinterpret_cast(x1988_ballInnerGlow.GetObj())), 0, 0.f); mgr.AddObject(l); } break; case EScriptObjectMessage::Deleted: DeleteLight(mgr); break; default: break; } } void CMorphBall::DrawBallShadow(const CStateManager& mgr) { if (!x1e50_shadow) return; float alpha = 1.f; switch (x0_player.x2f8_morphBallState) { case CPlayer::EPlayerMorphBallState::Unmorphed: return; case CPlayer::EPlayerMorphBallState::Unmorphing: alpha = 0.f; if (x0_player.x578_morphDuration != 0.f) alpha = zeus::clamp(0.f, x0_player.x574_morphTime / x0_player.x578_morphDuration, 1.f); alpha = 1.f - alpha; break; case CPlayer::EPlayerMorphBallState::Morphing: alpha = 0.f; if (x0_player.x578_morphDuration != 0.f) alpha = zeus::clamp(0.f, x0_player.x574_morphTime / x0_player.x578_morphDuration, 1.f); break; default: break; } x1e50_shadow->Render(mgr, alpha); } void CMorphBall::DeleteBallShadow() { x1e50_shadow.reset(); } void CMorphBall::CreateBallShadow() { x1e50_shadow = std::make_unique(); } void CMorphBall::RenderToShadowTex(CStateManager& mgr) { if (x1e50_shadow) { zeus::CVector3f center = x0_player.GetPrimitiveOffset() + x0_player.GetTranslation() + zeus::CVector3f(0.f, 0.f, xc_radius); zeus::CAABox aabb(center - zeus::CVector3f(1.25f * xc_radius, 1.25f * xc_radius, 10.f), center + zeus::CVector3f(1.25f * xc_radius, 1.25f * xc_radius, xc_radius)); x1e50_shadow->RenderIdBuffer(aabb, mgr, x0_player); } } static const u16 skBallRollSfx[] = { 0xFFFF, 0x05DE, 0x05DD, 0x062F, 0x0786, 0xFFFF, 0x05DC, 0x060B, 0x05C8, 0x088A, 0x0698, 0x0787, 0x0630, 0xFFFF, 0x0628, 0x05DD, 0x05DD, 0x05C8, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x05FE, 0x0628 }; static const u16 skBallLandSfx[] = { 0xFFFF, 0x05C3, 0x05E0, 0x062C, 0x065B, 0xFFFF, 0x05DA, 0x0609, 0x05C0, 0x0697, 0x0697, 0x065C, 0x062D, 0xFFFF, 0x0627, 0x05E0, 0x05E0, 0x05C0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x05FD, 0x0627 }; void CMorphBall::SelectMorphBallSounds(const CMaterialList& mat) { u16 rollSfx; if (x0_player.x9c5_30_selectFluidBallSound) { if (x0_player.x82c_inLava) rollSfx = 2186; else rollSfx = 1481; } else { rollSfx = CPlayer::SfxIdFromMaterial(mat, skBallRollSfx, 24, 0xffff); } x0_player.x9c5_30_selectFluidBallSound = false; if (rollSfx != 0xffff) { if (x1e34_rollSfx != rollSfx && x1e2c_rollSfxHandle) { CSfxManager::SfxStop(x1e2c_rollSfxHandle); x1e2c_rollSfxHandle.reset(); } x1e34_rollSfx = rollSfx; } x1e36_landSfx = CPlayer::SfxIdFromMaterial(mat, skBallLandSfx, 24, 0xffff); } void CMorphBall::UpdateMorphBallSounds(float dt) { zeus::CVector3f velocity = x0_player.GetVelocity(); if (x187c_spiderBallState != ESpiderBallState::Active) velocity.z = 0.f; switch (x0_player.GetPlayerMovementState()) { case CPlayer::EPlayerMovementState::OnGround: case CPlayer::EPlayerMovementState::FallingMorphed: { float vel = velocity.magnitude(); if (x187c_spiderBallState == ESpiderBallState::Active) vel += g_tweakBall->GetBallGravity() * dt * 4.f; if (vel > 0.8f) { if (!x1e2c_rollSfxHandle) { if (x1e34_rollSfx != 0xffff) { x1e2c_rollSfxHandle = CSfxManager::AddEmitter(x1e34_rollSfx, x0_player.GetTranslation(), zeus::CVector3f::skZero, true, true, 0x7f, kInvalidAreaId); } x0_player.ApplySubmergedPitchBend(x1e2c_rollSfxHandle); } CSfxManager::PitchBend(x1e2c_rollSfxHandle, zeus::clamp(-1.f, vel * 0.122f - 0.831f, 1.f)); float maxVol = zeus::clamp(0.f, 0.025f * vel + 0.5f, 1.f); CSfxManager::UpdateEmitter(x1e2c_rollSfxHandle, x0_player.GetTranslation(), zeus::CVector3f::skZero, maxVol); break; } } default: if (x1e2c_rollSfxHandle) { CSfxManager::SfxStop(x1e2c_rollSfxHandle); x1e2c_rollSfxHandle.reset(); } break; } if (x187c_spiderBallState == ESpiderBallState::Active) { if (!x1e30_spiderSfxHandle) { x1e30_spiderSfxHandle = CSfxManager::AddEmitter(1523, x0_player.GetTranslation(), zeus::CVector3f::skZero, true, true, 0xc8, kInvalidAreaId); x0_player.ApplySubmergedPitchBend(x1e30_spiderSfxHandle); } CSfxManager::UpdateEmitter(x1e30_spiderSfxHandle, x0_player.GetTranslation(), zeus::CVector3f::skZero, 1.f); } else if (x1e30_spiderSfxHandle) { CSfxManager::SfxStop(x1e30_spiderSfxHandle); x1e30_spiderSfxHandle.reset(); } } float CMorphBall::GetBallRadius() const { return g_tweakPlayer->GetPlayerBallHalfExtent(); } float CMorphBall::GetBallTouchRadius() const { return g_tweakBall->GetBallTouchRadius(); } void CMorphBall::ForwardInput(const CFinalInput&) const { } void CMorphBall::BallTurnInput(const CFinalInput&) const { } void CMorphBall::ComputeBallMovement(const CFinalInput&, CStateManager&, float) { } bool CMorphBall::IsMovementAllowed() const { return false; } void CMorphBall::UpdateSpiderBall(const CFinalInput&, CStateManager&, float) { } void CMorphBall::ApplySpiderBallSwingingForces(const CFinalInput&, CStateManager&, float) { } void CMorphBall::ApplySpiderBallRollForces(const CFinalInput&, CStateManager&, float) { } void CMorphBall::CalculateSpiderBallAttractionSurfaceForces(const CFinalInput&, CStateManager&, const zeus::CTransform&) { } void CMorphBall::CheckForSwitchToSpiderBallSwinging(CStateManager&) { } void CMorphBall::FindClosestSpiderBallWaypoint(CStateManager&, const zeus::CVector3f&, zeus::CVector3f&, zeus::CVector3f&, zeus::CVector3f&, float&, zeus::CVector3f&, bool&, zeus::CTransform&) const { } void CMorphBall::SetSpiderBallSwingingState(bool) { } void CMorphBall::GetSpiderBallControllerMovement(const CFinalInput&, bool, bool) { } void CMorphBall::ResetSpiderBallSwingControllerMovementTimer() { } void CMorphBall::UpdateSpiderBallSwingControllerMovementTimer(float, float) { } float CMorphBall::GetSpiderBallSwingControllerMovementScalar() const { return 0.f; } void CMorphBall::CreateSpiderBallParticles(const zeus::CVector3f&, const zeus::CVector3f&) { } void CMorphBall::ComputeMarioMovement(const CFinalInput&, CStateManager&, float) { } void CMorphBall::GetSwooshToWorld() const { } zeus::CTransform CMorphBall::GetBallToWorld() const { return zeus::CTransform::Translate(x0_player.GetTranslation() + zeus::CVector3f(0.f, 0.f, GetBallRadius())) * x0_player.GetTransform().getRotation(); } void CMorphBall::CalculateSurfaceToWorld(const zeus::CVector3f&, const zeus::CVector3f&, const zeus::CVector3f&) const { } void CMorphBall::CalculateBallContactInfo(zeus::CVector3f&, zeus::CVector3f&) const { } void CMorphBall::UpdateBallDynamics(CStateManager&, float) { } void CMorphBall::SwitchToMarble() { } void CMorphBall::SwitchToTire() { } void CMorphBall::Update(float dt, CStateManager& mgr) { if (x187c_spiderBallState == ESpiderBallState::Active) CreateSpiderBallParticles(GetBallToWorld().origin, x1890_spiderTrackPoint); if (x0_player.GetDeathTime() <= 0.f) UpdateEffects(dt, mgr); if (x1e44_ > 0.f) { x1e44_ -= x1e48_ * dt; if (x1e44_ <= 0.f) { x1e44_ = 0.f; x1e48_ = 0.f; x1e4c_ = 0.f; } else { x1e4c_ += dt; } } if (x58_ballModel) x58_ballModel->AdvanceAnimation(dt, mgr, kInvalidAreaId, true); if (x1c2c_) { x1c20_ += x1c28_ * dt; if (x1c20_ < 0.f) { x1c2c_ = false; x1c20_ = 0.f; } else if (x1c20_ > x1c24_) { x1c2c_ = false; x1c20_ = x1c24_; } } if (x1c1c_rainSplashGen) x1c1c_rainSplashGen->Update(dt, mgr); UpdateMorphBallSounds(dt); } void CMorphBall::UpdateScriptMessage(EScriptObjectMessage, TUniqueId, CStateManager&) { } void CMorphBall::DeleteLight(CStateManager&) { } void CMorphBall::SetBallLightActive(CStateManager&, bool) { } void CMorphBall::EnterMorphBallState(CStateManager&) { } void CMorphBall::LeaveMorphBallState(CStateManager&) { } void CMorphBall::UpdateEffects(float, CStateManager&) { } void CMorphBall::ComputeBoostBallMovement(const CFinalInput&, const CStateManager&, float) { } void CMorphBall::EnterBoosting() { } void CMorphBall::LeaveBoosting() { } void CMorphBall::CancelBoosting() { } void CMorphBall::UpdateMarbleDynamics(CStateManager&, float, const zeus::CVector3f&) { } void CMorphBall::ApplyFriction(float) { } void CMorphBall::DampLinearAndAngularVelocities(float, float) { } zeus::CTransform CMorphBall::GetPrimitiveTransform() const { return {}; } void CMorphBall::DrawCollisionPrimitive() const { } void CMorphBall::GetMinimumAlignmentSpeed() const { } void CMorphBall::PreRender(CStateManager&, const zeus::CFrustum&) { } void CMorphBall::Render(const CStateManager&, const CActorLights*) const { } void CMorphBall::ResetMorphBallTransitionFlash() { } void CMorphBall::UpdateMorphBallTransitionFlash(float) { } void CMorphBall::RenderMorphBallTransitionFlash(const CStateManager&) const { if (x19dc_morphBallTransitionFlashGen) { const u8* c = BallTransFlashColors[x8_]; zeus::CColor color = {c[0] / 255.f, c[1] / 255.f, c[2] / 255.f, 1.f}; x19dc_morphBallTransitionFlashGen->SetModulationColor(color); x19dc_morphBallTransitionFlashGen->Render(); } } void CMorphBall::UpdateIceBreakEffect(float dt) { } void CMorphBall::RenderIceBreakEffect(const CStateManager& mgr) const { if (x19e0_effect_morphBallIceBreakGen) x19e0_effect_morphBallIceBreakGen->Render(); } void CMorphBall::RenderDamageEffects(const CStateManager&, const zeus::CTransform&) const { } void CMorphBall::UpdateHalfPipeStatus(CStateManager&, float) { } void CMorphBall::DisableHalfPipeStatus() { SetIsInHalfPipeMode(false); SetIsInHalfPipeModeInAir(false); SetTouchedHalfPipeRecently(false); x1dfc_ = 0.f; x1e00_ = 0.f; x0_player.SetCollisionAccuracyModifier(1.f); x1e08_ = zeus::CVector3f::skZero; x1e14_ = zeus::CVector3f::skZero; } bool CMorphBall::BallCloseToCollision(const CStateManager&, float, const CMaterialFilter& filter) const { return false; } void CMorphBall::CollidedWith(TUniqueId id, const CCollisionInfoList& list, CStateManager& mgr) { x74_collisionInfos = list; CMaterialList allMats; for (const CCollisionInfo& info : list) allMats.Add(info.GetMaterialLeft()); zeus::CVector3f vel = x0_player.GetVelocity(); float velMag = vel.magnitude(); EMaterialTypes wakeMaterial = EMaterialTypes::Unknown; if (velMag > 7.f && x0_player.GetFluidCounter() == 0) { bool hitWall = false; for (const CCollisionInfo& info : list) { if (!hitWall) { if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Wall)) { hitWall = true; if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Stone) || info.GetMaterialLeft().HasMaterial(EMaterialTypes::Metal)) { x19cc_wallSparkGen->SetTranslation(info.GetPoint()); x19cc_wallSparkGen->SetParticleEmission(true); x1e38_ = 7; } } } if (wakeMaterial == EMaterialTypes::Unknown) { if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Floor)) { EMaterialTypes tmpMaterial; if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Dirt)) tmpMaterial = EMaterialTypes::Dirt; else tmpMaterial = wakeMaterial; if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Sand)) tmpMaterial = EMaterialTypes::Sand; if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Lava)) tmpMaterial = EMaterialTypes::Lava; if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::MudSlow)) tmpMaterial = EMaterialTypes::MudSlow; if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Snow)) tmpMaterial = EMaterialTypes::Snow; if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::Phazon)) tmpMaterial = EMaterialTypes::Phazon; wakeMaterial = tmpMaterial; if (tmpMaterial != EMaterialTypes::Unknown) { int mappedIdx = skWakeEffectMap[int(tmpMaterial)]; if (mappedIdx == 0) // Phazon { const CGameArea* area = mgr.GetWorld()->GetAreaAlways(mgr.GetNextAreaId()); if (const CScriptAreaAttributes* attribs = area->GetPostConstructed()->x10d8_areaAttributes) if (attribs->GetPhazonType() == EPhazonType::Orange) mappedIdx = 1; // Orange Phazon } if (x1c0c_wakeEffectIdx != mappedIdx) { if (x1c0c_wakeEffectIdx != -1) x1bc8_wakeEffectGens[x1c0c_wakeEffectIdx]->SetParticleEmission(false); x1c0c_wakeEffectIdx = mappedIdx; } x1bc8_wakeEffectGens[x1c0c_wakeEffectIdx]->SetParticleEmission(true); x1bc8_wakeEffectGens[x1c0c_wakeEffectIdx]->SetTranslation(info.GetPoint()); } } } } if (hitWall && !CSfxManager::IsPlaying(x1e28_wallHitSfxHandle)) { x1e28_wallHitSfxHandle = CSfxManager::AddEmitter(1525, x0_player.GetTranslation(), zeus::CVector3f::skZero, true, false, 0x7f, kInvalidAreaId); x0_player.ApplySubmergedPitchBend(x1e28_wallHitSfxHandle); } } if (wakeMaterial == EMaterialTypes::Unknown && x1c0c_wakeEffectIdx != -1) x1bcc_[int(wakeMaterial)]->SetParticleEmission(false); x1954_isProjectile = false; if (allMats.HasMaterial(EMaterialTypes::HalfPipe)) { x1dfc_ = 4.f; x1e04_ = 0.05f; for (const CCollisionInfo& info : list) { if (info.GetMaterialLeft().HasMaterial(EMaterialTypes::HalfPipe)) { if (info.GetNormalLeft().dot(x1e14_) < 0.99) { x1e08_ = x1e14_; x1e14_ = info.GetNormalLeft(); if (zeus::close_enough(x1e08_, zeus::CVector3f::skZero, 0.000011920929f)) x1e08_ = x1e14_; } } } } if (x28_ && allMats.HasMaterial(EMaterialTypes::Floor) && allMats.HasMaterial(EMaterialTypes::Wall)) SwitchToMarble(); if (!GetIsInHalfPipeMode() && x1de4_24 && velMag > 3.f) { zeus::CVector3f velNorm = vel.normalized(); for (const CCollisionInfo& info : list) { if (!info.GetMaterialLeft().HasMaterial(EMaterialTypes::HalfPipe) && info.GetNormalLeft().dot(velNorm) < -0.4f) { LeaveBoosting(); DampLinearAndAngularVelocities(0.4f, 0.01f); break; } } } if (id == kInvalidUniqueId) { zeus::CVector3f cvel = x0_player.GetVelocity(); float cvelMag = cvel.magnitude(); zeus::CVector3f pvel = x1c_; if (pvel.magnitude() > 1000.f && cvelMag > 8.f) { zeus::CVector3f pvelNorm = pvel.normalized(); zeus::CVector3f cvelNorm = cvel.normalized(); for (const CCollisionInfo& info : list) { if (IsClimbable(info) && info.GetNormalLeft().dot(pvelNorm) < -0.4f && info.GetNormalLeft().dot(cvelNorm) < -0.6f) { float f2 = 0.75f * cvelMag; float maxSpeed = g_tweakBall->GetBallTranslationMaxSpeed(int(x0_player.GetSurfaceRestraint())); float f0 = maxSpeed * 0.15f; float f3 = (f2 - f0) < 0.f ? f2 : f0; float f4 = maxSpeed * 0.25f; f4 = (f3 - f4) < 0.f ? f4 : f3; zeus::CVector3f newVel = cvel + zeus::CVector3f(0.f, 0.f, f4); x1dd8_ = newVel; x0_player.SetVelocityWR(newVel); x1dc8_ += 1; break; } } } } if (list.GetCount() > 2 && list.GetItem(0).GetNormalLeft().z > 0.2f && std::fabs(x0_player.GetVelocity().dot(list.GetItem(0).GetNormalLeft())) > 2.f) { float accum = 0.f; int count = 0; for (auto it = list.begin() + 1 ; it != list.end() ; ++it) { const CCollisionInfo& item1 = *it; for (auto it2 = list.begin() + 1 ; it2 != list.end() ; ++it2) { const CCollisionInfo& item2 = *it2; accum += item1.GetNormalLeft().dot(item2.GetNormalLeft()); count += 1; } } if (accum / float(count) < 0.5f) x1dc8_ += 1; } if (list.GetCount() != 0) SelectMorphBallSounds(list.GetItem(0).GetMaterialLeft()); } bool CMorphBall::IsInFrustum(const zeus::CFrustum& frustum) const { if (x58_ballModel->IsNull()) return false; if (x58_ballModel->IsInFrustum(GetBallToWorld(), frustum)) return true; auto swooshBounds = x19b8_slowBlueTailSwooshGen->GetBounds(); return x19b8_slowBlueTailSwooshGen->GetModulationColor().a != 0.f && swooshBounds && frustum.aabbFrustumTest(*swooshBounds); } void CMorphBall::ComputeLiftForces(const zeus::CVector3f&, const zeus::CVector3f&, const CStateManager&) { } float CMorphBall::CalculateSurfaceFriction() const { float friction = g_tweakBall->GetBallTranslationFriction(int(x0_player.GetSurfaceRestraint())); if (x0_player.GetAttachedActor() != kInvalidUniqueId) friction *= 2.f; size_t drainSourceCount = x0_player.GetEnergyDrain().GetEnergyDrainSources().size(); if (drainSourceCount > 0) friction *= drainSourceCount * 1.5f; return friction; } void CMorphBall::ApplyGravity(CStateManager& mgr) { if (x0_player.CheckSubmerged() && !mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GravitySuit)) x0_player.SetMomentumWR(zeus::CVector3f(0.f, 0.f, g_tweakBall->GetBallWaterGravity() * x0_player.GetMass())); else x0_player.SetMomentumWR(zeus::CVector3f(0.f, 0.f, g_tweakBall->GetBallGravity() * x0_player.GetMass())); } void CMorphBall::SpinToSpeed(float holdMag, zeus::CVector3f torque, float mag) { x0_player.ApplyTorqueWR(torque * ((holdMag - x0_player.GetAngularVelocityWR().getVector().magnitude()) * mag)); } float CMorphBall::ComputeMaxSpeed() const { if (GetIsInHalfPipeMode()) return std::min(x0_player.GetVelocity().magnitude() * 1.5f, 95.f); else return g_tweakBall->GetBallTranslationMaxSpeed(int(x0_player.GetSurfaceRestraint())); } static const CDamageInfo kBallDamage = { CWeaponMode(EWeaponType::BoostBall), 50000.f, 0.f, 0.f }; void CMorphBall::Touch(CActor& actor, CStateManager& mgr) { if (TCastToPtr act = actor) { if (x1de4_24 && (act->GetVelocity() - x0_player.GetVelocity()).magnitude() > g_tweakBall->GetBoostBallMinRelativeSpeedForDamage()) { mgr.ApplyDamage(x0_player.GetUniqueId(), actor.GetUniqueId(), x0_player.GetUniqueId(), kBallDamage, CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::CVector3f::skZero); } } } bool CMorphBall::IsClimbable(const CCollisionInfo& cinfo) const { if (std::fabs(cinfo.GetNormalLeft().z) < 0.7f) { float pointToBall = GetBallToWorld().origin.z - cinfo.GetPoint().z; if (pointToBall > 0.1f && pointToBall < GetBallRadius() - 0.05f) return true; } return false; } void CMorphBall::FluidFXThink(CActor::EFluidState, CScriptWater&, CStateManager&) { } void CMorphBall::GetMorphBallModel(const std::string&, float) { } void CMorphBall::LoadMorphBallModel(CStateManager& mgr) { } void CMorphBall::AddSpiderBallElectricalEffect() { } void CMorphBall::UpdateSpiderBallElectricalEffect() { } void CMorphBall::RenderSpiderBallElectricalEffect() const { } void CMorphBall::RenderEnergyDrainEffects(const CStateManager&) const { } void CMorphBall::TouchModel(const CStateManager& mgr) const { x58_ballModel->Touch(mgr, x5c_ballModelShader); if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::SpiderBall) && x60_spiderBallGlassModel) x60_spiderBallGlassModel->Touch(mgr, x64_spiderBallGlassModelShader); x68_lowPolyBallModel->Touch(mgr, x6c_lowPolyBallModelShader); } void CMorphBall::SetAsProjectile(const CDamageInfo&, const CDamageInfo&) { } void CMorphBall::TakeDamage(float) { } void CMorphBall::StartLandingSfx() { if (x0_player.GetVelocity().z < -5.f && x1e36_landSfx != 0xffff) { float vol = zeus::clamp(0.75f, 0.0125f * x0_player.GetLastVelocity().z + 0.75f, 1.f); CSfxHandle hnd = CSfxManager::SfxStart(x1e36_landSfx, vol, 0.f, true, 0x7f, false, kInvalidAreaId); x0_player.ApplySubmergedPitchBend(hnd); } } void CMorphBall::Stop() { x19b0_effect_morphBallIceBreak.Lock(); if (x19e0_effect_morphBallIceBreakGen) x19e0_effect_morphBallIceBreakGen.reset(); } void CMorphBall::StopSounds() { if (x1e2c_rollSfxHandle) { CSfxManager::SfxStop(x1e2c_rollSfxHandle); x1e2c_rollSfxHandle.reset(); } if (x1e30_spiderSfxHandle) { CSfxManager::SfxStop(x1e30_spiderSfxHandle); x1e30_spiderSfxHandle.reset(); } } void CMorphBall::ActorAttached() { } }