#include "Runtime/World/CSnakeWeedSwarm.hpp" #include "Runtime/CSimplePool.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Graphics/CCubeRenderer.hpp" #include "Runtime/Graphics/CSkinnedModel.hpp" #include "Runtime/Graphics/CVertexMorphEffect.hpp" #include "Runtime/Weapon/CGameProjectile.hpp" #include "Runtime/World/CActorParameters.hpp" #include "Runtime/World/CAnimationParameters.hpp" #include "Runtime/World/CPlayer.hpp" #include "Runtime/World/CWorld.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path namespace metaforce { static constexpr CMaterialFilter skMaterialFilter = CMaterialFilter::MakeInclude({EMaterialTypes::Solid}); CSnakeWeedSwarm::CSnakeWeedSwarm(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info, const zeus::CVector3f& pos, const zeus::CVector3f& scale, const CAnimRes& animRes, const CActorParameters& actParms, float spacing, float height, float f3, float weaponDamageRadius, float maxPlayerDistance, float loweredTime, float loweredTimeVariation, float maxZOffset, float speed, float speedVariation, float f11, float scaleMin, float scaleMax, float distanceBelowGround, const CDamageInfo& dInfo, float /*f15*/, u32 sfxId1, u32 sfxId2, u32 sfxId3, CAssetId particleGenDesc1, u32 w5, CAssetId particleGenDesc2, float f16) : CActor(uid, active, name, info, zeus::CTransform::Translate(pos), CModelData::CModelDataNull(), CMaterialList(EMaterialTypes::Trigger, EMaterialTypes::NonSolidDamageable), actParms, kInvalidUniqueId) , xe8_scale(scale) , xf4_boidSpacing(spacing) , xf8_height(height) , xfc_(f3) , x100_weaponDamageRadius(weaponDamageRadius) , x104_maxPlayerDistance(maxPlayerDistance) , x108_loweredTime(loweredTime) , x10c_loweredTimeVariation(loweredTimeVariation) , x110_maxZOffset(maxZOffset) , x114_speed(speed) , x118_speedVariation(speedVariation) , x11c_(std::cos(zeus::degToRad(f11))) , x120_scaleMin(scaleMin) , x124_scaleMax(scaleMax) , x128_distanceBelowGround(distanceBelowGround) , x15c_damageInfo(dInfo) , x1c8_boidPositions(std::make_unique>()) , x1cc_boidPlacement(std::make_unique>()) , x1d0_sfx1(CSfxManager::TranslateSFXID(sfxId1)) , x1d2_sfx2(CSfxManager::TranslateSFXID(sfxId2)) , x1d4_sfx3(CSfxManager::TranslateSFXID(sfxId3)) , x1fc_(w5) , x200_(f16) { SetActorLights(actParms.GetLightParameters().MakeActorLights()); x1b0_modelData.emplace_back(std::make_unique(animRes)); x1b0_modelData.emplace_back(std::make_unique(animRes)); x1b0_modelData.emplace_back(std::make_unique(animRes)); x1b0_modelData.emplace_back(std::make_unique(animRes)); if (actParms.GetXRayAssets().first.IsValid()) { for (const auto& modelData : x1b0_modelData) { modelData->SetXRayModel(actParms.GetXRayAssets()); } x140_25_modelAssetDirty = true; } if (actParms.GetThermalAssets().first.IsValid()) { for (const auto& modelData : x1b0_modelData) { modelData->SetInfraModel(actParms.GetThermalAssets()); } x140_25_modelAssetDirty = true; } if (particleGenDesc1.IsValid()) { x1dc_particleGenDesc = g_SimplePool->GetObj({FOURCC('PART'), particleGenDesc1}); x1ec_particleGen1 = std::make_unique(x1dc_particleGenDesc); } if (particleGenDesc2.IsValid()) { x1dc_particleGenDesc = g_SimplePool->GetObj({FOURCC('PART'), particleGenDesc2}); x1f4_particleGen2 = std::make_unique(x1dc_particleGenDesc); } } void CSnakeWeedSwarm::Accept(metaforce::IVisitor& visitor) { visitor.Visit(this); } void CSnakeWeedSwarm::AllocateSkinnedModels(CStateManager& mgr, CModelData::EWhichModel which) { x178_workspaces.clear(); for (int i = 0; i < x1b0_modelData.size(); ++i) { auto& modelData = *x1b0_modelData[i]; x178_workspaces.emplace_back(modelData.PickAnimatedModel(which).CloneWorkspace()); modelData.EnableLooping(true); const float dt = modelData.GetAnimationData()->GetAnimTimeRemaining("Whole Body"sv) * (i * 0.25f); modelData.AdvanceAnimation(dt, mgr, x4_areaId, true); } x1c4_which = which; } void CSnakeWeedSwarm::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId id, CStateManager& mgr) { CActor::AcceptScriptMsg(msg, id, mgr); if (msg == EScriptObjectMessage::Deleted) { if (x1d8_sfxHandle) { CSfxManager::RemoveEmitter(x1d8_sfxHandle); x1d8_sfxHandle.reset(); } } else if (msg == EScriptObjectMessage::InitializedInArea) { AllocateSkinnedModels(mgr, CModelData::EWhichModel::Normal); SetCalculateLighting(true); x90_actorLights->SetCastShadows(true); } } void CSnakeWeedSwarm::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) { if (!frustum.aabbFrustumTest(x144_touchBounds)) { xe4_30_outOfFrustum = true; return; } xe4_30_outOfFrustum = false; for (const auto& modelData : x1b0_modelData) { modelData->GetAnimationData()->PreRender(); } if (x90_actorLights && x140_24_hasGround) { bool buildLights = false; if (xe4_29_actorLightsDirty) { buildLights = true; xe4_29_actorLightsDirty = false; } else if (x90_actorLights->GetIsDirty()) { buildLights = true; } if (xe4_31_calculateLighting && x90_actorLights) { auto visor = mgr.GetPlayerState()->GetActiveVisor(mgr); if (visor == CPlayerState::EPlayerVisor::Thermal) { x90_actorLights->BuildConstantAmbientLighting(zeus::skWhite); } else if (buildLights && x4_areaId != kInvalidAreaId) { CGameArea* area = mgr.GetWorld()->GetArea(x4_areaId); if (area != nullptr) { x90_actorLights->BuildAreaLightList(mgr, *area, x144_touchBounds); } } x90_actorLights->BuildDynamicLightList(mgr, x144_touchBounds); } } } void CSnakeWeedSwarm::AddToRenderer(const zeus::CFrustum& frustum, CStateManager& mgr) { if (xe4_30_outOfFrustum) { return; } if (x1ec_particleGen1) { g_Renderer->AddParticleGen(*x1ec_particleGen1); } if (x1f4_particleGen2) { g_Renderer->AddParticleGen(*x1f4_particleGen2); } if (x90_actorLights) { for (const auto& modelData : x1b0_modelData) { x90_actorLights->ActivateLights(); } } else { CGraphics::DisableAllLights(); CGraphics::SetAmbientColor(zeus::skWhite); } u32 posesToBuild = -1; for (u32 i = 0; i < x134_boids.size(); ++i) { RenderBoid(i, x134_boids[i], posesToBuild); } CGraphics::DisableAllLights(); } void CSnakeWeedSwarm::Touch(CActor& actor, CStateManager& mgr) { if (TCastToPtr proj = actor) { if (proj->GetDamageInfo().GetWeaponMode().GetType() != EWeaponType::AI) { HandleRadiusDamage(x100_weaponDamageRadius, mgr, proj->GetTransform().origin); } } if (TCastToPtr player = actor) { for (const auto& boid : x134_boids) { float m = (boid.GetPosition() - player->GetTransform().origin).magnitude(); if (m < x104_maxPlayerDistance && boid.GetState() == EBoidState::Raised) { mgr.SendScriptMsg(player, kInvalidUniqueId, EScriptObjectMessage::InSnakeWeed); x140_26_playerTouching = true; } } } } void CSnakeWeedSwarm::HandleRadiusDamage(float radius, CStateManager& mgr, const zeus::CVector3f& pos) { float radiusSquared = radius * radius; for (auto& boid : x134_boids) { const auto& boidPosition = boid.GetPosition(); if ((boidPosition - pos).magSquared() < radiusSquared && (boid.GetState() == EBoidState::Raised || boid.GetState() == EBoidState::Raising)) { boid.SetState(EBoidState::Lowering); boid.SetSpeed(x118_speedVariation * mgr.GetActiveRandom()->Float() + x114_speed); CSfxManager::AddEmitter(x1d2_sfx2, boidPosition, zeus::skZero3f, true, false, 0x7f, x4_areaId); EmitParticles1(boidPosition); } } } void CSnakeWeedSwarm::Think(float dt, CStateManager& mgr) { CEntity::Think(dt, mgr); if (!x30_24_active) return; if (x1ec_particleGen1) x1ec_particleGen1->Update(dt); if (x1f4_particleGen2) x1f4_particleGen2->Update(dt); x204_particleTimer -= dt; bool emitParticle = false; if (x204_particleTimer < 0.f) { x204_particleTimer = 0.f; emitParticle = true; } if (!x140_24_hasGround) FindGround(mgr); if (x140_24_hasGround && x1c8_boidPositions && !x1c8_boidPositions->empty()) { int n = (u64)(dt * x1cc_boidPlacement->size()) >> 0x20; CreateBoids(mgr, n + 1); } CModelData::EWhichModel model = CModelData::GetRenderingModel(mgr); if (x140_25_modelAssetDirty && x1c4_which != model) AllocateSkinnedModels(mgr, model); for (const auto& modelData : x1b0_modelData) { modelData->GetAnimationData()->SetPlaybackRate(1.f); modelData->AdvanceAnimation(dt, mgr, x4_areaId, true); } int raisedBoids = 0; for (auto& boid : x134_boids) { const zeus::CVector3f& pos = boid.GetPosition(); const EBoidState state = boid.GetState(); if (state == EBoidState::Raised) { raisedBoids++; if (x1f4_particleGen2 && emitParticle) { EmitParticles2(pos); } } else if (state == EBoidState::Raising) { boid.SetZOffset(boid.GetZOffset() - dt * boid.GetSpeed()); if (boid.GetZOffset() <= 0.f) { boid.SetZOffset(0.f); boid.SetState(EBoidState::Raised); } } else if (state == EBoidState::Lowered) { boid.SetLoweredTimer(boid.GetLoweredTimer() - dt); if (boid.GetLoweredTimer() <= 0.f) { boid.SetState(EBoidState::Raising); CSfxManager::AddEmitter(x1d4_sfx3, pos, zeus::skZero3f, true, false, 0x7f, x4_areaId); EmitParticles1(pos); } } else if (state == EBoidState::Lowering) { boid.SetZOffset(boid.GetZOffset() + dt * boid.GetSpeed()); const float max = x110_maxZOffset * boid.GetScale(); if (boid.GetZOffset() >= max) { boid.SetZOffset(max); boid.SetLoweredTimer(x10c_loweredTimeVariation * mgr.GetActiveRandom()->Float() + x108_loweredTime); boid.SetState(EBoidState::Lowered); } } } if (raisedBoids == 0) { if (x1d8_sfxHandle) { CSfxManager::RemoveEmitter(x1d8_sfxHandle); x1d8_sfxHandle.reset(); } } else if (!x1d8_sfxHandle) { x1d8_sfxHandle = CSfxManager::AddEmitter(x1d0_sfx1, GetTransform().origin, zeus::skZero3f, true, true, 0x7f, x4_areaId); } if (x140_26_playerTouching) { mgr.ApplyDamage(x8_uid, mgr.GetPlayer().GetUniqueId(), x8_uid, {x15c_damageInfo, dt}, skMaterialFilter, zeus::skZero3f); } x140_26_playerTouching = false; } zeus::CAABox CSnakeWeedSwarm::GetBoidBox() const { const auto scale = xe8_scale * 0.75f; return {GetTransform().origin - scale, GetTransform().origin + scale}; } void CSnakeWeedSwarm::FindGround(const CStateManager& mgr) { const auto box = GetBoidBox(); const auto result = mgr.RayStaticIntersection(box.center(), zeus::skDown, box.max.z() - box.min.z(), skMaterialFilter); if (result.IsValid()) { int ct = GetNumBoidsX() * GetNumBoidsY(); x134_boids.reserve(ct); x1c8_boidPositions->reserve(ct); x1c8_boidPositions->push_back(result.GetPoint()); x1cc_boidPlacement->resize(ct, EBoidPlacement::None); x140_24_hasGround = true; } } int CSnakeWeedSwarm::GetNumBoidsY() const { const auto box = GetBoidBox(); return static_cast((box.max.y() - box.min.y()) / xf4_boidSpacing) + 1; } int CSnakeWeedSwarm::GetNumBoidsX() const { const auto box = GetBoidBox(); return static_cast((box.max.x() - box.min.x()) / xf4_boidSpacing) + 1; } void CSnakeWeedSwarm::CreateBoids(CStateManager& mgr, int num) { auto width = GetNumBoidsX(); for (int i = 0; i < num; ++i) { if (x1c8_boidPositions->empty()) break; const auto pos = x1c8_boidPositions->back(); x1c8_boidPositions->pop_back(); const auto& idx = GetBoidIndex(pos); if (CreateBoid(pos, mgr)) { x1cc_boidPlacement->at(idx.x + width * idx.y) = EBoidPlacement::Placed; AddBoidPosition({pos.x(), pos.y() - xf4_boidSpacing, pos.z()}); AddBoidPosition({pos.x(), xf4_boidSpacing + pos.y(), pos.z()}); AddBoidPosition({pos.x() - xf4_boidSpacing, pos.y(), pos.z()}); AddBoidPosition({xf4_boidSpacing + pos.x(), pos.y(), pos.z()}); } else { x1cc_boidPlacement->at(idx.x + width * idx.y) = EBoidPlacement::Invalid; } } CalculateTouchBounds(); if (x1c8_boidPositions->empty()) { x1c8_boidPositions = nullptr; x1cc_boidPlacement = nullptr; } } zeus::CVector2i CSnakeWeedSwarm::GetBoidIndex(const zeus::CVector3f& pos) const { const auto box = GetBoidBox(); return {static_cast((pos.x() - box.min.x()) / xf4_boidSpacing), static_cast((pos.y() - box.min.y()) / xf4_boidSpacing)}; } bool CSnakeWeedSwarm::CreateBoid(const zeus::CVector3f& vec, CStateManager& mgr) { const auto pos = vec + zeus::CVector3f(GetBoidOffsetX(vec), GetBoidOffsetY(vec), xf8_height); const auto result = mgr.RayStaticIntersection(pos, zeus::skDown, 2.f * xf8_height, skMaterialFilter); if (result.IsValid() && result.GetPlane().normal().dot(zeus::skUp) > x11c_) { const auto boidPosition = result.GetPoint() - zeus::CVector3f(0.f, 0.f, x128_distanceBelowGround); x134_boids.emplace_back(boidPosition, x110_maxZOffset, x114_speed + x118_speedVariation, (x124_scaleMax - x120_scaleMin) * mgr.GetActiveRandom()->Float() + x120_scaleMin); return true; } return false; } float CSnakeWeedSwarm::GetBoidOffsetY(const zeus::CVector3f& pos) const { const float f = 2.4729404f * pos.y() + 0.3478602f * pos.x() * pos.x(); return xfc_ * (2.f * std::abs(f - std::trunc(f)) - 1.f); } float CSnakeWeedSwarm::GetBoidOffsetX(const zeus::CVector3f& pos) const { const float f = 8.21395f * pos.x() + 0.112869f * pos.y() * pos.y(); return xfc_ * (2.f * std::abs(f - std::trunc(f)) - 1.f); } void CSnakeWeedSwarm::AddBoidPosition(const zeus::CVector3f& pos) { int x = GetNumBoidsX(); int y = GetNumBoidsY(); const auto& v = GetBoidIndex(pos); if (-1 < v.x && v.x < x && -1 < v.y && v.y < y) { int idx = v.x + x * v.y; auto& placement = x1cc_boidPlacement->at(idx); if (placement == EBoidPlacement::None) { placement = EBoidPlacement::Ready; x1c8_boidPositions->push_back(pos); } } } void CSnakeWeedSwarm::CalculateTouchBounds() { if (x134_boids.empty()) { x144_touchBounds = {x34_transform.origin, x34_transform.origin}; } else { x144_touchBounds = zeus::skInvertedBox; for (const auto& boid : x134_boids) { x144_touchBounds.accumulateBounds(boid.GetPosition() - x100_weaponDamageRadius); x144_touchBounds.accumulateBounds(boid.GetPosition() + x100_weaponDamageRadius); } } xe4_27_notInSortedLists = true; } void CSnakeWeedSwarm::EmitParticles1(const zeus::CVector3f& pos) { if (!x1ec_particleGen1) return; auto& particleGen = *x1ec_particleGen1; particleGen.SetParticleEmission(true); particleGen.SetTranslation(pos); particleGen.ForceParticleCreation(x1fc_); particleGen.SetParticleEmission(false); } void CSnakeWeedSwarm::EmitParticles2(const zeus::CVector3f& pos) { if (!x1f4_particleGen2) return; auto& particleGen = *x1f4_particleGen2; particleGen.SetParticleEmission(true); particleGen.SetTranslation(pos); particleGen.ForceParticleCreation(1); particleGen.SetParticleEmission(false); } void CSnakeWeedSwarm::RenderBoid(u32 idx, const CBoid& boid, u32& posesToBuild) const { const u32 modelIdx = idx & 3; auto& modelData = *x1b0_modelData[modelIdx]; auto& model = modelData.PickAnimatedModel(x1c4_which); auto& animData = *modelData.GetAnimationData(); auto& workspace = x178_workspaces[modelIdx]; if (posesToBuild & 1 << modelIdx) { posesToBuild &= ~(1 << modelIdx); animData.BuildPose(); model.Calculate(animData.GetPose(), nullptr, nullptr, &workspace); } CGraphics::SetModelMatrix( zeus::CTransform::Translate(boid.GetPosition() - zeus::CVector3f(0.f, 0.f, boid.GetZOffset())) * zeus::CTransform::Scale(boid.GetScale())); constexpr CModelFlags useFlags{0, 0, 3, zeus::skWhite}; model.Draw(&workspace.m_vertexWorkspace, &workspace.m_normalWorkspace, useFlags); } void CSnakeWeedSwarm::ApplyRadiusDamage(const zeus::CVector3f& pos, const CDamageInfo& info, CStateManager& mgr) { auto type = info.GetWeaponMode().GetType(); if (type == EWeaponType::Bomb || type == EWeaponType::PowerBomb) HandleRadiusDamage(info.GetRadius(), mgr, pos); } std::optional CSnakeWeedSwarm::GetTouchBounds() const { if (x140_24_hasGround) return x144_touchBounds; return std::nullopt; } } // namespace metaforce