#include "CFishCloud.hpp" #include "CActorParameters.hpp" #include "TCastTo.hpp" #include "GameGlobalObjects.hpp" #include "CSimplePool.hpp" #include "Weapon/CWeapon.hpp" #include "CStateManager.hpp" #include "World/CWorld.hpp" #include "World/CPlayer.hpp" #include "Graphics/CVertexMorphEffect.hpp" #include "Graphics/CSkinnedModel.hpp" #include "Graphics/CBooRenderer.hpp" namespace urde { CFishCloud::CFishCloud(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info, const zeus::CVector3f& scale, const zeus::CTransform& xf, CModelData&& mData, const CAnimRes& aRes, u32 numBoids, float speed, float separationRadius, float cohesionMagnitude, float alignmentWeight, float separationMagnitude, float weaponRepelMagnitude, float playerRepelMagnitude, float containmentMagnitude, float scatterVel, float maxScatterAngle, float weaponRepelDampingSpeed, float playerRepelDampingSpeed, float containmentRadius, u32 updateShift, const zeus::CColor& color, bool killable, float weaponKillRadius, CAssetId part1, u32 partCount1, CAssetId part2, u32 partCount2, CAssetId part3, u32 partCount3, CAssetId part4, u32 partCount4, u32 deathSfx, bool repelFromThreats, bool hotInThermal) : CActor(uid, active, name, info, xf, std::move(mData), {EMaterialTypes::NoStepLogic}, CActorParameters::None().HotInThermal(hotInThermal), kInvalidUniqueId) , x11c_updateMask(u32((1 << updateShift) - 1)) , x120_scale(scale) , x130_speed(speed) , x134_numBoids(numBoids) , x138_separationRadius(separationRadius) , x13c_cohesionMagnitude(cohesionMagnitude) , x140_alignmentWeight(alignmentWeight) , x144_separationMagnitude(separationMagnitude) , x148_weaponRepelMagnitude(weaponRepelMagnitude) , x14c_playerRepelMagnitude(playerRepelMagnitude) , x150_scatterVel(scatterVel) , x154_maxScatterAngle(maxScatterAngle) , x158_containmentMagnitude(containmentMagnitude) , x15c_playerRepelDampingSpeed(playerRepelDampingSpeed) , x160_weaponRepelDampingSpeed(weaponRepelDampingSpeed) , x164_playerRepelDamping(playerRepelDampingSpeed) , x168_weaponRepelDamping(weaponRepelDampingSpeed) , x16c_color(color) , x170_weaponKillRadius(weaponKillRadius) , x174_containmentRadius(containmentRadius) , x234_deathSfx(deathSfx != 0xffffffff ? CSfxManager::TranslateSFXID(u16(deathSfx & 0xffff)) : u16(0xffff)) { x250_28_killable = killable; x250_29_repelFromThreats = repelFromThreats; x108_modifierSources.reserve(10); x250_25_worldSpace = true; // The result of a close_enough paradox (weird inlined test?) if (aRes.GetId().IsValid()) { x1b0_models.emplace_back(new CModelData(aRes)); x1b0_models.emplace_back(new CModelData(aRes)); x1b0_models.emplace_back(new CModelData(aRes)); x1b0_models.emplace_back(new CModelData(aRes)); x250_27_validModel = true; } if (part1.IsValid()) x1c4_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part1})); if (part2.IsValid()) x1c4_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part2})); if (part3.IsValid()) x1c4_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part3})); if (part4.IsValid()) x1c4_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part4})); for (const auto& p : x1c4_particleDescs) { x1f8_particleGens.emplace_back(new CElementGen(p)); x1f8_particleGens.back()->SetParticleEmission(false); } x21c_deathParticleCounts.push_back(partCount1); x21c_deathParticleCounts.push_back(partCount2); x21c_deathParticleCounts.push_back(partCount3); x21c_deathParticleCounts.push_back(partCount4); zeus::CAABox aabb = GetBoundingBox(); x238_partitionPitch = (aabb.max - aabb.min) / 7.f; x244_ooPartitionPitch = 1.f / x238_partitionPitch; } void CFishCloud::Accept(IVisitor& visitor) { visitor.Visit(this); } void CFishCloud::UpdateParticles(float dt) { for (auto& p : x1f8_particleGens) p->Update(dt); } void CFishCloud::UpdatePartitionList() { xf8_boidPartitionLists.clear(); xf8_boidPartitionLists.resize(xf8_boidPartitionLists.capacity()); auto aabb = GetBoundingBox(); for (auto& b : xe8_boids) { zeus::CVector3f idxs = (b.x0_pos - aabb.min) * x244_ooPartitionPitch; int idx = int(idxs.x()) + int(idxs.y()) * 7 + int(idxs.z()) * 49; if (idx >= 0 && idx < 343) { b.x1c_next = xf8_boidPartitionLists[idx]; xf8_boidPartitionLists[idx] = &b; } } } bool CFishCloud::PointInBox(const zeus::CAABox& aabb, const zeus::CVector3f& point) const { if (!x250_25_worldSpace) return aabb.pointInside(point); return GetUntransformedBoundingBox().pointInside(GetTransform().transposeRotate(point - GetTranslation())); } zeus::CPlane CFishCloud::FindClosestPlane(const zeus::CAABox& aabb, const zeus::CVector3f& point) const { if (!x250_25_worldSpace) { float minDist = FLT_MAX; zeus::CAABox::EBoxFaceId minFace = zeus::CAABox::EBoxFaceId::YMin; for (int i = 0; i < 6; ++i) { auto tri = aabb.getTri(zeus::CAABox::EBoxFaceId(i), 0); float dist = zeus::CPlane(tri.x10_verts[0], tri.x10_verts[2], tri.x10_verts[1]).pointToPlaneDist(point); if (dist >= 0.f && dist < minDist) { minDist = dist; minFace = zeus::CAABox::EBoxFaceId(i); } } auto tri = aabb.getTri(minFace, 0); return zeus::CPlane(tri.x10_verts[0], tri.x10_verts[2], tri.x10_verts[1]); } else { auto unPoint = GetTransform().transposeRotate(point - GetTranslation()); auto unAabb = GetUntransformedBoundingBox(); float minDist = FLT_MAX; zeus::CAABox::EBoxFaceId minFace = zeus::CAABox::EBoxFaceId::YMin; for (int i = 0; i < 6; ++i) { auto tri = unAabb.getTri(zeus::CAABox::EBoxFaceId(i), 0); float dist = zeus::CPlane(tri.x10_verts[0], tri.x10_verts[2], tri.x10_verts[1]).pointToPlaneDist(unPoint); if (dist >= 0.f && dist < minDist) { minDist = dist; minFace = zeus::CAABox::EBoxFaceId(i); } } auto tri = unAabb.getTri(minFace, 0); return zeus::CPlane(GetTransform() * tri.x10_verts[0], GetTransform() * tri.x10_verts[2], GetTransform() * tri.x10_verts[1]); } } CFishCloud::CBoid* CFishCloud::GetListAt(const zeus::CVector3f& pos) { zeus::CAABox aabb = GetBoundingBox(); zeus::CVector3f ints = (pos - aabb.min) * x244_ooPartitionPitch; int idx = int(ints.x()) + int(ints.y()) * 7 + int(ints.z()) * 49; if (idx < 0 || idx >= 343) return nullptr; return xf8_boidPartitionLists[idx]; } void CFishCloud::BuildBoidNearList(const zeus::CVector3f& pos, float radius, rstl::reserved_vector& nearList) { float radiusSq = radius * radius; CBoid* b = GetListAt(pos); while (b && nearList.size() < 25) { if (b->x20_active) { float distSq = (b->GetTranslation() - pos).magSquared(); if (distSq != 0.f && distSq < radiusSq) nearList.push_back(b); } b = b->x1c_next; } } void CFishCloud::BuildBoidNearPartitionList(const zeus::CVector3f& pos, float radius, rstl::reserved_vector& nearList) { float radiusSq = radius * radius; zeus::CAABox aabb = GetBoundingBox(); float x = std::max(radius * x244_ooPartitionPitch.x(), float(x238_partitionPitch.x())); float y = std::max(radius * x244_ooPartitionPitch.y(), float(x238_partitionPitch.y())); float z = std::max(radius * x244_ooPartitionPitch.z(), float(x238_partitionPitch.z())); float nx = 0.01f - x; float ny = 0.01f - y; float nz = 0.01f - z; for (float lnx = nx; lnx < x; lnx += x238_partitionPitch.x()) { float cx = lnx + pos.x(); if (cx < aabb.min.x()) continue; if (cx >= aabb.max.x()) break; for (float lny = ny; lny < y; lny += x238_partitionPitch.y()) { float cy = lny + pos.y(); if (cy < aabb.min.y()) continue; if (cy >= aabb.max.y()) break; for (float lnz = nz; lnz < z; lnz += x238_partitionPitch.z()) { float cz = lnz + pos.z(); if (cz < aabb.min.z()) continue; if (cz >= aabb.max.z()) break; zeus::CVector3f ints = (zeus::CVector3f(cx, cy, cz) - aabb.min) * x244_ooPartitionPitch; int idx = int(ints.x()) + int(ints.y()) * 7 + int(ints.z()) * 49; if (idx < 0) continue; if (idx < 343) { CBoid* boid = xf8_boidPartitionLists[idx]; while (boid) { if (boid->x20_active) { float distSq = (boid->x0_pos - pos).magSquared(); if (distSq != 0.f && distSq < radiusSq) { nearList.push_back(boid); if (nearList.size() == 25) return; } } boid = boid->x1c_next; } } } } } } void CFishCloud::PlaceBoid(CStateManager& mgr, CBoid& boid, const zeus::CAABox& aabb) { auto plane = FindClosestPlane(aabb, boid.x0_pos); boid.x0_pos -= plane.pointToPlaneDist(boid.x0_pos) * plane.normal() + 0.0001f * plane.normal(); boid.xc_vel.y() = mgr.GetActiveRandom()->Float() - 0.5f; boid.xc_vel.x() = mgr.GetActiveRandom()->Float() - 0.5f; boid.xc_vel.z() = 0.f; if (!x250_25_worldSpace) { if (!aabb.pointInside(boid.x0_pos)) { boid.x0_pos.z() = mgr.GetActiveRandom()->Float() * (aabb.max.z() - aabb.min.z()) + aabb.min.z(); boid.x0_pos.y() = mgr.GetActiveRandom()->Float() * (aabb.max.y() - aabb.min.y()) + aabb.min.y(); boid.x0_pos.x() = mgr.GetActiveRandom()->Float() * (aabb.max.x() - aabb.min.x()) + aabb.min.x(); } } else { if (!PointInBox(aabb, boid.x0_pos)) { auto unAabb = GetUntransformedBoundingBox(); boid.x0_pos.z() = mgr.GetActiveRandom()->Float() * (unAabb.max.z() - unAabb.min.z()) + unAabb.min.z(); boid.x0_pos.y() = mgr.GetActiveRandom()->Float() * (unAabb.max.y() - unAabb.min.y()) + unAabb.min.y(); boid.x0_pos.x() = mgr.GetActiveRandom()->Float() * (unAabb.max.x() - unAabb.min.x()) + unAabb.min.x(); boid.x0_pos = GetTransform() * boid.x0_pos; } } } void CFishCloud::ApplySeparation(CBoid& boid, const rstl::reserved_vector& nearList) const { if (nearList.empty()) return; float minDist = FLT_MAX; zeus::CVector3f pos; for (CBoid* b : nearList) { float dist = (boid.GetTranslation() - b->GetTranslation()).magSquared(); if (dist < minDist) { minDist = dist; pos = b->GetTranslation(); } } ApplySeparation(boid, pos, x138_separationRadius, x144_separationMagnitude); } void CFishCloud::ApplySeparation(CBoid& boid, const zeus::CVector3f& separateFrom, float separationRadius, float separationMagnitude) const { zeus::CVector3f delta = boid.GetTranslation() - separateFrom; if (delta.canBeNormalized()) { float deltaDistSq = delta.magSquared(); float capDeltaDistSq = separationRadius * separationRadius; if (deltaDistSq < capDeltaDistSq) boid.xc_vel += (1.f - deltaDistSq / capDeltaDistSq) * delta.normalized() * separationMagnitude; } } void CFishCloud::ApplyCohesion(CBoid& boid, const rstl::reserved_vector& nearList) const { if (nearList.empty()) return; zeus::CVector3f avg; for (CBoid* b : nearList) avg += b->GetTranslation(); avg = avg / float(nearList.size()); ApplyCohesion(boid, avg, x138_separationRadius, x13c_cohesionMagnitude); } void CFishCloud::ApplyCohesion(CBoid& boid, const zeus::CVector3f& cohesionFrom, float cohesionRadius, float cohesionMagnitude) const { zeus::CVector3f delta = cohesionFrom - boid.GetTranslation(); if (delta.canBeNormalized()) { float distSq = delta.magSquared(); float capDistSq = cohesionRadius * cohesionRadius; boid.xc_vel += ((distSq > capDistSq) ? 1.f : distSq / capDistSq) * delta.normalized() * cohesionMagnitude; } } void CFishCloud::ApplyAlignment(CBoid& boid, const rstl::reserved_vector& nearList) const { if (nearList.empty()) return; zeus::CVector3f avg; for (CBoid* b : nearList) avg += b->xc_vel; avg = avg / float(nearList.size()); boid.xc_vel += zeus::CVector3f::getAngleDiff(boid.xc_vel, avg) / M_PIF * (avg * x140_alignmentWeight); } void CFishCloud::ApplyAttraction(CBoid& boid, const zeus::CVector3f& attractTo, float attractionRadius, float attractionMagnitude) const { zeus::CVector3f delta = attractTo - boid.GetTranslation(); if (delta.canBeNormalized()) { float distSq = delta.magSquared(); float capDistSq = attractionRadius * attractionRadius; boid.xc_vel += ((distSq > capDistSq) ? 0.f : (1.f - distSq / capDistSq)) * delta.normalized() * attractionMagnitude; } } void CFishCloud::ApplyRepulsion(CBoid& boid, const zeus::CVector3f& attractTo, float repulsionRadius, float repulsionMagnitude) const { ApplySeparation(boid, attractTo, repulsionRadius, repulsionMagnitude); } void CFishCloud::ApplySwirl(CBoid& boid, const zeus::CVector3f& swirlPoint, bool clockwise, float magnitude, float radius) const { zeus::CVector3f delta = boid.x0_pos - swirlPoint; float deltaMag = delta.magnitude(); zeus::CVector3f alignVec; if (clockwise) alignVec = delta.normalized().cross(zeus::skUp); else alignVec = zeus::skUp.cross(delta / deltaMag); float weight = deltaMag > radius ? 0.f : 1.f - deltaMag / radius; boid.xc_vel += zeus::CVector3f::getAngleDiff(boid.xc_vel, alignVec) / M_PIF * weight * (magnitude * alignVec); } void CFishCloud::ApplyContainment(CBoid& boid, const zeus::CAABox& aabb) const { if (boid.xc_vel.canBeNormalized()) { if (!PointInBox(aabb, boid.xc_vel.normalized() * x130_speed * x174_containmentRadius + boid.x0_pos)) { ApplyAttraction(boid, aabb.center(), 100000.f, x158_containmentMagnitude); } } } void CFishCloud::ScatterBoid(CStateManager& mgr, CBoid& b) const { float angle = (mgr.GetActiveRandom()->Float() - 0.5f) * M_PIF * x154_maxScatterAngle; float cosAngle = std::cos(angle); float sinAngle = std::sin(angle); b.xc_vel.x() += x150_scatterVel * (b.xc_vel.y() * sinAngle + b.xc_vel.x() * cosAngle); b.xc_vel.y() += x150_scatterVel * (b.xc_vel.y() * cosAngle + b.xc_vel.x() * sinAngle); } void CFishCloud::Think(float dt, CStateManager& mgr) { if (!GetActive()) return; const CGameArea* area = mgr.GetWorld()->GetAreaAlways(x4_areaId); auto occState = area->IsPostConstructed() ? area->GetOcclusionState() : CGameArea::EOcclusionState::Occluded; if (occState == CGameArea::EOcclusionState::Visible) { x168_weaponRepelDamping = std::max(0.f, x168_weaponRepelDamping - x160_weaponRepelDampingSpeed * dt * 0.1f); if (x250_26_enableWeaponRepelDamping) x168_weaponRepelDamping = std::min(x160_weaponRepelDampingSpeed * dt + x168_weaponRepelDamping, x148_weaponRepelMagnitude); x164_playerRepelDamping = std::max(0.f, x164_playerRepelDamping - x15c_playerRepelDampingSpeed * dt * 0.1f); if (x250_30_enablePlayerRepelDamping) x164_playerRepelDamping = std::min(x15c_playerRepelDampingSpeed * dt + x164_playerRepelDamping, x14c_playerRepelMagnitude); x250_26_enableWeaponRepelDamping = false; x250_30_enablePlayerRepelDamping = false; ++x118_thinkCounter; UpdateParticles(dt); UpdatePartitionList(); zeus::CAABox aabb = GetBoundingBox(); int idx = 0; for (auto& b : xe8_boids) { if (b.x20_active && (idx & x11c_updateMask) == (x118_thinkCounter & x11c_updateMask)) { rstl::reserved_vector nearList; if (x250_31_updateWithoutPartitions) BuildBoidNearList(b.x0_pos, x138_separationRadius, nearList); else BuildBoidNearPartitionList(b.x0_pos, x138_separationRadius, nearList); for (int i = 0; i < 5; ++i) { switch (i) { case 1: ApplySeparation(b, nearList); break; case 2: if (!x250_24_randomMovement || mgr.GetActiveRandom()->Float() > x12c_randomMovementTimer) ApplyCohesion(b, nearList); break; case 3: if (!x250_24_randomMovement || mgr.GetActiveRandom()->Float() > x12c_randomMovementTimer) ApplyAlignment(b, nearList); break; case 4: ScatterBoid(mgr, b); break; default: break; } if (b.xc_vel.magSquared() > 3.2f) break; } if (!x250_24_randomMovement && b.xc_vel.magSquared() < 3.2f) { for (auto& m : x108_modifierSources) { if (TCastToPtr act = mgr.ObjectById(m.x0_source)) { if (m.xd_isSwirl) { ApplySwirl(b, act->GetTranslation(), m.xc_isRepulsor, m.x8_priority, m.x4_radius); } else if (m.xc_isRepulsor) { ApplyRepulsion(b, act->GetTranslation(), m.x4_radius, m.x8_priority); } else { ApplyAttraction(b, act->GetTranslation(), m.x4_radius, m.x8_priority); } } else { if (m.xc_isRepulsor) RemoveRepulsor(m.x0_source); else RemoveAttractor(m.x0_source); break; } } } } ++idx; } for (auto& b : xe8_boids) { if (b.x20_active) { ApplyContainment(b, aabb); float velMag = b.xc_vel.magnitude(); if (!zeus::close_enough(velMag, 0.f)) b.xc_vel = b.xc_vel / velMag; b.xc_vel.z() *= 0.99f; } } if (x12c_randomMovementTimer > 0.f) { x12c_randomMovementTimer -= dt; } else { x12c_randomMovementTimer = 0.f; x250_24_randomMovement = false; } for (auto& b : xe8_boids) { if (b.x20_active) { b.x0_pos += b.xc_vel * dt * x130_speed; if (!PointInBox(aabb, b.x0_pos)) PlaceBoid(mgr, b, aabb); } } if (x250_27_validModel) { for (auto& m : x1b0_models) { m->AnimationData()->SetPlaybackRate(1.f); m->AdvanceAnimation(dt, mgr, x4_areaId, true); } } } } void CFishCloud::CreatePartitionList() { xf8_boidPartitionLists.reserve(343); } void CFishCloud::AllocateSkinnedModels(CStateManager& mgr, CModelData::EWhichModel which) { int idx = 0; for (auto& m : x1b0_models) { m->EnableLooping(true); m->AdvanceAnimation( m->AnimationData()->GetAnimTimeRemaining("Whole Body"sv) * 0.25f * idx, mgr, x4_areaId, true); ++idx; } x230_whichModel = which; } void CFishCloud::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) { CActor::AcceptScriptMsg(msg, sender, mgr); switch (msg) { case EScriptObjectMessage::Registered: { xe8_boids.reserve(x134_numBoids); zeus::CAABox aabb = GetUntransformedBoundingBox(); zeus::CVector3f extent = aabb.max - aabb.min; zeus::CVector3f randPoint; for (int i = 0; i < x134_numBoids; ++i) { randPoint.z() = mgr.GetActiveRandom()->Float() * extent.z() + aabb.min.z(); randPoint.y() = mgr.GetActiveRandom()->Float() * extent.y() + aabb.min.y(); randPoint.x() = mgr.GetActiveRandom()->Float() * extent.x() + aabb.min.x(); zeus::CVector3f vel; vel.y() = mgr.GetActiveRandom()->Float() - 0.5f; vel.x() = mgr.GetActiveRandom()->Float() - 0.5f; xe8_boids.emplace_back(x34_transform * randPoint, vel, 0.2f * std::pow(mgr.GetActiveRandom()->Float(), 7.f) + 0.9f); } CreatePartitionList(); if (x250_27_validModel) AllocateSkinnedModels(mgr, CModelData::EWhichModel::Normal); break; } default: break; } } void CFishCloud::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) { CActor::PreRender(mgr, frustum); if (x250_27_validModel) { for (auto& m : x1b0_models) { m->AnimationData()->PreRender(); } } xe4_30_outOfFrustum = false; } void CFishCloud::AddParticlesToRenderer() const { for (const auto& p : x1f8_particleGens) g_Renderer->AddParticleGen(*p); } void CFishCloud::RenderBoid(int idx, const CBoid& boid, u32& drawMask, bool thermalHot, const CModelFlags& flags) const { u32 modelIndex = idx & 0x3; CModelData& mData = *x1b0_models[modelIndex]; CSkinnedModel& model = mData.PickAnimatedModel(CModelData::EWhichModel::Normal); if (!model.GetModelInst()->TryLockTextures()) return; u32 thisDrawMask = 1u << modelIndex; if (drawMask & thisDrawMask) { drawMask &= ~thisDrawMask; mData.AnimationData()->BuildPose(); } model.GetModelInst()->SetAmbientColor(zeus::skWhite); CGraphics::SetModelMatrix(zeus::lookAt(boid.x0_pos, boid.x0_pos + boid.xc_vel)); if (thermalHot) { CModelFlags thermFlags(0, 0, 3, zeus::skWhite); mData.RenderThermal(zeus::skWhite, zeus::CColor(0.f, 0.25f), thermFlags); } else { mData.AnimationData()->Render(model, flags, {}, nullptr); } } void CFishCloud::Render(const CStateManager& mgr) const { if (!GetActive()) return; bool thermalHot = mgr.GetThermalDrawFlag() == EThermalDrawFlag::Hot; CModelFlags flags(0, 0, 3, zeus::skWhite); if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) flags = CModelFlags(5, 0, 3, x16c_color); else flags = CModelFlags(1, 0, 3, x16c_color); AddParticlesToRenderer(); if (x250_27_validModel) { // Ambient white int idx = 0; u32 drawMask = 0xffffffff; for (const auto& b : xe8_boids) { if (b.x20_active) RenderBoid(idx, b, drawMask, thermalHot, flags); ++idx; } } else { CGraphics::SetModelMatrix(zeus::CTransform()); for (const auto& b : xe8_boids) { if (b.x20_active) { x64_modelData->SetScale(zeus::CVector3f(b.x18_scale)); x64_modelData->Render(mgr, zeus::lookAt(b.x0_pos, b.x0_pos + b.xc_vel), nullptr, flags); } } } } void CFishCloud::CalculateRenderBounds() { x9c_renderBounds = GetBoundingBox(); } std::optional CFishCloud::GetTouchBounds() const { return {GetBoundingBox()}; } void CFishCloud::CreateBoidDeathParticle(CBoid& b) const { auto it = x21c_deathParticleCounts.begin(); for (auto& p : x1f8_particleGens) { p->SetParticleEmission(true); p->SetTranslation(b.x0_pos); p->ForceParticleCreation(*it); p->SetParticleEmission(false); ++it; } } void CFishCloud::KillBoid(CBoid& b) const { b.x20_active = false; CreateBoidDeathParticle(b); CAudioSys::C3DEmitterParmData parmData = {}; parmData.x0_pos = b.x0_pos; parmData.x18_maxDist = 250.f; parmData.x1c_distComp = 0.1f; parmData.x20_flags = 0x1; parmData.x24_sfxId = x234_deathSfx; parmData.x26_maxVol = 1.f; parmData.x27_minVol = 0.157f; parmData.x29_prio = 0x7f; CSfxManager::AddEmitter(parmData, true, 0x7f, false, x4_areaId); } void CFishCloud::Touch(CActor& other, CStateManager& mgr) { CActor::Touch(other, mgr); if (TCastToPtr weap = other) { if (!x250_26_enableWeaponRepelDamping && x250_29_repelFromThreats) { int idx = 0; for (auto& b : xe8_boids) { if ((idx & 0x3) == (x118_thinkCounter & 0x3)) ApplyRepulsion(b, weap->GetTranslation(), 8.f, x148_weaponRepelMagnitude - x168_weaponRepelDamping); ++idx; } } x250_26_enableWeaponRepelDamping = true; if (x250_28_killable) { if (auto tb = weap->GetTouchBounds()) { for (auto& b : xe8_boids) { if (b.x20_active && tb->intersects(zeus::CAABox(weap->GetTranslation() - x170_weaponKillRadius, weap->GetTranslation() + x170_weaponKillRadius))) KillBoid(b); } } } } if (x250_29_repelFromThreats) { if (TCastToPtr player = other) { zeus::CVector3f playerPos = player->GetTranslation(); for (auto& b : xe8_boids) { zeus::CVector3f adjPlayerPos = playerPos; float zDelta = b.x0_pos.z() - adjPlayerPos.z(); if (zDelta > 0.f && zDelta < 2.3f) adjPlayerPos.z() = float(b.x0_pos.z()); adjPlayerPos.x() += mgr.GetActiveRandom()->Float() * 0.2f - 0.1f; adjPlayerPos.y() += mgr.GetActiveRandom()->Float() * 0.2f - 0.1f; ApplyRepulsion(b, adjPlayerPos, 8.f, x14c_playerRepelMagnitude - x164_playerRepelDamping); } } x250_30_enablePlayerRepelDamping = true; } } zeus::CAABox CFishCloud::GetUntransformedBoundingBox() const { zeus::CVector3f extent = x120_scale * 0.75f; return zeus::CAABox(-extent, extent); } zeus::CAABox CFishCloud::GetBoundingBox() const { return GetUntransformedBoundingBox().getTransformedAABox(x34_transform); } void CFishCloud::RemoveRepulsor(TUniqueId sourceId) { CModifierSource source(sourceId, true, false, 0.f, 0.f); auto it = rstl::binary_find(x108_modifierSources.begin(), x108_modifierSources.end(), source); if (it != x108_modifierSources.end()) x108_modifierSources.erase(it); } void CFishCloud::RemoveAttractor(TUniqueId sourceId) { CModifierSource source(sourceId, false, false, 0.f, 0.f); auto it = rstl::binary_find(x108_modifierSources.begin(), x108_modifierSources.end(), source); if (it != x108_modifierSources.end()) x108_modifierSources.erase(it); } bool CFishCloud::AddRepulsor(TUniqueId sourceId, bool swirl, float radius, float priority) { CModifierSource source(sourceId, true, swirl, radius, priority); auto it = rstl::binary_find(x108_modifierSources.begin(), x108_modifierSources.end(), source); if (it != x108_modifierSources.end()) { it->x4_radius = radius; it->x8_priority = priority; return true; } else if (x108_modifierSources.size() < x108_modifierSources.capacity()) { x108_modifierSources.insert(std::lower_bound( x108_modifierSources.begin(), x108_modifierSources.end(), source), source); return true; } return false; } bool CFishCloud::AddAttractor(TUniqueId sourceId, bool swirl, float radius, float priority) { CModifierSource source(sourceId, false, swirl, radius, priority); auto it = rstl::binary_find(x108_modifierSources.begin(), x108_modifierSources.end(), source); if (it != x108_modifierSources.end()) { it->x4_radius = radius; it->x8_priority = priority; return true; } else if (x108_modifierSources.size() < x108_modifierSources.capacity()) { x108_modifierSources.insert(std::lower_bound( x108_modifierSources.begin(), x108_modifierSources.end(), source), source); return true; } return false; } } // namespace urde