mirror of https://github.com/AxioDL/metaforce.git
1240 lines
47 KiB
C++
1240 lines
47 KiB
C++
#include "Runtime/World/CWallCrawlerSwarm.hpp"
|
|
|
|
#include <array>
|
|
|
|
#include "Runtime/CSimplePool.hpp"
|
|
#include "Runtime/CStateManager.hpp"
|
|
#include "Runtime/GameGlobalObjects.hpp"
|
|
#include "Runtime/Camera/CFirstPersonCamera.hpp"
|
|
#include "Runtime/Character/CSteeringBehaviors.hpp"
|
|
#include "Runtime/Collision/CGameCollision.hpp"
|
|
#include "Runtime/Collision/CMaterialList.hpp"
|
|
#include "Runtime/Collision/CMetroidAreaCollider.hpp"
|
|
#include "Runtime/Graphics/CBooRenderer.hpp"
|
|
#include "Runtime/Graphics/CSkinnedModel.hpp"
|
|
#include "Runtime/Graphics/CVertexMorphEffect.hpp"
|
|
#include "Runtime/Weapon/CGameProjectile.hpp"
|
|
#include "Runtime/World/CActorParameters.hpp"
|
|
#include "Runtime/World/CPhysicsActor.hpp"
|
|
#include "Runtime/World/CPlayer.hpp"
|
|
#include "Runtime/World/CScriptDoor.hpp"
|
|
#include "Runtime/World/CScriptWaypoint.hpp"
|
|
#include "Runtime/World/CWorld.hpp"
|
|
|
|
#include "TCastTo.hpp" // Generated file, do not modify include path
|
|
|
|
namespace urde {
|
|
|
|
static CMaterialList MakeMaterialList() {
|
|
return CMaterialList(EMaterialTypes::Scannable, EMaterialTypes::Trigger, EMaterialTypes::NonSolidDamageable,
|
|
EMaterialTypes::RadarObject);
|
|
}
|
|
|
|
CWallCrawlerSwarm::CWallCrawlerSwarm(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info,
|
|
const zeus::CVector3f& boundingBoxExtent, const zeus::CTransform& xf,
|
|
EFlavor flavor, const CAnimRes& animRes, s32 launchAnim, s32 attractAnim,
|
|
CAssetId part1, CAssetId part2, CAssetId part3, CAssetId part4,
|
|
const CDamageInfo& crabDamage, const CDamageInfo& scarabExplodeDamage,
|
|
float crabDamageCooldown, float boidRadius, float touchRadius,
|
|
float playerTouchRadius, u32 numBoids, u32 maxCreatedBoids,
|
|
float animPlaybackSpeed, float separationRadius, float cohesionMagnitude,
|
|
float alignmentWeight, float separationMagnitude, float moveToWaypointWeight,
|
|
float attractionMagnitude, float attractionRadius, float boidGenRate,
|
|
u32 maxLaunches, float scarabBoxMargin, float scarabScatterXYVelocity,
|
|
float scarabTimeToExplode, const CHealthInfo& hInfo,
|
|
const CDamageVulnerability& dVuln, s32 launchSfx,
|
|
s32 scatterSfx, const CActorParameters& aParams)
|
|
: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), MakeMaterialList(), aParams, kInvalidUniqueId)
|
|
, x118_boundingBoxExtent(boundingBoxExtent)
|
|
, x13c_separationRadius(separationRadius)
|
|
, x140_cohesionMagnitude(cohesionMagnitude)
|
|
, x144_alignmentWeight(alignmentWeight)
|
|
, x148_separationMagnitude(separationMagnitude)
|
|
, x14c_moveToWaypointWeight(moveToWaypointWeight)
|
|
, x150_attractionMagnitude(attractionMagnitude)
|
|
, x154_attractionRadius(attractionRadius)
|
|
, x158_scarabScatterXYVelocity(scarabScatterXYVelocity)
|
|
, x15c_scarabTimeToExplode(scarabTimeToExplode)
|
|
, x160_animPlaybackSpeed(animPlaybackSpeed)
|
|
, x364_boidGenRate(boidGenRate)
|
|
, x370_crabDamageCooldown(crabDamageCooldown)
|
|
, x374_boidRadius(boidRadius)
|
|
, x378_touchRadius(touchRadius)
|
|
, x37c_scarabBoxMargin(scarabBoxMargin)
|
|
, x380_playerTouchRadius(playerTouchRadius)
|
|
, x384_crabDamage(crabDamage)
|
|
, x3a0_scarabExplodeDamage(scarabExplodeDamage)
|
|
, x3bc_hInfo(hInfo)
|
|
, x3c4_dVuln(dVuln)
|
|
, x548_numBoids(numBoids)
|
|
, x54c_maxCreatedBoids(maxCreatedBoids)
|
|
, x554_maxLaunches(maxLaunches)
|
|
, x558_flavor(flavor) {
|
|
x168_partitionedBoidLists.resize(125);
|
|
x55c_launchSfx = CSfxManager::TranslateSFXID(launchSfx != -1 ? u16(launchSfx) : u16(0xffff));
|
|
x55e_scatterSfx = CSfxManager::TranslateSFXID(scatterSfx != -1 ? u16(scatterSfx) : u16(0xffff));
|
|
x560_24_enableLighting = true;
|
|
x560_25_useSoftwareLight = true;
|
|
x560_26_modelAssetDirty = false;
|
|
CAnimRes attractAnimRes(animRes);
|
|
attractAnimRes.SetCanLoop(true);
|
|
attractAnimRes.SetDefaultAnim(attractAnim != -1 ? attractAnim : 0);
|
|
CAnimRes launchAnimRes(animRes);
|
|
launchAnimRes.SetCanLoop(true);
|
|
launchAnimRes.SetDefaultAnim(launchAnim != -1 ? launchAnim : 0);
|
|
x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(animRes));
|
|
x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(animRes));
|
|
x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(animRes));
|
|
x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(animRes));
|
|
x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(attractAnimRes));
|
|
x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(attractAnimRes));
|
|
x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(attractAnimRes));
|
|
x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(attractAnimRes));
|
|
x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(launchAnimRes));
|
|
x4b0_modelDatas.emplace_back(std::make_unique<CModelData>(animRes));
|
|
if (aParams.GetXRayAssets().first.IsValid()) {
|
|
for (size_t i = 0; i < 9; ++i) {
|
|
x4b0_modelDatas[i]->SetXRayModel(aParams.GetXRayAssets());
|
|
}
|
|
x560_26_modelAssetDirty = true;
|
|
}
|
|
if (aParams.GetThermalAssets().first.IsValid()) {
|
|
for (size_t i = 0; i < 9; ++i) {
|
|
x4b0_modelDatas[i]->SetInfraModel(aParams.GetThermalAssets());
|
|
}
|
|
x560_26_modelAssetDirty = true;
|
|
}
|
|
if (part1.IsValid()) {
|
|
x4f0_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part1}));
|
|
}
|
|
if (part2.IsValid()) {
|
|
x4f0_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part2}));
|
|
}
|
|
if (part3.IsValid()) {
|
|
x4f0_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part3}));
|
|
}
|
|
if (part4.IsValid()) {
|
|
x4f0_particleDescs.push_back(g_SimplePool->GetObj({FOURCC('PART'), part4}));
|
|
}
|
|
for (const auto& t : x4f0_particleDescs) {
|
|
x524_particleGens.emplace_back(new CElementGen(t));
|
|
x524_particleGens.back()->SetParticleEmission(false);
|
|
}
|
|
}
|
|
|
|
void CWallCrawlerSwarm::Accept(IVisitor& visitor) { visitor.Visit(this); }
|
|
|
|
void CWallCrawlerSwarm::AllocateSkinnedModels(CStateManager& mgr, CModelData::EWhichModel which) {
|
|
//x430_.clear();
|
|
for (size_t i = 0; i < 9; ++i) {
|
|
//x430_.push_back(x4b0_[i]->PickAnimatedModel(which).Clone());
|
|
x4b0_modelDatas[i]->EnableLooping(true);
|
|
x4b0_modelDatas[i]->AdvanceAnimation(x4b0_modelDatas[i]->GetAnimationData()->GetAnimTimeRemaining("Whole Body"sv) *
|
|
(float(i) * 0.0625f),
|
|
mgr, x4_areaId, true);
|
|
}
|
|
//x430_.push_back(x4b0_.back()->PickAnimatedModel(which).Clone());
|
|
x4dc_whichModel = which;
|
|
}
|
|
|
|
void CWallCrawlerSwarm::AddDoorRepulsors(CStateManager& mgr) {
|
|
size_t doorCount = 0;
|
|
for (const CEntity* ent : mgr.GetPhysicsActorObjectList()) {
|
|
if (const TCastToConstPtr<CScriptDoor> door = ent) {
|
|
if (door->GetAreaIdAlways() == x4_areaId) {
|
|
++doorCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
x4e0_doorRepulsors.reserve(doorCount);
|
|
|
|
for (const CEntity* ent : mgr.GetPhysicsActorObjectList()) {
|
|
if (const TCastToConstPtr<CScriptDoor> door = ent) {
|
|
if (door->GetAreaIdAlways() == x4_areaId) {
|
|
if (const auto tb = door->GetTouchBounds()) {
|
|
x4e0_doorRepulsors.emplace_back(tb->center(), (tb->min - tb->max).magnitude() * 0.75f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWallCrawlerSwarm::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId sender, CStateManager& mgr) {
|
|
CActor::AcceptScriptMsg(msg, sender, mgr);
|
|
switch (msg) {
|
|
case EScriptObjectMessage::Registered:
|
|
x108_boids.reserve(size_t(x548_numBoids));
|
|
for (int i = 0; i < x548_numBoids; ++i) {
|
|
x108_boids.emplace_back(zeus::CTransform(), i);
|
|
}
|
|
AllocateSkinnedModels(mgr, CModelData::EWhichModel::Normal);
|
|
AddDoorRepulsors(mgr);
|
|
CreateShadow(false);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CWallCrawlerSwarm::UpdateParticles(float dt) {
|
|
for (auto& p : x524_particleGens) {
|
|
p->Update(dt);
|
|
}
|
|
}
|
|
|
|
int CWallCrawlerSwarm::SelectLockOnIdx(CStateManager& mgr) const {
|
|
const zeus::CTransform fpCamXf = mgr.GetCameraManager()->GetFirstPersonCamera()->GetTransform();
|
|
if (x42c_lockOnIdx != -1) {
|
|
const CBoid& b = x108_boids[x42c_lockOnIdx];
|
|
if (b.GetActive()) {
|
|
zeus::CVector3f dir = b.GetTranslation() - fpCamXf.origin;
|
|
const float mag = dir.magnitude();
|
|
dir = dir / mag;
|
|
if (fpCamXf.basis[1].dot(dir) > 0.92388f) {
|
|
if (mgr.RayStaticIntersection(fpCamXf.origin, dir, mag, CMaterialFilter::MakeInclude(EMaterialTypes::Solid))
|
|
.IsInvalid()) {
|
|
return x42c_lockOnIdx;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int ret = -1;
|
|
const float omtd = mgr.GetPlayer().GetOrbitMaxTargetDistance(mgr);
|
|
const float omtdSq = omtd * omtd;
|
|
float maxDot = 0.5f;
|
|
for (size_t i = 0; i < x108_boids.size(); ++i) {
|
|
const CBoid& b = x108_boids[i];
|
|
if (b.GetActive()) {
|
|
zeus::CVector3f delta = b.GetTranslation() - fpCamXf.origin;
|
|
if (delta.magSquared() > omtdSq) {
|
|
continue;
|
|
}
|
|
if (delta.canBeNormalized()) {
|
|
const float thisDot = fpCamXf.basis[1].dot(delta.normalized());
|
|
if (thisDot > maxDot) {
|
|
ret = static_cast<int>(i);
|
|
maxDot = thisDot;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
zeus::CAABox CWallCrawlerSwarm::GetBoundingBox() const {
|
|
const zeus::CVector3f he = x118_boundingBoxExtent * 0.75f;
|
|
return zeus::CAABox(-he, he).getTransformedAABox(x34_transform);
|
|
}
|
|
|
|
TUniqueId CWallCrawlerSwarm::GetWaypointForState(EScriptObjectState state, CStateManager& mgr) const {
|
|
for (const auto& c : GetConnectionList()) {
|
|
if (c.x0_state == state && c.x4_msg == EScriptObjectMessage::Follow) {
|
|
return mgr.GetIdForScript(c.x8_objId);
|
|
}
|
|
}
|
|
return kInvalidUniqueId;
|
|
}
|
|
|
|
bool CWallCrawlerSwarm::PointOnSurface(const CCollisionSurface& surf, const zeus::CVector3f& pos,
|
|
const zeus::CPlane& plane) const {
|
|
const zeus::CVector3f projPt = ProjectPointToPlane(pos, surf.GetVert(0), plane.normal());
|
|
for (int i = 0; i < 3; ++i) {
|
|
if (plane.normal().dot((projPt - surf.GetVert(i)).cross(surf.GetVert((i + 2) % 3) - surf.GetVert(i))) < 0.f) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CWallCrawlerSwarm::FindBestSurface(const CAreaCollisionCache& ccache, const zeus::CVector3f& pos, float radius,
|
|
CCollisionSurface& out) const {
|
|
bool ret = false;
|
|
const float radiusSq = radius * radius;
|
|
zeus::CSphere sphere(pos, radius);
|
|
for (const auto& c : ccache) {
|
|
for (const auto& n : c) {
|
|
if (CCollidableSphere::Sphere_AABox_Bool(sphere, n.GetBoundingBox())) {
|
|
const auto triList = n.GetTriangleArray();
|
|
for (int i = 0; i < triList.GetSize(); ++i) {
|
|
CCollisionSurface surf = n.GetOwner().GetMasterListTriangle(triList.GetAt(i));
|
|
const zeus::CPlane plane = surf.GetPlane();
|
|
const float distSq = std::fabs(plane.pointToPlaneDist(pos));
|
|
if (distSq < radiusSq && PointOnSurface(surf, pos, plane)) {
|
|
float dist = 0.f;
|
|
if (distSq != 0.f) {
|
|
dist = std::sqrt(distSq);
|
|
}
|
|
sphere.radius = dist;
|
|
out = surf;
|
|
ret = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
CCollisionSurface CWallCrawlerSwarm::FindBestCollisionInBox(CStateManager& mgr, const zeus::CVector3f& wpPos) const {
|
|
CCollisionSurface ret(zeus::skRight, zeus::skForward, zeus::skUp, 0xffffffff);
|
|
const zeus::CVector3f aabbExtents = GetBoundingBox().extents();
|
|
float f25 = 0.1f;
|
|
while (f25 < 1.f) {
|
|
const zeus::CVector3f scaledExtents = aabbExtents * f25;
|
|
CAreaCollisionCache ccache(zeus::CAABox(wpPos - scaledExtents, wpPos + scaledExtents));
|
|
CGameCollision::BuildAreaCollisionCache(mgr, ccache);
|
|
if (FindBestSurface(ccache, wpPos, 2.f * scaledExtents.magnitude(), ret)) {
|
|
return ret;
|
|
}
|
|
f25 += 0.1f;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static zeus::CTransform LookAt(const zeus::CUnitVector3f& a, const zeus::CUnitVector3f& b, const zeus::CRelAngle& ang) {
|
|
const float dot = a.dot(b);
|
|
if (zeus::close_enough(dot, 1.f)) {
|
|
return zeus::CTransform();
|
|
}
|
|
if (dot > -0.99981f) {
|
|
return zeus::CQuaternion::clampedRotateTo(a, b, ang).toTransform();
|
|
}
|
|
if (a != zeus::skRight && b != zeus::skRight) {
|
|
return zeus::CQuaternion::fromAxisAngle(a.cross(zeus::skRight), ang).toTransform();
|
|
}
|
|
return zeus::CQuaternion::fromAxisAngle(a.cross(zeus::skUp), ang).toTransform();
|
|
}
|
|
|
|
void CWallCrawlerSwarm::CreateBoid(CStateManager& mgr, int idx) {
|
|
//zeus::CAABox aabb = GetBoundingBox();
|
|
const TUniqueId wpId = GetWaypointForState(EScriptObjectState::Patrol, mgr);
|
|
if (TCastToConstPtr<CScriptWaypoint> wp = mgr.GetObjectById(wpId)) {
|
|
const CCollisionSurface surf = FindBestCollisionInBox(mgr, wp->GetTranslation());
|
|
x108_boids[idx].Transform() =
|
|
zeus::CTransform::Translate(ProjectPointToPlane(wp->GetTranslation(), surf.GetVert(0), surf.GetNormal()) +
|
|
surf.GetNormal() * x374_boidRadius);
|
|
if (zeus::close_enough(zeus::skUp.dot(surf.GetNormal()), -1.f)) {
|
|
x108_boids[idx].Transform().setRotation(
|
|
zeus::CTransform(zeus::skRight, zeus::skBack, zeus::skDown, zeus::skZero3f));
|
|
} else {
|
|
x108_boids[idx].Transform().setRotation(LookAt(zeus::skUp, surf.GetNormal(), M_PIF));
|
|
}
|
|
x108_boids[idx].x80_24_active = true;
|
|
x108_boids[idx].x30_velocity = zeus::skZero3f;
|
|
x108_boids[idx].x3c_targetWaypoint = wpId;
|
|
x108_boids[idx].x7c_framesNotOnSurface = 0;
|
|
x108_boids[idx].x48_timeToDie = 0.f;
|
|
x108_boids[idx].x80_27_scarabExplodeTimerEnabled = false;
|
|
x108_boids[idx].x78_health = x3bc_hInfo.GetHP();
|
|
}
|
|
}
|
|
|
|
void CWallCrawlerSwarm::ExplodeBoid(CBoid& boid, CStateManager& mgr) {
|
|
KillBoid(boid, mgr, 0.f, 1.f);
|
|
mgr.ApplyDamageToWorld(GetUniqueId(), *this, boid.GetTranslation(), x3a0_scarabExplodeDamage,
|
|
CMaterialFilter::MakeInclude({EMaterialTypes::Player}));
|
|
}
|
|
|
|
void CWallCrawlerSwarm::SetExplodeTimers(const zeus::CVector3f& pos, float radius, float minTime, float maxTime) {
|
|
const float radiusSq = radius * radius;
|
|
const float range = maxTime - minTime;
|
|
|
|
for (auto& b : x108_boids) {
|
|
if (b.GetActive() && b.x48_timeToDie <= 0.f) {
|
|
const float dist = (b.GetTranslation() - pos).magSquared();
|
|
if (dist < radiusSq) {
|
|
const float fac = dist / radiusSq * range + minTime;
|
|
if (b.x4c_timeToExplode > fac || b.x4c_timeToExplode == 0.f) {
|
|
b.x4c_timeToExplode = fac;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CWallCrawlerSwarm::CBoid* CWallCrawlerSwarm::GetListAt(const zeus::CVector3f& pos) {
|
|
const zeus::CAABox aabb = GetBoundingBox();
|
|
const zeus::CVector3f ints = (pos - aabb.min) / ((aabb.max - aabb.min) / 5.f);
|
|
const int idx = int(ints.x()) + int(ints.y()) * 5 + int(ints.z()) * 25;
|
|
if (idx < 0 || idx >= 125) {
|
|
return x360_outlierBoidList;
|
|
}
|
|
return x168_partitionedBoidLists[idx];
|
|
}
|
|
|
|
void CWallCrawlerSwarm::BuildBoidNearList(const CBoid& boid, float radius,
|
|
rstl::reserved_vector<CBoid*, 50>& nearList) {
|
|
CBoid* b = GetListAt(boid.GetTranslation());
|
|
while (b && nearList.size() < 50) {
|
|
const float distSq = (b->GetTranslation() - boid.GetTranslation()).magSquared();
|
|
if (distSq != 0.f && distSq < radius) {
|
|
nearList.push_back(b);
|
|
}
|
|
b = b->x44_next;
|
|
}
|
|
}
|
|
|
|
void CWallCrawlerSwarm::ApplySeparation(const CBoid& boid, const rstl::reserved_vector<CBoid*, 50>& nearList,
|
|
zeus::CVector3f& aheadVec) const {
|
|
if (nearList.empty()) {
|
|
return;
|
|
}
|
|
|
|
float minDist = FLT_MAX;
|
|
zeus::CVector3f pos;
|
|
for (const CBoid* b : nearList) {
|
|
const float dist = (boid.GetTranslation() - b->GetTranslation()).magSquared();
|
|
if (dist != 0.f && dist < minDist) {
|
|
minDist = dist;
|
|
pos = b->GetTranslation();
|
|
}
|
|
}
|
|
|
|
ApplySeparation(boid, pos, x13c_separationRadius, x148_separationMagnitude, aheadVec);
|
|
}
|
|
|
|
void CWallCrawlerSwarm::ApplySeparation(const CBoid& boid, const zeus::CVector3f& separateFrom, float separationRadius,
|
|
float separationMagnitude, zeus::CVector3f& aheadVec) const {
|
|
const zeus::CVector3f delta = boid.GetTranslation() - separateFrom;
|
|
if (!delta.canBeNormalized()) {
|
|
return;
|
|
}
|
|
|
|
const float deltaDistSq = delta.magSquared();
|
|
const float capDeltaDistSq = separationRadius * separationRadius;
|
|
if (deltaDistSq < capDeltaDistSq) {
|
|
aheadVec += (1.f - deltaDistSq / capDeltaDistSq) * delta.normalized() * separationMagnitude;
|
|
}
|
|
}
|
|
|
|
void CWallCrawlerSwarm::ScatterScarabBoid(CBoid& boid, CStateManager& mgr) const {
|
|
const zeus::CVector3f oldDir = boid.GetTransform().basis[1];
|
|
boid.Transform().setRotation(zeus::CTransform());
|
|
boid.Transform() = LookAt(boid.GetTransform().basis[1], oldDir, M_PIF).multiplyIgnoreTranslation(boid.GetTransform());
|
|
boid.x30_velocity = zeus::skZero3f;
|
|
const float angle = mgr.GetActiveRandom()->Float() * (2.f * M_PIF);
|
|
const float mag = mgr.GetActiveRandom()->Float() * x158_scarabScatterXYVelocity;
|
|
boid.x30_velocity.x() = mag * std::cos(angle);
|
|
boid.x30_velocity.y() = mag * std::sin(angle);
|
|
boid.x80_26_launched = true;
|
|
boid.x7c_remainingLaunchNotOnSurfaceFrames = 5;
|
|
CSfxManager::AddEmitter(x55c_launchSfx, boid.GetTranslation(), zeus::skZero3f, true, false, 0x7f, x4_areaId);
|
|
}
|
|
|
|
void CWallCrawlerSwarm::MoveToWayPoint(CBoid& boid, CStateManager& mgr, zeus::CVector3f& aheadVec) const {
|
|
if (const TCastToConstPtr<CScriptWaypoint> wp = mgr.ObjectById(boid.x3c_targetWaypoint)) {
|
|
const CScriptWaypoint* useWp = wp.GetPtr();
|
|
if ((useWp->GetTranslation() - boid.GetTranslation()).magSquared() <
|
|
x164_waypointGoalRadius * x164_waypointGoalRadius) {
|
|
boid.x3c_targetWaypoint = useWp->NextWaypoint(mgr);
|
|
if (boid.x3c_targetWaypoint == kInvalidUniqueId) {
|
|
if (x558_flavor == EFlavor::Scarab) {
|
|
ScatterScarabBoid(boid, mgr);
|
|
} else {
|
|
boid.x80_24_active = false;
|
|
return;
|
|
}
|
|
} else {
|
|
useWp = TCastToConstPtr<CScriptWaypoint>(mgr.ObjectById(boid.x3c_targetWaypoint)).GetPtr();
|
|
}
|
|
}
|
|
aheadVec += (useWp->GetTranslation() - boid.GetTranslation()).normalized() * x14c_moveToWaypointWeight;
|
|
}
|
|
}
|
|
|
|
void CWallCrawlerSwarm::ApplyCohesion(const CBoid& boid, const rstl::reserved_vector<CBoid*, 50>& nearList,
|
|
zeus::CVector3f& aheadVec) const {
|
|
if (nearList.empty()) {
|
|
return;
|
|
}
|
|
|
|
zeus::CVector3f avg;
|
|
for (const CBoid* b : nearList) {
|
|
avg += b->GetTranslation();
|
|
}
|
|
|
|
avg = avg / float(nearList.size());
|
|
ApplyCohesion(boid, avg, x13c_separationRadius, x140_cohesionMagnitude, aheadVec);
|
|
}
|
|
|
|
void CWallCrawlerSwarm::ApplyCohesion(const CBoid& boid, const zeus::CVector3f& cohesionFrom, float cohesionRadius,
|
|
float cohesionMagnitude, zeus::CVector3f& aheadVec) const {
|
|
const zeus::CVector3f delta = cohesionFrom - boid.GetTranslation();
|
|
if (!delta.canBeNormalized()) {
|
|
return;
|
|
}
|
|
|
|
const float distSq = delta.magSquared();
|
|
const float capDistSq = cohesionRadius * cohesionRadius;
|
|
aheadVec += ((distSq > capDistSq) ? 1.f : distSq / capDistSq) * delta.normalized() * cohesionMagnitude;
|
|
}
|
|
|
|
void CWallCrawlerSwarm::ApplyAlignment(const CBoid& boid, const rstl::reserved_vector<CBoid*, 50>& nearList,
|
|
zeus::CVector3f& aheadVec) const {
|
|
if (nearList.empty()) {
|
|
return;
|
|
}
|
|
|
|
zeus::CVector3f avg;
|
|
for (const CBoid* b : nearList) {
|
|
avg += b->GetTransform().basis[1];
|
|
}
|
|
|
|
avg = avg / float(nearList.size());
|
|
aheadVec += zeus::CVector3f::getAngleDiff(boid.GetTransform().basis[1], avg) / M_PIF * (avg * x144_alignmentWeight);
|
|
}
|
|
|
|
void CWallCrawlerSwarm::ApplyAttraction(const CBoid& boid, const zeus::CVector3f& attractTo, float attractionRadius,
|
|
float attractionMagnitude, zeus::CVector3f& aheadVec) const {
|
|
const zeus::CVector3f delta = attractTo - boid.GetTranslation();
|
|
if (!delta.canBeNormalized()) {
|
|
return;
|
|
}
|
|
|
|
const float distSq = delta.magSquared();
|
|
const float capDistSq = attractionRadius * attractionRadius;
|
|
aheadVec += ((distSq > capDistSq) ? 0.f : (1.f - distSq / capDistSq)) * delta.normalized() * attractionMagnitude;
|
|
}
|
|
|
|
void CWallCrawlerSwarm::UpdateBoid(const CAreaCollisionCache& ccache, CStateManager& mgr, float dt, CBoid& boid) {
|
|
if (boid.x80_27_scarabExplodeTimerEnabled) {
|
|
if (x558_flavor == EFlavor::Scarab && boid.x4c_timeToExplode > 0.f) {
|
|
boid.x4c_timeToExplode -= 2.f * dt;
|
|
if (boid.x4c_timeToExplode <= 0.f) {
|
|
ExplodeBoid(boid, mgr);
|
|
}
|
|
}
|
|
} else if (boid.x80_26_launched) {
|
|
const float radius = 2.f * x374_boidRadius;
|
|
const float boidMag = boid.x30_velocity.magnitude();
|
|
float f20 = boidMag * dt;
|
|
const zeus::CVector3f f25 = (-boid.x30_velocity / boidMag) * x374_boidRadius;
|
|
zeus::CVector3f f28 = boid.GetTranslation();
|
|
bool found = false;
|
|
while (f20 >= 0.f && !found) {
|
|
CCollisionSurface surf(zeus::skRight, zeus::skForward, zeus::skUp, 0xffffffff);
|
|
if (FindBestSurface(ccache, boid.x30_velocity * dt * 1.5f + f28, radius, surf) &&
|
|
boid.x7c_remainingLaunchNotOnSurfaceFrames == 0) {
|
|
if (x558_flavor != EFlavor::Scarab) {
|
|
boid.Transform() = LookAt(boid.GetTransform().basis[2], surf.GetNormal(), M_PIF)
|
|
.multiplyIgnoreTranslation(boid.GetTransform());
|
|
}
|
|
const auto plane = surf.GetPlane();
|
|
boid.Translation() +=
|
|
-(plane.pointToPlaneDist(boid.GetTranslation()) - x374_boidRadius - 0.01f) * plane.normal();
|
|
boid.x7c_framesNotOnSurface = 0;
|
|
boid.x80_26_launched = false;
|
|
if (x558_flavor == EFlavor::Scarab) {
|
|
boid.x80_27_scarabExplodeTimerEnabled = true;
|
|
boid.x4c_timeToExplode = x15c_scarabTimeToExplode;
|
|
CSfxManager::AddEmitter(x55e_scatterSfx, boid.GetTranslation(), zeus::skZero3f, true, false, 0x7f, x4_areaId);
|
|
}
|
|
found = true;
|
|
}
|
|
f20 -= x374_boidRadius;
|
|
f28 += f25;
|
|
}
|
|
if (!found) {
|
|
boid.x30_velocity += zeus::CVector3f(0.f, 0.f,
|
|
-(x558_flavor == EFlavor::Scarab ? 3.f * CPhysicsActor::GravityConstant()
|
|
: CPhysicsActor::GravityConstant())) *
|
|
dt;
|
|
if (boid.x7c_remainingLaunchNotOnSurfaceFrames) {
|
|
boid.x7c_remainingLaunchNotOnSurfaceFrames -= 1;
|
|
}
|
|
}
|
|
} else if (boid.x7c_framesNotOnSurface >= 30) {
|
|
boid.x80_24_active = false;
|
|
} else {
|
|
const float radius = 2.f * x374_boidRadius;
|
|
bool found = false;
|
|
CCollisionSurface surf(zeus::skRight, zeus::skForward, zeus::skUp, 0xffffffff);
|
|
if (FindBestSurface(ccache, boid.GetTranslation() + boid.x30_velocity * dt * 1.5f, radius, surf)) {
|
|
boid.x50_surface = surf;
|
|
boid.Transform() = LookAt(boid.GetTransform().basis[2], surf.GetNormal(), zeus::degToRad(180.f * dt))
|
|
.multiplyIgnoreTranslation(boid.GetTransform());
|
|
const auto plane = surf.GetPlane();
|
|
const float dist = plane.pointToPlaneDist(boid.GetTranslation());
|
|
if (dist <= 1.5f * x374_boidRadius) {
|
|
boid.Translation() += -(dist - x374_boidRadius - 0.01f) * plane.normal();
|
|
boid.x7c_framesNotOnSurface = 0;
|
|
found = true;
|
|
}
|
|
}
|
|
if (!found) {
|
|
boid.Transform() = LookAt(boid.GetTransform().basis[2], boid.GetTransform().basis[1],
|
|
boid.x30_velocity.magnitude() / x374_boidRadius * dt)
|
|
.multiplyIgnoreTranslation(boid.GetTransform());
|
|
boid.x7c_framesNotOnSurface += 1;
|
|
}
|
|
rstl::reserved_vector<CBoid*, 50> nearList;
|
|
BuildBoidNearList(boid, x13c_separationRadius, nearList);
|
|
zeus::CVector3f aheadVec = boid.GetTransform().basis[1] * 0.3f;
|
|
for (int r26 = 0; r26 < 8; ++r26) {
|
|
switch (r26) {
|
|
case 0:
|
|
for (const auto& rep : x4e0_doorRepulsors) {
|
|
if ((rep.x0_center - boid.GetTranslation()).magSquared() < rep.xc_mag * rep.xc_mag) {
|
|
ApplySeparation(boid, rep.x0_center, rep.xc_mag, 4.5f, aheadVec);
|
|
}
|
|
}
|
|
break;
|
|
case 4:
|
|
ApplySeparation(boid, nearList, aheadVec);
|
|
break;
|
|
case 5:
|
|
MoveToWayPoint(boid, mgr, aheadVec);
|
|
break;
|
|
case 6:
|
|
ApplyCohesion(boid, nearList, aheadVec);
|
|
break;
|
|
case 7:
|
|
ApplyAlignment(boid, nearList, aheadVec);
|
|
break;
|
|
case 3:
|
|
ApplyAttraction(boid, mgr.GetPlayer().GetTranslation(), x154_attractionRadius,
|
|
x150_attractionMagnitude, aheadVec);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (aheadVec.magSquared() >= 9.f) {
|
|
break;
|
|
}
|
|
}
|
|
boid.Transform() = LookAt(boid.GetTransform().basis[1],
|
|
ProjectVectorToPlane(aheadVec, boid.GetTransform().basis[2]).normalized(), M_PIF * dt)
|
|
.multiplyIgnoreTranslation(boid.GetTransform());
|
|
}
|
|
}
|
|
|
|
void CWallCrawlerSwarm::LaunchBoid(CBoid& boid, const zeus::CVector3f& dir) {
|
|
const zeus::CVector3f pos = boid.GetTranslation();
|
|
static float skAttackTime = std::sqrt(2.5f / CPhysicsActor::GravityConstant()) * 2.f;
|
|
static float skAttackVelocity = 15.f / skAttackTime;
|
|
zeus::CVector3f deltaFlat = dir - pos;
|
|
const float deltaZ = deltaFlat.z();
|
|
deltaFlat.z() = 0.f;
|
|
const float deltaMag = deltaFlat.magnitude();
|
|
boid.Transform().setRotation(zeus::CTransform());
|
|
boid.Transform() = LookAt(boid.GetTransform().basis[1], deltaFlat.normalized(), M_PIF)
|
|
.multiplyIgnoreTranslation(boid.GetTransform());
|
|
zeus::CVector3f vec(skAttackVelocity * boid.GetTransform().basis[1].toVec2f(), 0.5f * skAttackVelocity);
|
|
if (deltaMag > FLT_EPSILON) {
|
|
deltaFlat = deltaFlat / deltaMag;
|
|
const float dot = deltaFlat.dot(vec);
|
|
if (dot > FLT_EPSILON) {
|
|
const bool r29 = deltaZ < 0.f;
|
|
float _12c, _130;
|
|
float f25 = 0.f;
|
|
if (CSteeringBehaviors::SolveQuadratic(-CPhysicsActor::GravityConstant(), vec.z(), -deltaZ, _12c, _130)) {
|
|
f25 = r29 ? _130 : _12c;
|
|
}
|
|
if (!r29) {
|
|
f25 += deltaMag / dot;
|
|
}
|
|
if (f25 < 10.f) {
|
|
vec.x() = deltaMag / f25 * deltaFlat.x() * 0.6f;
|
|
vec.y() = deltaMag / f25 * deltaFlat.y() * 0.6f;
|
|
vec.z() = deltaZ / f25 - 0.5f * -CPhysicsActor::GravityConstant() * f25;
|
|
}
|
|
}
|
|
}
|
|
boid.x30_velocity = vec;
|
|
boid.x80_26_launched = true;
|
|
boid.x7c_remainingLaunchNotOnSurfaceFrames = 1;
|
|
CSfxManager::AddEmitter(x55c_launchSfx, pos, zeus::skZero3f, true, false, 0x7f, x4_areaId);
|
|
}
|
|
|
|
void CWallCrawlerSwarm::AddParticle(const zeus::CTransform& xf) {
|
|
static constexpr std::array particleCounts{8, 2, 0, 0};
|
|
|
|
size_t i = 0;
|
|
for (auto& p : x524_particleGens) {
|
|
p->SetParticleEmission(true);
|
|
p->SetTranslation(xf.origin);
|
|
p->ForceParticleCreation(particleCounts[i]);
|
|
p->SetParticleEmission(false);
|
|
++i;
|
|
}
|
|
}
|
|
|
|
void CWallCrawlerSwarm::KillBoid(CBoid& boid, CStateManager& mgr, float deathRattleChance, float deadChance) {
|
|
x130_lastKilledOffset = boid.GetTranslation();
|
|
AddParticle(boid.GetTransform());
|
|
boid.x80_24_active = false;
|
|
|
|
const float sendDeadRoll = mgr.GetActiveRandom()->Float();
|
|
const float sendDeathRattleRoll = mgr.GetActiveRandom()->Float();
|
|
if (sendDeathRattleRoll < deathRattleChance) {
|
|
SendScriptMsgs(EScriptObjectState::DeathRattle, mgr, EScriptObjectMessage::None);
|
|
}
|
|
if (sendDeadRoll < deadChance) {
|
|
SendScriptMsgs(EScriptObjectState::Dead, mgr, EScriptObjectMessage::None);
|
|
}
|
|
}
|
|
|
|
void CWallCrawlerSwarm::UpdatePartition() {
|
|
x168_partitionedBoidLists.clear();
|
|
x168_partitionedBoidLists.resize(125);
|
|
x360_outlierBoidList = nullptr;
|
|
|
|
const zeus::CAABox aabb = GetBoundingBox();
|
|
const zeus::CVector3f vec = (aabb.max - aabb.min) / 5.f;
|
|
for (auto& b : x108_boids) {
|
|
if (b.GetActive()) {
|
|
const zeus::CVector3f divVec = (b.GetTranslation() - aabb.min) / vec;
|
|
const int xIdx = int(divVec.x());
|
|
const int yIdx = int(divVec.y());
|
|
const int zIdx = int(divVec.z());
|
|
const int idx = xIdx + yIdx * 5 + zIdx * 25;
|
|
if (idx < 0 || idx >= 125 || xIdx < 0 || xIdx >= 5 || yIdx < 0 || yIdx >= 5 || zIdx < 0 || zIdx >= 5) {
|
|
b.x44_next = x360_outlierBoidList;
|
|
x360_outlierBoidList = &b;
|
|
} else {
|
|
b.x44_next = x168_partitionedBoidLists[idx];
|
|
x168_partitionedBoidLists[idx] = &b;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
zeus::CVector3f CWallCrawlerSwarm::FindClosestCell(const zeus::CVector3f& pos) const {
|
|
float minDist = FLT_MAX;
|
|
zeus::CVector3f ret;
|
|
for (int r28 = 0; r28 < 5; ++r28) {
|
|
for (int r29 = 0; r29 < 5; ++r29) {
|
|
for (int r25 = 0; r25 < 5; ++r25) {
|
|
const zeus::CAABox aabb = BoxForPosition(r28, r29, r25, 0.1f);
|
|
const float dist = (aabb.center() - pos).magSquared();
|
|
if (dist < minDist) {
|
|
minDist = dist;
|
|
ret = aabb.center();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void CWallCrawlerSwarm::UpdateEffects(CStateManager& mgr, CAnimData& aData, int vol) {
|
|
if (aData.GetPassedSoundPOICount() == 0 || CAnimData::g_SoundPOINodes.empty()) {
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < aData.GetPassedSoundPOICount(); ++i) {
|
|
const CSoundPOINode& n = CAnimData::g_SoundPOINodes[i];
|
|
if (n.GetPoiType() != EPOIType::Sound ||
|
|
(n.GetCharacterIndex() != -1 && n.GetCharacterIndex() != aData.GetCharacterIndex())) {
|
|
continue;
|
|
}
|
|
|
|
const u16 sfx = CSfxManager::TranslateSFXID(u16(n.GetSfxId() & 0xffff));
|
|
const bool loop = bool(n.GetSfxId() >> 31);
|
|
if (loop) {
|
|
continue;
|
|
}
|
|
|
|
CAudioSys::C3DEmitterParmData parmData;
|
|
parmData.x0_pos = FindClosestCell(mgr.GetPlayer().GetTranslation());
|
|
static float maxDist = n.GetMaxDist();
|
|
static float falloff = n.GetFalloff();
|
|
parmData.x18_maxDist = maxDist;
|
|
parmData.x1c_distComp = falloff;
|
|
parmData.x20_flags = 0x1;
|
|
parmData.x24_sfxId = sfx;
|
|
parmData.x26_maxVol = zeus::clamp(0, vol, 127) / 127.f;
|
|
parmData.x27_minVol = 20.f / 127.f;
|
|
parmData.x28_important = false;
|
|
parmData.x29_prio = 0x7f;
|
|
CSfxManager::AddEmitter(parmData, true, 0x7f, false, x4_areaId);
|
|
}
|
|
}
|
|
|
|
zeus::CAABox CWallCrawlerSwarm::BoxForPosition(int x, int y, int z, float f) const {
|
|
const zeus::CAABox aabb = GetBoundingBox();
|
|
const zeus::CVector3f vec = (aabb.max - aabb.min) / 5.f;
|
|
return zeus::CAABox(zeus::CVector3f(x, y, z) * vec + aabb.min - f,
|
|
zeus::CVector3f(x + 1, y + 1, z + 1) * vec + aabb.min + f);
|
|
}
|
|
|
|
void CWallCrawlerSwarm::Think(float dt, CStateManager& mgr) {
|
|
if (!GetActive()) {
|
|
return;
|
|
}
|
|
|
|
if (x560_26_modelAssetDirty && CModelData::GetRenderingModel(mgr) != x4dc_whichModel) {
|
|
const auto which = CModelData::GetRenderingModel(mgr);
|
|
if (which != x4dc_whichModel) {
|
|
AllocateSkinnedModels(mgr, which);
|
|
}
|
|
}
|
|
|
|
xe4_27_notInSortedLists = true;
|
|
x368_boidGenCooldownTimer -= dt;
|
|
x36c_crabDamageCooldownTimer -= dt;
|
|
++x100_thinkCounter;
|
|
const CGameArea* area = mgr.GetWorld()->GetAreaAlways(x4_areaId);
|
|
const auto occState =
|
|
area->IsPostConstructed() ? area->GetOcclusionState() : CGameArea::EOcclusionState::Occluded;
|
|
if (occState != CGameArea::EOcclusionState::Visible) {
|
|
if (x104_occludedTimer > 0.f) {
|
|
x104_occludedTimer -= dt;
|
|
}
|
|
if (x104_occludedTimer <= 0.f) {
|
|
return;
|
|
}
|
|
if (x100_thinkCounter & 0x2) {
|
|
return;
|
|
}
|
|
} else {
|
|
x104_occludedTimer = 7.f;
|
|
}
|
|
|
|
UpdateParticles(dt);
|
|
x42c_lockOnIdx = SelectLockOnIdx(mgr);
|
|
xe7_31_targetable = x42c_lockOnIdx != -1;
|
|
|
|
if (x42c_lockOnIdx == -1) {
|
|
RemoveMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);
|
|
} else {
|
|
AddMaterial(EMaterialTypes::Target, EMaterialTypes::Orbit, mgr);
|
|
}
|
|
|
|
while ((x54c_maxCreatedBoids == 0 || x550_createdBoids < x54c_maxCreatedBoids) &&
|
|
x368_boidGenCooldownTimer <= 0.f) {
|
|
int idx = 0;
|
|
bool madeBoid = false;
|
|
for (const auto& b : x108_boids) {
|
|
if (!b.GetActive()) {
|
|
CreateBoid(mgr, idx);
|
|
x550_createdBoids += 1;
|
|
x368_boidGenCooldownTimer += 1.f / x364_boidGenRate;
|
|
madeBoid = true;
|
|
break;
|
|
}
|
|
++idx;
|
|
}
|
|
if (!madeBoid) {
|
|
x368_boidGenCooldownTimer += 1.f / x364_boidGenRate;
|
|
break;
|
|
}
|
|
}
|
|
UpdatePartition();
|
|
xe8_aabox = GetBoundingBox();
|
|
|
|
int r21 = 0;
|
|
for (int r26 = 0; r26 < 5; ++r26) {
|
|
for (int r27 = 0; r27 < 5; ++r27) {
|
|
for (int r20 = 0; r20 < 5; ++r20) {
|
|
const int idx = r20 * 25 + r27 * 5 + r26;
|
|
if (CBoid* boid = x168_partitionedBoidLists[idx]) {
|
|
const zeus::CAABox aabb = BoxForPosition(r26, r27, r20, x374_boidRadius + 0.5f);
|
|
CAreaCollisionCache ccache(aabb);
|
|
CGameCollision::BuildAreaCollisionCache(mgr, ccache);
|
|
while (boid) {
|
|
r21 += 1;
|
|
if (boid->GetActive()) {
|
|
if (x558_flavor == EFlavor::Scarab) {
|
|
xe8_aabox.accumulateBounds(boid->GetTranslation() + x37c_scarabBoxMargin);
|
|
xe8_aabox.accumulateBounds(boid->GetTranslation() - x37c_scarabBoxMargin);
|
|
} else {
|
|
xe8_aabox.accumulateBounds(boid->GetTranslation());
|
|
}
|
|
}
|
|
if (((x100_thinkCounter & 0x1) == (r21 & 0x1) && boid->GetActive() && boid->x48_timeToDie < 0.1f) ||
|
|
boid->x80_26_launched) {
|
|
UpdateBoid(ccache, mgr, dt, *boid);
|
|
}
|
|
boid = boid->x44_next;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (CBoid* boid = x360_outlierBoidList; boid; boid = boid->x44_next) {
|
|
r21 += 1;
|
|
if (boid->GetActive()) {
|
|
xe8_aabox.accumulateBounds(boid->GetTranslation());
|
|
}
|
|
if (((x100_thinkCounter & 0x1) == (r21 & 0x1) && boid->GetActive() && boid->x48_timeToDie < 0.1f) ||
|
|
boid->x80_26_launched) {
|
|
const float margin = 1.5f + x374_boidRadius + 0.5f;
|
|
const zeus::CAABox aabb(boid->GetTranslation() - margin, boid->GetTranslation() + margin);
|
|
CAreaCollisionCache ccache(aabb);
|
|
CGameCollision::BuildAreaCollisionCache(mgr, ccache);
|
|
UpdateBoid(ccache, mgr, dt, *boid);
|
|
}
|
|
}
|
|
|
|
x4b0_modelDatas[8]->GetAnimationData()->SetPlaybackRate(x160_animPlaybackSpeed);
|
|
x4b0_modelDatas[8]->AdvanceAnimation(dt, mgr, x4_areaId, true);
|
|
|
|
SAdvancementDeltas deltas1, deltas2;
|
|
|
|
int r9 = 0;
|
|
int r3 = 0;
|
|
int r8 = 0;
|
|
std::array<bool, 4> _38F8{};
|
|
std::array<bool, 4> _38F4{};
|
|
for (const auto& b : x108_boids) {
|
|
if (b.GetActive() && !b.x80_26_launched) {
|
|
if (b.x80_27_scarabExplodeTimerEnabled || b.x80_28_nearPlayer) {
|
|
_38F8[r9 & 0x3] = true;
|
|
++r3;
|
|
} else {
|
|
_38F4[r9 & 0x3] = true;
|
|
++r8;
|
|
}
|
|
}
|
|
++r9;
|
|
}
|
|
|
|
for (size_t i = 0; i < _38F4.size(); ++i) {
|
|
x4b0_modelDatas[i]->GetAnimationData()->SetPlaybackRate(x160_animPlaybackSpeed);
|
|
deltas1 = x4b0_modelDatas[i]->AdvanceAnimation(dt, mgr, x4_areaId, true);
|
|
x4b0_modelDatas[i + 4]->GetAnimationData()->SetPlaybackRate(x160_animPlaybackSpeed);
|
|
deltas2 = x4b0_modelDatas[i + 4]->AdvanceAnimation(dt, mgr, x4_areaId, true);
|
|
if (x4b0_modelDatas[i]->HasAnimData() && _38F4[i]) {
|
|
UpdateEffects(mgr, *x4b0_modelDatas[i]->GetAnimationData(), r8 * 44 / x548_numBoids + 0x53);
|
|
}
|
|
if (x4b0_modelDatas[i + 4]->HasAnimData() && _38F8[i]) {
|
|
UpdateEffects(mgr, *x4b0_modelDatas[i + 4]->GetAnimationData(), r3 * 44 / x548_numBoids + 0x53);
|
|
}
|
|
for (size_t r20 = i; r20 < x108_boids.size(); r20 += 4) {
|
|
CBoid& b = x108_boids[r20];
|
|
if (b.GetActive()) {
|
|
if (b.x80_26_launched) {
|
|
b.Translation() += b.x30_velocity * dt;
|
|
} else if (b.x48_timeToDie > 0.f) {
|
|
b.x48_timeToDie -= dt;
|
|
if (b.x48_timeToDie < 0.7f * mgr.GetActiveRandom()->Float()) {
|
|
KillBoid(b, mgr, 1.f, 0.05f);
|
|
}
|
|
} else if (b.x80_27_scarabExplodeTimerEnabled || b.x80_28_nearPlayer) {
|
|
b.x30_velocity = b.GetTransform().rotate(deltas2.x0_posDelta) * 1.5f / dt;
|
|
b.Translation() += b.x30_velocity * dt;
|
|
} else {
|
|
b.x30_velocity = b.GetTransform().rotate(deltas1.x0_posDelta) * 1.5f / dt;
|
|
b.Translation() += b.x30_velocity * dt;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (x558_flavor == EFlavor::Crab) {
|
|
const zeus::CVector3f playerPos = mgr.GetPlayer().GetTranslation();
|
|
for (auto& b : x108_boids) {
|
|
if (b.GetActive() && zeus::close_enough(b.x48_timeToDie, 0.f) && !b.x80_26_launched) {
|
|
b.x80_28_nearPlayer = (playerPos - b.GetTranslation()).magnitude() < x154_attractionRadius;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (x558_flavor == EFlavor::Parasite && x554_maxLaunches > 0) {
|
|
const zeus::CVector3f _383c = mgr.GetPlayer().GetTranslation() + zeus::skUp;
|
|
static constexpr auto filter = CMaterialFilter::MakeInclude(EMaterialTypes::Solid);
|
|
int numLaunched = 0;
|
|
for (const auto& b : x108_boids) {
|
|
if (b.GetActive() && b.x80_26_launched) {
|
|
++numLaunched;
|
|
}
|
|
}
|
|
|
|
for (auto it = x108_boids.begin(); it != x108_boids.end() && numLaunched < x554_maxLaunches; ++it) {
|
|
CBoid& b = *it;
|
|
if (b.GetActive() && zeus::close_enough(b.x48_timeToDie, 0.f) && !b.x80_26_launched &&
|
|
(b.GetTranslation() - _383c).magSquared() < 18.f * 18.f && mgr.GetActiveRandom()->Float() <= 0.02f) {
|
|
zeus::CVector3f dir = _383c - b.GetTranslation();
|
|
const float mag = dir.magnitude();
|
|
dir = dir / mag;
|
|
if (mgr.RayStaticIntersection(b.GetTranslation(), dir, mag, filter).IsInvalid()) {
|
|
LaunchBoid(b, _383c);
|
|
++numLaunched;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWallCrawlerSwarm::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) {
|
|
for (size_t i = 0; i < 5; ++i) {
|
|
x4b0_modelDatas[i]->GetAnimationData()->PreRender();
|
|
}
|
|
bool activeBoid = false;
|
|
for (auto& b : x108_boids) {
|
|
if (b.GetActive()) {
|
|
b.x80_25_inFrustum = frustum.sphereFrustumTest(zeus::CSphere(b.GetTranslation(), 2.f * x374_boidRadius));
|
|
activeBoid = true;
|
|
} else {
|
|
b.x80_25_inFrustum = false;
|
|
}
|
|
}
|
|
xe4_30_outOfFrustum = !activeBoid;
|
|
}
|
|
|
|
void CWallCrawlerSwarm::RenderParticles() const {
|
|
for (const auto& p : x524_particleGens) {
|
|
g_Renderer->AddParticleGen(*p);
|
|
}
|
|
}
|
|
|
|
void CWallCrawlerSwarm::AddToRenderer(const zeus::CFrustum&, const CStateManager& mgr) const {
|
|
if (!GetActive()) {
|
|
return;
|
|
}
|
|
|
|
RenderParticles();
|
|
|
|
if (xe4_30_outOfFrustum) {
|
|
return;
|
|
}
|
|
|
|
if (CanRenderUnsorted(mgr)) {
|
|
Render(mgr);
|
|
} else {
|
|
EnsureRendered(mgr);
|
|
}
|
|
}
|
|
|
|
zeus::CColor CWallCrawlerSwarm::SoftwareLight(const CStateManager& mgr, const zeus::CAABox& aabb) const {
|
|
CActorLights lights(8, zeus::skZero3f, 4, 4, false, false, false, 0.1f);
|
|
lights.SetDirty();
|
|
lights.SetCastShadows(false);
|
|
lights.SetFindShadowLight(false);
|
|
lights.BuildAreaLightList(mgr, *mgr.GetWorld()->GetAreaAlways(x4_areaId), aabb);
|
|
lights.BuildDynamicLightList(mgr, aabb);
|
|
zeus::CColor ret = lights.GetAmbientColor();
|
|
ret.a() = 1.f;
|
|
const zeus::CVector3f center = aabb.center();
|
|
const u32 lightCount = lights.GetActiveLightCount();
|
|
for (u32 i = 0; i < lightCount; ++i) {
|
|
const CLight& light = lights.GetLight(i);
|
|
const float dist = (light.GetPosition() - center).magnitude();
|
|
const float att = 1.f / (dist * dist * light.GetAttenuationQuadratic() + dist * light.GetAttenuationLinear() +
|
|
light.GetAttenuationConstant());
|
|
ret += zeus::CColor::lerp(zeus::skBlack, light.GetColor(), 0.8f * std::min(att, 1.f));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void CWallCrawlerSwarm::HardwareLight(const CStateManager& mgr, const zeus::CAABox& aabb) const {
|
|
CActorLights lights(8, zeus::skZero3f, 4, 4, false, false, false, 0.1f);
|
|
lights.SetDirty();
|
|
lights.SetCastShadows(false);
|
|
lights.SetFindShadowLight(false);
|
|
lights.BuildAreaLightList(mgr, *mgr.GetWorld()->GetAreaAlways(x4_areaId), aabb);
|
|
lights.BuildDynamicLightList(mgr, aabb);
|
|
for (const auto& m : x4b0_modelDatas) {
|
|
lights.ActivateLights(*m->PickAnimatedModel(x4dc_whichModel).GetModelInst());
|
|
if (const auto iceModel = m->GetAnimationData()->GetIceModel()) {
|
|
lights.ActivateLights(*iceModel->GetModelInst());
|
|
}
|
|
}
|
|
}
|
|
|
|
void CWallCrawlerSwarm::RenderBoid(const CBoid* boid, u32& drawMask, bool thermalHot, const CModelFlags& flags) const {
|
|
u32 modelIndex = 0x0;
|
|
if (boid->x80_26_launched) {
|
|
modelIndex = 0x8;
|
|
} else if (boid->x48_timeToDie > 0.f) {
|
|
modelIndex = 0x9;
|
|
} else if (boid->x80_27_scarabExplodeTimerEnabled || boid->x80_28_nearPlayer) {
|
|
modelIndex += 0x4;
|
|
}
|
|
|
|
CModelData& mData = *x4b0_modelDatas[modelIndex];
|
|
CSkinnedModel& model = mData.PickAnimatedModel(x4dc_whichModel);
|
|
if (!model.GetModelInst()->TryLockTextures()) {
|
|
return;
|
|
}
|
|
|
|
const u32 thisDrawMask = 1u << modelIndex;
|
|
if (drawMask & thisDrawMask) {
|
|
drawMask &= ~thisDrawMask;
|
|
mData.GetAnimationData()->BuildPose();
|
|
}
|
|
|
|
model.GetModelInst()->SetAmbientColor(boid->x40_ambientLighting);
|
|
CGraphics::SetModelMatrix(boid->GetTransform());
|
|
if (boid->x48_timeToDie > 0.f && !thermalHot) {
|
|
const CModelFlags useFlags(0, 0, 3, zeus::skWhite);
|
|
mData.GetAnimationData()->Render(model, useFlags, std::nullopt, nullptr);
|
|
if (auto iceModel = mData.GetAnimationData()->GetIceModel()) {
|
|
if (!iceModel->GetModelInst()->TryLockTextures()) {
|
|
return;
|
|
}
|
|
iceModel->GetModelInst()->SetAmbientColor(zeus::skWhite);
|
|
const float alpha = 1.f - boid->x48_timeToDie;
|
|
const zeus::CColor color(1.f, alpha > 0.f ? boid->x48_timeToDie : 1.f);
|
|
const CModelFlags iceFlags(5, 0, 3, color);
|
|
mData.GetAnimationData()->Render(*iceModel, iceFlags, std::nullopt, nullptr);
|
|
}
|
|
} else if (thermalHot) {
|
|
const CModelFlags thermFlags(5, 0, 3, zeus::skWhite);
|
|
mData.RenderThermal(zeus::skWhite, zeus::CColor(0.f, 0.25f), thermFlags);
|
|
} else {
|
|
mData.GetAnimationData()->Render(model, flags, std::nullopt, nullptr);
|
|
}
|
|
}
|
|
|
|
void CWallCrawlerSwarm::Render(const CStateManager& mgr) const {
|
|
SCOPED_GRAPHICS_DEBUG_GROUP(fmt::format(fmt("CWallCrawlerSwarm::Render {} {} {}"),
|
|
x8_uid, xc_editorId, x10_name).c_str(), zeus::skOrange);
|
|
u32 drawMask = 0xffffffff;
|
|
const bool r24 = x560_24_enableLighting;
|
|
const bool r23 = x560_25_useSoftwareLight;
|
|
if (!r24) {
|
|
// Ambient 50% grey
|
|
}
|
|
|
|
const 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, zeus::CColor(1.f, 0.3f));
|
|
}
|
|
|
|
for (int r27 = 0; r27 < 5; ++r27) {
|
|
for (int r28 = 0; r28 < 5; ++r28) {
|
|
for (int r21 = 0; r21 < 5; ++r21) {
|
|
const int idx = r21 * 25 + r28 * 5 + r27;
|
|
if (CBoid* b = x168_partitionedBoidLists[idx]) {
|
|
if (r24) {
|
|
const zeus::CAABox aabb = BoxForPosition(r27, r28, r21, 0.f);
|
|
if (r23) {
|
|
if ((idx & 0x3) == (x100_thinkCounter & 0x3)) {
|
|
const zeus::CColor color = SoftwareLight(mgr, aabb);
|
|
for (CBoid* b2 = b; b2; b2 = b2->x44_next) {
|
|
if (b2->GetActive()) {
|
|
b2->x40_ambientLighting = zeus::CColor::lerp(b2->x40_ambientLighting, color, 0.3f);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
HardwareLight(mgr, aabb);
|
|
}
|
|
}
|
|
for (CBoid* b2 = b; b2; b2 = b2->x44_next) {
|
|
if (b2->x80_25_inFrustum && b2->GetActive()) {
|
|
RenderBoid(b2, drawMask, thermalHot, flags);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CBoid* b = x360_outlierBoidList;
|
|
for (int i = 1; b; ++i, b = b->x44_next) {
|
|
if (b->x80_25_inFrustum && b->GetActive()) {
|
|
if (r24) {
|
|
const zeus::CAABox aabb(b->GetTranslation() - x374_boidRadius, b->GetTranslation() + x374_boidRadius);
|
|
if (r23) {
|
|
if ((i & 0x3) == (x100_thinkCounter & 0x3)) {
|
|
zeus::CColor color = SoftwareLight(mgr, aabb);
|
|
if (b->GetActive()) {
|
|
b->x40_ambientLighting = zeus::CColor::lerp(b->x40_ambientLighting, color, 0.3f);
|
|
}
|
|
}
|
|
} else {
|
|
HardwareLight(mgr, aabb);
|
|
}
|
|
}
|
|
RenderBoid(b, drawMask, thermalHot, flags);
|
|
}
|
|
}
|
|
DrawTouchBounds();
|
|
}
|
|
|
|
bool CWallCrawlerSwarm::CanRenderUnsorted(const CStateManager&) const {
|
|
return true;
|
|
}
|
|
|
|
void CWallCrawlerSwarm::CalculateRenderBounds() {
|
|
x9c_renderBounds = GetBoundingBox();
|
|
}
|
|
|
|
std::optional<zeus::CAABox> CWallCrawlerSwarm::GetTouchBounds() const {
|
|
return {xe8_aabox};
|
|
}
|
|
|
|
void CWallCrawlerSwarm::Touch(CActor& other, CStateManager& mgr) {
|
|
CActor::Touch(other, mgr);
|
|
if (TCastToConstPtr<CGameProjectile> proj = other) {
|
|
if (x3c4_dVuln.WeaponHurts(proj->GetDamageInfo().GetWeaponMode(), false)) {
|
|
if (auto projTb = proj->GetTouchBounds()) {
|
|
const float f0 = 0.1f + x378_touchRadius;
|
|
const float f30 = f0 * f0;
|
|
for (auto& b : x108_boids) {
|
|
if (b.GetActive()) {
|
|
const zeus::CAABox aabb(b.GetTranslation() - f30, b.GetTranslation() + f30);
|
|
if (aabb.intersects(*projTb)) {
|
|
b.x78_health -= proj->GetDamageInfo().GetDamage(x3c4_dVuln);
|
|
if (b.x78_health <= 0.f) {
|
|
KillBoid(b, mgr, 1.f, 0.1f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (TCastToConstPtr<CPlayer> player = other) {
|
|
const float radius = zeus::close_enough(x380_playerTouchRadius, 0.f) ? x378_touchRadius : x380_playerTouchRadius;
|
|
if (auto playerTb = player->GetTouchBounds()) {
|
|
for (auto& b : x108_boids) {
|
|
if (b.GetActive() && b.x48_timeToDie <= 0.f) {
|
|
if (x558_flavor == EFlavor::Scarab && b.x80_27_scarabExplodeTimerEnabled) {
|
|
const zeus::CAABox aabb(b.GetTranslation() - x37c_scarabBoxMargin,
|
|
b.GetTranslation() + x37c_scarabBoxMargin);
|
|
if (playerTb->intersects(aabb)) {
|
|
ExplodeBoid(b, mgr);
|
|
SetExplodeTimers(b.GetTranslation(), 0.5f, 0.5f, 2.5f);
|
|
}
|
|
}
|
|
const zeus::CAABox aabb(b.GetTranslation() - radius, b.GetTranslation() + radius);
|
|
if (playerTb->intersects(aabb)) {
|
|
if (b.GetActive() && x558_flavor == EFlavor::Parasite) {
|
|
constexpr CDamageInfo dInfo(CWeaponMode(EWeaponType::AI), 2.0e-05f, 0.f, 0.f);
|
|
mgr.ApplyDamage(GetUniqueId(), player->GetUniqueId(), GetUniqueId(), dInfo,
|
|
CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);
|
|
KillBoid(b, mgr, 0.f, 1.f);
|
|
} else if (x558_flavor == EFlavor::Scarab) {
|
|
ExplodeBoid(b, mgr);
|
|
} else if (x36c_crabDamageCooldownTimer <= 0.f) {
|
|
mgr.ApplyDamage(GetUniqueId(), player->GetUniqueId(), GetUniqueId(), x384_crabDamage,
|
|
CMaterialFilter::MakeIncludeExclude({EMaterialTypes::Solid}, {}), zeus::skZero3f);
|
|
x36c_crabDamageCooldownTimer = x370_crabDamageCooldown;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
zeus::CVector3f CWallCrawlerSwarm::GetOrbitPosition(const CStateManager&) const {
|
|
if (x42c_lockOnIdx == -1) {
|
|
return x124_lastOrbitPosition;
|
|
}
|
|
|
|
x124_lastOrbitPosition = x108_boids[x42c_lockOnIdx].GetTranslation();
|
|
return x124_lastOrbitPosition;
|
|
}
|
|
|
|
zeus::CVector3f CWallCrawlerSwarm::GetAimPosition(const CStateManager&, float dt) const {
|
|
if (x42c_lockOnIdx == -1) {
|
|
return x124_lastOrbitPosition;
|
|
}
|
|
|
|
return x108_boids[x42c_lockOnIdx].x30_velocity * dt + x124_lastOrbitPosition;
|
|
}
|
|
|
|
} // namespace urde
|