#include "Runtime/World/CScriptGunTurret.hpp" #include #include "Runtime/CSimplePool.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Character/CPASAnimParmData.hpp" #include "Runtime/Collision/CCollisionActor.hpp" #include "Runtime/Collision/CCollisionActorManager.hpp" #include "Runtime/Graphics/CCubeRenderer.hpp" #include "Runtime/Particle/CElementGen.hpp" #include "Runtime/Particle/CGenDescription.hpp" #include "Runtime/Weapon/CEnergyProjectile.hpp" #include "Runtime/Weapon/CGameProjectile.hpp" #include "Runtime/World/CAiFuncMap.hpp" #include "Runtime/World/CGameLight.hpp" #include "Runtime/World/CPlayer.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { namespace { constexpr CMaterialList skGunMaterialList = {EMaterialTypes::Solid, EMaterialTypes::Character, EMaterialTypes::Orbit, EMaterialTypes::Target}; constexpr CMaterialList skTurretMaterialList = {EMaterialTypes::Character}; constexpr std::array skBurst2InfoTemplate{{ {3, {1, 2, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {3, {7, 6, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {4, {3, 5, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {60, {16, 4, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {30, {4, 4, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000}, }}; constexpr std::array skBurst3InfoTemplate{{ {30, {4, 5, 4, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {30, {2, 3, 4, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {30, {3, 4, 5, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {5, {16, 1, 2, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {5, {8, 7, 6, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000}, }}; constexpr std::array skBurst4InfoTemplate{{ {5, {16, 1, 2, 3, 0, 0, 0, 0}, 0.150000, 0.050000}, {5, {9, 8, 7, 6, 0, 0, 0, 0}, 0.150000, 0.050000}, {15, {2, 3, 4, 5, 0, 0, 0, 0}, 0.150000, 0.050000}, {15, {5, 4, 3, 2, 0, 0, 0, 0}, 0.150000, 0.050000}, {15, {10, 11, 4, 13, 0, 0, 0, 0}, 0.150000, 0.050000}, {15, {14, 13, 4, 11, 0, 0, 0, 0}, 0.150000, 0.050000}, {30, {2, 4, 4, 6, 0, 0, 0, 0}, 0.150000, 0.050000}, {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000}, }}; constexpr std::array skOOVBurst2InfoTemplate{{ {20, {16, 15, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {20, {8, 9, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {20, {13, 11, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {20, {2, 6, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {20, {3, 4, -1, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000}, }}; constexpr std::array skOOVBurst3InfoTemplate{{ {10, {14, 4, 10, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {10, {15, 13, 4, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {10, {9, 11, 4, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {35, {15, 13, 11, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {35, {9, 11, 13, -1, 0, 0, 0, 0}, 0.150000, 0.050000}, {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000}, }}; constexpr std::array skOOVBurst4InfoTemplate{{ {10, {14, 13, 4, 11, 0, 0, 0, 0}, 0.150000, 0.050000}, {30, {1, 15, 13, 11, 0, 0, 0, 0}, 0.150000, 0.050000}, {20, {16, 15, 14, 13, 0, 0, 0, 0}, 0.150000, 0.050000}, {10, {8, 9, 11, 4, 0, 0, 0, 0}, 0.150000, 0.050000}, {10, {1, 15, 13, 4, 0, 0, 0, 0}, 0.150000, 0.050000}, {20, {8, 9, 10, 11, 0, 0, 0, 0}, 0.150000, 0.050000}, {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000}, }}; constexpr std::array skBursts{ skBurst2InfoTemplate.data(), skBurst3InfoTemplate.data(), skBurst4InfoTemplate.data(), skOOVBurst2InfoTemplate.data(), skOOVBurst3InfoTemplate.data(), skOOVBurst4InfoTemplate.data(), nullptr, }; constexpr std::array StateNames{ "Destroyed", "Deactive", "DeactiveFromReady", "Deactivating", "DeactivatingFromReady", "Inactive", "Ready", "PanningA", "PanningB", "Targeting", "Firing", "ExitTargeting", "Frenzy", }; constexpr std::array skStateToLocoTypeLookup{ 5, 7, 9, 0, 1, 0, 1, 2, 3, 1, 1, 1, 1, }; } // Anonymous namespace CScriptGunTurretData::CScriptGunTurretData(CInputStream& in, s32 propCount) : x0_intoDeactivateDelay(in.ReadFloat()) , x4_intoActivateDelay(in.ReadFloat()) , x8_reloadTime(in.ReadFloat()) , xc_reloadTimeVariance(in.ReadFloat()) , x10_panStartTime(in.ReadFloat()) , x14_panHoldTime(in.ReadFloat()) , x1c_leftMaxAngle(zeus::degToRad(in.ReadFloat())) , x20_rightMaxAngle(zeus::degToRad(in.ReadFloat())) , x24_downMaxAngle(zeus::degToRad(in.ReadFloat())) , x28_turnSpeed(zeus::degToRad(in.ReadFloat())) , x2c_detectionRange(in.ReadFloat()) , x30_detectionZRange(in.ReadFloat()) , x34_freezeDuration(in.ReadFloat()) , x38_freezeVariance(in.ReadFloat()) , x3c_freezeTimeout(propCount >= 48 ? in.ReadBool() : false) , x40_projectileRes(in) , x44_projectileDamage(in) , x60_idleLightRes(in.Get()) , x64_deactivateLightRes(in.Get()) , x68_targettingLightRes(in.Get()) , x6c_frozenEffectRes(in.Get()) , x70_chargingEffectRes(in.Get()) , x74_panningEffectRes(in.Get()) , x78_visorEffectRes(propCount >= 44 ? in.Get() : CAssetId()) , x7c_trackingSoundId(CSfxManager::TranslateSFXID(u16(in.ReadLong()))) , x7e_lockOnSoundId(CSfxManager::TranslateSFXID(u16(in.ReadLong()))) , x80_unfreezeSoundId(CSfxManager::TranslateSFXID(u16(in.ReadLong()))) , x82_stopClankSoundId(CSfxManager::TranslateSFXID(u16(in.ReadLong()))) , x84_chargingSoundId(CSfxManager::TranslateSFXID(u16(in.ReadLong()))) , x86_visorSoundId(propCount >= 45 ? CSfxManager::TranslateSFXID(u16(in.ReadLong())) : u16(0xFFFF)) , x88_extensionModelResId(in.Get()) , x8c_extensionDropDownDist(in.ReadFloat()) , x90_numInitialShots(in.ReadLong()) , x94_initialShotTableIndex(in.ReadLong()) , x98_numSubsequentShots(in.ReadLong()) , x9c_frenzyDuration(propCount >= 47 ? in.ReadFloat() : 3.f) , xa0_scriptedStartOnly(propCount >= 46 ? in.ReadBool() : false) {} CScriptGunTurret::CScriptGunTurret(TUniqueId uid, std::string_view name, ETurretComponent comp, const CEntityInfo& info, const zeus::CTransform& xf, CModelData&& mData, const zeus::CAABox& aabb, const CHealthInfo& hInfo, const CDamageVulnerability& dVuln, const CActorParameters& aParms, const CScriptGunTurretData& turretData) : CPhysicsActor(uid, true, name, info, xf, std::move(mData), comp == ETurretComponent::Base ? skTurretMaterialList : skGunMaterialList, aabb, SMoverData(1000.f), aParms, 0.3f, 0.1f) , x258_type(comp) , x264_healthInfo(hInfo) , x26c_damageVuln(dVuln) , x2d4_data(turretData) , x37c_projectileInfo(turretData.GetProjectileRes(), turretData.GetProjectileDamage()) , x3a4_burstFire(skBursts.data(), 1) , x410_idleLightDesc(g_SimplePool->GetObj({SBIG('PART'), turretData.GetIdleLightRes()})) , x41c_deactivateLightDesc(g_SimplePool->GetObj({SBIG('PART'), turretData.GetDeactivateLightRes()})) , x428_targettingLightDesc(g_SimplePool->GetObj({SBIG('PART'), turretData.GetTargettingLightRes()})) , x434_frozenEffectDesc(g_SimplePool->GetObj({SBIG('PART'), turretData.GetFrozenEffectRes()})) , x440_chargingEffectDesc(g_SimplePool->GetObj({SBIG('PART'), turretData.GetChargingEffectRes()})) , x44c_panningEffectDesc(g_SimplePool->GetObj({SBIG('PART'), turretData.GetPanningEffectRes()})) { if (turretData.GetVisorEffectRes().IsValid()) { x458_visorEffectDesc = g_SimplePool->GetObj({SBIG('PART'), turretData.GetVisorEffectRes()}); } x468_idleLight = std::make_unique(x410_idleLightDesc); x470_deactivateLight = std::make_unique(x41c_deactivateLightDesc); x478_targettingLight = std::make_unique(x428_targettingLightDesc); x480_frozenEffect = std::make_unique(x434_frozenEffectDesc); x488_chargingEffect = std::make_unique(x440_chargingEffectDesc); x490_panningEffect = std::make_unique(x44c_panningEffectDesc); x4fc_extensionOffset = xf.origin; x514_lastFrontVector = xf.frontVector(); x544_originalFrontVec = xf.frontVector(); x550_originalRightVec = xf.rightVector(); if (comp == ETurretComponent::Base && HasModelData() && GetModelData()->HasAnimData()) { GetModelData()->EnableLooping(true); } x37c_projectileInfo.Token().Lock(); } CScriptGunTurret::~CScriptGunTurret() = default; void CScriptGunTurret::Accept(IVisitor& visitor) { visitor.Visit(this); } void CScriptGunTurret::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) { CActor::AcceptScriptMsg(msg, uid, mgr); switch (msg) { case EScriptObjectMessage::Activate: if (x49c_collisionManager) { x49c_collisionManager->SetActive(mgr, true); } break; case EScriptObjectMessage::Deactivate: if (x49c_collisionManager) { x49c_collisionManager->SetActive(mgr, false); } break; case EScriptObjectMessage::Registered: if (x258_type == ETurretComponent::Gun) { if (x478_targettingLight->SystemHasLight()) { x498_lightId = mgr.AllocateUniqueId(); mgr.AddObject(new CGameLight(x498_lightId, GetAreaIdAlways(), GetActive(), std::string("ParticleLight_").append(GetName()), GetTransform(), GetUniqueId(), x478_targettingLight->GetLight(), 0, 1, 0.f)); } SetupCollisionManager(mgr); } else if (x258_type == ETurretComponent::Base) { const zeus::CVector3f scale = GetModelData()->GetScale(); if (x2d4_data.GetExtensionModelResId().IsValid()) { CModelData mData(CStaticRes(x2d4_data.GetExtensionModelResId(), scale)); x4a4_extensionModel.emplace(std::move(mData)); x4f4_extensionRange = x4a4_extensionModel->GetBounds().max.z() - x4a4_extensionModel->GetBounds().min.z(); } SetTurretState(ETurretState::Inactive, mgr); } break; case EScriptObjectMessage::Deleted: { if (x258_type == ETurretComponent::Gun) { if (x498_lightId != kInvalidUniqueId) { mgr.FreeScriptObject(x498_lightId); } } if (x50c_targetingEmitter) { CSfxManager::RemoveEmitter(x50c_targetingEmitter); } if (x49c_collisionManager) { x49c_collisionManager->Destroy(mgr); } break; } case EScriptObjectMessage::Start: if (x258_type == ETurretComponent::Base && x520_state == ETurretState::Inactive) { x560_29_scriptedStart = true; } break; case EScriptObjectMessage::Stop: if (x258_type == ETurretComponent::Base && x520_state != ETurretState::Deactive && x520_state != ETurretState::DeactiveFromReady && x520_state != ETurretState::Deactivating) { SetTurretState((x560_28_hasBeenActivated ? ETurretState::DeactivatingFromReady : ETurretState::Deactivating), mgr); } break; case EScriptObjectMessage::Action: { if (x258_type == ETurretComponent::Gun) { LaunchProjectile(mgr); } else if (x258_type == ETurretComponent::Base) { PlayAdditiveFlinchAnimation(mgr); } break; } case EScriptObjectMessage::SetToMax: { x560_25_frozen = false; SetMuted(false); break; } case EScriptObjectMessage::SetToZero: { x560_25_frozen = true; SetMuted(true); break; } case EScriptObjectMessage::InitializedInArea: { if (x258_type == ETurretComponent::Base) { for (const SConnection& conn : x20_conns) { if (conn.x0_state != EScriptObjectState::Play || conn.x4_msg != EScriptObjectMessage::Activate) { continue; } if (TCastToConstPtr gun = mgr.GetObjectById(mgr.GetIdForScript(conn.x8_objId))) { x25c_gunId = mgr.GetIdForScript(conn.x8_objId); x260_lastGunHP = gun->GetHealthInfo(mgr)->GetHP(); return; } } } break; } case EScriptObjectMessage::Damage: { if (x258_type == ETurretComponent::Gun && GetHealthInfo(mgr)->GetHP() <= 0.f) { if (const TCastToConstPtr proj = mgr.GetObjectById(uid)) { if ((proj->GetAttribField() & EProjectileAttrib::Wave) == EProjectileAttrib::Wave) { x520_state = ETurretState::Frenzy; RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr); mgr.GetPlayer().SetOrbitRequestForTarget(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr); x53c_freezeRemTime = 0.f; } } } break; } default: break; } } void CScriptGunTurret::Think(float dt, CStateManager& mgr) { if (!GetActive()) { return; } if (x258_type == ETurretComponent::Base) { if (!x560_25_frozen) { ProcessGunStateMachine(dt, mgr); UpdateTurretAnimation(); UpdateGunOrientation(dt, mgr); const zeus::CVector3f vec = UpdateExtensionModelState(dt); const SAdvancementDeltas advancementDeltas = UpdateAnimation(dt, mgr, true); SetTranslation(vec + advancementDeltas.x0_posDelta + GetTranslation()); RotateToOR(advancementDeltas.xc_rotDelta, dt); } else { Stop(); } UpdateTargettingSound(dt); } else if (x258_type == ETurretComponent::Gun) { UpdateGunParticles(dt, mgr); const SAdvancementDeltas deltas = UpdateAnimation(dt, mgr, true); MoveToOR(deltas.x0_posDelta, dt); RotateToOR(deltas.xc_rotDelta, dt); UpdateGunCollisionManager(dt, mgr); UpdateFrozenState(dt, mgr); } UpdateHealthInfo(mgr); } void CScriptGunTurret::Touch(CActor& act, CStateManager& mgr) { if (x258_type != ETurretComponent::Gun) { return; } if (const TCastToConstPtr proj = act) { const CPlayer& player = mgr.GetPlayer(); if (proj->GetOwnerId() == player.GetUniqueId()) { const CDamageVulnerability* dVuln = GetDamageVulnerability(); if (!x560_24_dead && x520_state != ETurretState::Frenzy && (proj->GetAttribField() & EProjectileAttrib::Ice) == EProjectileAttrib::Ice && dVuln->WeaponHits(CWeaponMode::Ice(), false)) { x560_25_frozen = true; SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None); x53c_freezeRemTime = mgr.GetActiveRandom()->Float() * x2d4_data.GetFreezeVariance() + x2d4_data.GetFreezeDuration(); SetMuted(true); } SendScriptMsgs(EScriptObjectState::Damage, mgr, EScriptObjectMessage::None); } } } std::optional CScriptGunTurret::GetTouchBounds() const { if (GetActive() && GetMaterialList().HasMaterial(EMaterialTypes::Solid)) { return GetBoundingBox(); } return std::nullopt; } zeus::CVector3f CScriptGunTurret::GetOrbitPosition(const CStateManager& mgr) const { return GetAimPosition(mgr, 0.f); } zeus::CVector3f CScriptGunTurret::GetAimPosition(const CStateManager&, float) const { if (x258_type == ETurretComponent::Base) { return GetTranslation() + x34_transform.rotate(GetLocatorTransform("Gun_SDK"sv).origin); } return GetTranslation(); } void CScriptGunTurret::SetupCollisionManager(CStateManager& mgr) { std::vector jointDescs; jointDescs.reserve(2); const CAnimData* animData = GetModelData()->GetAnimationData(); x508_gunSDKSeg = animData->GetLocatorSegId("Gun_SDK"sv); const CSegId blastLCTR = animData->GetLocatorSegId("Blast_LCTR"sv); jointDescs.push_back(CJointCollisionDescription::SphereSubdivideCollision( x508_gunSDKSeg, blastLCTR, 0.6f, 1.f, CJointCollisionDescription::EOrientationType::One, "Gun_SDK"sv, 1000.f)); jointDescs.push_back(CJointCollisionDescription::SphereCollision(blastLCTR, 0.3f, "Blast_LCTR"sv, 1000.f)); x49c_collisionManager = std::make_unique(mgr, GetUniqueId(), GetAreaIdAlways(), jointDescs, true); x49c_collisionManager->SetActive(mgr, GetActive()); for (int i = 0; i < x49c_collisionManager->GetNumCollisionActors(); ++i) { const auto& desc = x49c_collisionManager->GetCollisionDescFromIndex(i); if (const TCastToPtr cAct = mgr.ObjectById(desc.GetCollisionActorId())) { cAct->AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr); cAct->SetMaterialFilter(CMaterialFilter::MakeIncludeExclude( {EMaterialTypes::Player}, {EMaterialTypes::Character, EMaterialTypes::NoStaticCollision, EMaterialTypes::NoPlatformCollision})); if (desc.GetName().find("Blast_LCTR"sv) != std::string_view::npos) { x4a0_collisionActor = desc.GetCollisionActorId(); } } } } void CScriptGunTurret::SetTurretState(ETurretState state, CStateManager& mgr) { if (state < ETurretState::Destroyed || state > ETurretState::Frenzy) { return; } if (x520_state != ETurretState::Invalid) { ProcessCurrentState(EStateMsg::Deactivate, mgr, 0.f); } if (state != ETurretState::Invalid && x520_state != state) { #ifndef NDEBUG fmt::print(FMT_STRING("{} {} {} - {}\n"), GetUniqueId(), GetEditorId(), GetName(), StateNames[size_t(state)]); #endif } x520_state = state; x524_curStateTime = 0.f; ProcessCurrentState(EStateMsg::Activate, mgr, 0.f); } void CScriptGunTurret::LaunchProjectile(CStateManager& mgr) { if (!x37c_projectileInfo.Token().IsLoaded() || !mgr.CanCreateProjectile(GetUniqueId(), EWeaponType::AI, 8)) { return; } const zeus::CTransform xf = GetLocatorTransform("Blast_LCTR"sv); const zeus::CVector3f projPt = GetTranslation() + GetTransform().rotate(xf.origin); zeus::CVector3f lookPt = x404_targetPosition; const zeus::CVector3f aimDelta = x404_targetPosition - projPt; if (zeus::CVector3f::getAngleDiff(GetTransform().frontVector(), aimDelta) > zeus::degToRad(20.f)) { if (aimDelta.canBeNormalized()) { const zeus::CVector3f lookDir = zeus::CVector3f::slerp(GetTransform().frontVector(), aimDelta.normalized(), zeus::degToRad(20.f)); lookPt = lookDir * aimDelta.magnitude() + projPt; } else { lookPt = projPt + GetTransform().frontVector(); } } else if (!aimDelta.canBeNormalized()) { lookPt = projPt + GetTransform().frontVector(); } const zeus::CTransform useXf = zeus::lookAt(projPt, lookPt); auto* proj = new CEnergyProjectile(true, x37c_projectileInfo.Token(), EWeaponType::AI, useXf, EMaterialTypes::Character, x37c_projectileInfo.GetDamage(), mgr.AllocateUniqueId(), GetAreaIdAlways(), GetUniqueId(), kInvalidUniqueId, EProjectileAttrib::None, false, zeus::skOne3f, x458_visorEffectDesc, x2d4_data.GetVisorSoundId(), false); mgr.AddObject(proj); const auto pair = x64_modelData->GetAnimationData()->GetCharacterInfo().GetPASDatabase().FindBestAnimation( CPASAnimParmData(pas::EAnimationState::ProjectileAttack, CPASAnimParm::FromEnum(1), CPASAnimParm::FromReal32(90.f), CPASAnimParm::FromEnum(skStateToLocoTypeLookup[size_t(x520_state)])), -1); if (pair.first > 0.f) { x64_modelData->EnableLooping(false); x64_modelData->GetAnimationData()->SetAnimation(CAnimPlaybackParms(pair.second, -1, 1.f, true), false); } } void CScriptGunTurret::PlayAdditiveFlinchAnimation(CStateManager& mgr) { const auto pair = GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase().FindBestAnimation( CPASAnimParmData(pas::EAnimationState::AdditiveFlinch), *mgr.GetActiveRandom(), -1); if (pair.first > 0.f) { GetModelData()->GetAnimationData()->AddAdditiveAnimation(pair.second, 1.f, false, true); } } void CScriptGunTurret::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) { CActor::AddToRenderer(frustum, mgr); if (x258_type != ETurretComponent::Gun) { return; } if (!x560_25_frozen) { switch (x520_state) { case ETurretState::Deactive: case ETurretState::DeactiveFromReady: case ETurretState::Deactivating: case ETurretState::DeactivatingFromReady: g_Renderer->AddParticleGen(*x470_deactivateLight); break; case ETurretState::Inactive: g_Renderer->AddParticleGen(*x468_idleLight); break; case ETurretState::PanningA: case ETurretState::PanningB: g_Renderer->AddParticleGen(*x490_panningEffect); break; case ETurretState::Ready: case ETurretState::Targeting: case ETurretState::Firing: case ETurretState::ExitTargeting: case ETurretState::Frenzy: g_Renderer->AddParticleGen(*x478_targettingLight); if (x520_state == ETurretState::Firing || x520_state == ETurretState::Frenzy) { g_Renderer->AddParticleGen(*x488_chargingEffect); } break; default: break; } } else { g_Renderer->AddParticleGen(*x480_frozenEffect); } } void CScriptGunTurret::Render(CStateManager& mgr) { CPhysicsActor::Render(mgr); if (x258_type == ETurretComponent::Gun) { if (!x560_25_frozen) { switch (x520_state) { case ETurretState::Deactive: case ETurretState::DeactiveFromReady: case ETurretState::Deactivating: case ETurretState::DeactivatingFromReady: x470_deactivateLight->Render(x90_actorLights.get()); break; case ETurretState::Inactive: x468_idleLight->Render(x90_actorLights.get()); break; case ETurretState::PanningA: case ETurretState::PanningB: x490_panningEffect->Render(x90_actorLights.get()); break; case ETurretState::Ready: case ETurretState::Targeting: case ETurretState::Firing: case ETurretState::ExitTargeting: case ETurretState::Frenzy: x478_targettingLight->Render(x90_actorLights.get()); if (x520_state == ETurretState::Firing) { x488_chargingEffect->Render(x90_actorLights.get()); } break; default: break; } } else { x480_frozenEffect->Render(x90_actorLights.get()); } } else if (x258_type == ETurretComponent::Base) { if (x4a4_extensionModel && x4f8_extensionT > 0.f) { zeus::CTransform xf = GetTransform(); xf.origin = x4fc_extensionOffset + (x4f4_extensionRange * 0.5f * zeus::skDown); CModelFlags flags{0, 0, 3, zeus::skWhite}; x4a4_extensionModel->Render(mgr, xf, x90_actorLights.get(), flags); } } } void CScriptGunTurret::UpdateGunCollisionManager(float dt, CStateManager& mgr) { if (TCastToPtr colAct = mgr.ObjectById(x4a0_collisionActor)) { colAct->SetActive(mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed); } x49c_collisionManager->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace); } void CScriptGunTurret::UpdateFrozenState(float dt, CStateManager& mgr) { if (x560_25_frozen) { if (x53c_freezeRemTime <= 0.f) { x560_25_frozen = false; SendScriptMsgs(EScriptObjectState::UnFrozen, mgr, EScriptObjectMessage::None); CSfxManager::AddEmitter(x2d4_data.GetUnFreezeSoundId(), GetTranslation(), zeus::skUp, false, false, 0x7f, GetAreaIdAlways()); SetMuted(false); } else if (x2d4_data.GetFreezeTimeout()) { x53c_freezeRemTime -= dt; } } else { x53c_freezeRemTime = 0.f; } } void CScriptGunTurret::UpdateGunParticles(float dt, CStateManager& mgr) { CGameLight* light = nullptr; if (x498_lightId != kInvalidUniqueId) { light = TCastToPtr(mgr.ObjectById(x498_lightId)); } if (!x560_25_frozen) { const zeus::CTransform lightXf = GetLocatorTransform("light_LCTR"sv); zeus::CVector3f pos = x34_transform.rotate(lightXf.origin); pos += GetTranslation(); if (light) { light->SetActive(true); } switch (x520_state) { case ETurretState::Deactive: case ETurretState::DeactiveFromReady: case ETurretState::Deactivating: case ETurretState::DeactivatingFromReady: x468_idleLight->SetParticleEmission(false); x470_deactivateLight->SetParticleEmission(true); x478_targettingLight->SetParticleEmission(false); x480_frozenEffect->SetParticleEmission(false); x488_chargingEffect->SetParticleEmission(false); x490_panningEffect->SetParticleEmission(false); x470_deactivateLight->SetOrientation(GetTransform().getRotation()); x470_deactivateLight->SetGlobalTranslation(pos); x470_deactivateLight->SetGlobalScale(GetModelData()->GetScale()); x470_deactivateLight->Update(dt); if (light) { if (x470_deactivateLight->SystemHasLight()) { light->SetLight(x470_deactivateLight->GetLight()); } else { light->SetActive(false); } } break; case ETurretState::Inactive: x468_idleLight->SetParticleEmission(true); x470_deactivateLight->SetParticleEmission(false); x478_targettingLight->SetParticleEmission(false); x480_frozenEffect->SetParticleEmission(false); x488_chargingEffect->SetParticleEmission(false); x490_panningEffect->SetParticleEmission(false); x468_idleLight->SetOrientation(GetTransform().getRotation()); x468_idleLight->SetGlobalTranslation(pos); x468_idleLight->SetGlobalScale(GetModelData()->GetScale()); x468_idleLight->Update(dt); if (light) { light->SetActive(false); } break; case ETurretState::PanningA: case ETurretState::PanningB: x468_idleLight->SetParticleEmission(false); x470_deactivateLight->SetParticleEmission(false); x478_targettingLight->SetParticleEmission(false); x480_frozenEffect->SetParticleEmission(false); x488_chargingEffect->SetParticleEmission(false); x490_panningEffect->SetParticleEmission(true); x490_panningEffect->SetOrientation(GetTransform().getRotation()); x490_panningEffect->SetGlobalTranslation(GetTranslation()); x490_panningEffect->SetGlobalScale(GetModelData()->GetScale()); x490_panningEffect->Update(dt); if (light) { light->SetActive(false); } break; case ETurretState::Targeting: case ETurretState::Firing: case ETurretState::ExitTargeting: case ETurretState::Frenzy: { const bool doEmission = x520_state == ETurretState::Firing || x520_state == ETurretState::Frenzy; x468_idleLight->SetParticleEmission(false); x470_deactivateLight->SetParticleEmission(false); x478_targettingLight->SetParticleEmission(true); x480_frozenEffect->SetParticleEmission(false); x488_chargingEffect->SetParticleEmission(doEmission); x478_targettingLight->SetOrientation(GetTransform().getRotation()); x478_targettingLight->SetGlobalTranslation(pos); x478_targettingLight->SetGlobalScale(GetModelData()->GetScale()); x478_targettingLight->Update(dt); if (x478_targettingLight->SystemHasLight()) { light->SetLight(x478_targettingLight->GetLight()); } else { light->SetActive(false); } if (doEmission) { const zeus::CTransform blastXf = GetLocatorTransform("Blast_LCTR"sv); zeus::CVector3f blastPos = GetTransform().rotate(blastXf.origin); blastPos += GetTranslation(); x488_chargingEffect->SetOrientation(GetTransform().getRotation()); x488_chargingEffect->SetGlobalTranslation(blastPos); x488_chargingEffect->SetGlobalScale(GetModelData()->GetScale()); x488_chargingEffect->Update(dt); } break; } default: x468_idleLight->SetParticleEmission(false); x470_deactivateLight->SetParticleEmission(false); x478_targettingLight->SetParticleEmission(false); x480_frozenEffect->SetParticleEmission(false); x488_chargingEffect->SetParticleEmission(false); x490_panningEffect->SetParticleEmission(false); x490_panningEffect->Update(dt); if (light) { light->SetActive(false); } break; } } else { x468_idleLight->SetParticleEmission(false); x470_deactivateLight->SetParticleEmission(false); x478_targettingLight->SetParticleEmission(false); x480_frozenEffect->SetParticleEmission(true); x488_chargingEffect->SetParticleEmission(false); x490_panningEffect->SetParticleEmission(false); x480_frozenEffect->SetOrientation(GetTransform().getRotation()); x480_frozenEffect->SetGlobalTranslation(GetTranslation()); x480_frozenEffect->SetGlobalScale(GetModelData()->GetScale()); x480_frozenEffect->Update(dt); if (light) { light->SetActive(false); } } } void CScriptGunTurret::ProcessGunStateMachine(float dt, CStateManager& mgr) { ProcessCurrentState(EStateMsg::Update, mgr, dt); x524_curStateTime += dt; PlayAdditiveChargingAnimation(mgr); if (x25c_gunId == kInvalidUniqueId) { return; } if (const TCastToPtr gunTurret = mgr.ObjectById(x25c_gunId)) { if (gunTurret->x520_state != ETurretState::Frenzy) { gunTurret->x520_state = x520_state; } else if (x520_state != ETurretState::Frenzy) { SetTurretState(ETurretState::Frenzy, mgr); gunTurret->RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr); mgr.GetPlayer().SetOrbitRequestForTarget(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr); } } } void CScriptGunTurret::UpdateTurretAnimation() { if (!HasModelData() || !GetModelData()->HasAnimData()) { return; } if (x520_state > ETurretState::Frenzy) { return; } const auto parmData = CPASAnimParmData(pas::EAnimationState::Locomotion, CPASAnimParm::FromEnum(0), CPASAnimParm::FromEnum(skStateToLocoTypeLookup[size_t(x520_state)])); const auto pair = GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase().FindBestAnimation(parmData, -1); if (pair.first > 0.f && pair.second != x540_turretAnim) { GetModelData()->GetAnimationData()->SetAnimation(CAnimPlaybackParms(pair.second, -1, 1.f, true), false); GetModelData()->GetAnimationData()->EnableLooping(true); x540_turretAnim = pair.second; } } void CScriptGunTurret::ProcessCurrentState(EStateMsg msg, CStateManager& mgr, float dt) { switch (x520_state) { case ETurretState::Deactivating: case ETurretState::DeactivatingFromReady: ProcessDeactivatingState(msg, mgr); break; case ETurretState::Inactive: ProcessInactiveState(msg, mgr, dt); break; case ETurretState::Ready: ProcessReadyState(msg, mgr, dt); break; case ETurretState::PanningA: case ETurretState::PanningB: ProcessPanningState(msg, mgr, dt); break; case ETurretState::Targeting: case ETurretState::Firing: ProcessTargettingState(msg, mgr, dt); break; case ETurretState::ExitTargeting: ProcessExitTargettingState(msg, mgr); break; case ETurretState::Frenzy: ProcessFrenzyState(msg, mgr, dt); break; default: break; } } void CScriptGunTurret::ProcessDeactivatingState(EStateMsg msg, CStateManager& mgr) { if (msg == EStateMsg::Update && x524_curStateTime >= x2d4_data.GetIntoDeactivateDelay()) { SetTurretState(x560_28_hasBeenActivated ? ETurretState::DeactiveFromReady : ETurretState::Deactive, mgr); } } void CScriptGunTurret::ProcessInactiveState(EStateMsg msg, CStateManager& mgr, float dt) { if (msg == EStateMsg::Activate) { x528_curInactiveTime = 0.f; x560_27_burstSet = false; if (const TCastToPtr gunTurret = mgr.ObjectById(x25c_gunId)) { x260_lastGunHP = gunTurret->HealthInfo(mgr)->GetHP(); } } else if (msg == EStateMsg::Update) { bool forceActivate = false; if (x25c_gunId != kInvalidUniqueId) if (const TCastToPtr gun = mgr.ObjectById(x25c_gunId)) { forceActivate = gun->HealthInfo(mgr)->GetHP() < x260_lastGunHP; } if (x2d4_data.GetScriptedStartOnly() ? (forceActivate || x560_29_scriptedStart) : (forceActivate || x560_29_scriptedStart || InDetectionRange(mgr))) { x528_curInactiveTime += dt; if (forceActivate || x528_curInactiveTime >= x2d4_data.GetIntoActivateDelay()) SetTurretState(ETurretState::Ready, mgr); } else { x468_idleLight->SetParticleEmission(true); } } else if (msg == EStateMsg::Deactivate) { x560_28_hasBeenActivated = true; x468_idleLight->SetParticleEmission(false); if (const TCastToConstPtr gunTurret = mgr.ObjectById(x25c_gunId)) { x260_lastGunHP = gunTurret->GetHealthInfo(mgr)->GetHP(); } } } void CScriptGunTurret::ProcessReadyState(EStateMsg msg, CStateManager& mgr, float dt) { if (msg == EStateMsg::Activate) { x52c_curActiveTime = 0.f; } else if (msg == EStateMsg::Update) { x52c_curActiveTime += dt; if (x52c_curActiveTime < x2d4_data.GetPanStartTime()) { return; } if (IsPlayerInFiringRange(mgr) && InDetectionRange(mgr)) { SetTurretState(ETurretState::Targeting, mgr); CSfxManager::AddEmitter(x2d4_data.GetLockOnSoundId(), GetTranslation(), zeus::skUp, false, false, 0x7f, GetAreaIdAlways()); } else { SetTurretState(ETurretState::PanningA, mgr); x530_curPanTime = 0.f; } } } void CScriptGunTurret::ProcessPanningState(EStateMsg msg, CStateManager& mgr, float dt) { if (msg == EStateMsg::Activate) { x52c_curActiveTime = 0.f; } else if (msg == EStateMsg::Update) { if (IsPlayerInFiringRange(mgr) && InDetectionRange(mgr)) { SetTurretState(ETurretState::Targeting, mgr); CSfxManager::AddEmitter(x2d4_data.GetLockOnSoundId(), GetTranslation(), zeus::skUp, false, false, 0x7f, GetAreaIdAlways()); } else { x52c_curActiveTime += dt; x530_curPanTime += dt; if (x530_curPanTime >= x2d4_data.GetTotalPanSearchTime() && !x4a4_extensionModel && !x2d4_data.GetScriptedStartOnly()) { SetTurretState(ETurretState::Inactive, mgr); } else if (x52c_curActiveTime >= x2d4_data.GetPanHoldTime()) { SetTurretState(x520_state != ETurretState::PanningA ? ETurretState::PanningA : ETurretState::PanningB, mgr); } } } } void CScriptGunTurret::ProcessTargettingState(EStateMsg msg, CStateManager& mgr, float dt) { if (msg == EStateMsg::Activate) { x52c_curActiveTime = 0.f; } else if (msg == EStateMsg::Update) { if (x560_26_firedWithSetBurst || InDetectionRange(mgr)) { UpdateTargettingMode(dt, mgr); if (x25c_gunId != kInvalidUniqueId) { if (TCastToPtr gun = mgr.ObjectById(x25c_gunId)) { zeus::CVector3f vec = x404_targetPosition; if (IsPlayerInFiringRange(mgr)) { const zeus::CTransform blastXf = gun->GetLocatorTransform("Blast_LCTR"sv); const zeus::CVector3f rotatedBlastVec = GetTransform().rotate(blastXf.origin) + GetTranslation(); x404_targetPosition = mgr.GetPlayer().GetAimPosition(mgr, 0.f); vec = x37c_projectileInfo.PredictInterceptPos(rotatedBlastVec, mgr.GetPlayer().GetAimPosition(mgr, dt), mgr.GetPlayer(), false, dt); } zeus::CVector3f compensated = x3a4_burstFire.GetDistanceCompensatedError( (x404_targetPosition - gun->GetTranslation()).magnitude(), 20.f); compensated = gun->GetTransform().rotate(compensated); gun->x404_targetPosition = x404_targetPosition + (vec - x404_targetPosition) + compensated; } } zeus::CVector3f diffVec = x404_targetPosition - GetTranslation(); diffVec.z() = 0.f; if (diffVec.canBeNormalized()) { const zeus::CVector3f normDiff = diffVec.normalized(); const float angDif = zeus::CVector3f::getAngleDiff(normDiff, GetTransform().frontVector()); zeus::CQuaternion quat = zeus::CQuaternion::lookAt(GetTransform().frontVector(), normDiff, std::min(angDif, (dt * x2d4_data.GetTurnSpeed()))); quat.setImaginary(GetTransform().transposeRotate(quat.getImaginary())); RotateInOneFrameOR(quat, dt); } if (ShouldFire(mgr)) { SendScriptMsgs(EScriptObjectState::Attack, mgr, EScriptObjectMessage::None); x560_26_firedWithSetBurst = true; } x52c_curActiveTime = 0.f; } else { x52c_curActiveTime += dt; if (x52c_curActiveTime >= 10.f) { SetTurretState(ETurretState::ExitTargeting, mgr); } } } else if (msg == EStateMsg::Deactivate) { x560_30_needsStopClankSound = true; } } void CScriptGunTurret::ProcessExitTargettingState(EStateMsg msg, CStateManager& mgr) { if (msg != EStateMsg::Update || x25c_gunId == kInvalidUniqueId) return; if (TCastToPtr gun = mgr.ObjectById(x25c_gunId)) { // zeus::CTransform gunXf = GetTransform() * GetLocatorTransform("Gun_SDK"sv); if (zeus::CVector3f::getAngleDiff(gun->GetTransform().frontVector(), x544_originalFrontVec) < zeus::degToRad(0.9f)) SetTurretState(ETurretState::Ready, mgr); } } void CScriptGunTurret::ProcessFrenzyState(EStateMsg msg, CStateManager& mgr, float dt) { if (msg == EStateMsg::Activate) { x560_31_frenzyReverse = mgr.GetActiveRandom()->Float() < 0.f; x534_fireCycleRemTime = 0.15f; RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr); mgr.GetPlayer().SetOrbitRequestForTarget(GetUniqueId(), CPlayer::EPlayerOrbitRequest::ActivateOrbitSource, mgr); } else if (msg == EStateMsg::Update) { if (x524_curStateTime >= x2d4_data.GetFrenzyDuration()) { SetTurretState(ETurretState::Destroyed, mgr); if (const TCastToPtr gun = mgr.ObjectById(x25c_gunId)) { gun->x520_state = ETurretState::Destroyed; } return; } const zeus::CVector3f frontVec = GetTransform().frontVector(); if (x560_31_frenzyReverse && x550_originalRightVec.magSquared() < 0.f && zeus::CVector3f::getAngleDiff(x544_originalFrontVec, frontVec) >= zeus::degToRad(45.f)) { x560_31_frenzyReverse = false; } else if (!x560_31_frenzyReverse && x550_originalRightVec.magSquared() < 0.f && zeus::CVector3f::getAngleDiff(x544_originalFrontVec, frontVec) >= zeus::degToRad(45.f)) { x560_31_frenzyReverse = true; } if (const TCastToConstPtr gun = mgr.ObjectById(x25c_gunId)) { x534_fireCycleRemTime -= dt; if (x534_fireCycleRemTime >= 0.f) { return; } x404_targetPosition = gun->GetTranslation() + (100.f * gun->GetTransform().frontVector()); SendScriptMsgs(EScriptObjectState::Attack, mgr, EScriptObjectMessage::None); x534_fireCycleRemTime = 0.15f; } } } bool CScriptGunTurret::IsPlayerInFiringRange(CStateManager& mgr) const { const zeus::CVector3f posDif = mgr.GetPlayer().GetTranslation() - GetTranslation(); const zeus::CVector3f someVec(posDif.x(), posDif.y(), 0.f); if (x550_originalRightVec.dot(posDif) >= 0.f) { return zeus::CVector3f::getAngleDiff(x544_originalFrontVec, someVec) <= x2d4_data.GetRightMaxAngle(); } if (zeus::CVector3f::getAngleDiff(x544_originalFrontVec, someVec) <= x2d4_data.GetLeftMaxAngle()) { return true; } const float biasedAngle = zeus::CVector3f::getAngleDiff(posDif, zeus::skUp) - zeus::degToRad(90.f); return biasedAngle >= zeus::degToRad(-20.f) && biasedAngle <= x2d4_data.GetDownMaxAngle(); } bool CScriptGunTurret::LineOfSightTest(CStateManager& mgr) const { if (x25c_gunId == kInvalidUniqueId) { return false; } if (const TCastToConstPtr gun = mgr.ObjectById(x25c_gunId)) { if (x560_27_burstSet || (x520_state == ETurretState::Inactive && x4a4_extensionModel)) { return true; } const zeus::CTransform xf = GetLocatorTransform("Blast_LCTR"sv); const zeus::CVector3f muzzlePos = gun->GetTransform().rotate(xf.origin) + gun->GetTranslation(); zeus::CVector3f dir = mgr.GetPlayer().GetAimPosition(mgr, 0.f) - muzzlePos; const float mag = dir.magnitude(); dir = dir / mag; EntityList nearList; constexpr auto filter = CMaterialFilter::MakeIncludeExclude( {EMaterialTypes::Solid}, {EMaterialTypes::Player, EMaterialTypes::CollisionActor}); mgr.BuildNearList(nearList, muzzlePos, dir, mag, filter, gun.GetPtr()); TUniqueId id = kInvalidUniqueId; return mgr.RayWorldIntersection(id, muzzlePos, dir, mag, filter, nearList).IsInvalid(); } return false; } bool CScriptGunTurret::InDetectionRange(CStateManager& mgr) const { const zeus::CVector3f delta = mgr.GetPlayer().GetTranslation() - GetTranslation(); if (delta.dot(zeus::skDown) < 0.f && zeus::CVector3f::getAngleDiff(GetTransform().frontVector(), delta) > zeus::degToRad(20.f)) { return false; } if (delta.magSquared() > x2d4_data.GetDetectionRange() * x2d4_data.GetDetectionRange()) { return false; } if (x2d4_data.GetDetectionZRange() != 0.f && std::fabs(delta.z()) >= x2d4_data.GetDetectionZRange()) { return false; } return LineOfSightTest(mgr); } zeus::CVector3f CScriptGunTurret::UpdateExtensionModelState(float dt) { if (!x4a4_extensionModel) { return {}; } switch (x520_state) { case ETurretState::PanningA: case ETurretState::PanningB: case ETurretState::Targeting: case ETurretState::Firing: case ETurretState::ExitTargeting: x4f8_extensionT = std::min(0.9f, x4f8_extensionT + 1.5f * dt); break; default: x4f8_extensionT = std::max(0.f, x4f8_extensionT - 1.5f * dt); break; case ETurretState::Ready: case ETurretState::Deactivating: case ETurretState::DeactivatingFromReady: case ETurretState::Frenzy: break; } return (x4fc_extensionOffset + (x2d4_data.GetExtensionDropDownDist() * x4f8_extensionT * zeus::skDown)) - GetTranslation(); } void CScriptGunTurret::UpdateHealthInfo(CStateManager& mgr) { switch (x258_type) { case ETurretComponent::Base: if (x25c_gunId != kInvalidUniqueId) { if (!TCastToConstPtr(mgr.ObjectById(x25c_gunId))) { SetTurretState(ETurretState::Destroyed, mgr); x560_25_frozen = false; x25c_gunId = kInvalidUniqueId; if (x50c_targetingEmitter) { CSfxManager::RemoveEmitter(x50c_targetingEmitter); x50c_targetingEmitter.reset(); } } } else { SetTurretState(ETurretState::Destroyed, mgr); } break; case ETurretComponent::Gun: if (!x560_24_dead && x520_state != ETurretState::Frenzy && HealthInfo(mgr)->GetHP() <= 0.f) { x560_24_dead = true; SendScriptMsgs(EScriptObjectState::Dead, mgr, EScriptObjectMessage::None); mgr.FreeScriptObject(GetUniqueId()); } break; default: break; } } bool CScriptGunTurret::PlayerInsideTurretSphere(CStateManager& mgr) const { if (const TCastToConstPtr cAct = mgr.GetObjectById(x4a0_collisionActor)) { if (cAct->GetActive()) { zeus::CVector3f delta = mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetTranslation(); if (delta.z() < 0.f) { const float rad = cAct->GetSphereRadius() * 2.f + (cAct->GetTranslation() - GetTranslation()).magnitude(); return delta.magSquared() < rad * rad; } } } return false; } void CScriptGunTurret::UpdateGunOrientation(float dt, CStateManager& mgr) { if (x25c_gunId == kInvalidUniqueId) { return; } if (const TCastToPtr gun = mgr.ObjectById(x25c_gunId)) { zeus::CTransform xf = GetLocatorTransform("Gun_SDK"sv); xf = GetTransform() * xf; switch (x520_state) { case ETurretState::Targeting: case ETurretState::Firing: { const float xyMagSq = xf.frontVector().toVec2f().magSquared(); float useYaw = 0.f; if (std::sqrt(xyMagSq) > 0.001f) { useYaw = -std::atan2(xf.frontVector().x(), xf.frontVector().y()); } const float oldPitch = gun->GetPitch(); float usePitch = 0.f; if (!gun->PlayerInsideTurretSphere(mgr)) { zeus::CTransform newXf; if ((x404_targetPosition - xf.origin).canBeNormalized()) { newXf = zeus::lookAt(xf.origin, x404_targetPosition); } else { newXf = GetTransform(); } const float newPitch = -std::atan2(-newXf.frontVector().z(), newXf.frontVector().toVec2f().magnitude()); const float newPitchDelta = newPitch - oldPitch; const float f2 = newPitchDelta > 0.f ? dt * x2d4_data.GetTurnSpeed() : dt * -x2d4_data.GetTurnSpeed(); usePitch = std::max(std::fabs(newPitchDelta) <= std::fabs(f2) ? newPitch : oldPitch + f2, -x2d4_data.GetDownMaxAngle()); } zeus::CQuaternion qy, qx, qz; qy.rotateY(0.f); qx.rotateX(usePitch); qz.rotateZ(useYaw); gun->SetTransform((qz * qx * qy).toTransform(xf.origin)); break; } case ETurretState::ExitTargeting: { const zeus::CVector3f frontVec = GetTransform().frontVector(); const zeus::CVector3f gunFrontVec = gun->GetTransform().frontVector(); const float rotAngle = 0.3f * dt * x2d4_data.GetTurnSpeed(); zeus::CQuaternion quat = zeus::CQuaternion::lookAt(gunFrontVec, frontVec, rotAngle); quat.setImaginary(gun->GetTransform().transposeRotate(quat.getImaginary())); gun->RotateInOneFrameOR(quat, dt); zeus::CQuaternion quat2 = zeus::CQuaternion::lookAt(frontVec, x544_originalFrontVec, rotAngle); quat2.setImaginary(GetTransform().transposeRotate(quat2.getImaginary())); RotateInOneFrameOR(quat2, dt); break; } case ETurretState::Frenzy: { const float xyMagSq = xf.frontVector().toVec2f().magSquared(); float useYaw = 0.f; if (std::sqrt(xyMagSq) > 0.001f) { useYaw = -std::atan2(xf.frontVector().x(), xf.frontVector().y()); } const float f28 = -0.5f * x2d4_data.GetDownMaxAngle() * (1.f - std::cos(2.f * x524_curStateTime * x2d4_data.GetTurnSpeed())); const float pitch = gun->GetPitch(); const float f2 = f28 - pitch; const float f31 = x2d4_data.GetTurnSpeed() * dt; const float f3 = f2 > 0.f ? f31 : -f31; float usePitch = std::fabs(f2) <= std::fabs(f3) ? f28 : pitch + f3; usePitch = std::max(usePitch, -x2d4_data.GetDownMaxAngle()); zeus::CQuaternion qy, qx, qz; qy.rotateY(0.f); qx.rotateX(usePitch); qz.rotateZ(useYaw); gun->SetTransform((qz * qx * qy).toTransform(xf.origin)); zeus::CQuaternion rot = zeus::CQuaternion::lookAt( GetTransform().frontVector(), x560_31_frenzyReverse ? -x550_originalRightVec : x550_originalRightVec, f31); rot.setImaginary(GetTransform().transposeRotate(rot.getImaginary())); RotateInOneFrameOR(rot, dt); break; } default: gun->SetTransform(xf); break; } } } void CScriptGunTurret::UpdateTargettingSound(float dt) { x510_timeSinceLastTargetSfx += dt; const float angleDiff2D = zeus::CVector2f::getAngleDiff(x514_lastFrontVector.toVec2f(), GetTransform().frontVector().toVec2f()); if (x560_30_needsStopClankSound && angleDiff2D < zeus::degToRad(20.f) && (x520_state == ETurretState::Targeting || x520_state == ETurretState::Firing)) { if (!x560_25_frozen) { CSfxManager::AddEmitter(x2d4_data.GetStopClankSoundId(), GetTranslation(), zeus::skUp, false, false, 127, GetAreaIdAlways()); } x560_30_needsStopClankSound = false; } if (x510_timeSinceLastTargetSfx >= 0.5f && !x560_25_frozen) { if (x520_state == ETurretState::Targeting || x520_state == ETurretState::Firing || x520_state == ETurretState::Frenzy) { const bool insignificant = IsInsignificantRotation(dt); if (!insignificant && !x50c_targetingEmitter) { x50c_targetingEmitter = CSfxManager::AddEmitter(x2d4_data.GetTrackingSoundId(), GetTranslation(), zeus::skUp, false, true, 127, GetAreaIdAlways()); } else if (insignificant && x50c_targetingEmitter) { CSfxManager::RemoveEmitter(x50c_targetingEmitter); x50c_targetingEmitter.reset(); x510_timeSinceLastTargetSfx = 0.f; } if (x50c_targetingEmitter) { const float bendScale = dt * x2d4_data.GetTurnSpeed(); CSfxManager::PitchBend(x50c_targetingEmitter, std::max(0.f, (bendScale > 0.f ? angleDiff2D / bendScale : 0.f))); } } } else if (x560_25_frozen && x50c_targetingEmitter) { CSfxManager::RemoveEmitter(x50c_targetingEmitter); x50c_targetingEmitter.reset(); } x514_lastFrontVector = GetTransform().frontVector(); } void CScriptGunTurret::PlayAdditiveChargingAnimation(CStateManager& mgr) { if (x520_state == ETurretState::Firing) { if (x55c_additiveChargeAnim != -1) { return; } const auto pair = GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase().FindBestAnimation( CPASAnimParmData(pas::EAnimationState::AdditiveReaction, CPASAnimParm::FromEnum(2)), *mgr.GetActiveRandom(), -1); if (pair.first > 0.f) { x55c_additiveChargeAnim = pair.second; GetModelData()->GetAnimationData()->AddAdditiveAnimation(pair.second, 1.f, true, false); } } else if (x55c_additiveChargeAnim != -1) { GetModelData()->GetAnimationData()->DelAdditiveAnimation(x55c_additiveChargeAnim); x55c_additiveChargeAnim = -1; } } void CScriptGunTurret::UpdateTargettingMode(float dt, CStateManager& mgr) { if (mgr.GetCameraManager()->IsInCinematicCamera()) { x534_fireCycleRemTime = mgr.GetActiveRandom()->Float() * x2d4_data.GetReloadTimeVariance() + x2d4_data.GetReloadTime(); x538_halfFireCycleDur = 0.5f * x534_fireCycleRemTime; } if (x534_fireCycleRemTime > 0.f) { x534_fireCycleRemTime -= dt; if (x534_fireCycleRemTime < x538_halfFireCycleDur && x520_state != ETurretState::Firing) { CSfxManager::AddEmitter(x2d4_data.GetChargingSoundId(), GetTranslation(), zeus::skUp, false, false, 0x7f, GetAreaIdAlways()); SetTurretState(ETurretState::Firing, mgr); return; } } else { if (x520_state != ETurretState::Targeting) { SetTurretState(ETurretState::Targeting, mgr); } if (!x3a4_burstFire.IsBurstSet()) { UpdateBurstType(mgr); x534_fireCycleRemTime = mgr.GetActiveRandom()->Float() * x2d4_data.GetReloadTimeVariance() + x2d4_data.GetReloadTime(); x538_halfFireCycleDur = 0.5f * x534_fireCycleRemTime; } else { x3a4_burstFire.Update(mgr, dt); } } } void CScriptGunTurret::UpdateBurstType(CStateManager& mgr) { if (x560_27_burstSet) { bool inView = true; if (mgr.GetPlayer().GetMorphballTransitionState() != CPlayer::EPlayerMorphBallState::Morphed) { const zeus::CVector3f frontVec = GetTransform().frontVector(); const zeus::CVector3f plFrontVec = mgr.GetPlayer().GetTransform().frontVector(); const float dot = frontVec.dot(plFrontVec); if (dot >= 0.f) { inView = false; } } u32 r3 = mgr.GetActiveRandom()->Range(0, 3); r3 += 2; u32 type = 1; if (r3 <= 2 || x2d4_data.GetNumSubsequentShots() < 3) { type = 0; } else if (r3 >= 5 && x2d4_data.GetNumSubsequentShots() > 3) { type = 2; } x3a4_burstFire.SetBurstType(type + (inView ? 0 : 3)); } else { const u32 type = x2d4_data.GetNumInitialShots() - 2; x3a4_burstFire.SetBurstType(type); x3a4_burstFire.SetFirstBurstIndex(x2d4_data.GetInitialShotTableIndex()); } x3a4_burstFire.Start(mgr); x560_26_firedWithSetBurst = false; x560_27_burstSet = true; } bool CScriptGunTurret::ShouldFire(CStateManager& mgr) const { if (x520_state == ETurretState::Targeting && x534_fireCycleRemTime <= 0.f && x3a4_burstFire.ShouldFire()) { return IsPlayerInFiringRange(mgr); } return false; } bool CScriptGunTurret::IsInsignificantRotation(float dt) const { return zeus::CVector2f::getAngleDiff(x514_lastFrontVector.toVec2f(), GetTransform().frontVector().toVec2f()) < zeus::degToRad(2.f) * dt; } } // namespace metaforce