#include "Runtime/World/CPlayer.hpp" #include #include #include #include #include "Runtime/CDependencyGroup.hpp" #include "Runtime/CGameState.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/Audio/CStreamAudioManager.hpp" #include "Runtime/Camera/CBallCamera.hpp" #include "Runtime/Camera/CCinematicCamera.hpp" #include "Runtime/Camera/CFirstPersonCamera.hpp" #include "Runtime/Character/CSteeringBehaviors.hpp" #include "Runtime/Collision/CGameCollision.hpp" #include "Runtime/Collision/CMetroidAreaCollider.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Input/ControlMapper.hpp" #include "Runtime/MP1/CSamusHud.hpp" #include "Runtime/MP1/World/CMetroidBeta.hpp" #include "Runtime/MP1/World/CThardusRockProjectile.hpp" #include "Runtime/Weapon/CEnergyProjectile.hpp" #include "Runtime/World/CActorParameters.hpp" #include "Runtime/World/CHUDBillboardEffect.hpp" #include "Runtime/World/CPatterned.hpp" #include "Runtime/World/CScriptAreaAttributes.hpp" #include "Runtime/World/CScriptGrapplePoint.hpp" #include "Runtime/World/CScriptPlayerHint.hpp" #include "Runtime/World/CScriptWater.hpp" #include #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { namespace { logvisor::Module Log("metaforce::CPlayer"); constexpr CMaterialFilter SolidMaterialFilter = CMaterialFilter::MakeInclude(CMaterialList(EMaterialTypes::Solid)); constexpr CMaterialFilter LineOfSightFilter = CMaterialFilter::MakeIncludeExclude( {EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::ScanPassthrough, EMaterialTypes::Player}); constexpr CMaterialFilter OccluderFilter = CMaterialFilter::MakeIncludeExclude( {EMaterialTypes::Solid, EMaterialTypes::Occluder}, {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::ScanPassthrough, EMaterialTypes::Player}); constexpr CMaterialFilter BallTransitionCollide = CMaterialFilter::MakeIncludeExclude( {EMaterialTypes::Solid}, {EMaterialTypes::ProjectilePassthrough, EMaterialTypes::Player, EMaterialTypes::Character, EMaterialTypes::CameraPassthrough}); constexpr std::array skStrafeDistances{ 11.8f, 11.8f, 11.8f, 5.0f, 6.0f, 5.0f, 5.0f, 6.0f, }; constexpr std::array skDashStrafeDistances{ 11.8f, 30.0f, 22.6f, 10.0f, 10.0f, 10.0f, 10.0f, 10.0f, }; constexpr std::array skOrbitForwardDistances{ 11.8f, 11.8f, 11.8f, 5.0f, 6.0f, 5.0f, 5.0f, 6.0f, }; constexpr std::array UnlockMessageResBases{ "STRG_SlideShow_Unlock1_", "STRG_SlideShow_Unlock2_", }; constexpr std::array skPlayerLandSfxSoft{ 0xFFFF, SFXsam_landstone_00, SFXsam_landmetl_00, SFXsam_landgrass_00, SFXsam_landice_00, 0xFFFF, SFXsam_landgrate_00, SFXsam_landphazon_00, SFXsam_landdirt_00, SFXlav_landlava_00, SFXsam_landlavastone_00, SFXsam_landsnow_00, SFXsam_landmud_00, 0xFFFF, SFXsam_landgrass_00, SFXsam_landmetl_00, SFXsam_landmetl_00, SFXsam_landdirt_00, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, SFXsam_landwood_00, SFXsam_b_landorg_00, }; constexpr std::array skPlayerLandSfxHard{ 0xFFFF, SFXsam_landstone_02, SFXsam_b_landmetl_02, SFXsam_landgrass_02, SFXsam_landice_02, 0xFFFF, SFXsam_landgrate_02, SFXsam_landphazon_02, SFXsam_landdirt_02, SFXlav_landlava_02, SFXsam_landlavastone_02, SFXsam_landsnow_02, SFXsam_landmud_02, 0xFFFF, SFXsam_landgrass_02, SFXsam_b_landmetl_02, SFXsam_b_landmetl_02, SFXsam_landdirt_02, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, SFXsam_landwood_02, SFXsam_landorg_02, }; constexpr std::array skLeftStepSounds{ 0xFFFF, SFXsam_wlkstone_00, SFXsam_wlkmetal_00, SFXsam_b_wlkgrass_00, SFXsam_wlkice_00, 0xFFFF, SFXsam_wlkgrate_00, SFXsam_wlkphazon_00, SFXsam_wlkdirt_00, SFXlav_wlklava_00, SFXsam_wlklavastone_00, SFXsam_wlksnow_00, SFXsam_wlkmud_00, 0xFFFF, SFXsam_b_wlkorg_00, SFXsam_wlkmetal_00, SFXsam_wlkmetal_00, SFXsam_wlkdirt_00, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, SFXsam_wlkwood_00, SFXsam_b_wlkorg_00, }; constexpr std::array skRightStepSounds{ 0xFFFF, SFXsam_wlkstone_01, SFXsam_wlkmetal_01, SFXsam_b_wlkgrass_01, SFXsam_wlkice_01, 0xFFFF, SFXsam_wlkgrate_01, SFXsam_wlkphazon_01, SFXsam_wlkdirt_01, SFXlav_wlklava_01, SFXsam_wlklavastone_01, SFXsam_wlksnow_01, SFXsam_wlkmud_01, 0xFFFF, SFXsam_b_wlkorg_01, SFXsam_wlkmetal_01, SFXsam_wlkmetal_01, SFXsam_wlkdirt_01, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, SFXsam_wlkwood_01, SFXsam_b_wlkorg_01, }; constexpr float RCP_2PI = 0.15915494; constexpr std::array, 4> skVisorToItemMapping{{ {CPlayerState::EItemType::CombatVisor, ControlMapper::ECommands::NoVisor}, {CPlayerState::EItemType::XRayVisor, ControlMapper::ECommands::XrayVisor}, {CPlayerState::EItemType::ScanVisor, ControlMapper::ECommands::InviroVisor}, {CPlayerState::EItemType::ThermalVisor, ControlMapper::ECommands::ThermoVisor}, }}; CModelData MakePlayerAnimRes(CAssetId resId, const zeus::CVector3f& scale) { return CModelData{CAnimRes(resId, 0, scale, 0, true), 1}; } uint32_t GetOrbitScreenBoxHalfExtentXScaled(int zone) { return g_tweakPlayer->GetOrbitScreenBoxHalfExtentX(zone) * g_Viewport.x8_width / 640; } uint32_t GetOrbitScreenBoxHalfExtentYScaled(int zone) { return g_tweakPlayer->GetOrbitScreenBoxHalfExtentY(zone) * g_Viewport.xc_height / 448; } uint32_t GetOrbitScreenBoxCenterXScaled(int zone) { return g_tweakPlayer->GetOrbitScreenBoxCenterX(zone) * g_Viewport.x8_width / 640; } uint32_t GetOrbitScreenBoxCenterYScaled(int zone) { return g_tweakPlayer->GetOrbitScreenBoxCenterY(zone) * g_Viewport.xc_height / 448; } uint32_t GetOrbitZoneIdealXScaled(int zone) { return g_tweakPlayer->GetOrbitZoneIdealX(zone) * g_Viewport.x8_width / 640; } uint32_t GetOrbitZoneIdealYScaled(int zone) { return g_tweakPlayer->GetOrbitZoneIdealY(zone) * g_Viewport.xc_height / 448; } } // Anonymous namespace CPlayer::CPlayer(TUniqueId uid, const zeus::CTransform& xf, const zeus::CAABox& aabb, CAssetId resId, const zeus::CVector3f& playerScale, float mass, float stepUp, float stepDown, float ballRadius, const CMaterialList& ml) : CPhysicsActor(uid, true, "CPlayer", CEntityInfo(kInvalidAreaId, CEntity::NullConnectionList), xf, MakePlayerAnimRes(resId, playerScale), ml, aabb, SMoverData(mass), CActorParameters::None(), stepUp, stepDown) , x2d8_fpBounds(aabb) , x7d0_animRes(resId, 0, playerScale, 0, true) , x7d8_beamScale(playerScale) { x490_gun = std::make_unique(uid); x49c_gunHolsterRemTime = g_tweakPlayerGun->GetGunNotFiringTime(); x4a0_failsafeTest = std::make_unique(); x76c_cameraBob = std::make_unique(CPlayerCameraBob::ECameraBobType::One, CPlayerCameraBob::GetCameraBobExtent(), CPlayerCameraBob::GetCameraBobPeriod()); const CAssetId beamId = g_tweakPlayerRes->GetBeamBallTransitionModel(x7ec_beam); x7f0_ballTransitionBeamModel = std::make_unique(CStaticRes(beamId, playerScale)); x730_transitionModels.reserve(3); x768_morphball = std::make_unique(*this, ballRadius); SetInertiaTensorScalar(xe8_mass); x1f4_lastNonCollidingState = GetMotionState(); x490_gun->SetTransform(x34_transform); x490_gun->GetGrappleArm().SetTransform(x34_transform); InitializeBallTransition(); const zeus::CAABox ballTransAABB = x64_modelData->GetBounds(); x2f0_ballTransHeight = ballTransAABB.max.z() - ballTransAABB.min.z(); SetCalculateLighting(true); x90_actorLights->SetCastShadows(true); x50c_moveDir.z() = 0.f; if (x50c_moveDir.canBeNormalized()) { x50c_moveDir.normalize(); } x2b4_accelerationTable.push_back(20.f); x2b4_accelerationTable.push_back(80.f); x2b4_accelerationTable.push_back(80.f); x2b4_accelerationTable.push_back(270.f); SetMaximumCollisionVelocity(25.f); x354_onScreenOrbitObjects.reserve(64); x344_nearbyOrbitObjects.reserve(64); x364_offScreenOrbitObjects.reserve(64); x64_modelData->SetScale(playerScale); x7f0_ballTransitionBeamModel->SetScale(playerScale); LoadAnimationTokens(); _CreateReflectionCube(); } void CPlayer::InitializeBallTransition() { if (x64_modelData && x64_modelData->HasAnimData()) { x64_modelData->GetAnimationData()->SetAnimation(CAnimPlaybackParms(2, -1, 1.f, true), false); } } bool CPlayer::IsTransparent() const { return x588_alpha < 1.f; } float CPlayer::GetTransitionAlpha(const zeus::CVector3f& camPos, float zNear) const { const float zLimit = (x2d8_fpBounds.max.x() - x2d8_fpBounds.min.x()) * 0.5f + zNear; const float zStart = 1.f + zLimit; const float dist = (camPos - GetEyePosition()).magnitude(); if (dist >= zLimit && dist <= zStart) { return (dist - zLimit) / (zStart - zLimit); } if (dist > zStart) { return 1.f; } return 0.f; } s32 CPlayer::ChooseTransitionToAnimation(float dt, CStateManager& mgr) const { if (x258_movementState == EPlayerMovementState::ApplyJump) { return 3; // B_airposetoball_samus } const zeus::CVector3f localVel = x34_transform.transposeRotate(x138_velocity); zeus::CVector3f localVelFlat = localVel; localVelFlat.z() = 0.f; const float localVelFlatMag = localVelFlat.magnitude(); if (localVelFlatMag <= 1.f) { return 2; // B_readytoball_samus } float velAng = std::atan2(-localVelFlat.x(), localVelFlat.y()); constexpr float twoPi = zeus::degToRad(360.f); if (velAng > twoPi) { velAng = velAng - static_cast(velAng * RCP_2PI) * twoPi; } else if (velAng < 0.f) { velAng = twoPi + (velAng - static_cast(velAng * RCP_2PI) * twoPi); } const float velDeg = zeus::radToDeg(velAng); if (velDeg >= 45.f && velDeg <= 315.f) { return 1; // B_readytostationarybackwards_samus } if (localVelFlatMag < 0.5f * GetActualFirstPersonMaxVelocity(dt)) { return 0; // B_forwardtoballforward_samus } return 4; // B_runtoballfoward_samus } void CPlayer::TransitionToMorphBallState(float dt, CStateManager& mgr) { x584_ballTransitionAnim = ChooseTransitionToAnimation(dt, mgr); x58c_transitionVel = x138_velocity.magnitude(); if (x64_modelData && x64_modelData->HasAnimData()) { const CAnimPlaybackParms parms(x584_ballTransitionAnim, -1, 1.f, true); x64_modelData->GetAnimationData()->SetAnimation(parms, false); x64_modelData->GetAnimationData()->SetAnimDir(CAnimData::EAnimDir::Forward); } x64_modelData->EnableLooping(false); x64_modelData->Touch(mgr, 0); x150_momentum = zeus::skZero3f; CPhysicsActor::Stop(); SetMorphBallState(EPlayerMorphBallState::Morphing, mgr); SetCameraState(EPlayerCameraState::Transitioning, mgr); x500_lookDir = x34_transform.basis[1]; x50c_moveDir = x500_lookDir; x50c_moveDir.z() = 0.f; if (x50c_moveDir.canBeNormalized()) { x50c_moveDir.normalize(); } else { x500_lookDir = zeus::skForward; x50c_moveDir = zeus::skForward; } CBallCamera* ballCam = mgr.GetCameraManager()->GetBallCamera(); mgr.GetCameraManager()->SetPlayerCamera(mgr, ballCam->GetUniqueId()); if (!mgr.GetCameraManager()->HasBallCameraInitialPositionHint(mgr)) { mgr.GetCameraManager()->SetupBallCamera(mgr); ballCam->SetState(CBallCamera::EBallCameraState::ToBall, mgr); } else { ballCam->SetState(CBallCamera::EBallCameraState::Default, mgr); SetCameraState(EPlayerCameraState::Ball, mgr); const zeus::CTransform newXf = mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform(); ballCam->SetTransform(newXf); ballCam->TeleportCamera(newXf.origin, mgr); mgr.GetCameraManager()->SetupBallCamera(mgr); ballCam->SetFovInterpolation(mgr.GetCameraManager()->GetFirstPersonCamera()->GetFov(), CCameraManager::ThirdPersonFOV(), 1.f, 0.f); } SetOrbitRequest(EPlayerOrbitRequest::EnterMorphBall, mgr); x490_gun->CancelFiring(mgr); HolsterGun(mgr); } void CPlayer::TransitionFromMorphBallState(CStateManager& mgr) { x584_ballTransitionAnim = 14; // B_ball_unfurl x58c_transitionVel = zeus::CVector2f(x138_velocity.x(), x138_velocity.y()).magnitude(); if (x58c_transitionVel < 1.f) { x584_ballTransitionAnim = 5; // ballstationarytoready_random } if (x258_movementState != EPlayerMovementState::OnGround) { const zeus::CVector3f ballPos = GetBallPosition(); if (mgr.RayCollideWorld(ballPos, ballPos + zeus::CVector3f(0.f, 0.f, -7.f), BallTransitionCollide, this)) { x584_ballTransitionAnim = 7; // B_balljumptoairpose } } if (x64_modelData && x64_modelData->HasAnimData()) { const CAnimPlaybackParms parms(x584_ballTransitionAnim, -1, 1.f, true); x64_modelData->GetAnimationData()->SetAnimation(parms, false); x64_modelData->GetAnimationData()->SetAnimDir(CAnimData::EAnimDir::Forward); } x64_modelData->EnableLooping(false); x64_modelData->Touch(mgr, 0); SetMorphBallState(EPlayerMorphBallState::Unmorphing, mgr); x768_morphball->LeaveMorphBallState(mgr); mgr.GetCameraManager()->SetPlayerCamera(mgr, mgr.GetCameraManager()->GetFirstPersonCamera()->GetUniqueId()); zeus::CVector3f camToPlayer = GetTranslation() - mgr.GetCameraManager()->GetBallCamera()->GetTranslation(); if (camToPlayer.canBeNormalized()) { camToPlayer.normalize(); zeus::CVector3f vecFlat = x500_lookDir; vecFlat.z() = 0.f; zeus::CVector3f f31 = vecFlat.canBeNormalized() && vecFlat.magnitude() >= 0.1f ? x518_leaveMorphDir : camToPlayer; if (x9c6_26_outOfBallLookAtHint) { if (const TCastToConstPtr hint = mgr.GetObjectById(x830_playerHint)) { zeus::CVector3f deltaFlat = hint->GetTranslation() - GetTranslation(); deltaFlat.z() = 0.f; if (deltaFlat.canBeNormalized()) { f31 = deltaFlat.normalized(); } } } if (x9c7_25_outOfBallLookAtHintActor) { if (const TCastToConstPtr hint = mgr.GetObjectById(x830_playerHint)) { if (const TCastToConstPtr act = mgr.GetObjectById(hint->GetActorId())) { zeus::CVector3f deltaFlat = act->GetOrbitPosition(mgr) - GetTranslation(); deltaFlat.z() = 0.f; if (deltaFlat.canBeNormalized()) { f31 = deltaFlat.normalized(); } } } } if (std::acos(zeus::clamp(-1.f, camToPlayer.dot(f31), 1.f)) < M_PIF / 1.2f || x9c7_25_outOfBallLookAtHintActor) { SetTransform(zeus::lookAt(GetTranslation(), GetTranslation() + f31)); } else { SetTransform(zeus::lookAt(GetTranslation(), GetTranslation() + camToPlayer)); UpdateArmAndGunTransforms(0.01f, mgr); } } else { SetTransform(CreateTransformFromMovementDirection()); } CBallCamera* ballCam = mgr.GetCameraManager()->GetBallCamera(); if (const TCastToConstPtr act = mgr.GetObjectById(ballCam->GetTooCloseActorId())) { if (ballCam->GetTooCloseActorDistance() < 20.f && ballCam->GetTooCloseActorDistance() > 1.f) { zeus::CVector3f deltaFlat = act->GetTranslation() - GetTranslation(); deltaFlat.z() = 0.f; zeus::CVector3f deltaFlat2 = act->GetTranslation() - ballCam->GetTranslation(); deltaFlat2.z() = 0.f; if (deltaFlat.canBeNormalized() && deltaFlat2.canBeNormalized()) { deltaFlat.normalize(); zeus::CVector3f camLookFlat = ballCam->GetTransform().basis[1]; camLookFlat.z() = 0.f; camLookFlat.normalize(); deltaFlat2.normalize(); if (deltaFlat.dot(deltaFlat2) >= 0.3f && deltaFlat2.dot(camLookFlat) >= 0.7f) { SetTransform(zeus::lookAt(GetTranslation(), GetTranslation() + deltaFlat)); } } } } ForceGunOrientation(x34_transform, mgr); DrawGun(mgr); ballCam->SetState(CBallCamera::EBallCameraState::FromBall, mgr); ClearForcesAndTorques(); SetAngularVelocityWR(zeus::CAxisAngle()); AddMaterial(EMaterialTypes::GroundCollider, mgr); x150_momentum = zeus::skZero3f; SetCameraState(EPlayerCameraState::Transitioning, mgr); x824_transitionFilterTimer = 0.01f; x57c_ = 0; x580_ = 0; if (!ballCam->TransitionFromMorphBallState(mgr)) { x824_transitionFilterTimer = 0.95f; LeaveMorphBallState(mgr); } } s32 CPlayer::GetNextBallTransitionAnim(float dt, bool& loopOut, CStateManager& mgr) { loopOut = false; const zeus::CVector2f vel(x138_velocity.x(), x138_velocity.y()); if (!vel.canBeNormalized()) { return 12; // B_ball_ready_samus } const float velMag = vel.magnitude(); const float maxVel = GetActualFirstPersonMaxVelocity(dt); if (velMag <= 0.2f * maxVel) { return 12; // B_ball_ready_samus } loopOut = true; const s32 ret = velMag >= maxVel ? 13 : 15; // B_ball_runloop_samus : B_ball_walkloop_samus if (x50c_moveDir.dot(mgr.GetCameraManager()->GetBallCamera()->GetTransform().basis[1]) < -0.5f) { return 12; // B_ball_ready_samus } return ret; } void CPlayer::UpdateMorphBallTransition(float dt, CStateManager& mgr) { switch (x2f8_morphBallState) { case EPlayerMorphBallState::Unmorphed: case EPlayerMorphBallState::Morphed: { CPlayerState::EPlayerSuit suit = mgr.GetPlayerState()->GetCurrentSuitRaw(); if (mgr.GetPlayerState()->IsFusionEnabled()) { suit = CPlayerState::EPlayerSuit(int(suit) + 4); } if (x7cc_transitionSuit != suit) { x7cc_transitionSuit = suit; CAnimRes useRes = x7d0_animRes; useRes.SetCharacterNodeId(s32(x7cc_transitionSuit)); SetModelData(std::make_unique(useRes)); SetIntoBallReadyAnimation(mgr); } return; } case EPlayerMorphBallState::Unmorphing: if (x584_ballTransitionAnim == 14) // B_ball_unfurl { const float dur = x64_modelData->GetAnimationData()->GetAnimationDuration(x584_ballTransitionAnim); const float facRemaining = x64_modelData->GetAnimationData()->GetAnimTimeRemaining("Whole Body") / dur; if (facRemaining < 0.5f) { bool loop = false; x584_ballTransitionAnim = GetNextBallTransitionAnim(dt, loop, mgr); if (x64_modelData && x64_modelData->HasAnimData()) { const CAnimPlaybackParms parms(x584_ballTransitionAnim, -1, 1.f, true); x64_modelData->GetAnimationData()->SetAnimation(parms, false); x64_modelData->GetAnimationData()->EnableLooping(loop); } } } else if (x584_ballTransitionAnim != 5 && x584_ballTransitionAnim != 7) // ballstationarytoready_random, B_balljumptoairpose { const float velMag = zeus::CVector2f(x138_velocity.x(), x138_velocity.y()).magnitude(); if (std::fabs(x58c_transitionVel - velMag) > 0.04f * GetActualFirstPersonMaxVelocity(dt) || velMag < 1.f) { bool loop = false; const s32 nextAnim = GetNextBallTransitionAnim(dt, loop, mgr); if (x64_modelData && x64_modelData->HasAnimData() && x584_ballTransitionAnim != nextAnim && x584_ballTransitionAnim != 7) { x584_ballTransitionAnim = nextAnim; const CAnimPlaybackParms parms(x584_ballTransitionAnim, -1, 1.f, true); x64_modelData->GetAnimationData()->SetAnimation(parms, false); x64_modelData->GetAnimationData()->EnableLooping(loop); x58c_transitionVel = velMag; } } } break; default: break; } const SAdvancementDeltas deltas = UpdateAnimation(dt, mgr, true); MoveInOneFrameOR(deltas.x0_posDelta, dt); RotateInOneFrameOR(deltas.xc_rotDelta, dt); x574_morphTime = std::min(x574_morphTime + dt, x578_morphDuration); const float morphT = x574_morphTime / x578_morphDuration; if ((morphT >= 0.7f || x574_morphTime <= 2.f * dt) && x730_transitionModels.size() != 0) { x730_transitionModels.erase(x730_transitionModels.begin()); } for (auto& m : x730_transitionModels) { m->AdvanceAnimation(dt, mgr, kInvalidAreaId, true); } const CGameCamera* cam = mgr.GetCameraManager()->GetCurrentCamera(mgr); x588_alpha = GetTransitionAlpha(cam->GetTranslation(), cam->GetNearClipDistance()); if (x2f8_morphBallState == EPlayerMorphBallState::Morphing && morphT > 0.93f) { x588_alpha *= std::min(1.f, 1.f - (morphT - 0.93f) / 0.07f + 0.2f); xb4_drawFlags = CModelFlags(5, 0, 1, zeus::CColor(1.f, x588_alpha)); } else if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphing && x588_alpha < 1.f) { if (x588_alpha > 0.05f) { xb4_drawFlags = CModelFlags(5, 0, 0x21, zeus::CColor(1.f, x588_alpha)); } else { xb4_drawFlags = CModelFlags(5, 0, 1, zeus::CColor(1.f, x588_alpha)); } } else { xb4_drawFlags = CModelFlags(5, 0, 3, zeus::CColor(1.f, x588_alpha)); } x594_transisionBeamXfs.AddValue(x7f4_gunWorldXf); x658_transitionModelXfs.AddValue(x34_transform); x71c_transitionModelAlphas.AddValue(x588_alpha); switch (x2f8_morphBallState) { case EPlayerMorphBallState::Unmorphing: (void)GetCollisionPrimitive()->CalculateAABox(GetPrimitiveTransform()).center(); ClearForcesAndTorques(); SetAngularVelocityWR(zeus::CAxisAngle()); if (x574_morphTime >= x578_morphDuration || mgr.GetCameraManager()->IsInCinematicCamera()) { x824_transitionFilterTimer = std::max(x824_transitionFilterTimer, 0.95f); zeus::CVector3f pos; if (CanLeaveMorphBallState(mgr, pos)) { SetTranslation(GetTranslation() + pos); LeaveMorphBallState(mgr); xb4_drawFlags = CModelFlags(0, 0, 3, zeus::skWhite); } else { x574_morphTime = x578_morphDuration - x574_morphTime; TransitionToMorphBallState(dt, mgr); } } break; case EPlayerMorphBallState::Morphing: ClearForcesAndTorques(); SetAngularVelocityWR(zeus::CAxisAngle()); if (x574_morphTime >= x578_morphDuration || mgr.GetCameraManager()->IsInCinematicCamera()) { if (CanEnterMorphBallState(mgr, 1.f)) { ActivateMorphBallCamera(mgr); EnterMorphBallState(mgr); xb4_drawFlags = CModelFlags(0, 0, 3, zeus::skWhite); } else { x574_morphTime = x578_morphDuration - x574_morphTime; TransitionFromMorphBallState(mgr); } } if (x578_morphDuration != 0.f) { if (zeus::clamp(0.f, x574_morphTime, 1.f) >= 0.5f) { if (!x768_morphball->IsMorphBallTransitionFlashValid()) { x768_morphball->ResetMorphBallTransitionFlash(); } } } break; default: break; } } void CPlayer::UpdateGunAlpha() { switch (x498_gunHolsterState) { case EGunHolsterState::Holstered: x494_gunAlpha = 0.f; break; case EGunHolsterState::Holstering: x494_gunAlpha = zeus::clamp(0.f, x49c_gunHolsterRemTime / g_tweakPlayerGun->GetGunHolsterTime(), 1.f); break; case EGunHolsterState::Drawing: x494_gunAlpha = 1.f - zeus::clamp(0.f, x49c_gunHolsterRemTime / 0.45f, 1.f); break; case EGunHolsterState::Drawn: x494_gunAlpha = 1.f; break; } } void CPlayer::UpdatePlayerSounds(float dt) { if (x784_damageSfxTimer <= 0.f) { return; } x784_damageSfxTimer -= dt; if (x784_damageSfxTimer > 0.f) { return; } CSfxManager::SfxStop(x770_damageLoopSfx); x770_damageLoopSfx.reset(); } void CPlayer::Update(float dt, CStateManager& mgr) { SetCoefficientOfRestitutionModifier(0.f); UpdateMorphBallTransition(dt, mgr); const CPlayerState::EBeamId newBeam = mgr.GetPlayerState()->GetCurrentBeam(); if (newBeam != x7ec_beam) { x7ec_beam = newBeam; x7f0_ballTransitionBeamModel = std::make_unique( CStaticRes(g_tweakPlayerRes->GetBeamBallTransitionModel(x7ec_beam), x7d8_beamScale)); } if (!mgr.GetPlayerState()->IsPlayerAlive()) { if (x9f4_deathTime == 0.f) { CSfxManager::KillAll(CSfxManager::ESfxChannels::Game); CStreamAudioManager::StopAll(); if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed) { CSfxHandle hnd = CSfxManager::SfxStart(SFXsam_death, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); } } const float prevDeathTime = x9f4_deathTime; x9f4_deathTime += dt; if (x2f8_morphBallState != EPlayerMorphBallState::Unmorphed) { if (x9f4_deathTime >= 1.f && prevDeathTime < 1.f) { xa00_deathPowerBomb = x490_gun->DropPowerBomb(mgr); } if (x9f4_deathTime >= 4.f && prevDeathTime < 4.f) { CSfxHandle hnd = CSfxManager::SfxStart(SFXsam_death, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); } } } switch (x2f8_morphBallState) { case EPlayerMorphBallState::Unmorphed: case EPlayerMorphBallState::Morphing: case EPlayerMorphBallState::Unmorphing: x7f4_gunWorldXf = x34_transform * x64_modelData->GetScaledLocatorTransform("GUN_LCTR"); break; case EPlayerMorphBallState::Morphed: break; } if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed) { UpdateAimTargetTimer(dt); UpdateAimTarget(mgr); UpdateOrbitModeTimer(dt); } UpdateOrbitPreventionTimer(dt); if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) { x768_morphball->Update(dt, mgr); } else { x768_morphball->StopSounds(); } if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphing || x2f8_morphBallState == EPlayerMorphBallState::Morphing) { x768_morphball->UpdateEffects(dt, mgr); } UpdateGunAlpha(); UpdateDebugCamera(mgr); UpdateVisorTransition(dt, mgr); mgr.SetActorAreaId(*this, mgr.GetWorld()->GetCurrentAreaId()); UpdatePlayerSounds(dt); if (x26c_attachedActor != kInvalidUniqueId) { x270_attachedActorTime += dt; } x740_staticTimer = std::max(0.f, x740_staticTimer - dt); if (x740_staticTimer > 0.f) { x74c_visorStaticAlpha = std::max(0.f, x74c_visorStaticAlpha - x744_staticOutSpeed * dt); } else { x74c_visorStaticAlpha = std::min(1.f, x74c_visorStaticAlpha + x748_staticInSpeed * dt); } x274_energyDrain.ProcessEnergyDrain(mgr, dt); x4a4_moveSpeedAvg.AddValue(x4f8_moveSpeed); mgr.GetPlayerState()->UpdateStaticInterference(mgr, dt); if (!ShouldSampleFailsafe(mgr)) { CPhysicsActor::Stop(); } if (IsEnergyLow(mgr)) { xa30_samusExhaustedVoiceTimer -= dt; } else { xa30_samusExhaustedVoiceTimer = 4.f; } if (!mgr.GetCameraManager()->IsInCinematicCamera() && xa30_samusExhaustedVoiceTimer <= 0.f) { StartSamusVoiceSfx(SFXsam_vox_exhausted, 1.f, 7); xa30_samusExhaustedVoiceTimer = 4.f; } } float CPlayer::UpdateCameraBob(float dt, CStateManager& mgr) { float bobMag = 0.f; CPlayerCameraBob::ECameraBobState state; const zeus::CVector3f backupVel = x138_velocity; if (x304_orbitState == EPlayerOrbitState::NoOrbit) { bobMag = std::fabs(backupVel.dot(x34_transform.basis[1]) / GetActualFirstPersonMaxVelocity(dt)); state = CPlayerCameraBob::ECameraBobState::Walk; if (bobMag < 0.01f) { bobMag = 0.f; state = CPlayerCameraBob::ECameraBobState::WalkNoBob; } } else { state = CPlayerCameraBob::ECameraBobState::Orbit; const float f29 = backupVel.dot(x34_transform.basis[0]); const float f30 = backupVel.dot(x34_transform.basis[1]); const float maxVel = GetActualFirstPersonMaxVelocity(dt); const float strafeDist = skStrafeDistances[size_t(x2b0_outOfWaterTicks == 2 ? x2ac_surfaceRestraint : ESurfaceRestraints::Water)]; bobMag = std::min(std::sqrt(f30 * f30 + f29 * f29) / std::sqrt(strafeDist * strafeDist + maxVel * maxVel) * CPlayerCameraBob::GetOrbitBobScale(), CPlayerCameraBob::GetMaxOrbitBobScale()); if (bobMag < 0.01f) { bobMag = 0.f; } } if (x258_movementState != EPlayerMovementState::OnGround) { bobMag = 0.f; state = CPlayerCameraBob::ECameraBobState::InAir; } else if (bobMag < 0.01f) { if (x490_gun->GetLastFireButtonStates() != 0) { bobMag = 0.f; state = CPlayerCameraBob::ECameraBobState::GunFireNoBob; } else if (std::fabs(GetAngularVelocityOR().angle()) > 0.1f) { bobMag = 0.f; state = CPlayerCameraBob::ECameraBobState::TurningNoBob; } } if (x3dc_inFreeLook || x3dd_lookButtonHeld) { bobMag = 0.f; state = CPlayerCameraBob::ECameraBobState::FreeLookNoBob; } if (x304_orbitState == EPlayerOrbitState::Grapple) { bobMag = 0.f; state = CPlayerCameraBob::ECameraBobState::GrappleNoBob; } if (x3a8_scanState == EPlayerScanState::ScanComplete) { bobMag = 0.f; } if (x38c_doneSidewaysDashing) { bobMag *= 0.1f; state = CPlayerCameraBob::ECameraBobState::FreeLookNoBob; if (x258_movementState == EPlayerMovementState::OnGround) { x38c_doneSidewaysDashing = false; } } if (mgr.GetCameraManager()->IsInCinematicCamera()) { bobMag = 0.f; } bobMag *= mgr.GetCameraManager()->GetCameraBobMagnitude(); x76c_cameraBob->SetPlayerVelocity(backupVel); x76c_cameraBob->SetState(state, mgr); x76c_cameraBob->SetBobMagnitude(bobMag); x76c_cameraBob->SetBobTimeScale((1.f - CPlayerCameraBob::GetSlowSpeedPeriodScale()) * bobMag + CPlayerCameraBob::GetSlowSpeedPeriodScale()); x76c_cameraBob->Update(dt, mgr); return bobMag; } float CPlayer::GetAcceleration() const { if (x2d0_curAcceleration >= x2b4_accelerationTable.size()) { return x2b4_accelerationTable.back(); } return x2b4_accelerationTable[x2d0_curAcceleration]; } float CPlayer::CalculateOrbitMinDistance(EPlayerOrbitType type) const { return zeus::clamp(1.f, std::fabs(x314_orbitPoint.z() - GetTranslation().z()) / 20.f, 4.f) * g_tweakPlayer->GetOrbitMinDistance(int(type)); } void CPlayer::PostUpdate(float dt, CStateManager& mgr) { UpdateArmAndGunTransforms(dt, mgr); float grappleSwingT; if (x3b8_grappleState != EGrappleState::Swinging) { grappleSwingT = 0.f; } else { grappleSwingT = x3bc_grappleSwingTimer / g_tweakPlayer->GetGrappleSwingPeriod(); } float cameraBobT = 0.f; if (mgr.GetCameraManager()->IsInCinematicCamera()) { x76c_cameraBob = std::make_unique(CPlayerCameraBob::ECameraBobType::One, CPlayerCameraBob::GetCameraBobExtent(), CPlayerCameraBob::GetCameraBobPeriod()); } else { cameraBobT = UpdateCameraBob(dt, mgr); } x490_gun->Update(grappleSwingT, cameraBobT, dt, mgr); UpdateOrbitTarget(mgr); UpdateOrbitOrientation(mgr); } bool CPlayer::StartSamusVoiceSfx(u16 sfx, float vol, int prio) { if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) { return false; } bool started = true; if (x77c_samusVoiceSfx) { if (CSfxManager::IsPlaying(x77c_samusVoiceSfx)) { started = false; if (prio > x780_samusVoicePriority) { CSfxManager::SfxStop(x77c_samusVoiceSfx); started = true; } } if (started) { x77c_samusVoiceSfx = CSfxManager::SfxStart(sfx, vol, 0.f, false, 0x7f, false, kInvalidAreaId); x780_samusVoicePriority = prio; } } return started; } bool CPlayer::IsPlayerDeadEnough() const { if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed) { return x9f4_deathTime > 2.5f; } if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) { return x9f4_deathTime > 6.f; } return false; } void CPlayer::AsyncLoadSuit(CStateManager& mgr) { x490_gun->AsyncLoadSuit(mgr); } void CPlayer::LoadAnimationTokens() { TLockedToken transGroup = g_SimplePool->GetObj("BallTransition_DGRP"); CDependencyGroup& group = *transGroup; x25c_ballTransitionsRes.reserve(group.GetObjectTagVector().size()); for (const SObjectTag& tag : group.GetObjectTagVector()) { if (tag.type == FOURCC('CMDL') || tag.type == FOURCC('CSKR') || tag.type == FOURCC('TXTR')) { continue; } x25c_ballTransitionsRes.push_back(g_SimplePool->GetObj(tag)); } } bool CPlayer::HasTransitionBeamModel() const { return x7f0_ballTransitionBeamModel && !x7f0_ballTransitionBeamModel->IsNull(); } bool CPlayer::CanRenderUnsorted(const CStateManager& mgr) const { return false; } const CDamageVulnerability* CPlayer::GetDamageVulnerability(const zeus::CVector3f& v1, const zeus::CVector3f& v2, const CDamageInfo& info) const { if (x2f8_morphBallState == EPlayerMorphBallState::Morphed && x570_immuneTimer > 0.f && !info.NoImmunity()) { return &CDamageVulnerability::ImmuneVulnerabilty(); } return &CDamageVulnerability::NormalVulnerabilty(); } const CDamageVulnerability* CPlayer::GetDamageVulnerability() const { constexpr CDamageInfo info(CWeaponMode(EWeaponType::Power, false, false, false), 0.f, 0.f, 0.f); return GetDamageVulnerability(zeus::skZero3f, zeus::skUp, info); } zeus::CVector3f CPlayer::GetHomingPosition(const CStateManager& mgr, float dt) const { if (dt > 0.f) { return x34_transform.origin + PredictMotion(dt).x0_translation; } return x34_transform.origin; } zeus::CVector3f CPlayer::GetAimPosition(const CStateManager& mgr, float dt) const { zeus::CVector3f ret = x34_transform.origin; if (dt > 0.f) { if (x304_orbitState == EPlayerOrbitState::NoOrbit) { ret += PredictMotion(dt).x0_translation; } else { ret = CSteeringBehaviors::ProjectOrbitalPosition(ret, x138_velocity, x314_orbitPoint, dt, xa04_preThinkDt); } } if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) { ret.z() += g_tweakPlayer->GetPlayerBallHalfExtent(); } else { ret.z() += GetEyeHeight(); } return ret; } void CPlayer::FluidFXThink(EFluidState state, CScriptWater& water, CStateManager& mgr) { if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) { x768_morphball->FluidFXThink(state, water, mgr); if (state == EFluidState::InFluid) { x9c5_30_selectFluidBallSound = true; } } else if (x2f8_morphBallState != EPlayerMorphBallState::Unmorphed) { if (mgr.GetFluidPlaneManager()->GetLastSplashDeltaTime(x8_uid) >= 0.2f) { zeus::CVector3f position(x34_transform.origin); position.z() = float(water.GetTriggerBoundsWR().max.z()); mgr.GetFluidPlaneManager()->CreateSplash(x8_uid, mgr, water, position, 0.1f, state == EFluidState::EnteredFluid); } } else { if (mgr.GetFluidPlaneManager()->GetLastSplashDeltaTime(x8_uid) >= 0.2f) { zeus::CVector3f posOffset = x50c_moveDir; if (posOffset.canBeNormalized()) { posOffset = posOffset.normalized() * zeus::CVector3f(1.2f, 1.2f, 0.f); } switch (state) { case EFluidState::EnteredFluid: { bool doSplash = true; if (x4fc_flatMoveSpeed > 12.5f) { const zeus::CVector3f lookDir = x34_transform.basis[1].normalized(); zeus::CVector3f dcVel = GetDampedClampedVelocityWR(); dcVel.z() = 0.f; if (lookDir.dot(dcVel.normalized()) > 0.75f) { doSplash = false; } } if (doSplash) { zeus::CVector3f position = x34_transform.origin + posOffset; position.z() = float(water.GetTriggerBoundsWR().max.z()); mgr.GetFluidPlaneManager()->CreateSplash(x8_uid, mgr, water, position, 0.3f, true); if (water.GetFluidPlane().GetFluidType() == EFluidType::NormalWater) { const float velMag = mgr.GetPlayer().GetVelocity().magnitude() / 10.f; mgr.GetEnvFxManager()->SetSplashRate(10.f * std::max(1.f, velMag)); } } break; } case EFluidState::InFluid: { if (x138_velocity.magnitude() > 1.f && mgr.GetFluidPlaneManager()->GetLastRippleDeltaTime(x8_uid) >= 0.2f) { zeus::CVector3f position(x34_transform.origin); position.z() = float(water.GetTriggerBoundsWR().max.z()); water.GetFluidPlane().AddRipple(0.5f, x8_uid, position, water, mgr); } break; } case EFluidState::LeftFluid: { zeus::CVector3f position = x34_transform.origin + posOffset; position.z() = float(water.GetTriggerBoundsWR().max.z()); mgr.GetFluidPlaneManager()->CreateSplash(x8_uid, mgr, water, position, 0.15f, true); break; } default: break; } } } } void CPlayer::TakeDamage(bool significant, const zeus::CVector3f& location, float dam, EWeaponType type, CStateManager& mgr) { if (!significant) { return; } if (dam >= 0.f) { x570_immuneTimer = 0.5f; x55c_damageAmt = dam; x560_prevDamageAmt = (type == EWeaponType::AI && dam == 0.00002f) ? 10.f : dam; x564_damageLocation = location; x558_wasDamaged = true; bool doRumble = false; u16 suitDamageSfx = 0; u16 damageLoopSfx = 0; u16 damageSamusVoiceSfx = 0; switch (type) { case EWeaponType::Phazon: case EWeaponType::OrangePhazon: damageLoopSfx = SFXphz_damage_lp; damageSamusVoiceSfx = SFXsam_vox_damage_phazon; break; case EWeaponType::PoisonWater: damageLoopSfx = SFXsam_damage_poison_lp; damageSamusVoiceSfx = SFXsam_vox_damage_poison; break; case EWeaponType::Lava: damageLoopSfx = SFXpds_lava_damage_lp; [[fallthrough]]; case EWeaponType::Heat: damageSamusVoiceSfx = SFXsam_vox_damage_heat; break; default: if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed) { if (dam > 30.f) { damageSamusVoiceSfx = SFXsam_vox_damage30; } else if (dam > 15.f) { damageSamusVoiceSfx = SFXsam_vox_damage15; } else { damageSamusVoiceSfx = SFXsam_vox_damage; } suitDamageSfx = SFXsam_suit_damage; } else { if (dam > 30.f) { suitDamageSfx = SFXsam_ball_damage30; } else if (dam > 15.f) { suitDamageSfx = SFXsam_ball_damage15; } else { suitDamageSfx = SFXsam_ball_damage; } } break; } if (damageSamusVoiceSfx != 0 && x774_samusVoiceTimeout <= 0.f) { StartSamusVoiceSfx(damageSamusVoiceSfx, 1.f, 8); x774_samusVoiceTimeout = mgr.GetActiveRandom()->Range(3.f, 4.f); doRumble = true; } if (damageLoopSfx != 0 && !x9c7_24_noDamageLoopSfx && xa2c_damageLoopSfxDelayTicks >= 2) { if (!x770_damageLoopSfx || x788_damageLoopSfxId != damageLoopSfx) { if (x770_damageLoopSfx && x788_damageLoopSfxId != damageLoopSfx) { CSfxManager::SfxStop(x770_damageLoopSfx); } x770_damageLoopSfx = CSfxManager::SfxStart(damageLoopSfx, 1.f, 0.f, false, 0x7f, true, kInvalidAreaId); x788_damageLoopSfxId = damageLoopSfx; } x784_damageSfxTimer = 0.5f; } if (suitDamageSfx != 0) { if (x770_damageLoopSfx) { CSfxManager::SfxStop(x770_damageLoopSfx); x770_damageLoopSfx.reset(); } CSfxManager::SfxStart(suitDamageSfx, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); x788_damageLoopSfxId = suitDamageSfx; xa2c_damageLoopSfxDelayTicks = 0; doRumble = true; } if (doRumble) { if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed) { x490_gun->DamageRumble(location, dam, mgr); } float tmp = x55c_damageAmt / 25.f; if (std::fabs(tmp) > 1.f) { tmp = tmp > 0.f ? 1.f : -1.f; } mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerBump, tmp, ERumblePriority::One); } if (x2f8_morphBallState != EPlayerMorphBallState::Unmorphed) { x768_morphball->TakeDamage(x55c_damageAmt); x768_morphball->SetDamageTimer(0.4f); } } if (x3b8_grappleState != EGrappleState::None) { BreakGrapple(EPlayerOrbitRequest::DamageOnGrapple, mgr); } } void CPlayer::Accept(IVisitor& visitor) { visitor.Visit(this); } CHealthInfo* CPlayer::HealthInfo(CStateManager& mgr) { return &mgr.GetPlayerState()->GetHealthInfo(); } bool CPlayer::IsUnderBetaMetroidAttack(const CStateManager& mgr) const { if (x274_energyDrain.GetEnergyDrainIntensity() > 0.f) { for (const CEnergyDrainSource& source : x274_energyDrain.GetEnergyDrainSources()) { if (CPatterned::CastTo(mgr.GetObjectById(source.GetEnergyDrainSourceId()))) { return true; } } } return false; } std::optional CPlayer::GetTouchBounds() const { if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) { return GetBoundingBox(); } const float ballTouchRad = x768_morphball->GetBallTouchRadius(); const zeus::CVector3f ballCenter = GetTranslation() + zeus::CVector3f(0.f, 0.f, x768_morphball->GetBallRadius()); return zeus::CAABox(ballCenter - ballTouchRad, ballCenter + ballTouchRad); } void CPlayer::DoPreThink(float dt, CStateManager& mgr) { PreThink(dt, mgr); if (CEntity* ent = mgr.ObjectById(xa00_deathPowerBomb)) { ent->PreThink(dt, mgr); } } void CPlayer::DoThink(float dt, CStateManager& mgr) { Think(dt, mgr); if (CEntity* ent = mgr.ObjectById(xa00_deathPowerBomb)) { ent->Think(dt, mgr); } } void CPlayer::UpdateScanningState(const CFinalInput& input, CStateManager& mgr, float dt) { if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan) { SetScanningState(EPlayerScanState::NotScanning, mgr); return; } if (x3a8_scanState != EPlayerScanState::NotScanning && x3b4_scanningObject != x310_orbitTargetId && x310_orbitTargetId != kInvalidUniqueId) { SetScanningState(EPlayerScanState::NotScanning, mgr); } switch (x3a8_scanState) { case EPlayerScanState::NotScanning: if (ValidateScanning(input, mgr)) { if (const TCastToConstPtr act = mgr.ObjectById(x310_orbitTargetId)) { const CScannableObjectInfo* scanInfo = act->GetScannableObjectInfo(); float scanTime = mgr.GetPlayerState()->GetScanTime(scanInfo->GetScannableObjectId()); if (scanTime >= 1.f) { x9c6_30_newScanScanning = false; scanTime = 1.f; } else { x9c6_30_newScanScanning = true; } SetScanningState(EPlayerScanState::Scanning, mgr); x3ac_scanningTime = scanTime * scanInfo->GetTotalDownloadTime(); x3b0_curScanTime = 0.f; } } break; case EPlayerScanState::Scanning: if (ValidateScanning(input, mgr)) { if (const TCastToConstPtr act = mgr.ObjectById(x310_orbitTargetId)) { if (const CScannableObjectInfo* scanInfo = act->GetScannableObjectInfo()) { x3ac_scanningTime = std::min(scanInfo->GetTotalDownloadTime(), x3ac_scanningTime + dt); x3b0_curScanTime += dt; mgr.GetPlayerState()->SetScanTime(scanInfo->GetScannableObjectId(), x3ac_scanningTime / scanInfo->GetTotalDownloadTime()); if (x3ac_scanningTime >= scanInfo->GetTotalDownloadTime() && x3b0_curScanTime >= g_tweakGui->GetScanSidesStartTime()) { SetScanningState(EPlayerScanState::ScanComplete, mgr); } } } else { SetScanningState(EPlayerScanState::NotScanning, mgr); } } else { SetScanningState(EPlayerScanState::NotScanning, mgr); } break; case EPlayerScanState::ScanComplete: if (!ValidateScanning(input, mgr)) { SetScanningState(EPlayerScanState::NotScanning, mgr); } break; } } bool CPlayer::ValidateScanning(const CFinalInput& input, const CStateManager& mgr) const { if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::ScanItem, input)) { if (x304_orbitState == EPlayerOrbitState::OrbitObject) { if (const TCastToConstPtr act = mgr.ObjectById(x310_orbitTargetId)) { if (act->GetMaterialList().HasMaterial(EMaterialTypes::Scannable)) { const zeus::CVector3f targetToPlayer = GetTranslation() - act->GetTranslation(); if (targetToPlayer.canBeNormalized() && targetToPlayer.magnitude() < g_tweakPlayer->GetScanningRange()) { return true; } } } } } return false; } static bool IsDataLoreResearchScan(CAssetId id) { const auto it = g_MemoryCardSys->LookupScanState(id); if (it == g_MemoryCardSys->GetScanStates().cend()) { return false; } switch (it->second) { case CWorldSaveGameInfo::EScanCategory::Data: case CWorldSaveGameInfo::EScanCategory::Lore: case CWorldSaveGameInfo::EScanCategory::Research: return true; default: return false; } } static CAssetId UpdatePersistentScanPercent(u32 prevLogScans, u32 logScans, u32 totalLogScans) { if (prevLogScans == logScans) { return {}; } const float scanPercent = logScans / float(totalLogScans) * 100.f; const float prevScanPercent = prevLogScans / float(totalLogScans) * 100.f; const float scanMessageInterval = g_tweakSlideShow->GetScanPercentInterval(); const auto scanPercentProgStep = int(scanPercent / scanMessageInterval); const auto prevScanPercentProgStep = int(prevScanPercent / scanMessageInterval); const bool firstTime = scanPercent > g_GameState->SystemOptions().GetLogScanPercent(); if (firstTime) { g_GameState->SystemOptions().SetLogScanPercent(u32(scanPercent)); } if (scanPercentProgStep > prevScanPercentProgStep) { const char* const messageResBase = UnlockMessageResBases[zeus::clamp(0, scanPercentProgStep - 1, 1)]; const auto message = std::string(messageResBase).append(1, firstTime ? '1' : '2'); const auto* const id = g_ResFactory->GetResourceIdByName(message); if (id != nullptr) { return id->id; } } return {}; } void CPlayer::FinishNewScan(CStateManager& mgr) { const TCastToConstPtr act = mgr.ObjectById(x310_orbitTargetId); if (!act) { return; } if (!act->GetMaterialList().HasMaterial(EMaterialTypes::Scannable)) { return; } const auto* const scanInfo = act->GetScannableObjectInfo(); if (!scanInfo) { return; } if (mgr.GetPlayerState()->GetScanTime(scanInfo->GetScannableObjectId()) < 1.f) { return; } if (!IsDataLoreResearchScan(scanInfo->GetScannableObjectId())) { return; } const auto scanCompletion = mgr.CalculateScanCompletionRate(); const CAssetId message = UpdatePersistentScanPercent(mgr.GetPlayerState()->GetLogScans(), scanCompletion.first, scanCompletion.second); if (message.IsValid()) { mgr.ShowPausedHUDMemo(message, 0.f); } mgr.GetPlayerState()->SetScanCompletionRate(scanCompletion); } void CPlayer::SetScanningState(EPlayerScanState state, CStateManager& mgr) { if (x3a8_scanState == state) { return; } mgr.SetGameState(CStateManager::EGameState::Running); if (x3a8_scanState == EPlayerScanState::ScanComplete) { if (TCastToPtr act = mgr.ObjectById(x3b4_scanningObject)) { act->OnScanStateChanged(EScanState::Done, mgr); } } switch (state) { case EPlayerScanState::NotScanning: if (x3a8_scanState == EPlayerScanState::Scanning || x3a8_scanState == EPlayerScanState::ScanComplete) { if (x9c6_30_newScanScanning) { FinishNewScan(mgr); } } x3ac_scanningTime = 0.f; x3b0_curScanTime = 0.f; if (!g_tweakPlayer->GetScanRetention()) { if (const TCastToConstPtr act = mgr.ObjectById(x310_orbitTargetId)) { if (act->GetMaterialList().HasMaterial(EMaterialTypes::Scannable)) { if (const auto* scanInfo = act->GetScannableObjectInfo()) { if (mgr.GetPlayerState()->GetScanTime(scanInfo->GetScannableObjectId()) < 1.f) { mgr.GetPlayerState()->SetScanTime(scanInfo->GetScannableObjectId(), 0.f); } } } } } x3b4_scanningObject = kInvalidUniqueId; break; case EPlayerScanState::Scanning: x3b4_scanningObject = x310_orbitTargetId; break; case EPlayerScanState::ScanComplete: if (g_tweakPlayer->GetScanFreezesGame()) { mgr.SetGameState(CStateManager::EGameState::SoftPaused); } x3b4_scanningObject = x310_orbitTargetId; break; } x3a8_scanState = state; } bool CPlayer::GetExplorationMode() const { switch (x498_gunHolsterState) { case EGunHolsterState::Holstering: case EGunHolsterState::Holstered: return true; default: return false; } } bool CPlayer::GetCombatMode() const { switch (x498_gunHolsterState) { case EGunHolsterState::Drawing: case EGunHolsterState::Drawn: return true; default: return false; } } void CPlayer::RenderGun(const CStateManager& mgr, const zeus::CVector3f& pos) const { if (mgr.GetCameraManager()->IsInCinematicCamera()) { return; } if (x490_gun->GetGrappleArm().GetActive() && x490_gun->GetGrappleArm().GetAnimState() != CGrappleArm::EArmState::Done) { x490_gun->GetGrappleArm().RenderGrappleBeam(mgr, pos); } if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::Scan && mgr.GetPlayerState()->GetVisorTransitionFactor() >= 1.f) { return; } if ((mgr.GetCameraManager()->IsInFirstPersonCamera() && x2f4_cameraState == EPlayerCameraState::FirstPerson) || (x2f8_morphBallState == EPlayerMorphBallState::Morphing && x498_gunHolsterState == EGunHolsterState::Holstering)) { CBooModel::SetReflectionCube(m_reflectionCube); CModelFlags flags(5, 0, 3, zeus::CColor(1.f, x494_gunAlpha)); flags.m_extendedShader = EExtendedShader::LightingCubeReflection; x490_gun->Render(mgr, pos, flags); } } void CPlayer::Render(CStateManager& mgr) { bool doRender = x2f4_cameraState != EPlayerCameraState::Spawned; if (!doRender) { if (TCastToConstPtr cam = mgr.GetCameraManager()->GetCurrentCamera(mgr)) { doRender = (x2f8_morphBallState == EPlayerMorphBallState::Morphed && cam->GetFlags() & 0x40); } } if (x2f4_cameraState != EPlayerCameraState::FirstPerson && doRender) { SCOPED_GRAPHICS_DEBUG_GROUP("CPlayer::Render", zeus::skOrange); CBooModel::SetReflectionCube(m_reflectionCube); bool doTransitionRender = false; bool doBallRender = false; switch (x2f8_morphBallState) { case EPlayerMorphBallState::Unmorphed: x64_modelData->Touch(mgr, 0); CPhysicsActor::Render(mgr); if (HasTransitionBeamModel()) { x7f0_ballTransitionBeamModel->Touch(mgr, 0); CModelFlags flags(0, 0, 3, zeus::skWhite); flags.m_extendedShader = EExtendedShader::LightingCubeReflection; x7f0_ballTransitionBeamModel->Render(mgr, x7f4_gunWorldXf, x90_actorLights.get(), flags); } break; case EPlayerMorphBallState::Morphing: x768_morphball->TouchModel(mgr); doTransitionRender = true; doBallRender = true; break; case EPlayerMorphBallState::Unmorphing: x490_gun->TouchModel(mgr); doTransitionRender = true; doBallRender = true; break; case EPlayerMorphBallState::Morphed: x64_modelData->Touch(mgr, 0); x768_morphball->Render(mgr, x90_actorLights.get()); break; } if (doTransitionRender) { CPhysicsActor::Render(mgr); if (HasTransitionBeamModel()) { CModelFlags flags(5, 0, 3, zeus::CColor(1.f, x588_alpha)); flags.m_extendedShader = EExtendedShader::LightingCubeReflection; x7f0_ballTransitionBeamModel->Render(CModelData::EWhichModel::Normal, x7f4_gunWorldXf, x90_actorLights.get(), flags); } float morphFactor = x574_morphTime / x578_morphDuration; float transitionAlpha; if (morphFactor < 0.05f) { transitionAlpha = 0.f; } else if (morphFactor < 0.1f) { transitionAlpha = (morphFactor - 0.05f) / 0.05f; } else if (morphFactor < 0.8f) { transitionAlpha = 1.f; } else { transitionAlpha = 1.f - (morphFactor - 0.8f) / 0.2f; } const auto mdsp1 = int(x730_transitionModels.size() + 1); for (int i = 0; i < x730_transitionModels.size(); ++i) { const int ni = i + 1; const float alpha = transitionAlpha * (1.f - (ni + 1) / float(mdsp1)) * *x71c_transitionModelAlphas.GetEntry(ni); if (alpha != 0.f) { CModelData& data = *x730_transitionModels[i]; CModelFlags flags(5, 0, 3, zeus::CColor(1.f, alpha)); flags.m_extendedShader = EExtendedShader::LightingCubeReflection; data.Render(CModelData::GetRenderingModel(mgr), *x658_transitionModelXfs.GetEntry(ni), x90_actorLights.get(), flags); if (HasTransitionBeamModel()) { CModelFlags transFlags(5, 0, 3, zeus::CColor(1.f, alpha)); transFlags.m_extendedShader = EExtendedShader::LightingCubeReflection; x7f0_ballTransitionBeamModel->Render(CModelData::EWhichModel::Normal, *x594_transisionBeamXfs.GetEntry(ni), x90_actorLights.get(), transFlags); } } } if (doBallRender) { float morphFactor = x574_morphTime / x578_morphDuration; float ballAlphaStart = 0.75f; float ballAlphaMag = 4.f; if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphing) { ballAlphaStart = 0.875f; morphFactor = 1.f - morphFactor; ballAlphaMag = 8.f; } if (morphFactor > ballAlphaStart) { CModelFlags flags(5, u8(x768_morphball->GetMorphballModelShader()), 3, zeus::CColor(1.f, ballAlphaMag * (morphFactor - ballAlphaStart))); flags.m_extendedShader = EExtendedShader::LightingCubeReflection; x768_morphball->GetMorphballModelData().Render(mgr, x768_morphball->GetBallToWorld(), x90_actorLights.get(), flags); } if (x2f8_morphBallState == EPlayerMorphBallState::Morphing) { if (morphFactor > 0.5f) { float tmp = (morphFactor - 0.5f) / 0.5f; float rotate = 1.f - tmp; float scale = 0.75f * rotate + 1.f; float ballAlpha; if (tmp < 0.1f) { ballAlpha = 0.f; } else if (tmp < 0.2f) { ballAlpha = (tmp - 0.1f) / 0.1f; } else if (tmp < 0.9f) { ballAlpha = 1.f; } else { ballAlpha = 1.f - (morphFactor - 0.9f) / 0.1f; } const float theta = zeus::degToRad(360.f * rotate); ballAlpha *= 0.5f; if (ballAlpha > 0.f) { CModelFlags flags(7, 0, 3, zeus::CColor(1.f, ballAlpha)); flags.m_extendedShader = EExtendedShader::LightingCubeReflection; x768_morphball->GetMorphballModelData().Render( mgr, x768_morphball->GetBallToWorld() * zeus::CTransform::RotateZ(theta) * zeus::CTransform::Scale(scale), x90_actorLights.get(), flags); } } x768_morphball->RenderMorphBallTransitionFlash(mgr); } } } } } void CPlayer::RenderReflectedPlayer(CStateManager& mgr) { switch (x2f8_morphBallState) { case EPlayerMorphBallState::Unmorphed: case EPlayerMorphBallState::Morphing: case EPlayerMorphBallState::Unmorphing: SetCalculateLighting(true); if (x2f4_cameraState == EPlayerCameraState::FirstPerson) { const zeus::CFrustum frustum; CActor::PreRender(mgr, frustum); } CPhysicsActor::Render(mgr); if (HasTransitionBeamModel()) { constexpr CModelFlags flags(0, 0, 3, zeus::skWhite); x7f0_ballTransitionBeamModel->Render(mgr, x7f4_gunWorldXf, nullptr, flags); } break; case EPlayerMorphBallState::Morphed: x768_morphball->Render(mgr, x90_actorLights.get()); break; } } void CPlayer::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) { if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) { SetCalculateLighting(false); x768_morphball->PreRender(mgr, frustum); } else { SetCalculateLighting(true); if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed) { x490_gun->PreRender(mgr, frustum, mgr.GetCameraManager()->GetGlobalCameraTranslation(mgr)); } } if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed || mgr.GetCameraManager()->IsInCinematicCamera()) { x768_morphball->DeleteBallShadow(); } else { x768_morphball->CreateBallShadow(); x768_morphball->RenderToShadowTex(mgr); } for (auto& model : x730_transitionModels) { model->GetAnimationData()->PreRender(); } if (x2f4_cameraState != EPlayerCameraState::FirstPerson) { CActor::PreRender(mgr, frustum); } } void CPlayer::CalculateRenderBounds() { if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) { const float rad = x768_morphball->GetBallRadius(); x9c_renderBounds = zeus::CAABox(GetTranslation() - zeus::CVector3f(rad, rad, 0.f), GetTranslation() + zeus::CVector3f(rad, rad, rad * 2.f)); } else { CActor::CalculateRenderBounds(); } } void CPlayer::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) { if (x2f4_cameraState != EPlayerCameraState::FirstPerson && x2f8_morphBallState == EPlayerMorphBallState::Morphed) { if (x768_morphball->IsInFrustum(frustum)) { CActor::AddToRenderer(frustum, mgr); } else { x768_morphball->TouchModel(mgr); } } else { x490_gun->AddToRenderer(frustum, mgr); CActor::AddToRenderer(frustum, mgr); } } void CPlayer::ComputeFreeLook(const CFinalInput& input) { const float lookLeft = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookLeft, input); const float lookRight = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookRight, input); float lookUp = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookUp, input); float lookDown = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookDown, input); if (g_GameState->GameOptions().GetInvertYAxis()) { lookUp = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookDown, input); lookDown = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookUp, input); } if (!g_tweakPlayer->GetStayInFreeLookWhileFiring() && (ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input) || x304_orbitState != EPlayerOrbitState::NoOrbit)) { x3e8_horizFreeLookAngleVel = 0.f; x3f0_vertFreeLookAngleVel = 0.f; } else { if (x3dc_inFreeLook) { x3e8_horizFreeLookAngleVel = (lookLeft - lookRight) * g_tweakPlayer->GetHorizontalFreeLookAngleVel(); x3f0_vertFreeLookAngleVel = (lookUp - lookDown) * g_tweakPlayer->GetVerticalFreeLookAngleVel(); } if (!x3de_lookAnalogHeld || !x3dd_lookButtonHeld) { x3e8_horizFreeLookAngleVel = 0.f; x3f0_vertFreeLookAngleVel = 0.f; } } if (g_tweakPlayer->GetHoldButtonsForFreeLook()) { if ((g_tweakPlayer->GetTwoButtonsForFreeLook() && (!ControlMapper::GetDigitalInput(ControlMapper::ECommands::LookHold1, input) || !ControlMapper::GetDigitalInput(ControlMapper::ECommands::LookHold2, input))) || (!ControlMapper::GetDigitalInput(ControlMapper::ECommands::LookHold1, input) && !ControlMapper::GetDigitalInput(ControlMapper::ECommands::LookHold2, input))) { x3e8_horizFreeLookAngleVel = 0.f; x3f0_vertFreeLookAngleVel = 0.f; } } if (IsMorphBallTransitioning()) { x3e8_horizFreeLookAngleVel = 0.f; x3f0_vertFreeLookAngleVel = 0.f; } } void CPlayer::UpdateFreeLookState(const CFinalInput& input, float dt, CStateManager& mgr) { if (x304_orbitState == EPlayerOrbitState::ForcedOrbitObject || IsMorphBallTransitioning() || x2f8_morphBallState != EPlayerMorphBallState::Unmorphed || (x3b8_grappleState != EGrappleState::None && x3b8_grappleState != EGrappleState::Firing)) { x3dc_inFreeLook = false; x3dd_lookButtonHeld = false; x3de_lookAnalogHeld = false; x3e8_horizFreeLookAngleVel = 0.f; x3f0_vertFreeLookAngleVel = 0.f; x9c4_25_showCrosshairs = false; return; } if (g_tweakPlayer->GetHoldButtonsForFreeLook()) { if ((g_tweakPlayer->GetTwoButtonsForFreeLook() && (ControlMapper::GetDigitalInput(ControlMapper::ECommands::LookHold1, input) && ControlMapper::GetDigitalInput(ControlMapper::ECommands::LookHold2, input))) || (!g_tweakPlayer->GetTwoButtonsForFreeLook() && (ControlMapper::GetDigitalInput(ControlMapper::ECommands::LookHold1, input) || ControlMapper::GetDigitalInput(ControlMapper::ECommands::LookHold2, input)))) { if (!x3dd_lookButtonHeld) { const zeus::CVector3f lookDir = mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform().basis[1]; zeus::CVector3f lookDirFlat = lookDir; lookDirFlat.z() = 0.f; x3e4_freeLookYawAngle = 0.f; if (lookDirFlat.canBeNormalized()) { lookDirFlat.normalize(); x3ec_freeLookPitchAngle = std::acos(zeus::clamp(-1.f, lookDirFlat.dot(lookDir), 1.f)); if (lookDir.z() < 0.f) x3ec_freeLookPitchAngle = -x3ec_freeLookPitchAngle; } } x3dc_inFreeLook = true; x3dd_lookButtonHeld = true; x3de_lookAnalogHeld = (ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookLeft, input) >= 0.1f || ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookRight, input) >= 0.1f || ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookDown, input) >= 0.1f || ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookUp, input) >= 0.1f); } else { x3dc_inFreeLook = false; x3dd_lookButtonHeld = false; x3de_lookAnalogHeld = false; x3e8_horizFreeLookAngleVel = 0.f; x3f0_vertFreeLookAngleVel = 0.f; } } else { x3de_lookAnalogHeld = (ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookLeft, input) >= 0.1f || ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookRight, input) >= 0.1f || ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookDown, input) >= 0.1f || ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookUp, input) >= 0.1f); x3dd_lookButtonHeld = false; if (std::fabs(x3e4_freeLookYawAngle) < g_tweakPlayer->GetFreeLookCenteredThresholdAngle() && std::fabs(x3ec_freeLookPitchAngle) < g_tweakPlayer->GetFreeLookCenteredThresholdAngle()) { if (x3e0_curFreeLookCenteredTime > g_tweakPlayer->GetFreeLookCenteredTime()) { x3dc_inFreeLook = false; x3e8_horizFreeLookAngleVel = 0.f; x3f0_vertFreeLookAngleVel = 0.f; } else { x3e0_curFreeLookCenteredTime += dt; } } else { x3dc_inFreeLook = true; x3e0_curFreeLookCenteredTime = 0.f; } } UpdateCrosshairsState(input); } void CPlayer::UpdateFreeLook(float dt) { if (GetFrozenState()) { return; } float lookDeltaAngle = dt * g_tweakPlayer->GetFreeLookSpeed(); if (!x3de_lookAnalogHeld) { lookDeltaAngle = dt * g_tweakPlayer->GetFreeLookSnapSpeed(); } float angleVelP = x3f0_vertFreeLookAngleVel - x3ec_freeLookPitchAngle; const float vertLookDamp = zeus::clamp(0.f, std::fabs(angleVelP / 1.0471976f), 1.f); float dx = lookDeltaAngle * (2.f * vertLookDamp - std::sin((M_PIF / 2.f) * vertLookDamp)); if (0.f <= angleVelP) { x3ec_freeLookPitchAngle += dx; } else { x3ec_freeLookPitchAngle -= dx; } angleVelP = x3e8_horizFreeLookAngleVel - x3e4_freeLookYawAngle; dx = lookDeltaAngle * zeus::clamp(0.f, std::fabs(angleVelP / g_tweakPlayer->GetHorizontalFreeLookAngleVel()), 1.f); if (0.f <= angleVelP) { x3e4_freeLookYawAngle += dx; } else { x3e4_freeLookYawAngle -= dx; } if (g_tweakPlayer->GetFreeLookTurnsPlayer()) { x3e4_freeLookYawAngle = 0.f; } } float CPlayer::GetMaximumPlayerPositiveVerticalVelocity(CStateManager& mgr) const { return mgr.GetPlayerState()->GetItemAmount(CPlayerState::EItemType::SpaceJumpBoots) ? 14.f : 11.666666f; } void CPlayer::StartLandingControlFreeze() { x760_controlsFrozen = true; x764_controlsFrozenTimeout = 0.75f; } void CPlayer::EndLandingControlFreeze() { x760_controlsFrozen = false; x764_controlsFrozenTimeout = 0.f; } void CPlayer::ProcessFrozenInput(float dt, CStateManager& mgr) { x764_controlsFrozenTimeout -= dt; if (x764_controlsFrozenTimeout <= 0.f) { EndLandingControlFreeze(); } else { const CFinalInput dummy; if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) { x768_morphball->ComputeBallMovement(dummy, mgr, dt); x768_morphball->UpdateBallDynamics(mgr, dt); } else { ComputeMovement(dummy, mgr, dt); } } } void CPlayer::ProcessInput(const CFinalInput& input, CStateManager& mgr) { if (input.ControllerIdx() != 0) { return; } if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) { UpdateScanningState(input, mgr, input.DeltaTime()); } if (mgr.GetGameState() != CStateManager::EGameState::Running) { return; } if (!mgr.GetPlayerState()->IsPlayerAlive()) { return; } if (GetFrozenState()) { UpdateFrozenState(input, mgr); } if (GetFrozenState()) { if (x258_movementState == EPlayerMovementState::OnGround || x258_movementState == EPlayerMovementState::FallingMorphed) { return; } const CFinalInput dummyInput; if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) { x768_morphball->ComputeBallMovement(dummyInput, mgr, input.DeltaTime()); x768_morphball->UpdateBallDynamics(mgr, input.DeltaTime()); } else { ComputeMovement(dummyInput, mgr, input.DeltaTime()); } return; } if (x760_controlsFrozen) { ProcessFrozenInput(input.DeltaTime(), mgr); return; } if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed && x4a0_failsafeTest->Passes()) { const auto* prim = static_cast(GetCollisionPrimitive()); const zeus::CAABox tmpAABB(prim->GetBox().min - 0.2f, prim->GetBox().max + 0.2f); const CCollidableAABox tmpBox(tmpAABB, prim->GetMaterial()); CPhysicsActor::Stop(); const zeus::CAABox testBounds = prim->GetBox().getTransformedAABox(x34_transform); const zeus::CAABox expandedBounds(testBounds.min - 3.f, testBounds.max + 3.f); CAreaCollisionCache cache(expandedBounds); CGameCollision::BuildAreaCollisionCache(mgr, cache); EntityList nearList; mgr.BuildColliderList(nearList, *this, expandedBounds); const std::optional nonIntVec = CGameCollision::FindNonIntersectingVector(mgr, cache, *this, tmpBox, nearList); if (nonIntVec) { x4a0_failsafeTest->Reset(); SetTranslation(GetTranslation() + *nonIntVec); } } UpdateGrappleState(input, mgr); if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) { float leftDiv = g_tweakBall->GetLeftStickDivisor(); const float rightDiv = g_tweakBall->GetRightStickDivisor(); if (x26c_attachedActor != kInvalidUniqueId || IsUnderBetaMetroidAttack(mgr)) { leftDiv = 2.f; } const CFinalInput scaledInput = input.ScaleAnalogueSticks(leftDiv, rightDiv); x768_morphball->ComputeBallMovement(scaledInput, mgr, input.DeltaTime()); x768_morphball->UpdateBallDynamics(mgr, input.DeltaTime()); x4a0_failsafeTest->Reset(); } else { if (x304_orbitState == EPlayerOrbitState::Grapple) { ApplyGrappleForces(input, mgr, input.DeltaTime()); } else { const CFinalInput scaledInput = input.ScaleAnalogueSticks(IsUnderBetaMetroidAttack(mgr) ? 3.f : 1.f, 1.f); ComputeMovement(scaledInput, mgr, input.DeltaTime()); } if (ShouldSampleFailsafe(mgr)) { CFailsafeTest::EInputState inputState = CFailsafeTest::EInputState::Moving; if (x258_movementState == EPlayerMovementState::ApplyJump) { inputState = CFailsafeTest::EInputState::StartingJump; } else if (x258_movementState == EPlayerMovementState::Jump) { inputState = CFailsafeTest::EInputState::Jump; } x4a0_failsafeTest->AddSample(inputState, GetTranslation(), x138_velocity, zeus::CVector2f(input.ALeftX(), input.ALeftY())); } } ComputeFreeLook(input); UpdateFreeLookState(input, input.DeltaTime(), mgr); UpdateOrbitInput(input, mgr); UpdateOrbitZone(mgr); UpdateGunState(input, mgr); UpdateVisorState(input, input.DeltaTime(), mgr); if (x2f8_morphBallState == EPlayerMorphBallState::Morphed || (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed && x498_gunHolsterState == EGunHolsterState::Drawn)) { x490_gun->ProcessInput(input, mgr); if (x2f8_morphBallState == EPlayerMorphBallState::Morphed && x26c_attachedActor != kInvalidUniqueId) { if (ControlMapper::GetPressInput(ControlMapper::ECommands::TurnLeft, input) || ControlMapper::GetPressInput(ControlMapper::ECommands::TurnRight, input) || ControlMapper::GetPressInput(ControlMapper::ECommands::Forward, input) || ControlMapper::GetPressInput(ControlMapper::ECommands::Backward, input) || ControlMapper::GetPressInput(ControlMapper::ECommands::JumpOrBoost, input)) { xa28_attachedActorStruggle += input.DeltaTime() * 600.f * input.DeltaTime(); if (xa28_attachedActorStruggle > 1.f) { xa28_attachedActorStruggle = 1.f; } } else { const float tmp = 7.5f * input.DeltaTime(); xa28_attachedActorStruggle -= input.DeltaTime() * std::min(1.f, xa28_attachedActorStruggle * tmp + tmp); if (xa28_attachedActorStruggle < 0.f) { xa28_attachedActorStruggle = 0.f; } } } } UpdateCameraState(mgr); UpdateMorphBallState(input.DeltaTime(), input, mgr); UpdateCameraTimers(input.DeltaTime(), input); UpdateFootstepSounds(input, mgr, input.DeltaTime()); x2a8_timeSinceJump += input.DeltaTime(); if (CheckSubmerged()) { SetSoundEventPitchBend(0); } else { SetSoundEventPitchBend(8192); } CalculateLeaveMorphBallDirection(input); } bool CPlayer::ShouldSampleFailsafe(CStateManager& mgr) const { const TCastToConstPtr cineCam = mgr.GetCameraManager()->GetCurrentCamera(mgr); if (!mgr.GetPlayerState()->IsPlayerAlive()) { return false; } return x2f4_cameraState != EPlayerCameraState::Spawned || !cineCam || (cineCam->GetFlags() & 0x80) == 0; } void CPlayer::CalculateLeaveMorphBallDirection(const CFinalInput& input) { if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) { x518_leaveMorphDir = x50c_moveDir; } else { if (ControlMapper::GetAnalogInput(ControlMapper::ECommands::Forward, input) > 0.3f || ControlMapper::GetAnalogInput(ControlMapper::ECommands::Backward, input) > 0.3f || ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnLeft, input) > 0.3f || ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnRight, input) > 0.3f) { if (x138_velocity.magnitude() > 0.5f) { x518_leaveMorphDir = x50c_moveDir; } } } } void CPlayer::CalculatePlayerControlDirection(CStateManager& mgr) { if (x9c4_30_controlDirOverride) { if (x9d8_controlDirOverrideDir.canBeNormalized()) { x540_controlDir = x9d8_controlDirOverrideDir.normalized(); x54c_controlDirFlat = x9d8_controlDirOverrideDir; x54c_controlDirFlat.z() = 0.f; if (x54c_controlDirFlat.canBeNormalized()) { x54c_controlDirFlat.normalize(); } else { x540_controlDir = x54c_controlDirFlat = zeus::skForward; } } else { x540_controlDir = x54c_controlDirFlat = zeus::skForward; } } else { const zeus::CVector3f camToPlayer = GetTranslation() - mgr.GetCameraManager()->GetCurrentCamera(mgr)->GetTranslation(); if (!camToPlayer.canBeNormalized()) { x540_controlDir = x54c_controlDirFlat = zeus::skForward; } else { zeus::CVector3f camToPlayerFlat(camToPlayer.x(), camToPlayer.y(), 0.f); if (camToPlayerFlat.canBeNormalized()) { if (camToPlayerFlat.magnitude() > g_tweakBall->GetBallCameraControlDistance()) { x540_controlDir = camToPlayer.normalized(); if (camToPlayerFlat.canBeNormalized()) { camToPlayerFlat.normalize(); switch (x2f8_morphBallState) { case EPlayerMorphBallState::Morphed: x54c_controlDirFlat = camToPlayerFlat; break; default: x540_controlDir = GetTransform().basis[1]; x54c_controlDirFlat = x540_controlDir; x54c_controlDirFlat.z() = 0.f; if (x54c_controlDirFlat.canBeNormalized()) { x54c_controlDirFlat.normalize(); } } } else if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) { x540_controlDir = GetTransform().basis[1]; x54c_controlDirFlat.z() = 0.f; if (x54c_controlDirFlat.canBeNormalized()) { x54c_controlDirFlat.normalize(); } } } else { if (x4fc_flatMoveSpeed < 0.25f) { x540_controlDir = camToPlayer; x54c_controlDirFlat = camToPlayerFlat; } else if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) { x540_controlDir = GetTransform().basis[1]; x54c_controlDirFlat.z() = 0.f; if (x54c_controlDirFlat.canBeNormalized()) { x54c_controlDirFlat.normalize(); } } } } } } } void CPlayer::CalculatePlayerMovementDirection(float dt) { if (x2f8_morphBallState == EPlayerMorphBallState::Morphing || x2f8_morphBallState == EPlayerMorphBallState::Unmorphing) { return; } const zeus::CVector3f delta = GetTranslation() - x524_lastPosForDirCalc; if (delta.canBeNormalized() && delta.magnitude() > 0.02f) { x53c_timeMoving += dt; x4f8_moveSpeed = std::fabs(delta.magnitude() / dt); x500_lookDir = delta.normalized(); zeus::CVector3f flatDelta(delta.x(), delta.y(), 0.f); if (flatDelta.canBeNormalized()) { x4fc_flatMoveSpeed = std::fabs(flatDelta.magnitude() / dt); flatDelta.normalize(); switch (x2f8_morphBallState) { case EPlayerMorphBallState::Morphed: if (x4fc_flatMoveSpeed > 0.25f) { x50c_moveDir = flatDelta; } x530_gunDir = x50c_moveDir; x524_lastPosForDirCalc = GetTranslation(); break; default: x500_lookDir = GetTransform().basis[1]; x50c_moveDir = x500_lookDir; x50c_moveDir.z() = 0.f; if (x50c_moveDir.canBeNormalized()) { x50c_moveDir.normalize(); } x530_gunDir = x50c_moveDir; x524_lastPosForDirCalc = GetTranslation(); break; } } else { if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) { x500_lookDir = GetTransform().basis[1]; x50c_moveDir = x500_lookDir; x50c_moveDir.z() = 0.f; if (x50c_moveDir.canBeNormalized()) { x50c_moveDir.normalize(); } x530_gunDir = x50c_moveDir; x524_lastPosForDirCalc = GetTranslation(); } x4fc_flatMoveSpeed = 0.f; } } else { x53c_timeMoving = 0.f; switch (x2f8_morphBallState) { case EPlayerMorphBallState::Morphed: case EPlayerMorphBallState::Morphing: case EPlayerMorphBallState::Unmorphing: x500_lookDir = x50c_moveDir; break; default: x500_lookDir = GetTransform().basis[1]; x50c_moveDir = x500_lookDir; x50c_moveDir.z() = 0.f; if (x50c_moveDir.canBeNormalized()) { x50c_moveDir.normalize(); } x530_gunDir = x50c_moveDir; x524_lastPosForDirCalc = GetTranslation(); break; } x4f8_moveSpeed = 0.f; x4fc_flatMoveSpeed = 0.f; } x50c_moveDir.z() = 0.f; if (x50c_moveDir.canBeNormalized()) { x500_lookDir.normalize(); } } void CPlayer::UnFreeze(CStateManager& stateMgr) { if (!GetFrozenState()) { return; } x750_frozenTimeout = 0.f; x754_iceBreakJumps = 0; CPhysicsActor::Stop(); ClearForcesAndTorques(); RemoveMaterial(EMaterialTypes::Immovable, stateMgr); if (!stateMgr.GetCameraManager()->IsInCinematicCamera() && xa0c_iceTextureId.IsValid()) { std::optional> gpsm; gpsm.emplace(g_SimplePool->GetObj(SObjectTag(FOURCC('PART'), xa0c_iceTextureId))); auto* effect = new CHUDBillboardEffect(gpsm, {}, stateMgr.AllocateUniqueId(), true, "FrostExplosion", CHUDBillboardEffect::GetNearClipDistance(stateMgr), CHUDBillboardEffect::GetScaleForPOV(stateMgr), zeus::skWhite, zeus::skOne3f, zeus::skZero3f); stateMgr.AddObject(effect); CSfxHandle hnd = CSfxManager::SfxStart(SFXcrk_break_final, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); } x768_morphball->Stop(); SetVisorSteam(0.f, 6.f / 14.f, 1.f / 14.f, xa08_steamTextureId, false); } void CPlayer::Freeze(CStateManager& stateMgr, CAssetId steamTxtr, u16 sfx, CAssetId iceTxtr) { if (stateMgr.GetCameraManager()->IsInCinematicCamera() || GetFrozenState()) { return; } bool showMsg; if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed) { showMsg = g_GameState->SystemOptions().GetShowFrozenFpsMessage(); } else { showMsg = g_GameState->SystemOptions().GetShowFrozenBallMessage(); } if (showMsg) { const char16_t* msg = g_MainStringTable->GetString(int(x2f8_morphBallState >= EPlayerMorphBallState::Morphed) + 19); const CHUDMemoParms parms(5.f, true, false, false); MP1::CSamusHud::DisplayHudMemo(msg, parms); } x750_frozenTimeout = x758_frozenTimeoutBias + g_tweakPlayer->GetFrozenTimeout(); x754_iceBreakJumps = -x75c_additionalIceBreakJumps; CPhysicsActor::Stop(); ClearForcesAndTorques(); if (x3b8_grappleState != EGrappleState::None) { BreakGrapple(EPlayerOrbitRequest::Freeze, stateMgr); } else { SetOrbitRequest(EPlayerOrbitRequest::Freeze, stateMgr); } AddMaterial(EMaterialTypes::Immovable, stateMgr); xa08_steamTextureId = steamTxtr; xa0c_iceTextureId = iceTxtr; CSfxHandle hnd = CSfxManager::SfxStart(sfx, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); EndLandingControlFreeze(); } bool CPlayer::GetFrozenState() const { return x750_frozenTimeout > 0.f; } void CPlayer::UpdateFrozenState(const CFinalInput& input, CStateManager& mgr) { x750_frozenTimeout -= input.DeltaTime(); if (x750_frozenTimeout > 0.f) { SetVisorSteam(0.7f, 6.f / 14.f, 1.f / 14.f, xa08_steamTextureId, false); } else { UnFreeze(mgr); return; } if (x258_movementState == EPlayerMovementState::OnGround || x258_movementState == EPlayerMovementState::FallingMorphed) { Stop(); ClearForcesAndTorques(); } x7a0_visorSteam.Update(input.DeltaTime()); switch (x2f8_morphBallState) { case EPlayerMorphBallState::Morphed: x490_gun->ProcessInput(input, mgr); break; case EPlayerMorphBallState::Unmorphed: case EPlayerMorphBallState::Morphing: case EPlayerMorphBallState::Unmorphing: if (ControlMapper::GetPressInput(ControlMapper::ECommands::JumpOrBoost, input)) { if (x754_iceBreakJumps != 0) { /* Subsequent Breaks */ CSfxHandle hnd = CSfxManager::SfxStart(SFXcrk_break_subsequent, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); } else { /* Initial Break */ CSfxHandle hnd = CSfxManager::SfxStart(SFXcrk_break_initial, 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); } x754_iceBreakJumps += 1; if (x754_iceBreakJumps > g_tweakPlayer->GetIceBreakJumpCount()) { g_GameState->SystemOptions().IncrementFrozenFpsCount(); const CHUDMemoParms info(0.f, true, true, true); MP1::CSamusHud::DisplayHudMemo(u"", info); UnFreeze(mgr); } } break; } } void CPlayer::UpdateStepCameraZBias(float dt) { float newBias = GetTranslation().z() + GetUnbiasedEyeHeight(); if (x258_movementState == EPlayerMovementState::OnGround && !IsMorphBallTransitioning()) { const float oldBias = newBias; if (!x9c5_31_stepCameraZBiasDirty) { const float delta = newBias - x9cc_stepCameraZBias; float newDelta = 5.f * dt; if (delta > 0.f) { if (delta > dt * x138_velocity.z() && delta > newDelta) { if (delta > GetStepUpHeight()) { newDelta += delta - GetStepUpHeight(); } newBias = x9cc_stepCameraZBias + newDelta; } } else { if (delta < dt * x138_velocity.z() && delta < -newDelta) { if (delta < -GetStepDownHeight()) { newDelta += -delta - GetStepDownHeight(); } newBias = x9cc_stepCameraZBias - newDelta; } } } x9c8_eyeZBias = newBias - oldBias; } else { x9c8_eyeZBias = 0.f; } x9cc_stepCameraZBias = newBias; x9c5_31_stepCameraZBiasDirty = false; } void CPlayer::UpdateWaterSurfaceCameraBias(CStateManager& mgr) { const TCastToConstPtr water = mgr.GetObjectById(xc4_fluidId); if (!water) { return; } const float waterZ = water->GetTriggerBoundsWR().max.z(); const float biasToEyeDelta = GetEyePosition().z() - x9c8_eyeZBias; const float waterToDeltaDelta = biasToEyeDelta - waterZ; if (biasToEyeDelta >= waterZ && waterToDeltaDelta <= 0.25f) { x9c8_eyeZBias += waterZ + 0.25f - biasToEyeDelta; } else if (biasToEyeDelta < waterZ && waterToDeltaDelta >= -0.2f) { x9c8_eyeZBias += waterZ - 0.2f - biasToEyeDelta; } } void CPlayer::UpdateEnvironmentDamageCameraShake(float dt, CStateManager& mgr) { xa2c_damageLoopSfxDelayTicks = std::min(2, xa2c_damageLoopSfxDelayTicks + 1); if (xa10_envDmgCounter == 0) { return; } if (xa14_envDmgCameraShakeTimer == 0.f) { mgr.GetCameraManager()->AddCameraShaker(CCameraShakeData::BuildPhazonCameraShakeData(1.f, 0.075f), false); } xa14_envDmgCameraShakeTimer += dt; if (xa14_envDmgCameraShakeTimer > 2.f) { xa14_envDmgCameraShakeTimer = 0.f; } } void CPlayer::UpdatePhazonDamage(float dt, CStateManager& mgr) { if (x4_areaId == kInvalidAreaId) { return; } const CGameArea* area = mgr.GetWorld()->GetAreaAlways(x4_areaId); if (!area->IsPostConstructed()) { return; } bool touchingPhazon = false; EPhazonType phazonType; if (const CScriptAreaAttributes* attr = area->GetPostConstructed()->x10d8_areaAttributes) { phazonType = attr->GetPhazonType(); } else { phazonType = EPhazonType::None; } if (phazonType == EPhazonType::Orange || (!mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::PhazonSuit) && phazonType == EPhazonType::Blue)) { constexpr CMaterialFilter filter = CMaterialFilter::MakeInclude({EMaterialTypes::Phazon}); if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) { touchingPhazon = x768_morphball->BallCloseToCollision(mgr, 2.9f, filter); } else { constexpr CMaterialList primMaterial(EMaterialTypes::Player, EMaterialTypes::Solid); const CCollidableSphere prim( zeus::CSphere(GetCollisionPrimitive()->CalculateAABox(x34_transform).center(), 4.25f), primMaterial); EntityList nearList; mgr.BuildColliderList(nearList, *this, prim.CalculateLocalAABox()); if (CGameCollision::DetectStaticCollisionBoolean(mgr, prim, zeus::CTransform(), filter)) { touchingPhazon = true; } else { for (const auto& id : nearList) { if (const TCastToConstPtr act = mgr.GetObjectById(id)) { const CInternalCollisionStructure::CPrimDesc prim0(prim, filter, zeus::CTransform()); const CInternalCollisionStructure::CPrimDesc prim1( *act->GetCollisionPrimitive(), CMaterialFilter::skPassEverything, act->GetPrimitiveTransform()); if (CCollisionPrimitive::CollideBoolean(prim0, prim1)) { touchingPhazon = true; break; } } } } } } if (touchingPhazon) { xa18_phazonDamageLag += dt; xa18_phazonDamageLag = std::min(xa18_phazonDamageLag, 3.f); if (xa18_phazonDamageLag > 0.2f) { const float damage = (xa18_phazonDamageLag - 0.2f) / 3.f * 60.f * dt; const CDamageInfo dInfo( CWeaponMode(phazonType == EPhazonType::Orange ? EWeaponType::OrangePhazon : EWeaponType::Phazon), damage, 0.f, 0.f); mgr.ApplyDamage(kInvalidUniqueId, GetUniqueId(), kInvalidUniqueId, dInfo, CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f); } } else { xa18_phazonDamageLag -= dt; xa18_phazonDamageLag = std::min(0.2f, xa18_phazonDamageLag); xa18_phazonDamageLag = std::max(0.f, xa18_phazonDamageLag); } xa1c_threatOverride = std::min(1.f, xa18_phazonDamageLag / 0.2f); } void CPlayer::ResetPlayerHintState() { x9c4_26_ = true; x9c4_27_canEnterMorphBall = true; x9c4_28_canLeaveMorphBall = true; x9c4_30_controlDirOverride = false; x9c6_24_extendTargetDistance = false; x9c6_26_outOfBallLookAtHint = false; x9c4_29_spiderBallControlXY = false; x9c6_29_disableInput = false; x9c7_25_outOfBallLookAtHintActor = false; x768_morphball->SetBoostEnabed(true); ResetControlDirectionInterpolation(); } bool CPlayer::SetAreaPlayerHint(const CScriptPlayerHint& hint, CStateManager& mgr) { x9c4_26_ = (hint.GetOverrideFlags() & 0x1) != 0; x9c4_27_canEnterMorphBall = (hint.GetOverrideFlags() & 0x40) == 0; x9c4_28_canLeaveMorphBall = (hint.GetOverrideFlags() & 0x20) == 0; x9c4_30_controlDirOverride = (hint.GetOverrideFlags() & 0x2) != 0; if (x9c4_30_controlDirOverride) { x9d8_controlDirOverrideDir = hint.GetTransform().basis[1]; } x9c6_24_extendTargetDistance = (hint.GetOverrideFlags() & 0x4) != 0; x9c6_26_outOfBallLookAtHint = (hint.GetOverrideFlags() & 0x8) != 0; x9c4_29_spiderBallControlXY = (hint.GetOverrideFlags() & 0x10) != 0; x9c6_29_disableInput = (hint.GetOverrideFlags() & 0x80) != 0; x9c7_25_outOfBallLookAtHintActor = (hint.GetOverrideFlags() & 0x4000) != 0; x768_morphball->SetBoostEnabed((hint.GetOverrideFlags() & 0x100) == 0); bool switchedVisor = false; if ((hint.GetOverrideFlags() & 0x200) != 0) { if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::CombatVisor)) { mgr.GetPlayerState()->StartTransitionToVisor(CPlayerState::EPlayerVisor::Combat); } switchedVisor = true; } if ((hint.GetOverrideFlags() & 0x400) != 0) { if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::ScanVisor)) { mgr.GetPlayerState()->StartTransitionToVisor(CPlayerState::EPlayerVisor::Scan); } switchedVisor = true; } if ((hint.GetOverrideFlags() & 0x800) != 0) { if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::ThermalVisor)) { mgr.GetPlayerState()->StartTransitionToVisor(CPlayerState::EPlayerVisor::Thermal); } switchedVisor = true; } if ((hint.GetOverrideFlags() & 0x1000) != 0) { if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::XRayVisor)) { mgr.GetPlayerState()->StartTransitionToVisor(CPlayerState::EPlayerVisor::XRay); } switchedVisor = true; } return switchedVisor; } void CPlayer::AddToPlayerHintRemoveList(TUniqueId id, CStateManager& mgr) { const TCastToConstPtr hint = mgr.ObjectById(id); if (!hint) { return; } for (const auto& existId : x93c_playerHintsToRemove) { if (id == existId) { return; } } if (x93c_playerHintsToRemove.size() != 32) { x93c_playerHintsToRemove.push_back(id); } } void CPlayer::AddToPlayerHintAddList(TUniqueId id, CStateManager& mgr) { const TCastToConstPtr hint = mgr.ObjectById(id); if (!hint) { return; } for (const auto& existId : x980_playerHintsToAdd) { if (id == existId) { return; } } if (x980_playerHintsToAdd.size() != 32) { x980_playerHintsToAdd.push_back(id); } } void CPlayer::DeactivatePlayerHint(TUniqueId id, CStateManager& mgr) { const TCastToPtr hint = mgr.ObjectById(id); if (!hint) { return; } for (const auto& existId : x93c_playerHintsToRemove) { if (id == existId) { return; } } if (x93c_playerHintsToRemove.size() != 32) { x93c_playerHintsToRemove.push_back(id); hint->ClearObjectList(); hint->SetDeactivated(); } } void CPlayer::UpdatePlayerHints(CStateManager& mgr) { bool removedHint = false; for (auto it = x838_playerHints.begin(); it != x838_playerHints.end();) { auto& p = *it; const TCastToConstPtr hint = mgr.ObjectById(p.second); if (!hint) { it = x838_playerHints.erase(it); removedHint = true; } else { ++it; } } bool needsNewHint = false; for (const auto& id : x93c_playerHintsToRemove) { for (auto it = x838_playerHints.begin(); it != x838_playerHints.end();) { if (it->second == id) { it = x838_playerHints.erase(it); if (id == x830_playerHint) { needsNewHint = true; } break; } ++it; } } x93c_playerHintsToRemove.clear(); bool addedHint = false; for (const auto& id : x980_playerHintsToAdd) { if (const TCastToConstPtr hint = mgr.ObjectById(id)) { bool exists = false; for (auto& p : x838_playerHints) { if (p.second == id) { exists = true; break; } } if (!exists) { x838_playerHints.emplace_back(hint->GetPriority(), id); addedHint = true; } } } x980_playerHintsToAdd.clear(); if (needsNewHint || addedHint || removedHint) { std::sort(x838_playerHints.begin(), x838_playerHints.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); if ((needsNewHint || removedHint) && x838_playerHints.empty()) { x830_playerHint = kInvalidUniqueId; x834_playerHintPriority = 1000; ResetPlayerHintState(); return; } const CScriptPlayerHint* foundHint = nullptr; bool foundHintInArea = false; for (const auto& p : x838_playerHints) { if (const TCastToConstPtr hint = mgr.ObjectById(p.second)) { foundHint = hint.GetPtr(); if (hint->GetAreaIdAlways() == mgr.GetNextAreaId()) { foundHintInArea = true; break; } } } if (!foundHintInArea) { x830_playerHint = kInvalidUniqueId; x834_playerHintPriority = 1000; ResetPlayerHintState(); } if (foundHint != nullptr && foundHintInArea && x830_playerHint != foundHint->GetUniqueId()) { x830_playerHint = foundHint->GetUniqueId(); x834_playerHintPriority = foundHint->GetPriority(); if (SetAreaPlayerHint(*foundHint, mgr)) { DeactivatePlayerHint(x830_playerHint, mgr); } } } } void CPlayer::UpdateBombJumpStuff() { if (x9d0_bombJumpCount == 0) { return; } x9d4_bombJumpCheckDelayFrames -= 1; if (x9d4_bombJumpCheckDelayFrames > 0) { return; } zeus::CVector3f velFlat = x138_velocity; velFlat.z() = 0.f; if (x258_movementState == EPlayerMovementState::OnGround || (velFlat.canBeNormalized() && velFlat.magnitude() > 6.f)) { x9d0_bombJumpCount = 0; } } void CPlayer::UpdateTransitionFilter(float dt, CStateManager& mgr) { if (x824_transitionFilterTimer <= 0.f) { mgr.GetCameraFilterPass(8).DisableFilter(0.f); return; } x824_transitionFilterTimer += dt; if (x824_transitionFilterTimer > 1.25f) { x824_transitionFilterTimer = 0.f; mgr.GetCameraFilterPass(8).DisableFilter(0.f); return; } if (x824_transitionFilterTimer < 0.95f) { return; } const float time = x824_transitionFilterTimer - 0.95f; zeus::CColor color(1.f, 0.87f, 0.54f, 1.f); if (time < 0.1f) { color.a() = 0.3f * time / 0.1f; } else if (time >= 0.15f) { color.a() = 1.f - zeus::clamp(-1.f, (time - 0.15f) / 0.15f, 1.f) * 0.3f; } else { color.a() = 0.3f; } mgr.GetCameraFilterPass(8).SetFilter(EFilterType::Add, EFilterShape::ScanLinesEven, 0.f, color, {}); } void CPlayer::ResetControlDirectionInterpolation() { x9c6_25_interpolatingControlDir = false; x9f8_controlDirInterpTime = 0.f; } void CPlayer::SetControlDirectionInterpolation(float time) { x9c6_25_interpolatingControlDir = true; x9f8_controlDirInterpTime = 0.f; x9fc_controlDirInterpDur = time; } void CPlayer::UpdatePlayerControlDirection(float dt, CStateManager& mgr) { const zeus::CVector3f oldControlDir = x540_controlDir; const zeus::CVector3f oldControlDirFlat = x54c_controlDirFlat; CalculatePlayerControlDirection(mgr); if (!x9c6_25_interpolatingControlDir || x2f8_morphBallState != EPlayerMorphBallState::Morphed) { return; } x9f8_controlDirInterpTime += dt; if (x9f8_controlDirInterpTime > x9fc_controlDirInterpDur) { x9f8_controlDirInterpTime = x9fc_controlDirInterpDur; ResetControlDirectionInterpolation(); } const float t = zeus::clamp(-1.f, x9f8_controlDirInterpTime / x9fc_controlDirInterpDur, 1.f); x540_controlDir = zeus::CVector3f::lerp(oldControlDir, x540_controlDir, t); x54c_controlDirFlat = zeus::CVector3f::lerp(oldControlDirFlat, x540_controlDir, t); } void CPlayer::Think(float dt, CStateManager& mgr) { UpdateStepCameraZBias(dt); UpdateWaterSurfaceCameraBias(mgr); UpdateEnvironmentDamageCameraShake(dt, mgr); UpdatePhazonDamage(dt, mgr); UpdateFreeLook(dt); UpdatePlayerHints(mgr); if (x2b0_outOfWaterTicks < 2) { x2b0_outOfWaterTicks += 1; } x9c5_24_ = x9c4_24_visorChangeRequested; x9c4_31_inWaterMovement = x9c5_25_splashUpdated; x9c5_25_splashUpdated = false; UpdateBombJumpStuff(); if (0.f < x288_startingJumpTimeout) { x288_startingJumpTimeout -= dt; if (0.f >= x288_startingJumpTimeout) { SetMoveState(EPlayerMovementState::ApplyJump, mgr); } } if (x2a0_ > 0.f) { x2a0_ += dt; } if (x774_samusVoiceTimeout > 0.f) { x774_samusVoiceTimeout -= dt; } if (0.f < x28c_sjTimer) { x28c_sjTimer -= dt; } x300_fallingTime += dt; if (x258_movementState == EPlayerMovementState::FallingMorphed && x300_fallingTime > 0.4f) { SetMoveState(EPlayerMovementState::ApplyJump, mgr); } if (x570_immuneTimer > 0.f) { x570_immuneTimer -= dt; } Update(dt, mgr); UpdateTransitionFilter(dt, mgr); CalculatePlayerMovementDirection(dt); UpdatePlayerControlDirection(dt, mgr); #if 0 if (g_factoryManager == 4) { x2b0_ = 0; } else { x2ac_surfaceRestraint = ESurfaceRestraints::Normal; } #endif if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed && x9c5_27_camSubmerged && mgr.GetCameraManager()->GetFluidCounter() == 0) { if (const auto* water = GetVisorRunoffEffect(mgr)) { if (const auto& effect = water->GetVisorRunoffEffect()) { auto* sheets = new CHUDBillboardEffect( *effect, {}, mgr.AllocateUniqueId(), true, "WaterSheets", CHUDBillboardEffect::GetNearClipDistance(mgr), CHUDBillboardEffect::GetScaleForPOV(mgr), zeus::skWhite, zeus::skOne3f, zeus::skZero3f); mgr.AddObject(sheets); } CSfxHandle hnd = CSfxManager::SfxStart(water->GetVisorRunoffSfx(), 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); } } x9c5_27_camSubmerged = mgr.GetCameraManager()->GetFluidCounter() != 0; if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) { if (std::fabs(x34_transform.basis[0].z()) > FLT_EPSILON || std::fabs(x34_transform.basis[1].z()) > FLT_EPSILON) { const zeus::CVector3f backupTranslation = GetTranslation(); const zeus::CVector3f lookDirFlat(x34_transform.basis[1].x(), x34_transform.basis[1].y(), 0.f); if (lookDirFlat.canBeNormalized()) { SetTransform(zeus::lookAt(zeus::skZero3f, lookDirFlat.normalized())); } else { SetTransform(zeus::CTransform()); } SetTranslation(backupTranslation); } } x794_lastVelocity = x138_velocity; } void CPlayer::PreThink(float dt, CStateManager& mgr) { x558_wasDamaged = false; x55c_damageAmt = 0.f; x560_prevDamageAmt = 0.f; x564_damageLocation = zeus::skZero3f; xa04_preThinkDt = dt; } void CPlayer::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) { switch (msg) { case EScriptObjectMessage::OnFloor: if (x258_movementState != EPlayerMovementState::OnGround && x2f8_morphBallState != EPlayerMorphBallState::Morphed && x300_fallingTime > 0.3f) { if (x258_movementState != EPlayerMovementState::Falling) { float hardThres = 30.f * 2.f * -g_tweakPlayer->GetNormalGravAccel(); hardThres = (hardThres != 0.f) ? hardThres * (1.f / std::sqrt(hardThres)) : 0.f; const float landVol = zeus::clamp(95.f, 1.6f * -x794_lastVelocity.z() + 95.f, 127.f) / 127.f; u16 landSfx; if (-x794_lastVelocity.z() < hardThres) { landSfx = GetMaterialSoundUnderPlayer(mgr, skPlayerLandSfxSoft.data(), skPlayerLandSfxSoft.size(), 0xffff); } else { landSfx = GetMaterialSoundUnderPlayer(mgr, skPlayerLandSfxHard.data(), skPlayerLandSfxHard.size(), 0xffff); StartSamusVoiceSfx(SFXsam_voxland_02, 1.f, 5); x55c_damageAmt = 0.f; x560_prevDamageAmt = 10.f; x564_damageLocation = x34_transform.origin; x558_wasDamaged = true; mgr.GetCameraManager()->AddCameraShaker(CCameraShakeData::BuildLandingCameraShakeData(0.3f, 1.25f), false); StartLandingControlFreeze(); } CSfxHandle handle = CSfxManager::SfxStart(landSfx, landVol, 0.f, false, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(handle); float rumbleMag = -x794_lastVelocity.z() / 110.f; if (rumbleMag > 0.f) { if (std::fabs(rumbleMag) > 0.8f) { rumbleMag = (rumbleMag > 0.f) ? 0.8f : -0.8f; } mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerLand, rumbleMag, ERumblePriority::One); } x2a0_ = 0.f; } } else if (x258_movementState != EPlayerMovementState::OnGround && x2f8_morphBallState == EPlayerMorphBallState::Morphed) { if (x138_velocity.z() < -40.f && !x768_morphball->GetIsInHalfPipeMode() && x258_movementState == EPlayerMovementState::ApplyJump && x300_fallingTime > 0.75f) { SetCoefficientOfRestitutionModifier(0.2f); } x768_morphball->StartLandingSfx(); if (x138_velocity.z() < -5.f) { float rumbleMag = -x138_velocity.z() / 110.f * 0.5f; if (std::fabs(rumbleMag) > 0.8f) { rumbleMag = (rumbleMag > 0.f) ? 0.8f : -0.8f; } mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerLand, rumbleMag, ERumblePriority::One); x2a0_ = 0.f; } if (x138_velocity.z() < -30.f) { float rumbleMag = -x138_velocity.z() / 110.f; if (std::fabs(rumbleMag) > 0.8f) { rumbleMag = (rumbleMag > 0.f) ? 0.8f : -0.8f; } mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerLand, rumbleMag, ERumblePriority::One); x2a0_ = 0.f; } } x300_fallingTime = 0.f; SetMoveState(EPlayerMovementState::OnGround, mgr); break; case EScriptObjectMessage::Falling: if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) { if (x768_morphball->GetSpiderBallState() == CMorphBall::ESpiderBallState::Active) { break; } } if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) { SetMoveState(EPlayerMovementState::Falling, mgr); } else if (x258_movementState == EPlayerMovementState::OnGround) { SetMoveState(EPlayerMovementState::FallingMorphed, mgr); } break; case EScriptObjectMessage::LandOnNotFloor: if (x2f8_morphBallState == EPlayerMorphBallState::Morphed && x768_morphball->GetSpiderBallState() == CMorphBall::ESpiderBallState::Active && x258_movementState != EPlayerMovementState::ApplyJump) { SetMoveState(EPlayerMovementState::ApplyJump, mgr); } break; case EScriptObjectMessage::OnIceSurface: x2ac_surfaceRestraint = ESurfaceRestraints::Ice; break; case EScriptObjectMessage::OnMudSlowSurface: x2ac_surfaceRestraint = ESurfaceRestraints::Organic; break; case EScriptObjectMessage::OnNormalSurface: x2ac_surfaceRestraint = ESurfaceRestraints::Normal; break; case EScriptObjectMessage::InSnakeWeed: x2ac_surfaceRestraint = ESurfaceRestraints::Shrubbery; break; case EScriptObjectMessage::AddSplashInhabitant: { SetInFluid(true, sender); UpdateSubmerged(mgr); const CRayCastResult result = mgr.RayStaticIntersection(x34_transform.origin, zeus::skDown, 0.5f * GetEyeHeight(), SolidMaterialFilter); if (result.IsInvalid()) { SetVelocityWR(x138_velocity * 0.095f); xfc_constantForce *= zeus::CVector3f(0.095f); } break; } case EScriptObjectMessage::UpdateSplashInhabitant: UpdateSubmerged(mgr); if (CheckSubmerged() && !mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GravitySuit)) { if (const TCastToConstPtr water = mgr.ObjectById(xc4_fluidId)) { switch (water->GetFluidPlane().GetFluidType()) { case EFluidType::NormalWater: x2b0_outOfWaterTicks = 0; break; case EFluidType::Lava: case EFluidType::ThickLava: x2ac_surfaceRestraint = ESurfaceRestraints::Lava; break; case EFluidType::PoisonWater: x2b0_outOfWaterTicks = 0; break; case EFluidType::PhazonFluid: x2ac_surfaceRestraint = ESurfaceRestraints::Phazon; break; default: break; } } } x9c5_25_splashUpdated = true; break; case EScriptObjectMessage::RemoveSplashInhabitant: SetInFluid(false, kInvalidUniqueId); UpdateSubmerged(mgr); break; case EScriptObjectMessage::ProjectileCollide: x378_orbitPreventionTimer = g_tweakPlayer->GetOrbitPreventionTime(); SetOrbitRequest(EPlayerOrbitRequest::ProjectileCollide, mgr); break; case EScriptObjectMessage::AddPlatformRider: x82e_ridingPlatform = sender; break; case EScriptObjectMessage::Damage: if (const TCastToConstPtr energ = mgr.ObjectById(sender)) { if (True(energ->GetAttribField() & EProjectileAttrib::StaticInterference)) { mgr.GetPlayerState()->GetStaticInterference().AddSource(GetUniqueId(), 0.3f, energ->GetInterferenceDuration()); } } break; case EScriptObjectMessage::Deleted: mgr.GetPlayerState()->ResetVisor(); x730_transitionModels.clear(); break; default: break; } x490_gun->AcceptScriptMsg(msg, sender, mgr); x768_morphball->AcceptScriptMsg(msg, sender, mgr); CActor::AcceptScriptMsg(msg, sender, mgr); } void CPlayer::SetVisorSteam(float targetAlpha, float alphaInDur, float alphaOutDur, CAssetId txtr, bool affectsThermal) { x7a0_visorSteam.SetSteam(targetAlpha, alphaInDur, alphaOutDur, txtr, affectsThermal); } void CPlayer::UpdateFootstepSounds(const CFinalInput& input, CStateManager& mgr, float dt) { if (x2f8_morphBallState != EPlayerMorphBallState::Unmorphed || x258_movementState != EPlayerMovementState::OnGround || x3dc_inFreeLook || x3dd_lookButtonHeld) { return; } float sfxVol = 1.f; x78c_footstepSfxTimer += dt; float turn = TurnInput(input); const float forward = std::fabs(ForwardInput(input, turn)); turn = std::fabs(turn); float sfxDelay = 0.f; if (forward > 0.05f || x304_orbitState != EPlayerOrbitState::NoOrbit) { const float vel = std::min(1.f, x138_velocity.magnitude() / GetActualFirstPersonMaxVelocity(dt)); if (vel > 0.05f) { sfxDelay = -0.475f * vel + 0.85f; if (x790_footstepSfxSel == EFootstepSfx::None) { x790_footstepSfxSel = EFootstepSfx::Left; } } else { x78c_footstepSfxTimer = 0.f; x790_footstepSfxSel = EFootstepSfx::None; } sfxVol = 0.3f * vel + 0.7f; } else if (turn > 0.05f) { if (x790_footstepSfxSel == EFootstepSfx::Left) { sfxDelay = -0.813f * turn + 1.f; } else { sfxDelay = -2.438f * turn + 3.f; } if (x790_footstepSfxSel == EFootstepSfx::None) { x790_footstepSfxSel = EFootstepSfx::Left; sfxDelay = x78c_footstepSfxTimer; } sfxVol = 0.75f; } else { x78c_footstepSfxTimer = 0.f; x790_footstepSfxSel = EFootstepSfx::None; } if (x790_footstepSfxSel != EFootstepSfx::None && x78c_footstepSfxTimer > sfxDelay) { static float EarHeight = GetEyeHeight() - 0.1f; if (xe6_24_fluidCounter != 0 && x828_distanceUnderWater > 0.f && x828_distanceUnderWater < EarHeight) { if (x82c_inLava) { if (x790_footstepSfxSel == EFootstepSfx::Left) { CSfxHandle hnd = CSfxManager::SfxStart(SFXlav_wlklava_00, sfxVol, 0.f, true, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); } else { CSfxHandle hnd = CSfxManager::SfxStart(SFXlav_wlklava_01, sfxVol, 0.f, true, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); } } else { if (x790_footstepSfxSel == EFootstepSfx::Left) { CSfxHandle hnd = CSfxManager::SfxStart(SFXsam_wlkwater_00, sfxVol, 0.f, true, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); } else { CSfxHandle hnd = CSfxManager::SfxStart(SFXsam_wlkwater_01, sfxVol, 0.f, true, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); } } } else { u16 sfx; if (x790_footstepSfxSel == EFootstepSfx::Left) { sfx = GetMaterialSoundUnderPlayer(mgr, skLeftStepSounds.data(), skLeftStepSounds.size(), 0xffff); } else { sfx = GetMaterialSoundUnderPlayer(mgr, skRightStepSounds.data(), skRightStepSounds.size(), 0xffff); } CSfxHandle hnd = CSfxManager::SfxStart(sfx, sfxVol, 0.f, true, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); } x78c_footstepSfxTimer = 0.f; if (x790_footstepSfxSel == EFootstepSfx::Left) { x790_footstepSfxSel = EFootstepSfx::Right; } else { x790_footstepSfxSel = EFootstepSfx::Left; } } } u16 CPlayer::GetMaterialSoundUnderPlayer(const CStateManager& mgr, const u16* table, size_t length, u16 defId) const { u16 ret = defId; zeus::CAABox aabb = GetBoundingBox(); aabb.accumulateBounds(x34_transform.origin + zeus::skDown); EntityList nearList; mgr.BuildNearList(nearList, aabb, SolidMaterialFilter, nullptr); TUniqueId collideId = kInvalidUniqueId; const CRayCastResult result = mgr.RayWorldIntersection(collideId, x34_transform.origin, zeus::skDown, 1.5f, SolidMaterialFilter, nearList); if (result.IsValid()) { ret = SfxIdFromMaterial(result.GetMaterial(), table, length, defId); } return ret; } u16 CPlayer::SfxIdFromMaterial(const CMaterialList& mat, const u16* idList, size_t tableLen, u16 defId) { u16 id = defId; for (size_t i = 0; i < tableLen; ++i) { if (mat.HasMaterial(EMaterialTypes(i)) && idList[i] != 0xFFFF) { id = idList[i]; } } return id; } void CPlayer::UpdateCrosshairsState(const CFinalInput& input) { x9c4_25_showCrosshairs = ControlMapper::GetDigitalInput(ControlMapper::ECommands::ShowCrosshairs, input); } void CPlayer::UpdateVisorTransition(float dt, CStateManager& mgr) { if (mgr.GetPlayerState()->GetIsVisorTransitioning()) { mgr.GetPlayerState()->UpdateVisorTransition(dt); } } void CPlayer::UpdateVisorState(const CFinalInput& input, float dt, CStateManager& mgr) { x7a0_visorSteam.Update(dt); if (x7a0_visorSteam.AffectsThermal()) { mgr.SetThermalColdScale2(mgr.GetThermalColdScale2() + x7a0_visorSteam.GetAlpha()); } if (x304_orbitState == EPlayerOrbitState::Grapple || TCastToPtr(mgr.ObjectById(x310_orbitTargetId)) || x2f8_morphBallState != EPlayerMorphBallState::Unmorphed || mgr.GetPlayerState()->GetIsVisorTransitioning() || x3a8_scanState != EPlayerScanState::NotScanning) { return; } if (mgr.GetPlayerState()->GetTransitioningVisor() == CPlayerState::EPlayerVisor::Scan && (ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input) || ControlMapper::GetDigitalInput(ControlMapper::ECommands::MissileOrPowerBomb, input)) && mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::CombatVisor)) { mgr.GetPlayerState()->StartTransitionToVisor(CPlayerState::EPlayerVisor::Combat); DrawGun(mgr); } for (size_t i = 0; i < skVisorToItemMapping.size(); ++i) { const auto mapping = skVisorToItemMapping[i]; if (mgr.GetPlayerState()->HasPowerUp(mapping.first) && ControlMapper::GetPressInput(mapping.second, input)) { x9c4_24_visorChangeRequested = true; const auto visor = CPlayerState::EPlayerVisor(i); if (mgr.GetPlayerState()->GetTransitioningVisor() != visor) { mgr.GetPlayerState()->StartTransitionToVisor(visor); if (visor == CPlayerState::EPlayerVisor::Scan) { HolsterGun(mgr); } else { DrawGun(mgr); } } } } } void CPlayer::UpdateGunState(const CFinalInput& input, CStateManager& mgr) { switch (x498_gunHolsterState) { case EGunHolsterState::Drawn: { bool needsHolster = false; if (g_tweakPlayer->GetGunButtonTogglesHolster()) { if (ControlMapper::GetPressInput(ControlMapper::ECommands::ToggleHolster, input)) { needsHolster = true; } if (!ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input) && !ControlMapper::GetDigitalInput(ControlMapper::ECommands::MissileOrPowerBomb, input) && g_tweakPlayer->GetGunNotFiringHolstersGun()) { x49c_gunHolsterRemTime -= input.DeltaTime(); if (x49c_gunHolsterRemTime <= 0.f) { needsHolster = true; } } } else { if (!ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input) && !ControlMapper::GetDigitalInput(ControlMapper::ECommands::MissileOrPowerBomb, input) && x490_gun->IsFidgeting()) { if (g_tweakPlayer->GetGunNotFiringHolstersGun()) { x49c_gunHolsterRemTime -= input.DeltaTime(); } } else { x49c_gunHolsterRemTime = g_tweakPlayerGun->GetGunNotFiringTime(); } } if (needsHolster) { HolsterGun(mgr); } break; } case EGunHolsterState::Drawing: { if (x49c_gunHolsterRemTime > 0.f) { x49c_gunHolsterRemTime -= input.DeltaTime(); } else { x498_gunHolsterState = EGunHolsterState::Drawn; x49c_gunHolsterRemTime = g_tweakPlayerGun->GetGunNotFiringTime(); } break; } case EGunHolsterState::Holstered: { bool needsDraw = false; if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input) || ControlMapper::GetDigitalInput(ControlMapper::ECommands::MissileOrPowerBomb, input) || x3b8_grappleState == EGrappleState::None || (g_tweakPlayer->GetGunButtonTogglesHolster() && ControlMapper::GetPressInput(ControlMapper::ECommands::ToggleHolster, input))) { needsDraw = true; } if (x3b8_grappleState == EGrappleState::None && (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan || mgr.GetPlayerState()->GetTransitioningVisor() == CPlayerState::EPlayerVisor::Scan)) { needsDraw = false; } if (needsDraw) { DrawGun(mgr); } break; } case EGunHolsterState::Holstering: if (x49c_gunHolsterRemTime > 0.f) { x49c_gunHolsterRemTime -= input.DeltaTime(); } else { x498_gunHolsterState = EGunHolsterState::Holstered; } break; } } void CPlayer::ResetGun(CStateManager& mgr) { x498_gunHolsterState = EGunHolsterState::Holstered; x49c_gunHolsterRemTime = 0.f; x490_gun->CancelFiring(mgr); ResetAimTargetPrediction(kInvalidUniqueId); } void CPlayer::UpdateArmAndGunTransforms(float dt, CStateManager& mgr) { zeus::CVector3f grappleOffset; zeus::CVector3f gunOffset; if (x2f8_morphBallState == EPlayerMorphBallState::Morphed) { gunOffset = {0.f, 0.f, 0.6f}; } else { gunOffset = g_tweakPlayerGun->GetGunPosition(); grappleOffset = x490_gun->GetGrappleArm().IsArmMoving() ? zeus::skZero3f : g_tweakPlayerGun->GetGrapplingArmPosition(); gunOffset.z() += GetEyeHeight(); grappleOffset.z() += GetEyeHeight(); } UpdateGunTransform(gunOffset + x76c_cameraBob->GetGunBobTransformation().origin, mgr); UpdateGrappleArmTransform(grappleOffset, mgr, dt); } void CPlayer::ForceGunOrientation(const zeus::CTransform& xf, CStateManager& mgr) { ResetGun(mgr); x530_gunDir = xf.basis[1]; x490_gun->SetTransform(xf); UpdateArmAndGunTransforms(0.01f, mgr); } void CPlayer::UpdateCameraState(CStateManager& mgr) { UpdateCinematicState(mgr); } void CPlayer::UpdateDebugCamera(CStateManager& mgr) { // Empty } void CPlayer::UpdateCameraTimers(float dt, const CFinalInput& input) { if (x3dc_inFreeLook || x3dd_lookButtonHeld) { x294_jumpCameraTimer = 0.f; x29c_fallCameraTimer = 0.f; return; } if (g_tweakPlayer->GetFiringCancelsCameraPitch()) { if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input) || ControlMapper::GetDigitalInput(ControlMapper::ECommands::MissileOrPowerBomb, input)) { if (x288_startingJumpTimeout > 0.f) { x2a4_cancelCameraPitch = true; return; } } } if (ControlMapper::GetPressInput(ControlMapper::ECommands::JumpOrBoost, input)) { ++x298_jumpPresses; } if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::JumpOrBoost, input) && x294_jumpCameraTimer > 0.f && !x2a4_cancelCameraPitch && x298_jumpPresses <= 2) { x294_jumpCameraTimer += dt; } if (x29c_fallCameraTimer > 0.f && !x2a4_cancelCameraPitch) { x29c_fallCameraTimer += dt; } } void CPlayer::UpdateMorphBallState(float dt, const CFinalInput& input, CStateManager& mgr) { if (!ControlMapper::GetPressInput(ControlMapper::ECommands::Morph, input)) { return; } switch (x2f8_morphBallState) { case EPlayerMorphBallState::Unmorphed: if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::MorphBall) && CanEnterMorphBallState(mgr, 0.f)) { x574_morphTime = 0.f; x578_morphDuration = 1.f; TransitionToMorphBallState(dt, mgr); } else { CSfxHandle hnd = CSfxManager::SfxStart(SFXwpn_invalid_action, 1.f, 0.f, true, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); } break; case EPlayerMorphBallState::Morphed: { zeus::CVector3f posDelta; if (CanLeaveMorphBallState(mgr, posDelta)) { SetTranslation(x34_transform.origin + posDelta); x574_morphTime = 0.f; x578_morphDuration = 1.f; TransitionFromMorphBallState(mgr); } else { CSfxHandle hnd = CSfxManager::SfxStart(SFXwpn_invalid_action, 1.f, 0.f, true, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); } break; } default: break; } } CFirstPersonCamera& CPlayer::GetFirstPersonCamera(CStateManager& mgr) { return *mgr.GetCameraManager()->GetFirstPersonCamera(); } void CPlayer::UpdateGunTransform(const zeus::CVector3f& gunPos, CStateManager& mgr) { const float eyeHeight = GetEyeHeight(); const zeus::CTransform camXf = mgr.GetCameraManager()->GetCurrentCameraTransform(mgr); zeus::CTransform gunXf = camXf; zeus::CVector3f viewGunPos; if (x2f8_morphBallState == EPlayerMorphBallState::Morphing) { viewGunPos = camXf * (gunPos - zeus::CVector3f(0.f, 0.f, eyeHeight)); } else { viewGunPos = camXf.rotate(gunPos - zeus::CVector3f(0.f, 0.f, eyeHeight)) + GetEyePosition(); } const zeus::CUnitVector3f rightDir(gunXf.basis[0]); gunXf.origin = viewGunPos; switch (x498_gunHolsterState) { case EGunHolsterState::Drawing: { const float liftAngle = zeus::clamp(-1.f, x49c_gunHolsterRemTime / 0.45f, 1.f); if (liftAngle > 0.01f) { gunXf = zeus::CQuaternion::fromAxisAngle(rightDir, -liftAngle * g_tweakPlayerGun->GetFixedVerticalAim()) .toTransform() * camXf.getRotation(); gunXf.origin = viewGunPos; } break; } case EGunHolsterState::Holstered: { gunXf = zeus::CQuaternion::fromAxisAngle(rightDir, -g_tweakPlayerGun->GetFixedVerticalAim()).toTransform() * camXf.getRotation(); gunXf.origin = viewGunPos; break; } case EGunHolsterState::Holstering: { float liftAngle = 1.f - zeus::clamp(-1.f, x49c_gunHolsterRemTime / g_tweakPlayerGun->GetGunHolsterTime(), 1.f); if (x2f8_morphBallState == EPlayerMorphBallState::Morphing) { liftAngle = 1.f - zeus::clamp(-1.f, x49c_gunHolsterRemTime / 0.1f, 1.f); } if (liftAngle > 0.01f) { gunXf = zeus::CQuaternion::fromAxisAngle(rightDir, -liftAngle * g_tweakPlayerGun->GetFixedVerticalAim()) .toTransform() * camXf.getRotation(); gunXf.origin = viewGunPos; } break; } default: break; } x490_gun->SetTransform(gunXf); UpdateAimTargetPrediction(gunXf, mgr); UpdateAssistedAiming(gunXf, mgr); } void CPlayer::UpdateAssistedAiming(const zeus::CTransform& xf, const CStateManager& mgr) { zeus::CTransform assistXf = xf; const TCastToConstPtr target = mgr.GetObjectById(x3f4_aimTarget); if (target) { zeus::CVector3f gunToTarget = x480_assistedTargetAim - xf.origin; zeus::CVector3f gunToTargetFlat = gunToTarget; gunToTargetFlat.z() = 0.f; const float gunToTargetFlatMag = gunToTargetFlat.magnitude(); zeus::CVector3f gunDirFlat = xf.basis[1]; gunDirFlat.z() = 0.f; const float gunDirFlatMag = gunDirFlat.magnitude(); if (gunToTargetFlat.canBeNormalized() && gunDirFlat.canBeNormalized()) { gunToTargetFlat = gunToTargetFlat / gunToTargetFlatMag; gunDirFlat = gunDirFlat / gunDirFlatMag; float vAngleDelta = std::atan2(gunToTarget.z(), gunToTargetFlatMag) - std::atan2(xf.basis[1].z(), gunDirFlatMag); bool hasVAngleDelta = true; if (!x9c6_27_aimingAtProjectile && std::fabs(vAngleDelta) > g_tweakPlayer->GetAimAssistVerticalAngle()) { if (g_tweakPlayer->GetAssistedAimingIgnoreVertical()) { vAngleDelta = 0.f; hasVAngleDelta = false; } else if (vAngleDelta > 0.f) { vAngleDelta = g_tweakPlayer->GetAimAssistVerticalAngle(); } else { vAngleDelta = -g_tweakPlayer->GetAimAssistVerticalAngle(); } } const bool targetToLeft = gunDirFlat.cross(gunToTargetFlat).z() > 0.f; float hAngleDelta = std::acos(zeus::clamp(-1.f, gunDirFlat.dot(gunToTargetFlat), 1.f)); bool hasHAngleDelta = true; if (!x9c6_27_aimingAtProjectile && std::fabs(hAngleDelta) > g_tweakPlayer->GetAimAssistHorizontalAngle()) { hAngleDelta = g_tweakPlayer->GetAimAssistHorizontalAngle(); if (g_tweakPlayer->GetAssistedAimingIgnoreHorizontal()) { hAngleDelta = 0.f; hasHAngleDelta = false; } } if (targetToLeft) hAngleDelta = -hAngleDelta; if (!hasVAngleDelta || !hasHAngleDelta) { vAngleDelta = 0.f; hAngleDelta = 0.f; } gunToTarget.x() = std::sin(hAngleDelta) * std::cos(vAngleDelta); gunToTarget.y() = std::cos(hAngleDelta) * std::cos(vAngleDelta); gunToTarget.z() = std::sin(vAngleDelta); gunToTarget = xf.rotate(gunToTarget); assistXf = zeus::lookAt(zeus::skZero3f, gunToTarget, zeus::skUp); } } x490_gun->SetAssistAimTransform(assistXf); } void CPlayer::UpdateAimTargetPrediction(const zeus::CTransform& xf, const CStateManager& mgr) { if (x3f4_aimTarget == kInvalidUniqueId) { return; } const TCastToConstPtr target = mgr.GetObjectById(x3f4_aimTarget); if (!target) { return; } x9c6_27_aimingAtProjectile = TCastToConstPtr(target.GetPtr()).IsValid(); const zeus::CVector3f instantTarget = target->GetAimPosition(mgr, 0.f); const zeus::CVector3f gunToTarget = instantTarget - xf.origin; const float timeToTarget = gunToTarget.magnitude() / x490_gun->GetBeamVelocity(); const zeus::CVector3f predictTarget = target->GetAimPosition(mgr, timeToTarget); const zeus::CVector3f predictOffset = predictTarget - instantTarget; x3f8_targetAimPosition = instantTarget; if (predictOffset.magnitude() < 0.1f) { x404_aimTargetAverage.AddValue(zeus::skZero3f); } else { x404_aimTargetAverage.AddValue(predictOffset); } if (auto avg = x404_aimTargetAverage.GetAverage()) { x480_assistedTargetAim = instantTarget + *avg; } else { x480_assistedTargetAim = predictTarget; } } void CPlayer::ResetAimTargetPrediction(TUniqueId target) { if (target == kInvalidUniqueId || x3f4_aimTarget != target) { x404_aimTargetAverage.Clear(); } x3f4_aimTarget = target; } void CPlayer::DrawGun(CStateManager& mgr) { if (x498_gunHolsterState != EGunHolsterState::Holstered || InGrappleJumpCooldown()) { return; } x498_gunHolsterState = EGunHolsterState::Drawing; x49c_gunHolsterRemTime = 0.45f; x490_gun->ResetIdle(mgr); } void CPlayer::HolsterGun(CStateManager& mgr) { if (x498_gunHolsterState == EGunHolsterState::Holstered || x498_gunHolsterState == EGunHolsterState::Holstering) { return; } const float time = x2f8_morphBallState == EPlayerMorphBallState::Morphing ? 0.1f : g_tweakPlayerGun->GetGunHolsterTime(); if (x498_gunHolsterState == EGunHolsterState::Drawing) { x49c_gunHolsterRemTime = time * (1.f - x49c_gunHolsterRemTime / 0.45f); } else { x49c_gunHolsterRemTime = time; } x498_gunHolsterState = EGunHolsterState::Holstering; x490_gun->CancelFiring(mgr); ResetAimTargetPrediction(kInvalidUniqueId); } bool CPlayer::IsMorphBallTransitioning() const { switch (x2f8_morphBallState) { case EPlayerMorphBallState::Morphing: case EPlayerMorphBallState::Unmorphing: return true; default: return false; } } void CPlayer::UpdateGrappleArmTransform(const zeus::CVector3f& offset, CStateManager& mgr, float dt) { zeus::CTransform armXf = x34_transform; zeus::CVector3f armPosition = x34_transform.rotate(offset) + x34_transform.origin; armXf.origin = armPosition; if (x2f8_morphBallState != EPlayerMorphBallState::Unmorphed) { x490_gun->GetGrappleArm().SetTransform(armXf); } else if (!x490_gun->GetGrappleArm().IsArmMoving()) { zeus::CVector3f lookDir = x34_transform.basis[1]; zeus::CVector3f armToTarget = x490_gun->GetGrappleArm().GetTransform().basis[1]; if (lookDir.canBeNormalized()) { lookDir.normalize(); if (x3b8_grappleState != EGrappleState::None) { if (const TCastToConstPtr target = mgr.ObjectById(x310_orbitTargetId)) { armToTarget = target->GetTranslation() - armPosition; zeus::CVector3f armToTargetFlat = armToTarget; armToTargetFlat.z() = 0.f; if (armToTarget.canBeNormalized()) { armToTarget.normalize(); } if (armToTargetFlat.canBeNormalized() && x3b8_grappleState != EGrappleState::Firing) { const zeus::CQuaternion adjRot = zeus::CQuaternion::lookAt(armToTargetFlat.normalized(), lookDir, 2.f * M_PIF); armToTarget = adjRot.transform(armToTarget); if (x3bc_grappleSwingTimer >= 0.25f * g_tweakPlayer->GetGrappleSwingPeriod() && x3bc_grappleSwingTimer < 0.75f * g_tweakPlayer->GetGrappleSwingPeriod()) { armToTarget = x490_gun->GetGrappleArm().GetTransform().basis[1]; } } } } armXf = zeus::lookAt(zeus::skZero3f, armToTarget, zeus::skUp); armXf.origin = armPosition; x490_gun->GetGrappleArm().SetTransform(armXf); } } } float CPlayer::GetGravity() const { if (!g_GameState->GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GravitySuit) && CheckSubmerged()) { return g_tweakPlayer->GetFluidGravAccel(); } if (x37c_sidewaysDashing) { return -100.f; } return g_tweakPlayer->GetNormalGravAccel(); } void CPlayer::ApplyGrappleForces(const CFinalInput& input, CStateManager& mgr, float dt) { if (const TCastToConstPtr point = mgr.ObjectById(x310_orbitTargetId)) { switch (x3b8_grappleState) { case EGrappleState::Pull: { const zeus::CVector3f playerToPoint = point->GetTranslation() - GetTranslation(); if (playerToPoint.canBeNormalized()) { zeus::CVector3f playerToSwingLow = point->GetTranslation() + zeus::CVector3f(0.f, 0.f, -g_tweakPlayer->GetGrappleSwingLength()) - GetTranslation(); if (playerToSwingLow.canBeNormalized()) { const float distToSwingLow = playerToSwingLow.magnitude(); playerToSwingLow.normalize(); const float timeToLow = zeus::clamp(-1.f, distToSwingLow / g_tweakPlayer->GetGrapplePullSpeedProportion(), 1.f); const float pullSpeed = timeToLow * (g_tweakPlayer->GetGrapplePullSpeedMax() - g_tweakPlayer->GetGrapplePullSpeedMin()) + g_tweakPlayer->GetGrapplePullSpeedMin(); SetVelocityWR(playerToSwingLow * pullSpeed); if (distToSwingLow < g_tweakPlayer->GetMaxGrappleLockedTurnAlignDistance()) { x3b8_grappleState = EGrappleState::Swinging; x3bc_grappleSwingTimer = 0.25f * g_tweakPlayer->GetGrappleSwingPeriod(); x3d8_grappleJumpTimeout = 0.f; x9c6_28_aligningGrappleSwingTurn = point->GetGrappleParameters().GetLockSwingTurn(); } else { const CMotionState mState = PredictMotion(dt); zeus::CVector3f lookDirFlat = x34_transform.basis[1]; lookDirFlat.z() = 0.f; zeus::CVector3f newPlayerToPointFlat = point->GetTranslation() - (GetTranslation() + mState.x0_translation); newPlayerToPointFlat.z() = 0.f; if (lookDirFlat.canBeNormalized()) { lookDirFlat.normalize(); } if (newPlayerToPointFlat.canBeNormalized()) { newPlayerToPointFlat.normalize(); } const float lookToPointAngle = std::acos(zeus::clamp(-1.f, lookDirFlat.dot(newPlayerToPointFlat), 1.f)); if (lookToPointAngle > 0.001f) { float deltaAngle = dt * g_tweakPlayer->GetGrappleLookCenterSpeed(); if (lookToPointAngle >= deltaAngle) { zeus::CVector3f leftDirFlat(lookDirFlat.y(), -lookDirFlat.x(), 0.f); if (leftDirFlat.canBeNormalized()) { leftDirFlat.normalize(); } if (newPlayerToPointFlat.dot(leftDirFlat) >= 0.f) { deltaAngle = -deltaAngle; } RotateToOR(zeus::CQuaternion::fromAxisAngle(zeus::skUp, deltaAngle), dt); } else if (std::fabs(lookToPointAngle - M_PIF) > 0.001f) { RotateToOR(zeus::CQuaternion::shortestRotationArc(lookDirFlat, newPlayerToPointFlat), dt); } } else { SetAngularVelocityWR(zeus::CAxisAngle()); x174_torque = zeus::CAxisAngle(); } } } else { x3b8_grappleState = EGrappleState::Swinging; x3bc_grappleSwingTimer = 0.25f * g_tweakPlayer->GetGrappleSwingPeriod(); x3d8_grappleJumpTimeout = 0.f; } } break; } case EGrappleState::Swinging: { float turnAngleSpeed = zeus::degToRad(g_tweakPlayer->GetMaxGrappleTurnSpeed()); if (g_tweakPlayer->GetInvertGrappleTurn()) { turnAngleSpeed *= -1.f; } const zeus::CVector3f pointToPlayer = GetTranslation() - point->GetTranslation(); const float pointToPlayerZProj = zeus::clamp(-1.f, std::fabs(pointToPlayer.z() / pointToPlayer.magnitude()), 1.f); bool enableTurn = false; if (!point->GetGrappleParameters().GetLockSwingTurn()) { if (ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnLeft, input) > 0.05f) { enableTurn = true; turnAngleSpeed *= -ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnLeft, input); } if (ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnRight, input) > 0.05f) { enableTurn = true; turnAngleSpeed *= ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnRight, input); } } else if (x9c6_28_aligningGrappleSwingTurn) { enableTurn = true; } x3bc_grappleSwingTimer += dt; if (x3bc_grappleSwingTimer > g_tweakPlayer->GetGrappleSwingPeriod()) { x3bc_grappleSwingTimer -= g_tweakPlayer->GetGrappleSwingPeriod(); } zeus::CVector3f swingAxis = x3c0_grappleSwingAxis; if (x3bc_grappleSwingTimer < 0.5f * g_tweakPlayer->GetGrappleSwingPeriod()) { swingAxis *= zeus::skNegOne3f; } const float pullSpeed = std::fabs(zeus::clamp( -1.f, std::cos(2.f * M_PIF * (x3bc_grappleSwingTimer / g_tweakPlayer->GetGrappleSwingPeriod()) + (M_PIF / 2.f)), 1.f)) * g_tweakPlayer->GetGrapplePullSpeedMin(); zeus::CVector3f pullVec = pointToPlayer.normalized().cross(swingAxis) * pullSpeed; pullVec += pointToPlayer * zeus::clamp(-1.f, (pointToPlayer.magnitude() - g_tweakPlayer->GetGrappleSwingLength()) / g_tweakPlayer->GetGrappleSwingLength(), 1.f) * -32.f * pointToPlayerZProj; const zeus::CVector3f backupVel = x138_velocity; SetVelocityWR(pullVec); const zeus::CTransform backupXf = x34_transform; const CMotionState predMotion = PredictMotion(dt); const zeus::CVector3f newPos = x34_transform.origin + predMotion.x0_translation; if (ValidateFPPosition(newPos, mgr)) { if (enableTurn) { zeus::CQuaternion turnRot; turnRot.rotateZ(turnAngleSpeed * dt); if (point->GetGrappleParameters().GetLockSwingTurn() && x9c6_28_aligningGrappleSwingTurn) { zeus::CVector3f pointDir = point->GetTransform().basis[1].normalized(); const zeus::CVector3f playerDir = x34_transform.basis[1].normalized(); float playerPointProj = zeus::clamp(-1.f, playerDir.dot(pointDir), 1.f); if (std::fabs(playerPointProj) == 1.f) { x9c6_28_aligningGrappleSwingTurn = false; } if (playerPointProj < 0.f) { playerPointProj = -playerPointProj; pointDir = -pointDir; } const float turnAngleAdj = std::acos(playerPointProj) * dt; turnRot = zeus::CQuaternion::lookAt(playerDir, pointDir, turnAngleAdj); } if (pointToPlayer.magSquared() > 0.04f) { zeus::CVector3f pointToPlayerFlat = pointToPlayer; pointToPlayerFlat.z() = 0.f; zeus::CVector3f pointAtPlayerHeight = point->GetTranslation(); pointAtPlayerHeight.z() = GetTranslation().z(); const zeus::CVector3f playerToGrapplePlane = pointAtPlayerHeight + turnRot.transform(pointToPlayerFlat) - GetTranslation(); if (playerToGrapplePlane.canBeNormalized()) { pullVec += playerToGrapplePlane / dt; } } const zeus::CVector3f swingAxisBackup = x3c0_grappleSwingAxis; x3c0_grappleSwingAxis = turnRot.transform(x3c0_grappleSwingAxis); x3c0_grappleSwingAxis.normalize(); const zeus::CVector3f swingForward(-x3c0_grappleSwingAxis.y(), x3c0_grappleSwingAxis.x(), 0.f); SetTransform(zeus::CTransform(x3c0_grappleSwingAxis, swingForward, zeus::skUp, GetTranslation())); SetVelocityWR(pullVec); if (!ValidateFPPosition(GetTranslation(), mgr)) { x3c0_grappleSwingAxis = swingAxisBackup; SetTransform(backupXf); SetVelocityWR(backupVel); } } } else { BreakGrapple(EPlayerOrbitRequest::InvalidateTarget, mgr); } break; } case EGrappleState::JumpOff: { const zeus::CVector3f gravForce = {0.f, 0.f, GetGravity() * xe8_mass}; ApplyForceOR(gravForce, zeus::CAxisAngle()); break; } default: break; } } const zeus::CVector3f newAngVel = {0.f, 0.f, 0.9f * GetAngularVelocityOR().getVector().z()}; SetAngularVelocityOR(newAngVel); } bool CPlayer::ValidateFPPosition(const zeus::CVector3f& pos, const CStateManager& mgr) const { constexpr CMaterialFilter solidFilter = CMaterialFilter::MakeInclude({EMaterialTypes::Solid}); const zeus::CAABox aabb(x2d8_fpBounds.min - 1.f + pos, x2d8_fpBounds.max + 1.f + pos); EntityList nearList; mgr.BuildColliderList(nearList, *this, aabb); const CCollidableAABox colAABB({GetBaseBoundingBox().min + pos, GetBaseBoundingBox().max + pos}, {}); return !CGameCollision::DetectCollisionBoolean(mgr, colAABB, zeus::CTransform(), solidFilter, nearList); } void CPlayer::UpdateGrappleState(const CFinalInput& input, CStateManager& mgr) { if (!mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GrappleBeam) || x2f8_morphBallState == EPlayerMorphBallState::Morphed || mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan || mgr.GetPlayerState()->GetTransitioningVisor() == CPlayerState::EPlayerVisor::Scan) { return; } if (x310_orbitTargetId == kInvalidUniqueId) { x3b8_grappleState = EGrappleState::None; AddMaterial(EMaterialTypes::GroundCollider, mgr); return; } const TCastToConstPtr point = mgr.ObjectById(x310_orbitTargetId); if (point) { const zeus::CVector3f eyePosition = GetEyePosition(); zeus::CVector3f playerToPoint = point->GetTranslation() - eyePosition; zeus::CVector3f playerToPointFlat = playerToPoint; playerToPointFlat.z() = 0.f; if (playerToPoint.canBeNormalized() && playerToPointFlat.canBeNormalized() && playerToPointFlat.magnitude() > 2.f) { switch (x304_orbitState) { case EPlayerOrbitState::Grapple: switch (g_tweakPlayer->GetGrappleJumpMode()) { case 0: case 1: if (ControlMapper::GetPressInput(ControlMapper::ECommands::FireOrBomb, input)) { if (const TCastToConstPtr point2 = mgr.ObjectById(x33c_orbitNextTargetId)) { playerToPoint = point2->GetTranslation() - eyePosition; playerToPoint.z() = 0.f; if (playerToPoint.canBeNormalized()) { x490_gun->GetGrappleArm().GrappleBeamDisconnected(); x3c0_grappleSwingAxis.x() = float(playerToPoint.y()); x3c0_grappleSwingAxis.y() = -playerToPoint.x(); x3c0_grappleSwingAxis.normalize(); x3bc_grappleSwingTimer = 0.f; SetOrbitTargetId(x33c_orbitNextTargetId, mgr); x3b8_grappleState = EGrappleState::Pull; x33c_orbitNextTargetId = kInvalidUniqueId; x490_gun->GetGrappleArm().GrappleBeamConnected(); } } else { if (g_tweakPlayer->GetGrappleJumpMode() == 0 && x3d8_grappleJumpTimeout <= 0.f) { ApplyGrappleJump(mgr); } BreakGrapple(EPlayerOrbitRequest::StopOrbit, mgr); } } break; default: break; } break; case EPlayerOrbitState::OrbitObject: if (playerToPoint.canBeNormalized()) { const CRayCastResult result = mgr.RayStaticIntersection(eyePosition, playerToPoint.normalized(), playerToPoint.magnitude(), LineOfSightFilter); if (result.IsInvalid()) { HolsterGun(mgr); switch (x3b8_grappleState) { case EGrappleState::Firing: case EGrappleState::Swinging: switch (g_tweakPlayer->GetGrappleJumpMode()) { case 0: switch (x490_gun->GetGrappleArm().GetAnimState()) { case CGrappleArm::EArmState::IntoGrappleIdle: if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input)) { x490_gun->GetGrappleArm().SetAnimState(CGrappleArm::EArmState::FireGrapple); } break; case CGrappleArm::EArmState::Connected: BeginGrapple(playerToPoint, mgr); break; default: break; } break; case 1: if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input)) { switch (x490_gun->GetGrappleArm().GetAnimState()) { case CGrappleArm::EArmState::IntoGrappleIdle: x490_gun->GetGrappleArm().SetAnimState(CGrappleArm::EArmState::FireGrapple); break; case CGrappleArm::EArmState::Connected: BeginGrapple(playerToPoint, mgr); break; default: break; } break; } break; case 2: switch (x490_gun->GetGrappleArm().GetAnimState()) { case CGrappleArm::EArmState::IntoGrappleIdle: x490_gun->GetGrappleArm().SetAnimState(CGrappleArm::EArmState::FireGrapple); break; case CGrappleArm::EArmState::Connected: BeginGrapple(playerToPoint, mgr); break; default: break; } break; default: break; } break; case EGrappleState::None: x3b8_grappleState = EGrappleState::Firing; x490_gun->GetGrappleArm().Activate(true); break; default: break; } } } break; default: break; } } } if (x304_orbitState != EPlayerOrbitState::Grapple) { if (x304_orbitState >= EPlayerOrbitState::Grapple) { return; } if (x304_orbitState != EPlayerOrbitState::OrbitObject) { return; } } else { if (!point) { BreakGrapple(EPlayerOrbitRequest::Default, mgr); return; } switch (g_tweakPlayer->GetGrappleJumpMode()) { case 0: if (x3b8_grappleState == EGrappleState::JumpOff) { x3d8_grappleJumpTimeout -= input.DeltaTime(); if (x3d8_grappleJumpTimeout <= 0.f) { BreakGrapple(EPlayerOrbitRequest::StopOrbit, mgr); SetMoveState(EPlayerMovementState::ApplyJump, mgr); ComputeMovement(input, mgr, input.DeltaTime()); PreventFallingCameraPitch(); } } break; case 1: switch (x3b8_grappleState) { case EGrappleState::Swinging: if (!ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input) && x3d8_grappleJumpTimeout <= 0.f) { x3d8_grappleJumpTimeout = g_tweakPlayer->GetGrappleReleaseTime(); x3b8_grappleState = EGrappleState::JumpOff; ApplyGrappleJump(mgr); } break; case EGrappleState::JumpOff: x3d8_grappleJumpTimeout -= input.DeltaTime(); if (x3d8_grappleJumpTimeout <= 0.f) { SetMoveState(EPlayerMovementState::ApplyJump, mgr); ComputeMovement(input, mgr, input.DeltaTime()); BreakGrapple(EPlayerOrbitRequest::StopOrbit, mgr); PreventFallingCameraPitch(); } break; case EGrappleState::Firing: case EGrappleState::Pull: if (!ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input)) { BreakGrapple(EPlayerOrbitRequest::StopOrbit, mgr); } break; default: break; } break; default: break; } const zeus::CVector3f eyePos = GetEyePosition(); const zeus::CVector3f playerToPoint = point->GetTranslation() - eyePos; if (playerToPoint.canBeNormalized()) { const CRayCastResult result = mgr.RayStaticIntersection(eyePos, playerToPoint.normalized(), playerToPoint.magnitude(), LineOfSightFilter); if (result.IsValid()) { BreakGrapple(EPlayerOrbitRequest::LostGrappleLineOfSight, mgr); } } return; } if (x490_gun->GetGrappleArm().BeamActive() && g_tweakPlayer->GetGrappleJumpMode() == 1 && !ControlMapper::GetDigitalInput(ControlMapper::ECommands::FireOrBomb, input)) { BreakGrapple(EPlayerOrbitRequest::StopOrbit, mgr); } } void CPlayer::ApplyGrappleJump(CStateManager& mgr) { const TCastToConstPtr point = mgr.ObjectById(x310_orbitTargetId); if (!point) { return; } zeus::CVector3f tmp = x3c0_grappleSwingAxis; if (x3bc_grappleSwingTimer < 0.5f * g_tweakPlayer->GetGrappleSwingPeriod()) { tmp *= zeus::skNegOne3f; } const zeus::CVector3f pointToPlayer = GetTranslation() - point->GetTranslation(); const zeus::CVector3f cross = pointToPlayer.normalized().cross(tmp); const zeus::CVector3f pointToPlayerFlat(pointToPlayer.x(), pointToPlayer.y(), 0.f); float dot = 1.f; if (pointToPlayerFlat.canBeNormalized() && cross.canBeNormalized()) { dot = zeus::clamp(-1.f, std::fabs(cross.normalized().dot(pointToPlayerFlat.normalized())), 1.f); } ApplyForceWR(g_tweakPlayer->GetGrappleJumpForce() * cross * 10000.f * dot, zeus::CAxisAngle()); } void CPlayer::BeginGrapple(zeus::CVector3f& vec, CStateManager& mgr) { vec.z() = 0.f; if (vec.canBeNormalized()) { x3c0_grappleSwingAxis.x() = float(vec.y()); x3c0_grappleSwingAxis.y() = -vec.x(); x3c0_grappleSwingAxis.normalize(); x3bc_grappleSwingTimer = 0.f; SetOrbitState(EPlayerOrbitState::Grapple, mgr); x3b8_grappleState = EGrappleState::Pull; RemoveMaterial(EMaterialTypes::GroundCollider, mgr); } } void CPlayer::BreakGrapple(EPlayerOrbitRequest req, CStateManager& mgr) { x294_jumpCameraTimer = 0.f; x29c_fallCameraTimer = 0.f; if (g_tweakPlayer->GetGrappleJumpMode() == 2 && x3b8_grappleState == EGrappleState::Swinging) { ApplyGrappleJump(mgr); PreventFallingCameraPitch(); } SetOrbitRequest(req, mgr); x3b8_grappleState = EGrappleState::None; AddMaterial(EMaterialTypes::GroundCollider, mgr); x490_gun->GetGrappleArm().SetAnimState(CGrappleArm::EArmState::OutOfGrapple); if (!InGrappleJumpCooldown() && x3b8_grappleState != EGrappleState::JumpOff) { DrawGun(mgr); } } void CPlayer::SetOrbitRequest(EPlayerOrbitRequest req, CStateManager& mgr) { x30c_orbitRequest = req; switch (req) { case EPlayerOrbitRequest::ActivateOrbitSource: ActivateOrbitSource(mgr); break; case EPlayerOrbitRequest::BadVerticalAngle: SetOrbitState(EPlayerOrbitState::OrbitPoint, mgr); x314_orbitPoint = g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)) * x34_transform.basis[1] + x34_transform.origin; break; default: SetOrbitState(EPlayerOrbitState::NoOrbit, mgr); break; } } void CPlayer::SetOrbitRequestForTarget(TUniqueId id, EPlayerOrbitRequest req, CStateManager& mgr) { switch (x304_orbitState) { case EPlayerOrbitState::OrbitObject: case EPlayerOrbitState::ForcedOrbitObject: case EPlayerOrbitState::Grapple: if (id == x310_orbitTargetId) SetOrbitRequest(req, mgr); break; default: break; } } bool CPlayer::InGrappleJumpCooldown() const { if (x258_movementState == EPlayerMovementState::OnGround) { return false; } return x3d8_grappleJumpTimeout > 0.f || x294_jumpCameraTimer == 0.f; } void CPlayer::PreventFallingCameraPitch() { x294_jumpCameraTimer = 0.f; x29c_fallCameraTimer = 0.01f; x2a4_cancelCameraPitch = true; } void CPlayer::OrbitCarcass(CStateManager& mgr) { if (x304_orbitState != EPlayerOrbitState::OrbitObject) { return; } x308_orbitType = EPlayerOrbitType::Default; SetOrbitState(EPlayerOrbitState::OrbitCarcass, mgr); } void CPlayer::OrbitPoint(EPlayerOrbitType type, CStateManager& mgr) { x308_orbitType = type; SetOrbitState(EPlayerOrbitState::OrbitPoint, mgr); SetOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr); } zeus::CVector3f CPlayer::GetHUDOrbitTargetPosition() const { return x314_orbitPoint + x76c_cameraBob->GetCameraBobTransformation().origin; } void CPlayer::SetOrbitState(EPlayerOrbitState state, CStateManager& mgr) { x304_orbitState = state; CFirstPersonCamera* cam = mgr.GetCameraManager()->GetFirstPersonCamera(); switch (x304_orbitState) { case EPlayerOrbitState::OrbitObject: #ifndef NDEBUG if (x310_orbitTargetId != kInvalidUniqueId) { if (const CEntity* ent = mgr.GetObjectById(x310_orbitTargetId)) { Log.report(logvisor::Info, FMT_STRING("Orbiting {} {}"), ent->GetEditorId(), ent->GetName()); } } #endif cam->SetLockCamera(false); break; case EPlayerOrbitState::OrbitCarcass: { cam->SetLockCamera(true); const zeus::CVector3f playerToPoint = x314_orbitPoint - GetTranslation(); if (playerToPoint.canBeNormalized()) { x340_ = playerToPoint.magnitude(); } else { x340_ = 0.f; } SetOrbitTargetId(kInvalidUniqueId, mgr); x33c_orbitNextTargetId = kInvalidUniqueId; break; } case EPlayerOrbitState::NoOrbit: x32c_orbitModeTimer = g_tweakPlayer->GetOrbitModeTimer(); x32c_orbitModeTimer = 0.28f; SetOrbitTargetId(kInvalidUniqueId, mgr); x33c_orbitNextTargetId = kInvalidUniqueId; break; case EPlayerOrbitState::OrbitPoint: SetOrbitTargetId(kInvalidUniqueId, mgr); x33c_orbitNextTargetId = kInvalidUniqueId; break; default: break; } } void CPlayer::SetOrbitTargetId(TUniqueId id, CStateManager& mgr) { if (id != kInvalidUniqueId) { x394_orbitingEnemy = (TCastToPtr(mgr.ObjectById(id)) || TCastToPtr(mgr.ObjectById(id)) || CPatterned::CastTo(mgr.ObjectById(id)) || TCastToPtr(mgr.ObjectById(id))); } x310_orbitTargetId = id; if (x310_orbitTargetId == kInvalidUniqueId) { x374_orbitLockEstablished = false; } } void CPlayer::UpdateOrbitPosition(float dist, CStateManager& mgr) { switch (x304_orbitState) { case EPlayerOrbitState::OrbitPoint: case EPlayerOrbitState::OrbitCarcass: SetOrbitPosition(dist, mgr); break; case EPlayerOrbitState::OrbitObject: case EPlayerOrbitState::ForcedOrbitObject: case EPlayerOrbitState::Grapple: if (const TCastToConstPtr act = mgr.ObjectById(x310_orbitTargetId)) { if (x310_orbitTargetId != kInvalidUniqueId) { x314_orbitPoint = act->GetOrbitPosition(mgr); } } break; default: break; } } void CPlayer::UpdateOrbitZPosition() { if (x304_orbitState != EPlayerOrbitState::OrbitPoint) { return; } if (std::fabs(x320_orbitVector.z()) >= g_tweakPlayer->GetOrbitZRange()) { return; } x314_orbitPoint.z() = x320_orbitVector.z() + x34_transform.origin.z() + GetEyeHeight(); } void CPlayer::UpdateOrbitFixedPosition() { x314_orbitPoint = x34_transform.rotate(x320_orbitVector) + GetEyePosition(); } void CPlayer::SetOrbitPosition(float dist, CStateManager& mgr) { zeus::CTransform camXf = GetFirstPersonCameraTransform(mgr); if (x304_orbitState == EPlayerOrbitState::OrbitPoint && x30c_orbitRequest == EPlayerOrbitRequest::BadVerticalAngle) { camXf = x34_transform; } const zeus::CVector3f fwd = camXf.basis[1]; float dot = fwd.normalized().dot(fwd); if (std::fabs(dot) > 1.f) { dot = (dot > 0.f) ? 1.f : -1.f; } x314_orbitPoint = camXf.rotate(zeus::CVector3f(0.f, dist / dot, 0.f)) + camXf.origin; x320_orbitVector = zeus::CVector3f(0.f, dist, x314_orbitPoint.z() - camXf.origin.z()); } void CPlayer::UpdateAimTarget(CStateManager& mgr) { if (!ValidateAimTargetId(x3f4_aimTarget, mgr)) { ResetAimTargetPrediction(kInvalidUniqueId); } if (!GetCombatMode()) { ResetAimTargetPrediction(kInvalidUniqueId); x48c_aimTargetTimer = 0.f; return; } #if 0 if (!0 && 0) { ResetAimTargetPrediction(kInvalidUniqueId); x48c_aimTargetTimer = 0.f; if (x304_orbitState == EPlayerOrbitState::One || x304_orbitState == EPlayerOrbitState::Four) { if (!ValidateOrbitTargetId(x310_orbitTargetId, mgr)) { ResetAimTargetPrediction(x310_orbitTargetId); } } return; } #endif bool needsReset = false; const TCastToConstPtr act = mgr.ObjectById(x3f4_aimTarget); const CActor* actp = act.GetPtr(); if (act) { if (!act->GetMaterialList().HasMaterial(EMaterialTypes::Target)) { actp = nullptr; } } if (g_tweakPlayer->GetAimWhenOrbitingPoint()) { if (x304_orbitState == EPlayerOrbitState::OrbitObject || x304_orbitState == EPlayerOrbitState::ForcedOrbitObject) { if (ValidateOrbitTargetId(x310_orbitTargetId, mgr) == EOrbitValidationResult::OK) { ResetAimTargetPrediction(x310_orbitTargetId); } else { needsReset = true; } } else { needsReset = true; } } else { if (x304_orbitState == EPlayerOrbitState::NoOrbit) { needsReset = true; } } if (!needsReset) { return; } if (actp && ValidateObjectForMode(x3f4_aimTarget, mgr)) { ResetAimTargetPrediction(kInvalidUniqueId); } else { ResetAimTargetPrediction(FindAimTargetId(mgr)); } } void CPlayer::UpdateAimTargetTimer(float dt) { if (x3f4_aimTarget == kInvalidUniqueId) { return; } if (x48c_aimTargetTimer <= 0.f) { return; } x48c_aimTargetTimer -= dt; } bool CPlayer::ValidateAimTargetId(TUniqueId uid, CStateManager& mgr) { if (uid == kInvalidUniqueId) { x404_aimTargetAverage.Clear(); x48c_aimTargetTimer = 0.f; return false; } const TCastToConstPtr act = mgr.ObjectById(uid); if (!act || !act->GetMaterialList().HasMaterial(EMaterialTypes::Target) || !act->GetIsTargetable()) { return false; } if (x304_orbitState == EPlayerOrbitState::OrbitObject || x304_orbitState == EPlayerOrbitState::ForcedOrbitObject) { if (ValidateOrbitTargetId(x310_orbitTargetId, mgr) != EOrbitValidationResult::OK) { ResetAimTargetPrediction(kInvalidUniqueId); x48c_aimTargetTimer = 0.f; return false; } return true; } if (act->GetMaterialList().HasMaterial(EMaterialTypes::Target) && uid != kInvalidUniqueId && ValidateObjectForMode(uid, mgr)) { const float vpWHalf = g_Viewport.x8_width / 2; const float vpHHalf = g_Viewport.xc_height / 2; const zeus::CVector3f aimPos = act->GetAimPosition(mgr, 0.f); const zeus::CVector3f eyePos = GetEyePosition(); zeus::CVector3f eyeToAim = aimPos - eyePos; const zeus::CVector3f screenPos = mgr.GetCameraManager()->GetFirstPersonCamera()->ConvertToScreenSpace(aimPos); const zeus::CVector3f posInBox(vpWHalf + screenPos.x() * vpWHalf, vpHHalf + screenPos.y() * vpHHalf, screenPos.z()); if (WithinOrbitScreenBox(posInBox, x330_orbitZoneMode, x334_orbitType) || (x330_orbitZoneMode != EPlayerZoneInfo::Targeting && WithinOrbitScreenBox(posInBox, EPlayerZoneInfo::Targeting, x334_orbitType))) { const float eyeToAimMag = eyeToAim.magnitude(); if (eyeToAimMag <= g_tweakPlayer->GetAimMaxDistance()) { EntityList nearList; TUniqueId intersectId = kInvalidUniqueId; eyeToAim.normalize(); mgr.BuildNearList(nearList, eyePos, eyeToAim, eyeToAimMag, OccluderFilter, act); eyeToAim.normalize(); const CRayCastResult result = mgr.RayWorldIntersection(intersectId, eyePos, eyeToAim, eyeToAimMag, LineOfSightFilter, nearList); if (result.IsInvalid()) { x48c_aimTargetTimer = g_tweakPlayer->GetAimTargetTimer(); return true; } } } if (x48c_aimTargetTimer > 0.f) { return true; } } ResetAimTargetPrediction(kInvalidUniqueId); x48c_aimTargetTimer = 0.f; return false; } bool CPlayer::ValidateObjectForMode(TUniqueId uid, CStateManager& mgr) const { const TCastToPtr act = mgr.ObjectById(uid); if (!act || uid == kInvalidUniqueId) { return false; } if (TCastToConstPtr(mgr.ObjectById(uid))) { return true; } if (GetCombatMode()) { if (const CHealthInfo* hInfo = act->HealthInfo(mgr)) { if (hInfo->GetHP() > 0.f) { return true; } } else { if (act->GetMaterialList().HasMaterial(EMaterialTypes::Projectile) || act->GetMaterialList().HasMaterial(EMaterialTypes::Scannable)) { return true; } if (const TCastToConstPtr point = mgr.ObjectById(uid)) { const zeus::CVector3f playerToPoint = point->GetTranslation() - GetTranslation(); if (playerToPoint.canBeNormalized() && playerToPoint.magnitude() < g_tweakPlayer->GetOrbitDistanceMax()) { return true; } } } } if (GetExplorationMode()) { if (!act->HealthInfo(mgr)) { if (const TCastToConstPtr point = mgr.ObjectById(uid)) { const zeus::CVector3f playerToPoint = point->GetTranslation() - GetTranslation(); if (playerToPoint.canBeNormalized() && playerToPoint.magnitude() < g_tweakPlayer->GetOrbitDistanceMax()) { return true; } } else { return true; } } else { return true; } } return false; } static zeus::CAABox BuildNearListBox(bool cropBottom, const zeus::CTransform& xf, float x, float z, float y) { const zeus::CAABox aabb(-x, cropBottom ? 0.f : -y, -z, x, y, z); return aabb.getTransformedAABox(xf); } TUniqueId CPlayer::FindAimTargetId(CStateManager& mgr) const { float dist = g_tweakPlayer->GetAimMaxDistance(); if (x9c6_24_extendTargetDistance) { dist *= 5.f; } const zeus::CAABox aabb = BuildNearListBox(true, GetFirstPersonCameraTransform(mgr), g_tweakPlayer->GetAimBoxWidth(), g_tweakPlayer->GetAimBoxHeight(), dist); EntityList nearList; mgr.BuildNearList(nearList, aabb, CMaterialFilter::MakeInclude({EMaterialTypes::Target}), this); return CheckEnemiesAgainstOrbitZone(nearList, EPlayerZoneInfo::Targeting, EPlayerZoneType::Ellipse, mgr); } const zeus::CTransform& CPlayer::GetFirstPersonCameraTransform(const CStateManager& mgr) const { return mgr.GetCameraManager()->GetFirstPersonCamera()->GetGunFollowTransform(); } TUniqueId CPlayer::CheckEnemiesAgainstOrbitZone(const EntityList& list, EPlayerZoneInfo info, EPlayerZoneType zone, CStateManager& mgr) const { const zeus::CVector3f eyePos = GetEyePosition(); float minEyeToAimMag = 10000.f; float minPosInBoxMagSq = 10000.f; TUniqueId bestId = kInvalidUniqueId; const float vpWHalf = g_Viewport.x8_width / 2; const float vpHHalf = g_Viewport.xc_height / 2; const float boxLeft = (GetOrbitZoneIdealXScaled(int(info)) - vpWHalf) / vpWHalf; const float boxTop = (GetOrbitZoneIdealYScaled(int(info)) - vpHHalf) / vpHHalf; const CFirstPersonCamera* fpCam = mgr.GetCameraManager()->GetFirstPersonCamera(); for (const auto& id : list) { if (const auto* act = static_cast(mgr.ObjectById(id))) { if (act->GetUniqueId() != GetUniqueId() && ValidateObjectForMode(act->GetUniqueId(), mgr)) { const zeus::CVector3f aimPos = act->GetAimPosition(mgr, 0.f); const zeus::CVector3f screenPos = fpCam->ConvertToScreenSpace(aimPos); const zeus::CVector3f posInBox(vpWHalf + screenPos.x() * vpWHalf, vpHHalf + screenPos.y() * vpHHalf, screenPos.z()); if (WithinOrbitScreenBox(posInBox, info, zone)) { zeus::CVector3f eyeToAim = aimPos - eyePos; const float eyeToAimMag = eyeToAim.magnitude(); if (eyeToAimMag <= g_tweakPlayer->GetAimMaxDistance()) { if (minEyeToAimMag - eyeToAimMag > g_tweakPlayer->GetAimThresholdDistance()) { EntityList nearList; TUniqueId intersectId = kInvalidUniqueId; eyeToAim.normalize(); mgr.BuildNearList(nearList, eyePos, eyeToAim, eyeToAimMag, OccluderFilter, act); eyeToAim.normalize(); const CRayCastResult result = mgr.RayWorldIntersection(intersectId, eyePos, eyeToAim, eyeToAimMag, LineOfSightFilter, nearList); if (result.IsInvalid()) { bestId = act->GetUniqueId(); const float posInBoxLeft = posInBox.x() - boxLeft; const float posInBoxTop = posInBox.y() - boxTop; minEyeToAimMag = eyeToAimMag; minPosInBoxMagSq = posInBoxLeft * posInBoxLeft + posInBoxTop * posInBoxTop; } } else if (std::fabs(eyeToAimMag - minEyeToAimMag) < g_tweakPlayer->GetAimThresholdDistance()) { const float posInBoxLeft = posInBox.x() - boxLeft; const float posInBoxTop = posInBox.y() - boxTop; const float posInBoxMagSq = posInBoxLeft * posInBoxLeft + posInBoxTop * posInBoxTop; if (posInBoxMagSq < minPosInBoxMagSq) { EntityList nearList; TUniqueId intersectId = kInvalidUniqueId; eyeToAim.normalize(); mgr.BuildNearList(nearList, eyePos, eyeToAim, eyeToAimMag, OccluderFilter, act); eyeToAim.normalize(); const CRayCastResult result = mgr.RayWorldIntersection(intersectId, eyePos, eyeToAim, eyeToAimMag, LineOfSightFilter, nearList); if (result.IsInvalid()) { bestId = act->GetUniqueId(); minEyeToAimMag = eyeToAimMag; minPosInBoxMagSq = posInBoxMagSq; } } } } } } } } return bestId; } TUniqueId CPlayer::FindOrbitTargetId(CStateManager& mgr) const { return FindBestOrbitableObject(x354_onScreenOrbitObjects, x330_orbitZoneMode, mgr); } void CPlayer::UpdateOrbitableObjects(CStateManager& mgr) { x354_onScreenOrbitObjects.clear(); x344_nearbyOrbitObjects.clear(); x364_offScreenOrbitObjects.clear(); if (CheckOrbitDisableSourceList(mgr)) { return; } float dist = GetOrbitMaxTargetDistance(mgr); if (x9c6_24_extendTargetDistance) { dist *= 5.f; } const zeus::CAABox nearAABB = BuildNearListBox(true, GetFirstPersonCameraTransform(mgr), g_tweakPlayer->GetOrbitNearX(), g_tweakPlayer->GetOrbitNearZ(), dist); const CMaterialFilter filter = mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan ? CMaterialFilter::MakeInclude({EMaterialTypes::Scannable}) : CMaterialFilter::MakeInclude({EMaterialTypes::Orbit}); EntityList nearList; mgr.BuildNearList(nearList, nearAABB, filter, nullptr); FindOrbitableObjects(nearList, x344_nearbyOrbitObjects, x330_orbitZoneMode, EPlayerZoneType::Always, mgr, true); FindOrbitableObjects(nearList, x354_onScreenOrbitObjects, x330_orbitZoneMode, x334_orbitType, mgr, true); FindOrbitableObjects(nearList, x364_offScreenOrbitObjects, x330_orbitZoneMode, x334_orbitType, mgr, false); } TUniqueId CPlayer::FindBestOrbitableObject(const std::vector& ids, EPlayerZoneInfo info, CStateManager& mgr) const { const zeus::CVector3f eyePos = GetEyePosition(); float minEyeToOrbitMag = 10000.f; float minPosInBoxMagSq = 10000.f; TUniqueId bestId = kInvalidUniqueId; const float vpWidthHalf = g_Viewport.x8_width / 2; const float vpHeightHalf = g_Viewport.xc_height / 2; const float boxLeft = (GetOrbitZoneIdealXScaled(int(info)) - vpWidthHalf) / vpWidthHalf; const float boxTop = (GetOrbitZoneIdealYScaled(int(info)) - vpHeightHalf) / vpHeightHalf; const CFirstPersonCamera* fpCam = mgr.GetCameraManager()->GetFirstPersonCamera(); for (const auto& id : ids) { if (const TCastToConstPtr act = mgr.ObjectById(id)) { const zeus::CVector3f orbitPos = act->GetOrbitPosition(mgr); zeus::CVector3f eyeToOrbit = orbitPos - eyePos; const float eyeToOrbitMag = eyeToOrbit.magnitude(); const zeus::CVector3f orbitPosScreen = fpCam->ConvertToScreenSpace(orbitPos); if (orbitPosScreen.z() >= 0.f) { if (x310_orbitTargetId != id) { if (const TCastToConstPtr point = act.GetPtr()) { if (x310_orbitTargetId != point->GetUniqueId()) { if (mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GrappleBeam) && eyeToOrbitMag < minEyeToOrbitMag && eyeToOrbitMag < g_tweakPlayer->GetOrbitDistanceMax()) { EntityList nearList; TUniqueId intersectId = kInvalidUniqueId; eyeToOrbit.normalize(); mgr.BuildNearList(nearList, eyePos, eyeToOrbit, eyeToOrbitMag, OccluderFilter, act.GetPtr()); eyeToOrbit.normalize(); const CRayCastResult result = mgr.RayWorldIntersection(intersectId, eyePos, eyeToOrbit, eyeToOrbitMag, LineOfSightFilter, nearList); if (result.IsInvalid()) { if (point->GetGrappleParameters().GetLockSwingTurn()) { zeus::CVector3f pointToPlayer = GetTranslation() - point->GetTranslation(); if (pointToPlayer.canBeNormalized()) { pointToPlayer.z() = 0.f; if (std::fabs(point->GetTransform().basis[1].normalized().dot(pointToPlayer.normalized())) <= M_SQRT1_2F) { continue; } } } bestId = act->GetUniqueId(); const float posInBoxLeft = orbitPosScreen.x() - boxLeft; const float posInBoxTop = orbitPosScreen.y() - boxTop; minEyeToOrbitMag = eyeToOrbitMag; minPosInBoxMagSq = posInBoxLeft * posInBoxLeft + posInBoxTop * posInBoxTop; } } continue; } } if (minEyeToOrbitMag - eyeToOrbitMag > g_tweakPlayer->GetOrbitDistanceThreshold() && mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan) { EntityList nearList; TUniqueId idOut = kInvalidUniqueId; eyeToOrbit.normalize(); mgr.BuildNearList(nearList, eyePos, eyeToOrbit, eyeToOrbitMag, OccluderFilter, act.GetPtr()); for (auto it = nearList.begin(); it != nearList.end();) { if (const CEntity* obj = mgr.ObjectById(*it)) { if (obj->GetAreaIdAlways() != kInvalidAreaId) { if (mgr.GetNextAreaId() != obj->GetAreaIdAlways()) { const CGameArea* area = mgr.GetWorld()->GetAreaAlways(obj->GetAreaIdAlways()); const CGameArea::EOcclusionState state = area->IsPostConstructed() ? area->GetOcclusionState() : CGameArea::EOcclusionState::Occluded; if (state == CGameArea::EOcclusionState::Occluded) { it = nearList.erase(it); continue; } } } else { it = nearList.erase(it); continue; } } ++it; } eyeToOrbit.normalize(); const CRayCastResult result = mgr.RayWorldIntersection(idOut, eyePos, eyeToOrbit, eyeToOrbitMag, LineOfSightFilter, nearList); if (result.IsInvalid()) { bestId = act->GetUniqueId(); const float posInBoxLeft = orbitPosScreen.x() - boxLeft; const float posInBoxTop = orbitPosScreen.y() - boxTop; minEyeToOrbitMag = eyeToOrbitMag; minPosInBoxMagSq = posInBoxLeft * posInBoxLeft + posInBoxTop * posInBoxTop; } continue; } if (std::fabs(eyeToOrbitMag - minEyeToOrbitMag) < g_tweakPlayer->GetOrbitDistanceThreshold() || mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan) { const float posInBoxLeft = orbitPosScreen.x() - boxLeft; const float posInBoxTop = orbitPosScreen.y() - boxTop; const float posInBoxMagSq = posInBoxLeft * posInBoxLeft + posInBoxTop * posInBoxTop; if (posInBoxMagSq < minPosInBoxMagSq) { EntityList nearList; TUniqueId idOut = kInvalidUniqueId; eyeToOrbit.normalize(); mgr.BuildNearList(nearList, eyePos, eyeToOrbit, eyeToOrbitMag, OccluderFilter, act.GetPtr()); for (auto it = nearList.begin(); it != nearList.end();) { if (const CEntity* obj = mgr.ObjectById(*it)) { if (obj->GetAreaIdAlways() != kInvalidAreaId) { if (mgr.GetNextAreaId() != obj->GetAreaIdAlways()) { const CGameArea* area = mgr.GetWorld()->GetAreaAlways(obj->GetAreaIdAlways()); const CGameArea::EOcclusionState state = area->IsPostConstructed() ? area->GetOcclusionState() : CGameArea::EOcclusionState::Occluded; if (state == CGameArea::EOcclusionState::Occluded) { it = nearList.erase(it); continue; } } } else { it = nearList.erase(it); continue; } } ++it; } eyeToOrbit.normalize(); const CRayCastResult result = mgr.RayWorldIntersection(idOut, eyePos, eyeToOrbit, eyeToOrbitMag, LineOfSightFilter, nearList); if (result.IsInvalid()) { bestId = act->GetUniqueId(); minPosInBoxMagSq = posInBoxMagSq; minEyeToOrbitMag = eyeToOrbitMag; } } } } } } } return bestId; } void CPlayer::FindOrbitableObjects(const EntityList& nearObjects, std::vector& listOut, EPlayerZoneInfo zone, EPlayerZoneType type, CStateManager& mgr, bool onScreenTest) const { const CFirstPersonCamera* fpCam = mgr.GetCameraManager()->GetFirstPersonCamera(); const zeus::CVector3f eyePos = GetEyePosition(); for (const auto& id : nearObjects) { if (const TCastToConstPtr act = mgr.GetObjectById(id)) { if (GetUniqueId() == act->GetUniqueId()) { continue; } if (ValidateOrbitTargetId(act->GetUniqueId(), mgr) != EOrbitValidationResult::OK) { continue; } const zeus::CVector3f orbitPos = act->GetOrbitPosition(mgr); zeus::CVector3f screenPos = fpCam->ConvertToScreenSpace(orbitPos); screenPos.x() = g_Viewport.x8_width * screenPos.x() / 2.f + g_Viewport.x8_width / 2.f; screenPos.y() = g_Viewport.xc_height * screenPos.y() / 2.f + g_Viewport.xc_height / 2.f; bool pass = false; if (onScreenTest) { if (WithinOrbitScreenBox(screenPos, zone, type)) { pass = true; } } else { if (!WithinOrbitScreenBox(screenPos, zone, type)) { pass = true; } } if (pass && (!act->GetDoTargetDistanceTest() || (orbitPos - eyePos).magnitude() <= GetOrbitMaxTargetDistance(mgr))) { listOut.push_back(id); } } } } bool CPlayer::WithinOrbitScreenBox(const zeus::CVector3f& screenCoords, EPlayerZoneInfo zone, EPlayerZoneType type) const { if (screenCoords.z() >= 1.f) { return false; } switch (type) { case EPlayerZoneType::Box: return std::fabs(screenCoords.x() - GetOrbitScreenBoxCenterXScaled(int(zone))) < GetOrbitScreenBoxHalfExtentXScaled(int(zone)) && std::fabs(screenCoords.y() - GetOrbitScreenBoxCenterYScaled(int(zone))) < GetOrbitScreenBoxHalfExtentYScaled(int(zone)) && screenCoords.z() < 1.f; case EPlayerZoneType::Ellipse: return WithinOrbitScreenEllipse(screenCoords, zone); default: return true; } } bool CPlayer::WithinOrbitScreenEllipse(const zeus::CVector3f& screenCoords, EPlayerZoneInfo zone) const { if (screenCoords.z() >= 1.f) { return false; } float heYSq = GetOrbitScreenBoxHalfExtentYScaled(int(zone)); heYSq *= heYSq; float heXSq = GetOrbitScreenBoxHalfExtentXScaled(int(zone)); heXSq *= heXSq; const float tmpY = std::fabs(screenCoords.y() - GetOrbitScreenBoxCenterYScaled(int(zone))); const float tmpX = std::fabs(screenCoords.x() - GetOrbitScreenBoxCenterXScaled(int(zone))); return tmpX * tmpX <= (1.f - tmpY * tmpY / heYSq) * heXSq; } bool CPlayer::CheckOrbitDisableSourceList(CStateManager& mgr) { for (auto it = x9e4_orbitDisableList.begin(); it != x9e4_orbitDisableList.end();) { if (mgr.GetObjectById(*it) == nullptr) { it = x9e4_orbitDisableList.erase(it); continue; } ++it; } return !x9e4_orbitDisableList.empty(); } void CPlayer::RemoveOrbitDisableSource(TUniqueId uid) { for (auto it = x9e4_orbitDisableList.begin(); it != x9e4_orbitDisableList.end();) { if (*it == uid) { it = x9e4_orbitDisableList.erase(it); return; } ++it; } } void CPlayer::AddOrbitDisableSource(CStateManager& mgr, TUniqueId addId) { if (x9e4_orbitDisableList.size() >= 5) { return; } for (const auto& uid : x9e4_orbitDisableList) { if (uid == addId) { return; } } x9e4_orbitDisableList.push_back(addId); ResetAimTargetPrediction(kInvalidUniqueId); if (!TCastToConstPtr(mgr.GetObjectById(x310_orbitTargetId))) { SetOrbitTargetId(kInvalidUniqueId, mgr); } } void CPlayer::UpdateOrbitPreventionTimer(float dt) { if (x378_orbitPreventionTimer <= 0.f) { return; } x378_orbitPreventionTimer -= dt; } void CPlayer::UpdateOrbitModeTimer(float dt) { if (x304_orbitState == EPlayerOrbitState::NoOrbit && x32c_orbitModeTimer > 0.f) { x32c_orbitModeTimer -= dt; return; } x32c_orbitModeTimer = 0.f; } void CPlayer::UpdateOrbitZone(CStateManager& mgr) { if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan) { x334_orbitType = EPlayerZoneType::Ellipse; x338_ = 1; x330_orbitZoneMode = EPlayerZoneInfo::Targeting; } else { x334_orbitType = EPlayerZoneType::Box; x338_ = 2; x330_orbitZoneMode = EPlayerZoneInfo::Scan; } } void CPlayer::UpdateOrbitInput(const CFinalInput& input, CStateManager& mgr) { if (x2f8_morphBallState != EPlayerMorphBallState::Unmorphed || x378_orbitPreventionTimer > 0.f) { return; } UpdateOrbitableObjects(mgr); if (x304_orbitState == EPlayerOrbitState::NoOrbit) { x33c_orbitNextTargetId = FindOrbitTargetId(mgr); } if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::OrbitClose, input) || ControlMapper::GetDigitalInput(ControlMapper::ECommands::OrbitFar, input) || ControlMapper::GetDigitalInput(ControlMapper::ECommands::OrbitObject, input)) { switch (x304_orbitState) { case EPlayerOrbitState::NoOrbit: /* Disabled transitions directly from NoOrbit to OrbitObject for better keyboard handling */ #if 0 if (ControlMapper::GetPressInput(ControlMapper::ECommands::OrbitObject, input)) { SetOrbitTargetId(x33c_orbitNextTargetId, mgr); if (x310_orbitTargetId != kInvalidUniqueId) { if (ValidateAimTargetId(x310_orbitTargetId, mgr)) { ResetAimTargetPrediction(x310_orbitTargetId); } SetOrbitState(EPlayerOrbitState::OrbitObject, mgr); UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr); } } else { #else m_deferredOrbitObject = ControlMapper::GetPressInput(ControlMapper::ECommands::OrbitObject, input); #endif if (ControlMapper::GetPressInput(ControlMapper::ECommands::OrbitFar, input)) { OrbitPoint(EPlayerOrbitType::Far, mgr); } if (ControlMapper::GetPressInput(ControlMapper::ECommands::OrbitClose, input)) { OrbitPoint(EPlayerOrbitType::Close, mgr); } #if 0 } #endif break; case EPlayerOrbitState::Grapple: if (x310_orbitTargetId == kInvalidUniqueId) { BreakGrapple(EPlayerOrbitRequest::StopOrbit, mgr); } break; case EPlayerOrbitState::OrbitObject: if (TCastToConstPtr(mgr.GetObjectById(x310_orbitTargetId))) { if (ValidateCurrentOrbitTargetId(mgr) == EOrbitValidationResult::OK) { UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr); } else { BreakGrapple(EPlayerOrbitRequest::InvalidateTarget, mgr); } } else { const EOrbitValidationResult result = ValidateCurrentOrbitTargetId(mgr); if (result == EOrbitValidationResult::OK) { UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr); } else if (result == EOrbitValidationResult::BrokenLookAngle) { OrbitPoint(EPlayerOrbitType::Far, mgr); } else if (result == EOrbitValidationResult::ExtremeHorizonAngle) { SetOrbitRequest(EPlayerOrbitRequest::BadVerticalAngle, mgr); } else { ActivateOrbitSource(mgr); } } UpdateOrbitSelection(input, mgr); break; case EPlayerOrbitState::OrbitPoint: if (ControlMapper::GetPressInput(ControlMapper::ECommands::OrbitObject, input) || m_deferredOrbitObject) { m_deferredOrbitObject = false; SetOrbitTargetId(FindOrbitTargetId(mgr), mgr); if (x310_orbitTargetId != kInvalidUniqueId) { if (ValidateAimTargetId(x310_orbitTargetId, mgr)) { ResetAimTargetPrediction(x310_orbitTargetId); } SetOrbitState(EPlayerOrbitState::OrbitObject, mgr); UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr); } } else { switch (x308_orbitType) { case EPlayerOrbitType::Far: if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::OrbitClose, input)) { x308_orbitType = EPlayerOrbitType::Close; UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr); } break; case EPlayerOrbitType::Close: if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::OrbitFar, input) && !ControlMapper::GetDigitalInput(ControlMapper::ECommands::OrbitClose, input)) { x308_orbitType = EPlayerOrbitType::Far; UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr); } break; default: break; } } UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr); break; case EPlayerOrbitState::OrbitCarcass: if (ControlMapper::GetPressInput(ControlMapper::ECommands::OrbitObject, input) || m_deferredOrbitObject) { m_deferredOrbitObject = false; SetOrbitTargetId(FindOrbitTargetId(mgr), mgr); if (x310_orbitTargetId != kInvalidUniqueId) { if (ValidateAimTargetId(x310_orbitTargetId, mgr)) { ResetAimTargetPrediction(x310_orbitTargetId); } SetOrbitState(EPlayerOrbitState::OrbitObject, mgr); UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr); } } UpdateOrbitSelection(input, mgr); break; case EPlayerOrbitState::ForcedOrbitObject: UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr); UpdateOrbitSelection(input, mgr); break; } if (x304_orbitState == EPlayerOrbitState::Grapple) { x33c_orbitNextTargetId = FindOrbitTargetId(mgr); if (x33c_orbitNextTargetId == x310_orbitTargetId) { x33c_orbitNextTargetId = kInvalidUniqueId; } } } else { switch (x304_orbitState) { case EPlayerOrbitState::NoOrbit: break; case EPlayerOrbitState::OrbitObject: if (TCastToConstPtr(mgr.GetObjectById(x310_orbitTargetId))) { BreakGrapple(EPlayerOrbitRequest::Default, mgr); } else { SetOrbitRequest(EPlayerOrbitRequest::StopOrbit, mgr); } break; case EPlayerOrbitState::Grapple: if (!g_tweakPlayer->GetOrbitReleaseBreaksGrapple()) { x33c_orbitNextTargetId = FindOrbitTargetId(mgr); if (x33c_orbitNextTargetId == x310_orbitTargetId) { x33c_orbitNextTargetId = kInvalidUniqueId; } } else { BreakGrapple(EPlayerOrbitRequest::StopOrbit, mgr); } break; case EPlayerOrbitState::ForcedOrbitObject: UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr); UpdateOrbitSelection(input, mgr); break; default: SetOrbitRequest(EPlayerOrbitRequest::StopOrbit, mgr); break; } } } void CPlayer::ActivateOrbitSource(CStateManager& mgr) { switch (x390_orbitSource) { default: OrbitCarcass(mgr); break; case 1: SetOrbitRequest(EPlayerOrbitRequest::InvalidateTarget, mgr); break; case 2: if (x394_orbitingEnemy) { OrbitPoint(EPlayerOrbitType::Far, mgr); } else { OrbitCarcass(mgr); } break; } } void CPlayer::UpdateOrbitSelection(const CFinalInput& input, CStateManager& mgr) { x33c_orbitNextTargetId = FindOrbitTargetId(mgr); const TCastToConstPtr curPoint = mgr.GetObjectById(x310_orbitTargetId); const TCastToConstPtr nextPoint = mgr.GetObjectById(x33c_orbitNextTargetId); if (curPoint || (x304_orbitState == EPlayerOrbitState::Grapple && !nextPoint)) { x33c_orbitNextTargetId = kInvalidUniqueId; return; } if (ControlMapper::GetPressInput(ControlMapper::ECommands::OrbitObject, input) && x33c_orbitNextTargetId != kInvalidUniqueId) { SetOrbitTargetId(x33c_orbitNextTargetId, mgr); if (ValidateAimTargetId(x310_orbitTargetId, mgr)) { ResetAimTargetPrediction(x310_orbitTargetId); } SetOrbitState(EPlayerOrbitState::OrbitObject, mgr); UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr); } } void CPlayer::UpdateOrbitOrientation(CStateManager& mgr) { if (x2f8_morphBallState != EPlayerMorphBallState::Unmorphed) { return; } switch (x304_orbitState) { case EPlayerOrbitState::OrbitPoint: if (x3dc_inFreeLook) { return; } [[fallthrough]]; case EPlayerOrbitState::OrbitObject: case EPlayerOrbitState::OrbitCarcass: case EPlayerOrbitState::ForcedOrbitObject: { zeus::CVector3f playerToPoint = x314_orbitPoint - GetTranslation(); if (!x374_orbitLockEstablished) { playerToPoint = mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform().basis[1]; } playerToPoint.z() = 0.f; if (playerToPoint.canBeNormalized()) { zeus::CTransform xf = zeus::lookAt(zeus::skZero3f, playerToPoint); xf.origin = GetTranslation(); SetTransform(xf); } break; } default: break; } } void CPlayer::UpdateOrbitTarget(CStateManager& mgr) { if (!ValidateOrbitTargetIdAndPointer(x310_orbitTargetId, mgr)) { SetOrbitTargetId(kInvalidUniqueId, mgr); } if (!ValidateOrbitTargetIdAndPointer(x33c_orbitNextTargetId, mgr)) { x33c_orbitNextTargetId = kInvalidUniqueId; } zeus::CVector3f playerToPoint = x314_orbitPoint - GetTranslation(); playerToPoint.z() = 0.f; const float playerToPointMag = playerToPoint.magnitude(); switch (x304_orbitState) { case EPlayerOrbitState::OrbitObject: if (const auto* ent = static_cast(mgr.GetObjectById(x310_orbitTargetId))) { if (ent->GetDoTargetDistanceTest() && (playerToPointMag >= GetOrbitMaxLockDistance(mgr) || playerToPointMag < 0.5f)) { if (playerToPointMag < 0.5f) { SetOrbitRequest(EPlayerOrbitRequest::BadVerticalAngle, mgr); } else { ActivateOrbitSource(mgr); } } } UpdateOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr); break; case EPlayerOrbitState::OrbitPoint: { if (g_tweakPlayer->GetOrbitFixedOffset() && std::fabs(x320_orbitVector.z()) > g_tweakPlayer->GetOrbitFixedOffsetZDiff()) { UpdateOrbitFixedPosition(); return; } if (playerToPointMag < CalculateOrbitMinDistance(x308_orbitType)) { UpdateOrbitPosition(CalculateOrbitMinDistance(x308_orbitType), mgr); } const float maxDist = g_tweakPlayer->GetOrbitMaxDistance(int(x308_orbitType)); if (playerToPointMag > maxDist) { UpdateOrbitPosition(maxDist, mgr); } if (x3dd_lookButtonHeld) { SetOrbitPosition(g_tweakPlayer->GetOrbitNormalDistance(int(x308_orbitType)), mgr); } const zeus::CVector3f eyeToPoint = x314_orbitPoint - GetEyePosition(); const float angleToPoint = std::asin(zeus::clamp(-1.f, std::fabs(eyeToPoint.z()) / eyeToPoint.magnitude(), 1.f)); if ((eyeToPoint.z() >= 0.f && angleToPoint >= g_tweakPlayer->GetOrbitUpperAngle()) || (eyeToPoint.z() < 0.f && angleToPoint >= g_tweakPlayer->GetOrbitLowerAngle())) { SetOrbitRequest(EPlayerOrbitRequest::BadVerticalAngle, mgr); } break; } case EPlayerOrbitState::OrbitCarcass: { if (x3dd_lookButtonHeld) { SetOrbitPosition(x340_, mgr); } if (playerToPointMag < CalculateOrbitMinDistance(x308_orbitType)) { UpdateOrbitPosition(CalculateOrbitMinDistance(x308_orbitType), mgr); x340_ = CalculateOrbitMinDistance(x308_orbitType); } const float maxDist = g_tweakPlayer->GetOrbitMaxDistance(int(x308_orbitType)); if (playerToPointMag > maxDist) { UpdateOrbitPosition(maxDist, mgr); x340_ = g_tweakPlayer->GetOrbitMaxDistance(int(x308_orbitType)); } break; } case EPlayerOrbitState::NoOrbit: SetOrbitTargetId(kInvalidUniqueId, mgr); break; default: break; } UpdateOrbitZPosition(); } float CPlayer::GetOrbitMaxLockDistance(CStateManager& mgr) const { return mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan ? g_tweakPlayer->GetScanMaxLockDistance() : g_tweakPlayer->GetOrbitMaxLockDistance(); } float CPlayer::GetOrbitMaxTargetDistance(CStateManager& mgr) const { return mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan ? g_tweakPlayer->GetScanMaxTargetDistance() : g_tweakPlayer->GetOrbitMaxTargetDistance(); } CPlayer::EOrbitValidationResult CPlayer::ValidateOrbitTargetId(TUniqueId uid, CStateManager& mgr) const { if (uid == kInvalidUniqueId) { return EOrbitValidationResult::InvalidTarget; } const TCastToConstPtr act = mgr.GetObjectById(uid); if (!act || !act->GetIsTargetable() || !act->GetActive()) { return EOrbitValidationResult::InvalidTarget; } if (x740_staticTimer != 0.f) { return EOrbitValidationResult::PlayerNotReadyToTarget; } const zeus::CVector3f eyePos = GetEyePosition(); const zeus::CVector3f eyeToOrbit = act->GetOrbitPosition(mgr) - eyePos; zeus::CVector3f eyeToOrbitFlat = eyeToOrbit; eyeToOrbitFlat.z() = 0.f; if (eyeToOrbitFlat.canBeNormalized() && eyeToOrbitFlat.magnitude() > 1.f) { const float angleFromHorizon = std::asin(zeus::clamp(-1.f, std::fabs(eyeToOrbit.z()) / eyeToOrbit.magnitude(), 1.f)); if ((eyeToOrbit.z() >= 0.f && angleFromHorizon >= g_tweakPlayer->GetOrbitUpperAngle()) || (eyeToOrbit.z() < 0.f && angleFromHorizon >= g_tweakPlayer->GetOrbitLowerAngle())) { return EOrbitValidationResult::ExtremeHorizonAngle; } } else { return EOrbitValidationResult::ExtremeHorizonAngle; } const CPlayerState::EPlayerVisor visor = mgr.GetPlayerState()->GetCurrentVisor(); const u8 flags = act->GetTargetableVisorFlags(); if (visor == CPlayerState::EPlayerVisor::Combat && (flags & 1) == 0) { return EOrbitValidationResult::PlayerNotReadyToTarget; } if (visor == CPlayerState::EPlayerVisor::Scan && (flags & 2) == 0) { return EOrbitValidationResult::PlayerNotReadyToTarget; } if (visor == CPlayerState::EPlayerVisor::Thermal && (flags & 4) == 0) { return EOrbitValidationResult::PlayerNotReadyToTarget; } if (visor == CPlayerState::EPlayerVisor::XRay && (flags & 8) == 0) { return EOrbitValidationResult::PlayerNotReadyToTarget; } if (visor == CPlayerState::EPlayerVisor::Scan && act->GetAreaIdAlways() != GetAreaIdAlways()) { return EOrbitValidationResult::TargetingThroughDoor; } return EOrbitValidationResult::OK; } CPlayer::EOrbitValidationResult CPlayer::ValidateCurrentOrbitTargetId(CStateManager& mgr) { const TCastToConstPtr act = mgr.GetObjectById(x310_orbitTargetId); if (!act || !act->GetIsTargetable() || !act->GetActive()) { return EOrbitValidationResult::InvalidTarget; } if (!act->GetMaterialList().HasMaterial(EMaterialTypes::Orbit)) { if (!act->GetMaterialList().HasMaterial(EMaterialTypes::Scannable)) { return EOrbitValidationResult::NonTargetableTarget; } if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan) { return EOrbitValidationResult::NonTargetableTarget; } } const EOrbitValidationResult type = ValidateOrbitTargetId(x310_orbitTargetId, mgr); if (type != EOrbitValidationResult::OK) { return type; } if (mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan && act->GetAreaIdAlways() != GetAreaIdAlways()) { return EOrbitValidationResult::TargetingThroughDoor; } const TCastToConstPtr point = mgr.GetObjectById(x310_orbitTargetId); if ((mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Scan && g_tweakPlayer->GetOrbitWhileScanning()) || point || act->GetAreaIdAlways() != GetAreaIdAlways()) { const zeus::CVector3f eyePos = GetEyePosition(); TUniqueId bestId = kInvalidUniqueId; const zeus::CVector3f eyeToOrbit = act->GetOrbitPosition(mgr) - eyePos; if (eyeToOrbit.canBeNormalized()) { EntityList nearList; mgr.BuildNearList(nearList, eyePos, eyeToOrbit.normalized(), eyeToOrbit.magnitude(), OccluderFilter, act.GetPtr()); for (auto it = nearList.begin(); it != nearList.end();) { if (const CEntity* ent = mgr.GetObjectById(*it)) { if (ent->GetAreaIdAlways() != mgr.GetNextAreaId()) { const CGameArea* area = mgr.GetWorld()->GetAreaAlways(ent->GetAreaIdAlways()); CGameArea::EOcclusionState occState = CGameArea::EOcclusionState::Occluded; if (area->IsPostConstructed()) { occState = area->GetOcclusionState(); } if (occState == CGameArea::EOcclusionState::Occluded) { it = nearList.erase(it); continue; } } } ++it; } const CRayCastResult result = mgr.RayWorldIntersection(bestId, eyePos, eyeToOrbit.normalized(), eyeToOrbit.magnitude(), LineOfSightFilter, nearList); if (result.IsValid()) { if (TCastToConstPtr(mgr.ObjectById(bestId)) || point) { return EOrbitValidationResult::TargetingThroughDoor; } } } zeus::CVector3f eyeToOrbitFlat = eyeToOrbit; eyeToOrbitFlat.z() = 0.f; if (eyeToOrbitFlat.canBeNormalized()) { const float lookToOrbitAngle = std::acos(zeus::clamp(-1.f, eyeToOrbitFlat.normalized().dot(GetTransform().basis[1]), 1.f)); if (x374_orbitLockEstablished) { if (lookToOrbitAngle >= g_tweakPlayer->GetOrbitHorizAngle()) { return EOrbitValidationResult::BrokenLookAngle; } } else { if (lookToOrbitAngle <= M_PIF / 180.f) { x374_orbitLockEstablished = true; } } } else { return EOrbitValidationResult::BrokenLookAngle; } } return EOrbitValidationResult::OK; } bool CPlayer::ValidateOrbitTargetIdAndPointer(TUniqueId uid, CStateManager& mgr) const { if (uid == kInvalidUniqueId) { return false; } return TCastToConstPtr(mgr.GetObjectById(uid)).IsValid(); } zeus::CVector3f CPlayer::GetBallPosition() const { return GetTranslation() + zeus::CVector3f(0.f, 0.f, g_tweakPlayer->GetPlayerBallHalfExtent()); } zeus::CVector3f CPlayer::GetEyePosition() const { return GetTranslation() + zeus::CVector3f(0.f, 0.f, GetEyeHeight()); } float CPlayer::GetEyeHeight() const { return x9c8_eyeZBias + (x2d8_fpBounds.max.z() - g_tweakPlayer->GetEyeOffset()); } float CPlayer::GetUnbiasedEyeHeight() const { return x2d8_fpBounds.max.z() - g_tweakPlayer->GetEyeOffset(); } float CPlayer::GetStepUpHeight() const { if (x258_movementState == EPlayerMovementState::Jump || x258_movementState == EPlayerMovementState::ApplyJump) { return 0.3f; } return CPhysicsActor::GetStepUpHeight(); } float CPlayer::GetStepDownHeight() const { if (x258_movementState == EPlayerMovementState::Jump) { return -1.f; } if (x258_movementState == EPlayerMovementState::ApplyJump) { return 0.1f; } return CPhysicsActor::GetStepDownHeight(); } void CPlayer::Teleport(const zeus::CTransform& xf, CStateManager& mgr, bool resetBallCam) { CPhysicsActor::Stop(); zeus::CVector3f lookDir = xf.basis[1]; if (lookDir.canBeNormalized()) { lookDir.normalize(); SetTransform(zeus::lookAt(zeus::skZero3f, lookDir)); SetTranslation(xf.origin); x500_lookDir = lookDir; x50c_moveDir = lookDir; x530_gunDir = lookDir; x524_lastPosForDirCalc = xf.origin; x4f8_moveSpeed = 0.f; x4fc_flatMoveSpeed = 0.f; x53c_timeMoving = 0.f; x4a4_moveSpeedAvg.Clear(); x540_controlDir = lookDir; x54c_controlDirFlat = lookDir; } else { SetTranslation(xf.origin); } x9c5_31_stepCameraZBiasDirty = true; x9c8_eyeZBias = 0.f; x1f4_lastNonCollidingState = GetMotionState(); SetMoveState(EPlayerMovementState::OnGround, mgr); zeus::CTransform eyeXf = x34_transform; eyeXf.origin = GetEyePosition(); mgr.GetCameraManager()->GetFirstPersonCamera()->Reset(eyeXf, mgr); if (resetBallCam) { mgr.GetCameraManager()->GetBallCamera()->Reset(eyeXf, mgr); } ForceGunOrientation(x34_transform, mgr); SetOrbitRequest(EPlayerOrbitRequest::Respawn, mgr); } void CPlayer::BombJump(const zeus::CVector3f& pos, CStateManager& mgr) { if (x2f8_morphBallState == EPlayerMorphBallState::Morphed && x768_morphball->GetBombJumpState() != CMorphBall::EBombJumpState::BombJumpDisabled) { const zeus::CVector3f posToBall = GetTranslation() + zeus::CVector3f(0.f, 0.f, g_tweakPlayer->GetPlayerBallHalfExtent()) - pos; const float maxJump = g_tweakPlayer->GetBombJumpHeight(); if (posToBall.magSquared() < maxJump * maxJump && posToBall.dot(zeus::skUp) >= -g_tweakPlayer->GetPlayerBallHalfExtent()) { float upVel = std::sqrt(2.f * std::fabs(g_tweakPlayer->GetNormalGravAccel()) * g_tweakPlayer->GetBombJumpRadius()); mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerBump, 0.3f, ERumblePriority::One); x2a0_ = 0.01f; switch (GetSurfaceRestraint()) { case ESurfaceRestraints::Water: upVel *= g_tweakPlayer->GetWaterBallJumpFactor(); break; case ESurfaceRestraints::Lava: upVel *= g_tweakPlayer->GetLavaBallJumpFactor(); break; case ESurfaceRestraints::Phazon: upVel *= g_tweakPlayer->GetPhazonBallJumpFactor(); break; default: break; } SetVelocityWR(zeus::CVector3f(0.f, 0.f, upVel)); x768_morphball->SetDamageTimer(0.1f); x768_morphball->CancelBoosting(); if (x9d0_bombJumpCount > 0) { if (x9d0_bombJumpCount > 2) { x9d0_bombJumpCount = 0; x9d4_bombJumpCheckDelayFrames = 0; } else { ++x9d0_bombJumpCount; } } else { const CBallCamera* ballCam = mgr.GetCameraManager()->GetBallCamera(); if (ballCam->GetTooCloseActorId() != kInvalidUniqueId && ballCam->GetTooCloseActorDistance() < 5.f) { x9d0_bombJumpCount = 1; x9d4_bombJumpCheckDelayFrames = 2; } } CSfxHandle hnd = CSfxManager::AddEmitter(SFXsam_ball_jump, GetTranslation(), zeus::skZero3f, false, false, 0x7f, kInvalidAreaId); ApplySubmergedPitchBend(hnd); } } } zeus::CTransform CPlayer::CreateTransformFromMovementDirection() const { zeus::CVector3f moveDir = x50c_moveDir; if (moveDir.canBeNormalized()) { moveDir.normalize(); } else { moveDir = zeus::skForward; } return {zeus::CVector3f(moveDir.y(), -moveDir.x(), 0.f), moveDir, zeus::skUp, GetTranslation()}; } const CCollisionPrimitive* CPlayer::GetCollisionPrimitive() const { switch (x2f8_morphBallState) { case EPlayerMorphBallState::Morphed: return GetCollidableSphere(); default: return CPhysicsActor::GetCollisionPrimitive(); } } const CCollidableSphere* CPlayer::GetCollidableSphere() const { return &x768_morphball->GetCollidableSphere(); } zeus::CTransform CPlayer::GetPrimitiveTransform() const { return CPhysicsActor::GetPrimitiveTransform(); } void CPlayer::CollidedWith(TUniqueId id, const CCollisionInfoList& list, CStateManager& mgr) { if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphed) { return; } x768_morphball->CollidedWith(id, list, mgr); } float CPlayer::GetBallMaxVelocity() const { return g_tweakBall->GetBallTranslationMaxSpeed(int(GetSurfaceRestraint())); } float CPlayer::GetActualBallMaxVelocity(float dt) const { const ESurfaceRestraints surf = GetSurfaceRestraint(); const float friction = g_tweakBall->GetBallTranslationFriction(int(surf)); const float maxSpeed = g_tweakBall->GetBallTranslationMaxSpeed(int(surf)); const float acceleration = g_tweakBall->GetMaxBallTranslationAcceleration(int(surf)); return -(friction * xe8_mass * maxSpeed / (acceleration * dt) - maxSpeed - friction); } float CPlayer::GetActualFirstPersonMaxVelocity(float dt) const { const ESurfaceRestraints surf = GetSurfaceRestraint(); const float friction = g_tweakPlayer->GetPlayerTranslationFriction(int(surf)); const float maxSpeed = g_tweakPlayer->GetPlayerTranslationMaxSpeed(int(surf)); const float acceleration = g_tweakPlayer->GetMaxTranslationalAcceleration(int(surf)); return -(friction * xe8_mass * maxSpeed / (acceleration * dt) - maxSpeed - friction); } void CPlayer::SetMoveState(EPlayerMovementState newState, CStateManager& mgr) { switch (newState) { case EPlayerMovementState::Jump: if (x258_movementState == EPlayerMovementState::ApplyJump) { CSfxHandle hnd = CSfxManager::SfxStart(SFXsam_b_jump_00, 1.f, 0.f, true, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerBump, 0.2015f, ERumblePriority::One); x288_startingJumpTimeout = g_tweakPlayer->GetAllowedDoubleJumpTime(); x290_minJumpTimeout = g_tweakPlayer->GetAllowedDoubleJumpTime() - g_tweakPlayer->GetMinDoubleJumpTime(); x28c_sjTimer = 0.f; } else if (x258_movementState != EPlayerMovementState::Jump) { CSfxHandle hnd = CSfxManager::SfxStart(SFXsam_firstjump, 1.f, 0.f, true, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); x2a0_ = 0.01f; x288_startingJumpTimeout = g_tweakPlayer->GetAllowedJumpTime(); x290_minJumpTimeout = g_tweakPlayer->GetAllowedJumpTime() - g_tweakPlayer->GetMinJumpTime(); if (mgr.GetPlayerState()->GetItemAmount(CPlayerState::EItemType::SpaceJumpBoots) != 0) { x28c_sjTimer = g_tweakPlayer->GetMaxDoubleJumpWindow(); } else { x28c_sjTimer = 0.f; } if (x294_jumpCameraTimer <= 0.f && x29c_fallCameraTimer <= 0.f && !x3dc_inFreeLook && !x3dd_lookButtonHeld) { x294_jumpCameraTimer = 0.01f; x2a4_cancelCameraPitch = false; } } x258_movementState = EPlayerMovementState::Jump; x2ac_surfaceRestraint = ESurfaceRestraints::Air; x2a8_timeSinceJump = 0.f; break; case EPlayerMovementState::Falling: if (x258_movementState == EPlayerMovementState::OnGround) { x288_startingJumpTimeout = g_tweakPlayer->GetAllowedLedgeTime(); x258_movementState = EPlayerMovementState::Falling; x2a0_ = 0.01f; if (g_tweakPlayer->GetFallingDoubleJump()) { x28c_sjTimer = g_tweakPlayer->GetMaxDoubleJumpWindow(); } else { x28c_sjTimer = 0.f; } } break; case EPlayerMovementState::FallingMorphed: x258_movementState = EPlayerMovementState::FallingMorphed; x2ac_surfaceRestraint = ESurfaceRestraints::Normal; break; case EPlayerMovementState::OnGround: x300_fallingTime = 0.f; x258_movementState = EPlayerMovementState::OnGround; x288_startingJumpTimeout = 0.f; x28c_sjTimer = 0.f; x2ac_surfaceRestraint = ESurfaceRestraints::Normal; if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) { AddMaterial(EMaterialTypes::GroundCollider); } x294_jumpCameraTimer = 0.f; x29c_fallCameraTimer = 0.f; x2a4_cancelCameraPitch = false; x298_jumpPresses = 0; break; case EPlayerMovementState::ApplyJump: x288_startingJumpTimeout = 0.f; if (x258_movementState != EPlayerMovementState::ApplyJump) { x258_movementState = EPlayerMovementState::ApplyJump; if (x294_jumpCameraTimer <= x288_startingJumpTimeout && x29c_fallCameraTimer <= x288_startingJumpTimeout && !x3dc_inFreeLook && !x3dd_lookButtonHeld) { x29c_fallCameraTimer = 0.01f; x2a4_cancelCameraPitch = false; } } x2ac_surfaceRestraint = ESurfaceRestraints::Air; break; } } float CPlayer::JumpInput(const CFinalInput& input, CStateManager& mgr) { if (IsMorphBallTransitioning()) { return GetGravity() * xe8_mass; } float jumpFactor = 1.f; if (!mgr.GetPlayerState()->HasPowerUp(CPlayerState::EItemType::GravitySuit)) { const ESurfaceRestraints restraints = GetSurfaceRestraint(); switch (restraints) { case ESurfaceRestraints::Water: jumpFactor = g_tweakPlayer->GetWaterJumpFactor(); break; case ESurfaceRestraints::Lava: jumpFactor = g_tweakPlayer->GetLavaJumpFactor(); break; case ESurfaceRestraints::Phazon: jumpFactor = g_tweakPlayer->GetPhazonJumpFactor(); break; default: break; } } const float vJumpAccel = g_tweakPlayer->GetVerticalJumpAccel(); const float hJumpAccel = g_tweakPlayer->GetHorizontalJumpAccel(); float doubleJumpImpulse = g_tweakPlayer->GetDoubleJumpImpulse(); float vDoubleJumpAccel = g_tweakPlayer->GetVerticalDoubleJumpAccel(); float hDoubleJumpAccel = g_tweakPlayer->GetHorizontalDoubleJumpAccel(); if (x37c_sidewaysDashing) { doubleJumpImpulse = g_tweakPlayer->GetSidewaysDoubleJumpImpulse(); vDoubleJumpAccel = g_tweakPlayer->GetSidewaysVerticalDoubleJumpAccel(); hDoubleJumpAccel = g_tweakPlayer->GetSidewaysHorizontalDoubleJumpAccel(); } if (x828_distanceUnderWater >= 0.8f * GetEyeHeight()) { doubleJumpImpulse *= jumpFactor; } if (x258_movementState == EPlayerMovementState::ApplyJump) { if (g_tweakPlayer->GetMaxDoubleJumpWindow() - g_tweakPlayer->GetMinDoubleJumpWindow() >= x28c_sjTimer && 0.f < x28c_sjTimer && ControlMapper::GetPressInput(ControlMapper::ECommands::JumpOrBoost, input)) { SetMoveState(EPlayerMovementState::Jump, mgr); x384_dashTimer = 0.f; x380_strafeInputAtDash = StrafeInput(input); if (g_tweakPlayer->GetImpulseDoubleJump()) { const zeus::CVector3f impulse(0.f, 0.f, (doubleJumpImpulse - x138_velocity.z()) * xe8_mass); ApplyImpulseWR(impulse, zeus::CAxisAngle()); } float forwards = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Forward, input); const float backwards = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Backward, input); if (forwards < backwards) { forwards = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Backward, input); } return ((vDoubleJumpAccel - (vDoubleJumpAccel - hDoubleJumpAccel) * forwards) * xe8_mass) * jumpFactor; } return GetGravity() * xe8_mass; } if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::JumpOrBoost, input) || (x258_movementState == EPlayerMovementState::Jump && x290_minJumpTimeout <= x288_startingJumpTimeout)) { if (x258_movementState != EPlayerMovementState::Jump) { if (ControlMapper::GetPressInput(ControlMapper::ECommands::JumpOrBoost, input)) { SetMoveState(EPlayerMovementState::Jump, mgr); return (vJumpAccel * xe8_mass) * jumpFactor; } return 0.f; } float forwards = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Forward, input); const float backwards = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Backward, input); if (forwards < backwards) { forwards = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Backward, input); } return ((vJumpAccel - (vJumpAccel - hJumpAccel) * forwards) * xe8_mass) * jumpFactor; } if (x258_movementState == EPlayerMovementState::Jump) { SetMoveState(EPlayerMovementState::ApplyJump, mgr); } return 0.f; } float CPlayer::TurnInput(const CFinalInput& input) const { if (x304_orbitState == EPlayerOrbitState::OrbitObject || x304_orbitState == EPlayerOrbitState::Grapple) { return 0.f; } if (IsMorphBallTransitioning()) { return 0.f; } float left = ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnLeft, input); float right = ControlMapper::GetAnalogInput(ControlMapper::ECommands::TurnRight, input); if (g_tweakPlayer->GetFreeLookTurnsPlayer()) { if (!g_tweakPlayer->GetHoldButtonsForFreeLook() || x3dd_lookButtonHeld) { if (left < 0.01f && right < 0.01f) { left = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookLeft, input); right = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookRight, input); } } } else { if (!g_tweakPlayer->GetHoldButtonsForFreeLook() || x3dd_lookButtonHeld) { if (left < 0.01f && right < 0.01f) { const float f31 = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookLeft, input); const float f1 = ControlMapper::GetAnalogInput(ControlMapper::ECommands::LookRight, input); if (f31 > 0.01f || f1 > 0.01f) { return 0.f; } } } } right = left - right; if (x32c_orbitModeTimer > 0.f) { right *= (1.f - zeus::clamp(0.f, x32c_orbitModeTimer / g_tweakPlayer->GetOrbitModeTimer(), 1.f) * 0.5f); } return zeus::clamp(-1.f, right, 1.f); } float CPlayer::StrafeInput(const CFinalInput& input) const { if (IsMorphBallTransitioning() || x304_orbitState == EPlayerOrbitState::NoOrbit) { return 0.f; } return ControlMapper::GetAnalogInput(ControlMapper::ECommands::StrafeRight, input) - ControlMapper::GetAnalogInput(ControlMapper::ECommands::StrafeLeft, input); } float CPlayer::ForwardInput(const CFinalInput& input, float turnInput) const { float forwards = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Forward, input); float backwards = ControlMapper::GetAnalogInput(ControlMapper::ECommands::Backward, input); if (x2f8_morphBallState != EPlayerMorphBallState::Unmorphed || InGrappleJumpCooldown()) { backwards = 0.f; } if (x2f8_morphBallState == EPlayerMorphBallState::Morphing && x584_ballTransitionAnim == 2) { forwards = 0.f; } if (x2f8_morphBallState == EPlayerMorphBallState::Unmorphing && x584_ballTransitionAnim == 5) { forwards = 0.f; } if (forwards >= 0.001f) { forwards = zeus::clamp(-1.f, forwards / 0.8f, 1.f); if (std::fabs(std::atan2(std::fabs(turnInput), forwards)) < M_PIF / 3.6f) { const zeus::CVector3f x50(std::fabs(turnInput), forwards, 0.f); if (x50.canBeNormalized()) { forwards = x50.magnitude(); } } } if (backwards >= 0.001f) { backwards = zeus::clamp(-1.f, backwards / 0.8f, 1.f); if (std::fabs(std::atan2(std::fabs(turnInput), backwards)) < M_PIF / 3.6f) { const zeus::CVector3f x5c(std::fabs(turnInput), backwards, 0.f); if (x5c.canBeNormalized()) { backwards = x5c.magnitude(); } } } if (!g_tweakPlayer->GetMoveDuringFreeLook()) { zeus::CVector3f velFlat = x138_velocity; velFlat.z() = 0.f; if (x3dc_inFreeLook || x3dd_lookButtonHeld) { if (x258_movementState == EPlayerMovementState::OnGround || std::fabs(velFlat.magnitude()) < 0.00001f) { return 0.f; } } } return zeus::clamp(-1.f, forwards - backwards * g_tweakPlayer->GetBackwardsForceMultiplier(), 1.f); } zeus::CVector3f CPlayer::CalculateLeftStickEdgePosition(float strafeInput, float forwardInput) const { float f31 = -1.f; float f30 = -0.555f; float f29 = 0.555f; if (strafeInput >= 0.f) { f31 = -f31; f30 = -f30; } if (forwardInput < 0.f) { f29 = -0.555f; } const float f4 = zeus::clamp(-1.f, std::atan(std::fabs(forwardInput) / std::fabs(strafeInput)) / (M_PIF / 4.f), 1.f); return zeus::CVector3f(f30 - f31, f29, 0.f) * f4 + zeus::CVector3f(f31, 0.f, 0.f); } bool CPlayer::SidewaysDashAllowed(float strafeInput, float forwardInput, const CFinalInput& input, CStateManager& mgr) const { if (x9c5_28_slidingOnWall || x9c5_29_hitWall || x304_orbitState != EPlayerOrbitState::OrbitObject) { return false; } if (g_tweakPlayer->GetDashOnButtonRelease()) { if (x304_orbitState != EPlayerOrbitState::NoOrbit && g_tweakPlayer->GetDashEnabled() && x288_startingJumpTimeout > 0.f && !ControlMapper::GetDigitalInput(ControlMapper::ECommands::JumpOrBoost, input) && x388_dashButtonHoldTime < g_tweakPlayer->GetDashButtonHoldCancelTime() && std::fabs(strafeInput) >= std::fabs(forwardInput) && std::fabs(strafeInput) > g_tweakPlayer->GetDashStrafeInputThreshold()) { return true; } } else { if (x304_orbitState != EPlayerOrbitState::NoOrbit && g_tweakPlayer->GetDashEnabled() && ControlMapper::GetPressInput(ControlMapper::ECommands::JumpOrBoost, input) && x288_startingJumpTimeout > 0.f && std::fabs(strafeInput) >= std::fabs(forwardInput) && std::fabs(strafeInput) > 0.01f) { const float threshold = std::sqrt(strafeInput * strafeInput + forwardInput * forwardInput) / CalculateLeftStickEdgePosition(strafeInput, forwardInput).magnitude(); if (threshold >= g_tweakPlayer->GetDashStrafeInputThreshold()) { return true; } } } return false; } void CPlayer::FinishSidewaysDash() { if (x37c_sidewaysDashing) { x38c_doneSidewaysDashing = true; } x37c_sidewaysDashing = false; x380_strafeInputAtDash = 0.f; x384_dashTimer = 0.f; } void CPlayer::ComputeDash(const CFinalInput& input, float dt, CStateManager& mgr) { const float strafeInput = StrafeInput(input); const float forwardInput = ForwardInput(input, TurnInput(input)); zeus::CVector3f orbitPointFlattened(x314_orbitPoint.x(), x314_orbitPoint.y(), GetTranslation().z()); const zeus::CVector3f orbitToPlayer = GetTranslation() - orbitPointFlattened; if (!orbitToPlayer.canBeNormalized()) { return; } zeus::CVector3f useOrbitToPlayer = orbitToPlayer; const ESurfaceRestraints restraints = GetSurfaceRestraint(); float strafeVel = dt * skStrafeDistances[size_t(restraints)]; if (ControlMapper::GetDigitalInput(ControlMapper::ECommands::JumpOrBoost, input)) { x388_dashButtonHoldTime += dt; } if (!x37c_sidewaysDashing) { if (SidewaysDashAllowed(strafeInput, forwardInput, input, mgr)) { x37c_sidewaysDashing = true; x380_strafeInputAtDash = strafeInput; x38c_doneSidewaysDashing = true; x384_dashTimer = 0.f; zeus::CVector3f vel = x138_velocity; if (vel.z() > 0.f) { vel.z() *= 0.1f; if (!x9c5_28_slidingOnWall) { SetVelocityWR(vel); x778_dashSfx = CSfxManager::SfxStart(SFXsam_dash, 1.f, 0.f, true, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(x778_dashSfx); mgr.GetRumbleManager().Rumble(mgr, ERumbleFxId::PlayerBump, 0.24375f, ERumblePriority::One); } } } strafeVel *= strafeInput; } else { x384_dashTimer += dt; if (x258_movementState == EPlayerMovementState::OnGround || x384_dashTimer >= x3a0_dashDuration || x9c5_28_slidingOnWall || x9c5_29_hitWall || x304_orbitState != EPlayerOrbitState::OrbitObject) { FinishSidewaysDash(); strafeVel *= strafeInput; CSfxManager::RemoveEmitter(x778_dashSfx); } else { if (x39c_noStrafeDashBlend) { strafeVel = dt * skDashStrafeDistances[size_t(restraints)] * x398_dashSpeedMultiplier; } else { strafeVel = ((skDashStrafeDistances[size_t(restraints)] - skStrafeDistances[size_t(restraints)]) * (1.f - zeus::clamp(-1.f, x384_dashTimer / x3a4_strafeDashBlendDuration, 1.f)) + skStrafeDistances[size_t(restraints)]) * x398_dashSpeedMultiplier * dt; } if (x380_strafeInputAtDash < 0.f) { strafeVel = -strafeVel; } } } const float f3 = strafeVel / orbitToPlayer.magnitude(); const float f2 = dt * (x37c_sidewaysDashing ? M_PIF : (M_PIF * 2.f / 3.f)); useOrbitToPlayer = zeus::CQuaternion::fromAxisAngle(zeus::skUp, zeus::clamp(-f2, f3, f2)).transform(orbitToPlayer); orbitPointFlattened += useOrbitToPlayer; if (!ControlMapper::GetDigitalInput(ControlMapper::ECommands::JumpOrBoost, input)) { x388_dashButtonHoldTime = 0.f; } strafeVel = skOrbitForwardDistances[size_t(restraints)] * forwardInput * dt; orbitPointFlattened += -useOrbitToPlayer.normalized() * strafeVel; const zeus::CVector2f velFlat(x138_velocity.x(), x138_velocity.y()); zeus::CVector3f newVelocity = (orbitPointFlattened - GetTranslation()) / dt; const zeus::CVector3f velDelta(newVelocity.x() - x138_velocity.x(), newVelocity.y() - x138_velocity.y(), 0.f); strafeVel = velDelta.magnitude(); if (strafeVel <= FLT_EPSILON) { return; } const float accel = dt * GetAcceleration(); newVelocity = velDelta / strafeVel * accel * zeus::clamp(-1.f, strafeVel / accel, 1.f) + x138_velocity; if (x9c5_28_slidingOnWall) { return; } SetVelocityWR(newVelocity); } void CPlayer::ComputeMovement(const CFinalInput& input, CStateManager& mgr, float dt) { ESurfaceRestraints restraints = GetSurfaceRestraint(); const float jumpInput = JumpInput(input, mgr); float zRotateInput = TurnInput(input); const float forwardInput = ForwardInput(input, zRotateInput); SetVelocityWR(GetDampedClampedVelocityWR()); float turnSpeedMul = g_tweakPlayer->GetTurnSpeedMultiplier(); if (g_tweakPlayer->GetFreeLookTurnsPlayer()) { if (!g_tweakPlayer->GetHoldButtonsForFreeLook() || x3dd_lookButtonHeld) { turnSpeedMul = g_tweakPlayer->GetFreeLookTurnSpeedMultiplier(); } } if (x304_orbitState == EPlayerOrbitState::NoOrbit || (x3dd_lookButtonHeld && x304_orbitState != EPlayerOrbitState::OrbitObject && x304_orbitState != EPlayerOrbitState::Grapple)) { if (std::fabs(zRotateInput) < 0.00001f) { const float frictionZAngVel = g_tweakPlayer->GetPlayerRotationFriction(int(restraints)) * GetAngularVelocityOR().getVector().z(); SetAngularVelocityOR({0.f, 0.f, frictionZAngVel}); } const float curZAngVel = GetAngularVelocityOR().getVector().z(); const float maxZAngVel = g_tweakPlayer->GetPlayerRotationMaxSpeed(int(restraints)) * turnSpeedMul; if (curZAngVel > maxZAngVel) { SetAngularVelocityOR({0.f, 0.f, maxZAngVel}); } else if (-curZAngVel > maxZAngVel) { SetAngularVelocityOR({0.f, 0.f, -maxZAngVel}); } } float f26 = g_tweakPlayer->GetPlayerRotationMaxSpeed(int(restraints)) * zRotateInput * turnSpeedMul; f26 -= GetAngularVelocityOR().getVector().z(); const float remainVel = zeus::clamp( 0.f, std::fabs(f26) / (turnSpeedMul * g_tweakPlayer->GetPlayerRotationMaxSpeed(int(restraints))), 1.f); if (f26 < 0.f) { zRotateInput = remainVel * -g_tweakPlayer->GetMaxRotationalAcceleration(int(restraints)); } else { zRotateInput = remainVel * g_tweakPlayer->GetMaxRotationalAcceleration(int(restraints)); } float forwardForce; if (std::fabs(forwardInput) >= 0.00001f) { const float maxSpeed = g_tweakPlayer->GetPlayerTranslationMaxSpeed(int(restraints)); const float calcSpeed = g_tweakPlayer->GetPlayerTranslationFriction(int(restraints)) * xe8_mass / (dt * g_tweakPlayer->GetMaxTranslationalAcceleration(int(restraints))) * maxSpeed; const float f28 = (forwardInput > 0.f ? 1.f : -1.f) * calcSpeed + (maxSpeed - calcSpeed) * forwardInput; forwardForce = zeus::clamp(-1.f, (f28 - x34_transform.transposeRotate(x138_velocity).y()) / maxSpeed, 1.f) * g_tweakPlayer->GetMaxTranslationalAcceleration(int(restraints)); } else { forwardForce = 0.f; } if (x304_orbitState != EPlayerOrbitState::NoOrbit && x3dd_lookButtonHeld) { forwardForce = 0.f; } if (x304_orbitState == EPlayerOrbitState::NoOrbit || x3dd_lookButtonHeld) { ApplyForceOR({0.f, forwardForce, jumpInput}, zeus::CAxisAngle()); if (zRotateInput != 0.f) { ApplyForceOR(zeus::skZero3f, zeus::CAxisAngle({0.f, 0.f, 1.f}, zRotateInput)); } if (x37c_sidewaysDashing) { x38c_doneSidewaysDashing = true; } x37c_sidewaysDashing = false; x380_strafeInputAtDash = 0.f; x384_dashTimer = 0.f; } else { switch (x304_orbitState) { case EPlayerOrbitState::OrbitObject: case EPlayerOrbitState::OrbitPoint: case EPlayerOrbitState::OrbitCarcass: case EPlayerOrbitState::ForcedOrbitObject: if (!InGrappleJumpCooldown()) { ComputeDash(input, dt, mgr); } break; default: break; } ApplyForceOR({0.f, 0.f, jumpInput}, zeus::CAxisAngle()); } if (x3dc_inFreeLook || x3dd_lookButtonHeld) { if (!x9c5_28_slidingOnWall && x258_movementState == EPlayerMovementState::OnGround) { const zeus::CVector3f revVelFlat(-x138_velocity.x(), -x138_velocity.y(), 0.f); const float revVelFlatMag = revVelFlat.magnitude(); if (revVelFlatMag > FLT_EPSILON) { const float accel = 0.2f * dt * GetAcceleration(); const float damp = zeus::clamp(-1.f, revVelFlatMag / accel, 1.f); SetVelocityWR(revVelFlat / revVelFlatMag * accel * damp + x138_velocity); } } } x9c5_29_hitWall = false; if (x2d4_accelerationChangeTimer > 0.f) { x2d0_curAcceleration = 0; } else { x2d0_curAcceleration += 1; } x2d4_accelerationChangeTimer -= dt; x2d4_accelerationChangeTimer = std::max(0.f, x2d4_accelerationChangeTimer); } float CPlayer::GetWeight() const { return xe8_mass * -GetGravity(); } zeus::CVector3f CPlayer::GetDampedClampedVelocityWR() const { zeus::CVector3f localVel = x34_transform.transposeRotate(x138_velocity); if ((x258_movementState != EPlayerMovementState::ApplyJump || GetSurfaceRestraint() != ESurfaceRestraints::Air) && x304_orbitState == EPlayerOrbitState::NoOrbit) { const float friction = g_tweakPlayer->GetPlayerTranslationFriction(int(GetSurfaceRestraint())); if (localVel.y() > 0.f) { localVel.y() = std::max(0.f, localVel.y() - friction); } else { localVel.y() = std::min(0.f, localVel.y() + friction); } if (localVel.x() > 0.f) { localVel.x() = std::max(0.f, localVel.x() - friction); } else { localVel.x() = std::min(0.f, localVel.x() + friction); } } const float maxSpeed = g_tweakPlayer->GetPlayerTranslationMaxSpeed(int(GetSurfaceRestraint())); localVel.y() = zeus::clamp(-maxSpeed, float(localVel.y()), maxSpeed); if (x258_movementState == EPlayerMovementState::OnGround) { localVel.z() = 0.f; } return x34_transform.rotate(localVel); } const CScriptWater* CPlayer::GetVisorRunoffEffect(const CStateManager& mgr) const { if (xc4_fluidId == kInvalidUniqueId) { return nullptr; } return TCastToConstPtr(mgr.GetObjectById(xc4_fluidId)).GetPtr(); } void CPlayer::SetMorphBallState(EPlayerMorphBallState state, CStateManager& mgr) { if (x2f8_morphBallState == EPlayerMorphBallState::Morphed && state != EPlayerMorphBallState::Morphed) { x9c5_26_ = x9c4_31_inWaterMovement; } x2f8_morphBallState = state; xf9_standardCollider = state == EPlayerMorphBallState::Morphed; switch (x2f8_morphBallState) { case EPlayerMorphBallState::Unmorphed: if (x9c5_26_ && mgr.GetCameraManager()->GetFluidCounter() == 0) { if (const auto* water = GetVisorRunoffEffect(mgr)) { if (const auto& effect = water->GetUnmorphVisorRunoffEffect()) { auto* sheets = new CHUDBillboardEffect( *effect, {}, mgr.AllocateUniqueId(), true, "WaterSheets", CHUDBillboardEffect::GetNearClipDistance(mgr), CHUDBillboardEffect::GetScaleForPOV(mgr), zeus::skWhite, zeus::skOne3f, zeus::skZero3f); mgr.AddObject(sheets); } CSfxHandle hnd = CSfxManager::SfxStart(water->GetUnmorphVisorRunoffSfx(), 1.f, 0.f, false, 0x7f, false, kInvalidAreaId); ApplySubmergedPitchBend(hnd); } } break; case EPlayerMorphBallState::Morphed: case EPlayerMorphBallState::Morphing: x768_morphball->LoadMorphBallModel(mgr); break; default: break; } } bool CPlayer::CanLeaveMorphBallState(CStateManager& mgr, zeus::CVector3f& pos) const { if (x768_morphball->IsProjectile() || !x590_leaveMorphballAllowed || (IsUnderBetaMetroidAttack(mgr) && x2f8_morphBallState == EPlayerMorphBallState::Morphed)) { return false; } if (!x9c4_28_canLeaveMorphBall) { return false; } constexpr CMaterialFilter filter = CMaterialFilter::MakeInclude({EMaterialTypes::Solid}); const zeus::CAABox aabb(x2d8_fpBounds.min - zeus::CVector3f(1.f) + GetTranslation(), x2d8_fpBounds.max + zeus::CVector3f(1.f) + GetTranslation()); EntityList nearList; mgr.BuildColliderList(nearList, *this, aabb); const zeus::CAABox& baseAABB = GetBaseBoundingBox(); pos = zeus::skZero3f; for (int i = 0; i < 8; ++i) { const zeus::CAABox aabb2(baseAABB.min + pos + GetTranslation(), baseAABB.max + pos + GetTranslation()); const CCollidableAABox cAABB(aabb2, CMaterialList()); if (!CGameCollision::DetectCollisionBoolean(mgr, cAABB, zeus::CTransform(), filter, nearList)) { return true; } pos.z() += 0.1f; } return false; } void CPlayer::SetHudDisable(float staticTimer, float outSpeed, float inSpeed) { x740_staticTimer = staticTimer; x744_staticOutSpeed = outSpeed; x748_staticInSpeed = inSpeed; if (x744_staticOutSpeed != 0.f) { return; } if (x740_staticTimer == 0.f) { x74c_visorStaticAlpha = 1.f; } else { x74c_visorStaticAlpha = 0.f; } } void CPlayer::SetIntoBallReadyAnimation(CStateManager& mgr) { constexpr CAnimPlaybackParms parms(2, -1, 1.f, true); x64_modelData->GetAnimationData()->SetAnimation(parms, false); x64_modelData->GetAnimationData()->EnableLooping(false); x64_modelData->AdvanceAnimation(0.f, mgr, kInvalidAreaId, true); x64_modelData->GetAnimationData()->EnableAnimation(false); } void CPlayer::LeaveMorphBallState(CStateManager& mgr) { x730_transitionModels.clear(); AddMaterial(EMaterialTypes::GroundCollider, mgr); x150_momentum = zeus::skZero3f; SetMorphBallState(EPlayerMorphBallState::Unmorphed, mgr); SetHudDisable(FLT_EPSILON, 0.f, 2.f); SetHudDisable(FLT_EPSILON, 0.f, 2.f); SetIntoBallReadyAnimation(mgr); CPhysicsActor::Stop(); x3e4_freeLookYawAngle = 0.f; x3e8_horizFreeLookAngleVel = 0.f; x3ec_freeLookPitchAngle = 0.f; x3f0_vertFreeLookAngleVel = 0.f; x768_morphball->LeaveMorphBallState(mgr); mgr.GetCameraManager()->SetPlayerCamera(mgr, mgr.GetCameraManager()->GetFirstPersonCamera()->GetUniqueId()); mgr.GetCameraManager()->GetBallCamera()->SetState(CBallCamera::EBallCameraState::Default, mgr); SetCameraState(EPlayerCameraState::FirstPerson, mgr); mgr.GetCameraManager()->GetFirstPersonCamera()->DeferBallTransitionProcessing(); mgr.GetCameraManager()->GetFirstPersonCamera()->Think(0.f, mgr); ForceGunOrientation(x34_transform, mgr); DrawGun(mgr); } bool CPlayer::CanEnterMorphBallState(CStateManager& mgr, float f1) const { if (x3b8_grappleState != EGrappleState::None || (IsUnderBetaMetroidAttack(mgr) && x2f8_morphBallState == EPlayerMorphBallState::Unmorphed)) { return false; } return x9c4_27_canEnterMorphBall; } void CPlayer::EnterMorphBallState(CStateManager& mgr) { SetMorphBallState(EPlayerMorphBallState::Morphed, mgr); RemoveMaterial(EMaterialTypes::GroundCollider, mgr); x730_transitionModels.clear(); SetAngularVelocityOR(zeus::CAxisAngle( zeus::CVector3f(x138_velocity.magnitude() / g_tweakPlayer->GetPlayerBallHalfExtent(), 0.f, 0.f))); x768_morphball->EnterMorphBallState(mgr); x768_morphball->TakeDamage(-1.f); x768_morphball->SetDamageTimer(0.f); mgr.GetPlayerState()->StartTransitionToVisor(CPlayerState::EPlayerVisor::Combat); } void CPlayer::ActivateMorphBallCamera(CStateManager& mgr) { SetCameraState(EPlayerCameraState::Ball, mgr); mgr.GetCameraManager()->GetBallCamera()->SetState(CBallCamera::EBallCameraState::Default, mgr); } void CPlayer::UpdateCinematicState(CStateManager& mgr) { if (mgr.GetCameraManager()->IsInCinematicCamera()) { if (x2f4_cameraState != EPlayerCameraState::Spawned) { x2fc_spawnedMorphBallState = x2f8_morphBallState; if (x2fc_spawnedMorphBallState == EPlayerMorphBallState::Unmorphing) { x2fc_spawnedMorphBallState = EPlayerMorphBallState::Unmorphed; } if (x2fc_spawnedMorphBallState == EPlayerMorphBallState::Morphing) { x2fc_spawnedMorphBallState = EPlayerMorphBallState::Morphed; } SetCameraState(EPlayerCameraState::Spawned, mgr); } } else { if (x2f4_cameraState == EPlayerCameraState::Spawned) { if (x2fc_spawnedMorphBallState == x2f8_morphBallState) { switch (x2fc_spawnedMorphBallState) { case EPlayerMorphBallState::Morphed: SetCameraState(EPlayerCameraState::Ball, mgr); break; case EPlayerMorphBallState::Unmorphed: SetCameraState(EPlayerCameraState::FirstPerson, mgr); if (mgr.GetPlayerState()->GetCurrentVisor() != CPlayerState::EPlayerVisor::Scan) { ForceGunOrientation(x34_transform, mgr); DrawGun(mgr); } break; default: break; } } else { CPhysicsActor::Stop(); SetOrbitRequest(EPlayerOrbitRequest::Respawn, mgr); switch (x2fc_spawnedMorphBallState) { case EPlayerMorphBallState::Unmorphed: { zeus::CVector3f vec; if (CanLeaveMorphBallState(mgr, vec)) { SetTranslation(GetTranslation() + vec); LeaveMorphBallState(mgr); SetCameraState(EPlayerCameraState::FirstPerson, mgr); ForceGunOrientation(x34_transform, mgr); DrawGun(mgr); } break; } case EPlayerMorphBallState::Morphed: EnterMorphBallState(mgr); ActivateMorphBallCamera(mgr); mgr.GetCameraManager()->SetupBallCamera(mgr); mgr.GetCameraManager()->GetBallCamera()->Reset(CreateTransformFromMovementDirection(), mgr); break; default: break; } } } } } void CPlayer::SetCameraState(EPlayerCameraState camState, CStateManager& stateMgr) { if (x2f4_cameraState == camState) { return; } x2f4_cameraState = camState; CCameraManager* camMgr = stateMgr.GetCameraManager(); switch (camState) { case EPlayerCameraState::FirstPerson: camMgr->SetCurrentCameraId(camMgr->GetFirstPersonCamera()->GetUniqueId(), stateMgr); x768_morphball->SetBallLightActive(stateMgr, false); break; case EPlayerCameraState::Ball: case EPlayerCameraState::Transitioning: camMgr->SetCurrentCameraId(camMgr->GetBallCamera()->GetUniqueId(), stateMgr); x768_morphball->SetBallLightActive(stateMgr, true); break; case EPlayerCameraState::Two: break; case EPlayerCameraState::Spawned: { bool ballLight = false; if (const TCastToConstPtr cineCam = camMgr->GetCurrentCamera(stateMgr)) { ballLight = x2f8_morphBallState == EPlayerMorphBallState::Morphed && cineCam->GetFlags() & 0x40; } x768_morphball->SetBallLightActive(stateMgr, ballLight); break; } } } bool CPlayer::IsEnergyLow(const CStateManager& mgr) const { const float lowThreshold = mgr.GetPlayerState()->GetItemCapacity(CPlayerState::EItemType::EnergyTanks) < 4 ? 30.f : 100.f; return GetHealthInfo(mgr)->GetHP() < lowThreshold; } bool CPlayer::ObjectInScanningRange(TUniqueId id, const CStateManager& mgr) const { const CEntity* ent = mgr.GetObjectById(id); if (const TCastToConstPtr act = ent) { const zeus::CVector3f delta = act->GetTranslation() - GetTranslation(); if (delta.canBeNormalized()) { return delta.magnitude() < g_tweakPlayer->GetScanningRange(); } } return false; } void CPlayer::SetPlayerHitWallDuringMove() { x9c5_29_hitWall = true; x2d0_curAcceleration = 1; } void CPlayer::Touch(CActor& actor, CStateManager& mgr) { if (x2f8_morphBallState != EPlayerMorphBallState::Morphed) { return; } x768_morphball->Touch(actor, mgr); } void CPlayer::CVisorSteam::SetSteam(float targetAlpha, float alphaInDur, float alphaOutDur, CAssetId txtr, bool affectsThermal) { if (!x1c_txtr.IsValid() || targetAlpha > x10_nextTargetAlpha) { x10_nextTargetAlpha = targetAlpha; x14_nextAlphaInDur = alphaInDur; x18_nextAlphaOutDur = alphaOutDur; x1c_txtr = txtr; } x28_affectsThermal = affectsThermal; } CAssetId CPlayer::CVisorSteam::GetTextureId() const { return xc_tex; } void CPlayer::CVisorSteam::Update(float dt) { if (!x1c_txtr.IsValid()) { x0_curTargetAlpha = 0.f; } else { x0_curTargetAlpha = x10_nextTargetAlpha; x4_curAlphaInDur = x14_nextAlphaInDur; x8_curAlphaOutDur = x18_nextAlphaOutDur; xc_tex = x1c_txtr; } x1c_txtr.Reset(); if (zeus::close_enough(x20_alpha, x0_curTargetAlpha) && zeus::close_enough(x20_alpha, 0.f)) { return; } if (x20_alpha > x0_curTargetAlpha) { if (x24_delayTimer <= 0.f) { x20_alpha -= dt / x8_curAlphaOutDur; if (x20_alpha < x0_curTargetAlpha) { x20_alpha = x0_curTargetAlpha; } } else { x24_delayTimer -= dt; if (x24_delayTimer < 0.f) { x24_delayTimer = 0.f; } } return; } const CToken tmpTex = g_SimplePool->GetObj({SBIG('TXTR'), xc_tex}); if (!tmpTex) { return; } x20_alpha += dt / x4_curAlphaInDur; if (x20_alpha > x0_curTargetAlpha) { x20_alpha = x0_curTargetAlpha; } x24_delayTimer = 0.1f; } void CPlayer::CFailsafeTest::Reset() { x0_stateSamples.clear(); x54_posSamples.clear(); x148_velSamples.clear(); x23c_inputSamples.clear(); } void CPlayer::CFailsafeTest::AddSample(EInputState state, const zeus::CVector3f& pos, const zeus::CVector3f& vel, const zeus::CVector2f& input) { if (x0_stateSamples.size() >= 20) { x0_stateSamples.resize(19); } x0_stateSamples.insert(x0_stateSamples.begin(), state); if (x54_posSamples.size() >= 20) { x54_posSamples.resize(19); } x54_posSamples.insert(x54_posSamples.begin(), pos); if (x148_velSamples.size() >= 20) { x148_velSamples.resize(19); } x148_velSamples.insert(x148_velSamples.begin(), vel); if (x23c_inputSamples.size() >= 20) { x23c_inputSamples.resize(19); } x23c_inputSamples.insert(x23c_inputSamples.begin(), input); } bool CPlayer::CFailsafeTest::Passes() const { if (x0_stateSamples.size() != 20) { return false; } float posMag = 0.f; zeus::CAABox velAABB(x148_velSamples[0], x148_velSamples[0]); zeus::CAABox posAABB(x54_posSamples[0], x54_posSamples[0]); zeus::CVector3f inputVec(x23c_inputSamples[0].x(), x23c_inputSamples[0].y(), 0.f); zeus::CAABox inputAABB(inputVec, inputVec); float maxVelMag = x148_velSamples[0].magnitude(); float minVelMag = maxVelMag; u32 notEqualStates = 0; for (int i = 1; i < 20; ++i) { const float mag = (x54_posSamples[i - 1] - x54_posSamples[i]).magSquared(); if (mag > FLT_EPSILON) { posMag += std::sqrt(mag); } posAABB.accumulateBounds(x54_posSamples[i]); velAABB.accumulateBounds(x148_velSamples[i]); const float velMag = x148_velSamples[i].magnitude(); minVelMag = std::min(minVelMag, velMag); maxVelMag = std::max(maxVelMag, velMag); const zeus::CVector3f inputVec2(x23c_inputSamples[i].x(), x23c_inputSamples[i].y(), 0.f); inputAABB.accumulateBounds(inputVec2); if (x0_stateSamples[i] != x0_stateSamples[i - 1]) { notEqualStates += 1; } } bool test1 = true; if (posMag >= 1.f / 30.f && posMag >= minVelMag / 30.f) { test1 = false; } if (notEqualStates == 0 && x0_stateSamples[0] == EInputState::StartingJump) { const float inputMag = (inputAABB.max - inputAABB.min).magnitude(); zeus::CAABox inputFrom0AABB(inputAABB); inputFrom0AABB.accumulateBounds(zeus::skZero3f); bool test2 = true; if ((inputFrom0AABB.max - inputFrom0AABB.min).magnitude() >= 0.01f && inputMag <= 1.5f) { test2 = false; } return test1 && test2; } return false; } void CPlayer::SetSpawnedMorphBallState(EPlayerMorphBallState state, CStateManager& mgr) { x2fc_spawnedMorphBallState = state; SetCameraState(EPlayerCameraState::Spawned, mgr); if (x2fc_spawnedMorphBallState != x2f8_morphBallState) { CPhysicsActor::Stop(); SetOrbitRequest(EPlayerOrbitRequest::Respawn, mgr); switch (x2fc_spawnedMorphBallState) { case EPlayerMorphBallState::Unmorphed: { zeus::CVector3f pos; if (CanLeaveMorphBallState(mgr, pos)) { SetTranslation(GetTranslation() + pos); LeaveMorphBallState(mgr); ForceGunOrientation(x34_transform, mgr); DrawGun(mgr); } break; } case EPlayerMorphBallState::Morphed: EnterMorphBallState(mgr); ActivateMorphBallCamera(mgr); mgr.GetCameraManager()->SetupBallCamera(mgr); mgr.GetCameraManager()->GetBallCamera()->Reset(CreateTransformFromMovementDirection(), mgr); break; default: break; } } } void CPlayer::DecrementEnvironmentDamage() { if (xa10_envDmgCounter == 0) { return; } xa10_envDmgCounter--; } void CPlayer::IncrementEnvironmentDamage() { if (xa10_envDmgCounter != 0) { xa10_envDmgCounter++; } else { xa14_envDmgCameraShakeTimer = 0.f; } } bool CPlayer::CheckSubmerged() const { if (xe6_24_fluidCounter == 0) { return false; } return x828_distanceUnderWater >= (x2f8_morphBallState == EPlayerMorphBallState::Morphed ? 2.f * g_tweakPlayer->GetPlayerBallHalfExtent() : 0.5f * GetEyeHeight()); } void CPlayer::UpdateSubmerged(CStateManager& mgr) { x82c_inLava = false; x828_distanceUnderWater = 0.f; if (xe6_24_fluidCounter == 0) { return; } if (const TCastToConstPtr water = mgr.ObjectById(xc4_fluidId)) { x828_distanceUnderWater = -(zeus::skUp.dot(x34_transform.origin) - water->GetTriggerBoundsWR().max.z()); const EFluidType fluidType = water->GetFluidPlane().GetFluidType(); x82c_inLava = (fluidType == EFluidType::Lava || fluidType == EFluidType::ThickLava); CheckSubmerged(); } } void CPlayer::ApplySubmergedPitchBend(CSfxHandle& sfx) { if (!CheckSubmerged()) { return; } CSfxManager::PitchBend(sfx, -1.f); } void CPlayer::DetachActorFromPlayer() { x26c_attachedActor = kInvalidUniqueId; x270_attachedActorTime = 0.f; xa28_attachedActorStruggle = 0.f; x490_gun->SetActorAttached(false); } bool CPlayer::AttachActorToPlayer(TUniqueId id, bool disableGun) { if (x26c_attachedActor != kInvalidUniqueId) { return false; } if (disableGun) { x490_gun->SetActorAttached(true); } x26c_attachedActor = id; x270_attachedActorTime = 0.f; xa28_attachedActorStruggle = 0.f; x768_morphball->StopEffects(); return true; } float CPlayer::GetAverageSpeed() const { if (const auto avg = x4a4_moveSpeedAvg.GetAverage()) { return *avg; } return x4f8_moveSpeed; } } // namespace metaforce