diff --git a/DataSpec/DNAMP1/ScriptObjects/Parameters.hpp b/DataSpec/DNAMP1/ScriptObjects/Parameters.hpp index f63fa39fa..a034d949f 100644 --- a/DataSpec/DNAMP1/ScriptObjects/Parameters.hpp +++ b/DataSpec/DNAMP1/ScriptObjects/Parameters.hpp @@ -56,7 +56,7 @@ enum class ESpecialFunctionType : atUint32 { PlayerFollowLocator, SpinnerController, ObjectFollowLocator, - Function4, + ChaffTarget, InventoryActivator, MapStation, SaveStation, @@ -74,7 +74,7 @@ enum class ESpecialFunctionType : atUint32 { ObjectFollowObject, HintSystem, DropBomb, - Function22, + ScaleActor, MissileStation, Billboard, PlayerInAreaRelay, @@ -85,7 +85,7 @@ enum class ESpecialFunctionType : atUint32 { Ending, FusionRelay, WeaponSwitch // PAL Only -} SPECTER_ENUM("Special Function", "", EPickupType); +} SPECTER_ENUM("Special Function", "", ESpecialFunctionType); struct AnimationParameters : BigDNA { AT_DECL_DNA_YAML diff --git a/DataSpec/DNAMP1/ScriptObjects/WallCrawlerSwarm.hpp b/DataSpec/DNAMP1/ScriptObjects/WallCrawlerSwarm.hpp index df64f79fd..b5b8f92e6 100644 --- a/DataSpec/DNAMP1/ScriptObjects/WallCrawlerSwarm.hpp +++ b/DataSpec/DNAMP1/ScriptObjects/WallCrawlerSwarm.hpp @@ -14,60 +14,70 @@ struct WallCrawlerSwarm : IScriptObject { Value volume; Value active; ActorParameters actorParameters; - Value unknown1; + Value flavor; AnimationParameters animationParameters; - Value unknown2; - Value unknown3; - UniqueID32 particle1; - UniqueID32 particle2; - Value unknown4; // always FF - Value unknown5; // always FF - DamageInfo damageInfo1; - Value unknown6; - DamageInfo damageInfo2; - Value unknown7; - Value unknown8; - Value unknown9; - Value unknown10; - Value unknown11; - Value unknown12; - Value unknown13; - Value unknown14; - Value unknown15; - Value unknown16; - Value unknown17; - Value unknown18; - Value unknown19; - Value unknown20; - Value unknown21; - Value unkown22; - Value unkown23; - Value unkown24; + Value launchAnim; + Value attractAnim; + UniqueID32 part1; + UniqueID32 part2; + UniqueID32 part3; + UniqueID32 part4; + DamageInfo crabDamage; + Value crabDamageCooldown; + DamageInfo scarabExplodeDamage; + Value boidRadius; + Value touchRadius; + Value playerTouchRadius; + Value animPlaybackSpeed; + Value numBoids; + Value maxCreatedBoids; + Value separationRadius; + Value cohesionMagnitude; + Value alignmentWeight; + Value separationMagnitude; + Value moveToWaypointWeight; + Value attractionMagnitude; + Value attractionRadius; + Value boidGenRate; + Value maxLaunches; + Value scarabBoxMargin; + Value scarabScatterXYVelocity; + Value scarabTimeToExplode; HealthInfo healthInfo; DamageVulnerability damageVulnerabilty; - Value soundID1; // verification needed - Value soundID2; // verification needed + Value launchSfx; + Value scatterSfx; void addCMDLRigPairs(PAKRouter& pakRouter, CharacterAssociations& charAssoc) const { actorParameters.addCMDLRigPairs(pakRouter, charAssoc, animationParameters); } void nameIDs(PAKRouter& pakRouter) const { - if (particle1) { - PAK::Entry* ent = (PAK::Entry*)pakRouter.lookupEntry(particle1); + if (part1) { + PAK::Entry* ent = (PAK::Entry*)pakRouter.lookupEntry(part1); ent->name = name + "_part1"; } - if (particle2) { - PAK::Entry* ent = (PAK::Entry*)pakRouter.lookupEntry(particle2); + if (part2) { + PAK::Entry* ent = (PAK::Entry*)pakRouter.lookupEntry(part2); ent->name = name + "_part2"; } + if (part3) { + PAK::Entry* ent = (PAK::Entry*)pakRouter.lookupEntry(part3); + ent->name = name + "_part3"; + } + if (part4) { + PAK::Entry* ent = (PAK::Entry*)pakRouter.lookupEntry(part4); + ent->name = name + "_part4"; + } animationParameters.nameANCS(pakRouter, name + "_animp"); actorParameters.nameIDs(pakRouter, name + "_actp"); } void gatherDependencies(std::vector& pathsOut, std::vector& lazyOut) const { - g_curSpec->flattenDependencies(particle1, pathsOut); - g_curSpec->flattenDependencies(particle2, pathsOut); + g_curSpec->flattenDependencies(part1, pathsOut); + g_curSpec->flattenDependencies(part2, pathsOut); + g_curSpec->flattenDependencies(part3, pathsOut); + g_curSpec->flattenDependencies(part4, pathsOut); animationParameters.depANCS(pathsOut); actorParameters.depIDs(pathsOut, lazyOut); } diff --git a/Runtime/Character/CAnimData.hpp b/Runtime/Character/CAnimData.hpp index 3e6113956..3b50bac44 100644 --- a/Runtime/Character/CAnimData.hpp +++ b/Runtime/Character/CAnimData.hpp @@ -83,6 +83,7 @@ class CAnimData { friend class CActor; friend class CPlayerGun; friend class CGrappleArm; + friend class CWallCrawlerSwarm; public: enum class EAnimDir { Forward, Backward }; diff --git a/Runtime/Character/CBodyController.cpp b/Runtime/Character/CBodyController.cpp index 469e1067c..559247485 100644 --- a/Runtime/Character/CBodyController.cpp +++ b/Runtime/Character/CBodyController.cpp @@ -117,7 +117,7 @@ void CBodyController::FaceDirection3D(const zeus::CVector3f& v0, const zeus::CVe zeus::CUnitVector3f uv0 = v0; zeus::CUnitVector3f uv1 = v1; float dot = uv0.dot(uv1); - if (std::fabs(dot - 1.f) >= 0.00001f) { + if (!zeus::close_enough(dot, 1.f)) { if (dot < -0.9999f) { zeus::CQuaternion rot = zeus::CQuaternion::fromAxisAngle(act->GetTransform().basis[2], zeus::degToRad(dt * x2fc_turnSpeed)); diff --git a/Runtime/Character/CModelData.cpp b/Runtime/Character/CModelData.cpp index 0291101b9..5741a1998 100644 --- a/Runtime/Character/CModelData.cpp +++ b/Runtime/Character/CModelData.cpp @@ -273,10 +273,8 @@ void CModelData::Touch(const CStateManager& stateMgr, int shaderIdx) const { Touch(const_cast(*this).GetRenderingModel(stateMgr), shaderIdx); } -void CModelData::RenderThermal(const zeus::CTransform& xf, const zeus::CColor& mulColor, const zeus::CColor& addColor, +void CModelData::RenderThermal(const zeus::CColor& mulColor, const zeus::CColor& addColor, const CModelFlags& flags) const { - CGraphics::SetModelMatrix(xf * zeus::CTransform::Scale(x0_scale)); - CGraphics::DisableAllLights(); CModelFlags drawFlags = flags; drawFlags.x4_color *= mulColor; drawFlags.addColor = addColor; @@ -292,6 +290,13 @@ void CModelData::RenderThermal(const zeus::CTransform& xf, const zeus::CColor& m } } +void CModelData::RenderThermal(const zeus::CTransform& xf, const zeus::CColor& mulColor, const zeus::CColor& addColor, + const CModelFlags& flags) const { + CGraphics::SetModelMatrix(xf * zeus::CTransform::Scale(x0_scale)); + CGraphics::DisableAllLights(); + RenderThermal(mulColor, addColor, flags); +} + void CModelData::RenderUnsortedParts(EWhichModel which, const zeus::CTransform& xf, const CActorLights* lights, const CModelFlags& drawFlags) const { if ((x14_25_sortThermal && which == EWhichModel::ThermalHot) || x10_animData || !x1c_normalModel || diff --git a/Runtime/Character/CModelData.hpp b/Runtime/Character/CModelData.hpp index 224f2e4d3..60bf68e9d 100644 --- a/Runtime/Character/CModelData.hpp +++ b/Runtime/Character/CModelData.hpp @@ -48,7 +48,9 @@ public: void SetCharacterNodeId(s32 id) { x4_charIdx = id; } const zeus::CVector3f& GetScale() const { return x8_scale; } bool CanLoop() const { return x14_canLoop; } + void SetCanLoop(bool l) { x14_canLoop = l; } s32 GetDefaultAnim() const { return x18_defaultAnim; } + void SetDefaultAnim(s32 anim) { x18_defaultAnim = anim; } }; class CModelData { @@ -116,6 +118,8 @@ public: void RenderParticles(const zeus::CFrustum& frustum) const; void Touch(EWhichModel, int shaderIdx) const; void Touch(const CStateManager& stateMgr, int shaderIdx) const; + void RenderThermal(const zeus::CColor& mulColor, const zeus::CColor& addColor, + const CModelFlags& flags) const; void RenderThermal(const zeus::CTransform& xf, const zeus::CColor& mulColor, const zeus::CColor& addColor, const CModelFlags& flags) const; void RenderUnsortedParts(EWhichModel, const zeus::CTransform& xf, const CActorLights* lights, diff --git a/Runtime/Collision/CGameCollision.cpp b/Runtime/Collision/CGameCollision.cpp index f84df173a..65db8f6e4 100644 --- a/Runtime/Collision/CGameCollision.cpp +++ b/Runtime/Collision/CGameCollision.cpp @@ -447,13 +447,13 @@ bool CGameCollision::DetectStaticCollisionBoolean_Cached(const CStateManager& mg return DetectStaticCollisionBoolean(mgr, prim, xf, filter); if (prim.GetPrimType() == FOURCC('AABX')) { - for (CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) + for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) if (CMetroidAreaCollider::AABoxCollisionCheckBoolean_Cached(leafCache, aabb, filter)) return true; } else if (prim.GetPrimType() == FOURCC('SPHR')) { const CCollidableSphere& sphere = static_cast(prim); zeus::CSphere xfSphere = sphere.Transform(xf); - for (CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) + for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) if (CMetroidAreaCollider::SphereCollisionCheckBoolean_Cached(leafCache, aabb, xfSphere, filter)) return true; } else if (prim.GetPrimType() == FOURCC('ABSH')) { @@ -566,13 +566,13 @@ bool CGameCollision::DetectStaticCollision_Cached(const CStateManager& mgr, CAre return DetectStaticCollision(mgr, prim, xf, filter, list); if (prim.GetPrimType() == FOURCC('AABX')) { - for (CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) + for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) if (CMetroidAreaCollider::AABoxCollisionCheck_Cached(leafCache, calcAABB, filter, prim.GetMaterial(), list)) ret = true; } else if (prim.GetPrimType() == FOURCC('SPHR')) { const CCollidableSphere& sphere = static_cast(prim); zeus::CSphere xfSphere = sphere.Transform(xf); - for (CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) + for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) if (CMetroidAreaCollider::SphereCollisionCheck_Cached(leafCache, calcAABB, xfSphere, prim.GetMaterial(), filter, list)) ret = true; @@ -604,7 +604,7 @@ bool CGameCollision::DetectStaticCollision_Cached_Moving(const CStateManager& mg } if (prim.GetPrimType() == FOURCC('AABX')) { - for (CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) { + for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) { CCollisionInfo info; double d = dOut; if (CMetroidAreaCollider::MovingAABoxCollisionCheck_Cached( @@ -617,7 +617,7 @@ bool CGameCollision::DetectStaticCollision_Cached_Moving(const CStateManager& mg } else if (prim.GetPrimType() == FOURCC('SPHR')) { const CCollidableSphere& sphere = static_cast(prim); zeus::CSphere xfSphere = sphere.Transform(xf); - for (CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) { + for (const CMetroidAreaCollider::COctreeLeafCache& leafCache : cache) { CCollisionInfo info; double d = dOut; if (CMetroidAreaCollider::MovingSphereCollisionCheck_Cached( diff --git a/Runtime/Collision/CMetroidAreaCollider.hpp b/Runtime/Collision/CMetroidAreaCollider.hpp index c5822cf2e..e95c37e56 100644 --- a/Runtime/Collision/CMetroidAreaCollider.hpp +++ b/Runtime/Collision/CMetroidAreaCollider.hpp @@ -121,8 +121,8 @@ public: u32 GetNumLeaves() const { return x4_nodeCache.size(); } bool HasCacheOverflowed() const { return x908_24_overflow; } const CAreaOctTree& GetOctTree() const { return x0_octTree; } - rstl::reserved_vector::iterator begin() { return x4_nodeCache.begin(); } - rstl::reserved_vector::iterator end() { return x4_nodeCache.end(); } + rstl::reserved_vector::const_iterator begin() const { return x4_nodeCache.begin(); } + rstl::reserved_vector::const_iterator end() const { return x4_nodeCache.end(); } }; static void BuildOctreeLeafCache(const CAreaOctTree::Node& root, const zeus::CAABox& aabb, CMetroidAreaCollider::COctreeLeafCache& cache); @@ -182,8 +182,10 @@ public: u32 GetNumCaches() const { return x18_leafCaches.size(); } const CMetroidAreaCollider::COctreeLeafCache& GetOctreeLeafCache(int idx) { return x18_leafCaches[idx]; } bool HasCacheOverflowed() const { return x1b40_24_leafOverflow; } - rstl::reserved_vector::iterator begin() { return x18_leafCaches.begin(); } - rstl::reserved_vector::iterator end() { return x18_leafCaches.end(); } + rstl::reserved_vector::const_iterator begin() const + { return x18_leafCaches.begin(); } + rstl::reserved_vector::const_iterator end() const + { return x18_leafCaches.end(); } }; } // namespace urde diff --git a/Runtime/Graphics/CModel.hpp b/Runtime/Graphics/CModel.hpp index 6bef48ffa..2ca00c39f 100644 --- a/Runtime/Graphics/CModel.hpp +++ b/Runtime/Graphics/CModel.hpp @@ -200,6 +200,7 @@ public: bool IsOpaque() const { return x3c_firstSortedSurface == nullptr; } void ActivateLights(const std::vector& lights); + void SetAmbientColor(const zeus::CColor& color) { m_lightingData.ambient = color; } void DisableAllLights(); void RemapMaterialData(SShader& shader); void RemapMaterialData(SShader& shader, const std::unordered_map& pipelines); diff --git a/Runtime/Graphics/CModelBoo.cpp b/Runtime/Graphics/CModelBoo.cpp index bfbee58c6..7223f84c3 100644 --- a/Runtime/Graphics/CModelBoo.cpp +++ b/Runtime/Graphics/CModelBoo.cpp @@ -898,6 +898,9 @@ void GeometryUniformLayout::Update(const CModelFlags& flags, const CSkinRules* c boo::ObjToken CBooModel::UpdateUniformData(const CModelFlags& flags, const CSkinRules* cskr, const CPoseAsTransforms* pose, int sharedLayoutBuf) const { + if (!TryLockTextures()) + return {}; + /* Invalidate instances if new shadow being drawn */ if (flags.m_extendedShader == EExtendedShader::WorldShadow && m_lastDrawnShadowMap != g_shadowMap) { const_cast(this)->m_lastDrawnShadowMap = g_shadowMap; @@ -916,7 +919,7 @@ boo::ObjToken CBooModel::UpdateUniformData(const CModelFl do { inst = const_cast(this)->PushNewModelInstance(m_instances.size()); if (!inst) - return nullptr; + return {}; } while (m_instances.size() <= sharedLayoutBuf); } else inst = &m_instances[sharedLayoutBuf]; @@ -925,7 +928,7 @@ boo::ObjToken CBooModel::UpdateUniformData(const CModelFl if (m_instances.size() <= m_uniUpdateCount) { inst = const_cast(this)->PushNewModelInstance(sharedLayoutBuf); if (!inst) - return nullptr; + return {}; } else inst = &m_instances[m_uniUpdateCount]; ++const_cast(this)->m_uniUpdateCount; diff --git a/Runtime/Graphics/CSkinnedModel.hpp b/Runtime/Graphics/CSkinnedModel.hpp index 9d9c7cffe..e0f41cfc8 100644 --- a/Runtime/Graphics/CSkinnedModel.hpp +++ b/Runtime/Graphics/CSkinnedModel.hpp @@ -27,6 +27,9 @@ public: TLockedToken layoutInfo, int shaderIdx, int drawInsts); CSkinnedModel(IObjectStore& store, CAssetId model, CAssetId skinRules, CAssetId layoutInfo, int shaderIdx, int drawInsts); + std::unique_ptr Clone(int shaderIdx = 0, int drawInsts = 1) const { + return std::make_unique(x4_model, x10_skinRules, x1c_layoutInfo, shaderIdx, drawInsts); + } const TLockedToken& GetModel() const { return x4_model; } const std::unique_ptr& GetModelInst() const { return m_modelInst; } diff --git a/Runtime/MkCastTo.py b/Runtime/MkCastTo.py index 918219893..878fb1a55 100644 --- a/Runtime/MkCastTo.py +++ b/Runtime/MkCastTo.py @@ -89,8 +89,7 @@ sourcef = open('TCastTo.cpp', 'w') headerf.write('''#pragma once -namespace urde -{ +namespace urde { class CEntity; ''') @@ -98,58 +97,56 @@ for tp in CENTITY_TYPES: if type(tp) == tuple: headerf.write('class %s;\n' % tp[0]) elif isinstance(tp, Namespace): - headerf.write('namespace %s\n{\n' % tp.name) + headerf.write('namespace %s {\n' % tp.name) elif isinstance(tp, EndNamespace): headerf.write('}\n') -headerf.write('\nclass IVisitor\n{\npublic:\n') +headerf.write('\nclass IVisitor {\npublic:\n') for tp in CENTITY_TYPES: if type(tp) == tuple: - headerf.write(' virtual void Visit(%s* p)=0;\n' % getqualified(tp)) + headerf.write(' virtual void Visit(%s* p)=0;\n' % getqualified(tp)) headerf.write('''}; template -class TCastToPtr : public IVisitor -{ +class TCastToPtr : public IVisitor { protected: - T* ptr = nullptr; + T* ptr = nullptr; public: - TCastToPtr() = default; - TCastToPtr(CEntity* p); - TCastToPtr(CEntity& p); - TCastToPtr& operator=(CEntity& p); - TCastToPtr& operator=(CEntity* p); + TCastToPtr() = default; + TCastToPtr(CEntity* p); + TCastToPtr(CEntity& p); + TCastToPtr& operator=(CEntity& p); + TCastToPtr& operator=(CEntity* p); ''') for tp in CENTITY_TYPES: if type(tp) == tuple: - headerf.write(' void Visit(%s* p);\n' % getqualified(tp)) + headerf.write(' void Visit(%s* p);\n' % getqualified(tp)) headerf.write(''' - T* GetPtr() const { return ptr; } - operator T*() const { return GetPtr(); } - T& operator*() const { return *GetPtr(); } - T* operator->() const { return GetPtr(); } - operator bool() const { return ptr != nullptr; } + T* GetPtr() const { return ptr; } + operator T*() const { return GetPtr(); } + T& operator*() const { return *GetPtr(); } + T* operator->() const { return GetPtr(); } + operator bool() const { return ptr != nullptr; } }; template -class TCastToConstPtr : TCastToPtr -{ +class TCastToConstPtr : TCastToPtr { public: - TCastToConstPtr() = default; - TCastToConstPtr(const CEntity* p) : TCastToPtr(const_cast(p)) {} - TCastToConstPtr(const CEntity& p) : TCastToPtr(const_cast(p)) {} - TCastToConstPtr& operator=(const CEntity& p) { TCastToPtr::operator=(const_cast(p)); return *this; } - TCastToConstPtr& operator=(const CEntity* p) { TCastToPtr::operator=(const_cast(p)); return *this; } - const T* GetPtr() const { return TCastToPtr::ptr; } - operator const T*() const { return GetPtr(); } - const T& operator*() const { return *GetPtr(); } - const T* operator->() const { return GetPtr(); } - operator bool() const { return TCastToPtr::ptr != nullptr; } + TCastToConstPtr() = default; + TCastToConstPtr(const CEntity* p) : TCastToPtr(const_cast(p)) {} + TCastToConstPtr(const CEntity& p) : TCastToPtr(const_cast(p)) {} + TCastToConstPtr& operator=(const CEntity& p) { TCastToPtr::operator=(const_cast(p)); return *this; } + TCastToConstPtr& operator=(const CEntity* p) { TCastToPtr::operator=(const_cast(p)); return *this; } + const T* GetPtr() const { return TCastToPtr::ptr; } + operator const T*() const { return GetPtr(); } + const T& operator*() const { return *GetPtr(); } + const T* operator->() const { return GetPtr(); } + operator bool() const { return TCastToPtr::ptr != nullptr; } }; } @@ -164,8 +161,7 @@ for tp in CENTITY_TYPES: sourcef.write('#include "%s"\n' % tp[1]) sourcef.write(''' -namespace urde -{ +namespace urde { template TCastToPtr::TCastToPtr(CEntity* p) { if (p) p->Accept(*this); else ptr = nullptr; } @@ -185,10 +181,9 @@ for tp in CENTITY_TYPES: if type(tp) == tuple: qual = getqualified(tp) sourcef.write('''template -void TCastToPtr::Visit(%s* p) -{ - static_assert(sizeof(T) > 0 && !std::is_void::value, "TCastToPtr can not cast to incomplete type"); - ptr = reinterpret_cast(std::is_convertible<%s*, T*>::value ? p : nullptr); +void TCastToPtr::Visit(%s* p) { + static_assert(sizeof(T) > 0 && !std::is_void::value, "TCastToPtr can not cast to incomplete type"); + ptr = reinterpret_cast(std::is_convertible<%s*, T*>::value ? p : nullptr); } ''' % (qual, qual)) diff --git a/Runtime/World/CActorParameters.hpp b/Runtime/World/CActorParameters.hpp index 94935b0de..30942901d 100644 --- a/Runtime/World/CActorParameters.hpp +++ b/Runtime/World/CActorParameters.hpp @@ -67,5 +67,7 @@ public: const CLightParameters& GetLightParameters() const { return x0_lightParms; } bool HasThermalHeat() const { return x58_25_thermalHeat; } float GetThermalMag() const { return x64_thermalMag; } + const std::pair& GetXRayAssets() const { return x44_xrayAssets; } + const std::pair& GetThermalAssets() const { return x4c_thermalAssets; } }; } // namespace urde diff --git a/Runtime/World/CWallCrawlerSwarm.cpp b/Runtime/World/CWallCrawlerSwarm.cpp index 213a047ef..f0015f81e 100644 --- a/Runtime/World/CWallCrawlerSwarm.cpp +++ b/Runtime/World/CWallCrawlerSwarm.cpp @@ -1,23 +1,1130 @@ #include "World/CWallCrawlerSwarm.hpp" #include "World/CActorParameters.hpp" #include "Collision/CMaterialList.hpp" +#include "Graphics/CSkinnedModel.hpp" +#include "Graphics/CBooRenderer.hpp" +#include "World/CScriptDoor.hpp" +#include "World/CWorld.hpp" +#include "World/CPlayer.hpp" +#include "World/CScriptWaypoint.hpp" +#include "Collision/CMetroidAreaCollider.hpp" +#include "Collision/CGameCollision.hpp" +#include "Camera/CFirstPersonCamera.hpp" +#include "Character/CSteeringBehaviors.hpp" +#include "Weapon/CGameProjectile.hpp" +#include "Graphics/CVertexMorphEffect.hpp" +#include "GameGlobalObjects.hpp" +#include "CStateManager.hpp" +#include "CSimplePool.hpp" #include "TCastTo.hpp" namespace urde { static CMaterialList MakeMaterialList() { return CMaterialList(EMaterialTypes::Scannable, EMaterialTypes::Trigger, EMaterialTypes::NonSolidDamageable, - EMaterialTypes::ExcludeFromLineOfSightTest); + EMaterialTypes::RadarObject); } CWallCrawlerSwarm::CWallCrawlerSwarm(TUniqueId uid, bool active, std::string_view name, const CEntityInfo& info, - const zeus::CVector3f&, const zeus::CTransform& xf, u32, const CAnimRes&, u32, u32, - u32, u32, u32, u32, const CDamageInfo&, const CDamageInfo&, float, float, float, - float, u32, u32, float, float, float, float, float, float, float, float, float, - u32, float, float, float, const CHealthInfo&, const CDamageVulnerability&, u32, - u32, const CActorParameters& aParams) -: CActor(uid, active, name, info, xf, CModelData::CModelDataNull(), MakeMaterialList(), aParams, kInvalidUniqueId) {} + 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(new CModelData(animRes)); + x4b0_modelDatas.emplace_back(new CModelData(animRes)); + x4b0_modelDatas.emplace_back(new CModelData(animRes)); + x4b0_modelDatas.emplace_back(new CModelData(animRes)); + x4b0_modelDatas.emplace_back(new CModelData(attractAnimRes)); + x4b0_modelDatas.emplace_back(new CModelData(attractAnimRes)); + x4b0_modelDatas.emplace_back(new CModelData(attractAnimRes)); + x4b0_modelDatas.emplace_back(new CModelData(attractAnimRes)); + x4b0_modelDatas.emplace_back(new CModelData(launchAnimRes)); + x4b0_modelDatas.emplace_back(new CModelData(animRes)); + if (aParams.GetXRayAssets().first.IsValid()) { + for (int i = 0; i < 9; ++i) + x4b0_modelDatas[i]->SetXRayModel(aParams.GetXRayAssets()); + x560_26_modelAssetDirty = true; + } + if (aParams.GetThermalAssets().first.IsValid()) { + for (int i = 0; i < 9; ++i) + x4b0_modelDatas[i]->SetXRayModel(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 (int 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]->AnimationData()->GetAnimTimeRemaining("Whole Body"sv) * (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 (CEntity* ent : mgr.GetPhysicsActorObjectList()) { + if (TCastToPtr door = ent) + if (door->GetAreaIdAlways() == x4_areaId) + ++doorCount; + } + x4e0_doorRepulsors.reserve(doorCount); + for (CEntity* ent : mgr.GetPhysicsActorObjectList()) { + if (TCastToPtr door = ent) { + if (door->GetAreaIdAlways() == x4_areaId) { + if (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 { + 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; + 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; + float omtd = mgr.GetPlayer().GetOrbitMaxTargetDistance(mgr); + float omtdSq = omtd * omtd; + float maxDot = 0.5f; + for (int 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()) { + float thisDot = fpCamXf.basis[1].dot(delta.normalized()); + if (thisDot > maxDot) { + ret = i; + maxDot = thisDot; + } + } + } + } + return ret; +} + +zeus::CAABox CWallCrawlerSwarm::GetBoundingBox() 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 { + 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; + 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())) { + auto triList = n.GetTriangleArray(); + for (int i = 0; i < triList.GetSize(); ++i) { + CCollisionSurface surf = n.GetOwner().GetMasterListTriangle(triList.GetAt(i)); + zeus::CPlane plane = surf.GetPlane(); + 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); + zeus::CVector3f aabbExtents = GetBoundingBox().extents(); + float f25 = 0.1f; + while (f25 < 1.f) { + 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) { + 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(); + TUniqueId wpId = GetWaypointForState(EScriptObjectState::Patrol, mgr); + if (TCastToConstPtr wp = mgr.GetObjectById(wpId)) { + 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) { + float radiusSq = radius * radius; + float range = maxTime - minTime; + for (auto& b : x108_boids) { + if (b.GetActive() && b.x48_timeToDie <= 0.f) { + float dist = (b.GetTranslation() - pos).magSquared(); + if (dist < radiusSq) { + 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) { + zeus::CAABox aabb = GetBoundingBox(); + zeus::CVector3f ints = (pos - aabb.min) / ((aabb.max - aabb.min) / 5.f); + 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& nearList) { + CBoid* b = GetListAt(boid.GetTranslation()); + while (b && nearList.size() < 50) { + 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& nearList, + zeus::CVector3f& aheadVec) 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 != 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 { + zeus::CVector3f delta = boid.GetTranslation() - separateFrom; + if (delta.canBeNormalized()) { + float deltaDistSq = delta.magSquared(); + float capDeltaDistSq = separationRadius * separationRadius; + if (deltaDistSq < capDeltaDistSq) + aheadVec += (1.f - deltaDistSq / capDeltaDistSq) * delta.normalized() * separationMagnitude; + } +} + +void CWallCrawlerSwarm::ScatterScarabBoid(CBoid& boid, CStateManager& mgr) const { + zeus::CVector3f oldDir = boid.Transform().basis[1]; + boid.Transform().setRotation(zeus::CTransform()); + boid.Transform() = LookAt(boid.Transform().basis[1], oldDir, M_PIF).multiplyIgnoreTranslation(boid.Transform()); + boid.x30_velocity = zeus::skZero3f; + float angle = mgr.GetActiveRandom()->Float() * (2.f * M_PIF); + 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 (TCastToPtr wp = mgr.ObjectById(boid.x3c_targetWaypoint)) { + 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 = TCastToPtr(mgr.ObjectById(boid.x3c_targetWaypoint)).GetPtr(); + } + } + aheadVec += (useWp->GetTranslation() - boid.GetTranslation()).normalized() * x14c_moveToWaypointWeight; + } +} + +void CWallCrawlerSwarm::ApplyCohesion(const CBoid& boid, const rstl::reserved_vector& nearList, + zeus::CVector3f& aheadVec) const { + if (nearList.empty()) + return; + zeus::CVector3f avg; + for (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 { + zeus::CVector3f delta = cohesionFrom - boid.GetTranslation(); + if (delta.canBeNormalized()) { + float distSq = delta.magSquared(); + float capDistSq = cohesionRadius * cohesionRadius; + aheadVec += ((distSq > capDistSq) ? 1.f : distSq / capDistSq) * delta.normalized() * cohesionMagnitude; + } +} + +void CWallCrawlerSwarm::ApplyAlignment(const CBoid& boid, const rstl::reserved_vector& nearList, + zeus::CVector3f& aheadVec) const { + if (nearList.empty()) + return; + zeus::CVector3f avg; + for (CBoid* b : nearList) + avg += b->Transform().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 { + zeus::CVector3f delta = attractTo - boid.GetTranslation(); + if (delta.canBeNormalized()) { + float distSq = delta.magSquared(); + 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) { + float radius = 2.f * x374_boidRadius; + float boidMag = boid.x30_velocity.magnitude(); + float f20 = boidMag * dt; + 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.Transform().basis[2], surf.GetNormal(), M_PIF).multiplyIgnoreTranslation(boid.Transform()); + } + 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 * 24.525f : 24.525f)) * dt; + if (boid.x7c_remainingLaunchNotOnSurfaceFrames) + boid.x7c_remainingLaunchNotOnSurfaceFrames -= 1; + } + } else if (boid.x7c_framesNotOnSurface >= 30) { + boid.x80_24_active = false; + } else { + 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.Transform().basis[2], surf.GetNormal(), zeus::degToRad(180.f * dt)). + multiplyIgnoreTranslation(boid.Transform()); + auto plane = surf.GetPlane(); + 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.Transform().basis[2], boid.Transform().basis[1], + boid.x30_velocity.magnitude() / x374_boidRadius * dt). + multiplyIgnoreTranslation(boid.Transform()); + boid.x7c_framesNotOnSurface += 1; + } + rstl::reserved_vector nearList; + BuildBoidNearList(boid, x13c_separationRadius, nearList); + zeus::CVector3f aheadVec = boid.Transform().basis[1] * 0.3f; + for (int r26 = 0; r26 < 8; ++r26) { + switch (r26) { + case 0: + for (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.Transform().basis[1], + ProjectVectorToPlane(aheadVec, boid.Transform().basis[2]).normalized(), M_PIF * dt). + multiplyIgnoreTranslation(boid.Transform()); + } +} + +void CWallCrawlerSwarm::LaunchBoid(CBoid& boid, const zeus::CVector3f& dir) { + zeus::CVector3f pos = boid.GetTranslation(); + static float skAttackTime = std::sqrt(2.5f / 24.525f) * 2.f; + static float skAttackVelocity = 15.f / skAttackTime; + zeus::CVector3f deltaFlat = dir - pos; + float deltaZ = deltaFlat.z(); + deltaFlat.z() = 0.f; + float deltaMag = deltaFlat.magnitude(); + boid.Transform().setRotation(zeus::CTransform()); + boid.Transform() = + LookAt(boid.Transform().basis[1], deltaFlat.normalized(), M_PIF).multiplyIgnoreTranslation(boid.Transform()); + zeus::CVector3f vec(skAttackVelocity * boid.Transform().basis[1].toVec2f(), 0.5f * skAttackVelocity); + if (deltaMag > FLT_EPSILON) { + deltaFlat = deltaFlat / deltaMag; + float dot = deltaFlat.dot(vec); + if (dot > FLT_EPSILON) { + bool r29 = deltaZ < 0.f; + float _12c, _130; + float f25 = 0.f; + if (CSteeringBehaviors::SolveQuadratic(-24.525f, 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 * -24.525f * 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); +} + +static const int kParticleCounts[] = {8, 2, 0, 0}; + +void CWallCrawlerSwarm::AddParticle(const zeus::CTransform& xf) { + int i = 0; + for (auto& p : x524_particleGens) { + p->SetParticleEmission(true); + p->SetTranslation(xf.origin); + p->ForceParticleCreation(kParticleCounts[i]); + p->SetParticleEmission(false); + ++i; + } +} + +void CWallCrawlerSwarm::KillBoid(CBoid& boid, CStateManager& mgr, float deathRattleChance, float deadChance) { + x130_lastKilledOffset = boid.GetTranslation(); + AddParticle(boid.Transform()); + boid.x80_24_active = false; + float sendDeadRoll = mgr.GetActiveRandom()->Float(); + 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; + zeus::CAABox aabb = GetBoundingBox(); + zeus::CVector3f vec = (aabb.max - aabb.min) / 5.f; + for (auto& b : x108_boids) { + if (b.GetActive()) { + zeus::CVector3f divVec = (b.Translation() - aabb.min) / vec; + int xIdx = int(divVec.x()); + int yIdx = int(divVec.y()); + int zIdx = int(divVec.z()); + 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) { + zeus::CAABox aabb = BoxForPosition(r28, r29, r25, 0.1f); + 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()) { + for (int 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())) { + u16 sfx = CSfxManager::TranslateSFXID(u16(n.GetSfxId() & 0xffff)); + bool loop = bool(n.GetSfxId() >> 31); + if (!loop) { + 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 { + zeus::CAABox aabb = GetBoundingBox(); + 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) { + 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); + 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 (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) { + int idx = r20 * 25 + r27 * 5 + r26; + if (CBoid* boid = x168_partitionedBoidLists[idx]) { + 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->Translation() + x37c_scarabBoxMargin); + xe8_aabox.accumulateBounds(boid->Translation() - x37c_scarabBoxMargin); + } else { + xe8_aabox.accumulateBounds(boid->Translation()); + } + } + 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->Translation()); + if (((x100_thinkCounter & 0x1) == (r21 & 0x1) && boid->GetActive() && boid->x48_timeToDie < 0.1f) || + boid->x80_26_launched) { + float margin = 1.5f + x374_boidRadius + 0.5f; + zeus::CAABox aabb(boid->Translation() - margin, boid->Translation() + margin); + CAreaCollisionCache ccache(aabb); + CGameCollision::BuildAreaCollisionCache(mgr, ccache); + UpdateBoid(ccache, mgr, dt, *boid); + } + } + + x4b0_modelDatas[8]->AnimationData()->SetPlaybackRate(x160_animPlaybackSpeed); + x4b0_modelDatas[8]->AdvanceAnimation(dt, mgr, x4_areaId, true); + + SAdvancementDeltas deltas1, deltas2; + + int r9 = 0; + int r3 = 0; + int r8 = 0; + bool _38F8[4] = {}; + bool _38F4[4] = {}; + 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 (int i = 0; i < 4; ++i) { + x4b0_modelDatas[i]->AnimationData()->SetPlaybackRate(x160_animPlaybackSpeed); + deltas1 = x4b0_modelDatas[i]->AdvanceAnimation(dt, mgr, x4_areaId, true); + x4b0_modelDatas[i+4]->AnimationData()->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]->AnimationData(), r8 * 44 / x548_numBoids + 0x53); + if (x4b0_modelDatas[i+4]->HasAnimData() && _38F8[i]) + UpdateEffects(mgr, *x4b0_modelDatas[i+4]->AnimationData(), r3 * 44 / x548_numBoids + 0x53); + for (int 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.Transform().rotate(deltas2.x0_posDelta) * 1.5f / dt; + b.Translation() += b.x30_velocity * dt; + } else { + b.x30_velocity = b.Transform().rotate(deltas1.x0_posDelta) * 1.5f / dt; + b.Translation() += b.x30_velocity * dt; + } + } + } + } + + if (x558_flavor == EFlavor::Crab) { + 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.Translation()).magnitude() < x154_attractionRadius; + } + } + + if (x558_flavor == EFlavor::Parasite && x554_maxLaunches > 0) { + zeus::CVector3f _383c = mgr.GetPlayer().GetTranslation() + zeus::skUp; + static const CMaterialFilter filter = CMaterialFilter::MakeInclude(EMaterialTypes::Solid); + int numLaunched = 0; + for (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.Translation() - _383c).magSquared() < 18.f * 18.f && mgr.GetActiveRandom()->Float() <= 0.02f) { + zeus::CVector3f dir = _383c - b.Translation(); + float mag = dir.magnitude(); + dir = dir / mag; + if (mgr.RayStaticIntersection(b.Translation(), dir, mag, filter).IsInvalid()) { + LaunchBoid(b, _383c); + ++numLaunched; + } + } + } + } +} + +void CWallCrawlerSwarm::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) { + for (int i = 0; i < 5; ++i) + x4b0_modelDatas[i]->AnimationData()->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()) { + RenderParticles(); + if (!xe4_30_outOfFrustum) { + 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; + zeus::CVector3f center = aabb.center(); + u32 lightCount = lights.GetActiveLightCount(); + for (u32 i = 0; i < lightCount; ++i) { + const CLight& light = lights.GetLight(i); + float dist = (light.GetPosition() - center).magnitude(); + 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 (auto& m : x4b0_modelDatas) { + lights.ActivateLights(*m->PickAnimatedModel(x4dc_whichModel).GetModelInst()); + if (auto iceModel = m->AnimationData()->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; + u32 thisDrawMask = 1u << modelIndex; + if (drawMask & thisDrawMask) { + drawMask &= ~thisDrawMask; + mData.AnimationData()->BuildPose(); + } + model.GetModelInst()->SetAmbientColor(boid->x40_ambientLighting); + CGraphics::SetModelMatrix(boid->GetTransform()); + if (boid->x48_timeToDie > 0.f && !thermalHot) { + CModelFlags useFlags(0, 0, 3, zeus::skWhite); + mData.AnimationData()->Render(model, useFlags, {}, nullptr); + if (auto iceModel = mData.AnimationData()->GetIceModel()) { + if (!iceModel->GetModelInst()->TryLockTextures()) + return; + iceModel->GetModelInst()->SetAmbientColor(zeus::skWhite); + float alpha = 1.f - boid->x48_timeToDie; + zeus::CColor color(1.f, alpha > 0.f ? boid->x48_timeToDie : 1.f); + CModelFlags iceFlags(5, 0, 3, color); + mData.AnimationData()->Render(*iceModel, iceFlags, {}, nullptr); + } + } else if (thermalHot) { + CModelFlags thermFlags(5, 0, 3, zeus::skWhite); + mData.RenderThermal(zeus::skWhite, zeus::CColor(0.f, 0.25f), thermFlags); + } else { + mData.AnimationData()->Render(model, flags, {}, nullptr); + } +} + +void CWallCrawlerSwarm::Render(const CStateManager& mgr) const { + u32 drawMask = 0xffffffff; + bool r24 = x560_24_enableLighting; + bool r23 = x560_25_useSoftwareLight; + if (!r24) { + // Ambient 50% grey + } + 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) { + int idx = r21 * 25 + r28 * 5 + r27; + if (CBoid* b = x168_partitionedBoidLists[idx]) { + if (r24) { + zeus::CAABox aabb = BoxForPosition(r27, r28, r21, 0.f); + if (r23) { + if ((idx & 0x3) == (x100_thinkCounter & 0x3)) { + 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) { + 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(); +} + +rstl::optional CWallCrawlerSwarm::GetTouchBounds() const { + return {xe8_aabox}; +} + +void CWallCrawlerSwarm::Touch(CActor& other, CStateManager& mgr) { + CActor::Touch(other, mgr); + if (TCastToPtr proj = other) { + if (x3c4_dVuln.WeaponHurts(proj->GetDamageInfo().GetWeaponMode(), false)) { + if (auto projTb = proj->GetTouchBounds()) { + float f0 = 0.1f + x378_touchRadius; + float f30 = f0 * f0; + for (auto& b : x108_boids) { + if (b.GetActive()) { + 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 (TCastToPtr player = other) { + 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) { + 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); + } + } + zeus::CAABox aabb(b.GetTranslation() - radius, b.GetTranslation() + radius); + if (playerTb->intersects(aabb)) { + if (b.GetActive() && x558_flavor == EFlavor::Parasite) { + 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 diff --git a/Runtime/World/CWallCrawlerSwarm.hpp b/Runtime/World/CWallCrawlerSwarm.hpp index e7335bd03..89335cad6 100644 --- a/Runtime/World/CWallCrawlerSwarm.hpp +++ b/Runtime/World/CWallCrawlerSwarm.hpp @@ -2,65 +2,200 @@ #include "World/CActor.hpp" #include "Collision/CCollisionSurface.hpp" +#include "World/CDamageInfo.hpp" +#include "World/CDamageVulnerability.hpp" +#include "Particle/CElementGen.hpp" namespace urde { +class CAreaCollisionCache; class CWallCrawlerSwarm : public CActor { public: + enum class EFlavor { + Parasite, + Scarab, + Crab + }; class CBoid { - zeus::CTransform x0_; - float x30_ = 0.f; - float x34_ = 0.f; - float x38_ = 0.f; - TUniqueId x3c_ = kInvalidUniqueId; - zeus::CColor x40_ = zeus::CColor(0.3f, 0.3f, 0.3f, 1.f); - u32 x44_ = 0; - float x48_ = 0.f; - float x4c_ = 0.f; - CCollisionSurface x50_ = CCollisionSurface(zeus::CVector3f(0.f, 0.f, 1.f), zeus::CVector3f(0.f, 1.f, 0.f), - zeus::CVector3f(1.f, 0.f, 0.f), -1); - union { - struct { - u32 x7c_unk1 : 8; - u32 x7c_unk2 : 10; - }; - u32 x7c_; - }; + friend class CWallCrawlerSwarm; + zeus::CTransform x0_xf; + zeus::CVector3f x30_velocity; + TUniqueId x3c_targetWaypoint = kInvalidUniqueId; + zeus::CColor x40_ambientLighting = zeus::CColor(0.3f, 0.3f, 0.3f, 1.f); + CBoid* x44_next = nullptr; + float x48_timeToDie = 0.f; + float x4c_timeToExplode = 0.f; + CCollisionSurface x50_surface = + CCollisionSurface(zeus::CVector3f(0.f, 0.f, 1.f), zeus::CVector3f(0.f, 1.f, 0.f), + zeus::CVector3f(1.f, 0.f, 0.f), 0xffffffff); + float x78_health; + int x7c_framesNotOnSurface : 8; + int x7c_idx : 10; + int x7c_remainingLaunchNotOnSurfaceFrames : 8; union { struct { bool x80_24_active : 1; - bool x80_25_ : 1; - bool x80_26_ : 1; - bool x80_27_ : 1; - bool x80_28_ : 1; + bool x80_25_inFrustum : 1; + bool x80_26_launched : 1; + bool x80_27_scarabExplodeTimerEnabled : 1; + bool x80_28_nearPlayer : 1; }; - u32 x80_; + u32 x80_ = 0; }; public: - const zeus::CTransform& GetTransform() const { return x0_; } - const zeus::CVector3f& GetTranslation() const { return x0_.origin; } + CBoid(const zeus::CTransform& xf, int idx) : x0_xf(xf) { + x7c_framesNotOnSurface = 0; + x7c_idx = idx; + } + zeus::CTransform& Transform() { return x0_xf; } + zeus::CVector3f& Translation() { return x0_xf.origin; } + const zeus::CTransform& GetTransform() const { return x0_xf; } + const zeus::CVector3f& GetTranslation() const { return x0_xf.origin; } bool GetActive() const { return x80_24_active; } }; + class CRepulsor { + friend class CWallCrawlerSwarm; + zeus::CVector3f x0_center; + float xc_mag; + public: + CRepulsor(const zeus::CVector3f& center, float mag) : x0_center(center), xc_mag(mag) {} + }; private: zeus::CAABox xe8_aabox = zeus::skNullBox; + s32 x100_thinkCounter = 0; + float x104_occludedTimer = 5.f; std::vector x108_boids; + zeus::CVector3f x118_boundingBoxExtent; + mutable zeus::CVector3f x124_lastOrbitPosition; zeus::CVector3f x130_lastKilledOffset; - int x42c_lockOnId = -1; + float x13c_separationRadius; + float x140_cohesionMagnitude; + float x144_alignmentWeight; + float x148_separationMagnitude; + float x14c_moveToWaypointWeight; + float x150_attractionMagnitude; + float x154_attractionRadius; + float x158_scarabScatterXYVelocity; + float x15c_scarabTimeToExplode; + float x160_animPlaybackSpeed; + float x164_waypointGoalRadius = 3.f; + rstl::reserved_vector x168_partitionedBoidLists; + CBoid* x360_outlierBoidList = nullptr; + float x364_boidGenRate; + float x368_boidGenCooldownTimer = 0.f; + float x36c_crabDamageCooldownTimer = 0.f; + float x370_crabDamageCooldown; + float x374_boidRadius; + float x378_touchRadius; + float x37c_scarabBoxMargin; + float x380_playerTouchRadius; + CDamageInfo x384_crabDamage; + CDamageInfo x3a0_scarabExplodeDamage; + CHealthInfo x3bc_hInfo; + CDamageVulnerability x3c4_dVuln; + s32 x42c_lockOnIdx = -1; + /* Used to be position and normal array pointers */ + //rstl::reserved_vector, 10> x430_; + //rstl::reserved_vector, 10> x484_; + rstl::reserved_vector, 10> x4b0_modelDatas; + CModelData::EWhichModel x4dc_whichModel = CModelData::EWhichModel::Normal; + std::vector x4e0_doorRepulsors; + rstl::reserved_vector, 4> x4f0_particleDescs; + rstl::reserved_vector, 4> x524_particleGens; + s32 x548_numBoids; + s32 x54c_maxCreatedBoids; + u32 x550_createdBoids = 0; + s32 x554_maxLaunches; + EFlavor x558_flavor; + u16 x55c_launchSfx; + u16 x55e_scatterSfx; + bool x560_24_enableLighting : 1; + bool x560_25_useSoftwareLight : 1; + bool x560_26_modelAssetDirty : 1; + + void AllocateSkinnedModels(CStateManager& mgr, CModelData::EWhichModel which); + void AddDoorRepulsors(CStateManager& mgr); + void UpdateParticles(float dt); + int SelectLockOnIdx(CStateManager& mgr) const; + zeus::CAABox GetBoundingBox() const; + TUniqueId GetWaypointForState(EScriptObjectState state, CStateManager& mgr) const; + static zeus::CVector3f ProjectVectorToPlane(const zeus::CVector3f& pt, const zeus::CVector3f& plane) { + return pt - plane * pt.dot(plane); + } + static zeus::CVector3f ProjectPointToPlane(const zeus::CVector3f& p0, const zeus::CVector3f& p1, + const zeus::CVector3f& plane) { + return p0 - (p0 - p1).dot(plane) * plane; + } + bool PointOnSurface(const CCollisionSurface& surf, const zeus::CVector3f& pos, const zeus::CPlane& plane) const; + bool FindBestSurface(const CAreaCollisionCache& ccache, const zeus::CVector3f& pos, float radius, + CCollisionSurface& out) const; + CCollisionSurface FindBestCollisionInBox(CStateManager& mgr, const zeus::CVector3f& wpPos) const; + void CreateBoid(CStateManager& mgr, int idx); + void ExplodeBoid(CBoid& boid, CStateManager& mgr); + void SetExplodeTimers(const zeus::CVector3f& pos, float radius, float minTime, float maxTime); + CBoid* GetListAt(const zeus::CVector3f& pos); + void BuildBoidNearList(const CBoid& boid, float radius, rstl::reserved_vector& nearList); + void ApplySeparation(const CBoid& boid, const rstl::reserved_vector& nearList, + zeus::CVector3f& aheadVec) const; + void ApplySeparation(const CBoid& boid, const zeus::CVector3f& separateFrom, float separationRadius, float separationMagnitude, + zeus::CVector3f& aheadVec) const; + void ScatterScarabBoid(CBoid& boid, CStateManager& mgr) const; + void MoveToWayPoint(CBoid& boid, CStateManager& mgr, zeus::CVector3f& aheadVec) const; + void ApplyCohesion(const CBoid& boid, const rstl::reserved_vector& nearList, + zeus::CVector3f& aheadVec) const; + void ApplyCohesion(const CBoid& boid, const zeus::CVector3f& cohesionFrom, float cohesionRadius, float cohesionMagnitude, + zeus::CVector3f& aheadVec) const; + void ApplyAlignment(const CBoid& boid, const rstl::reserved_vector& nearList, + zeus::CVector3f& aheadVec) const; + void ApplyAttraction(const CBoid& boid, const zeus::CVector3f& attractTo, float attractionRadius, float attractionMagnitude, + zeus::CVector3f& aheadVec) const; + void UpdateBoid(const CAreaCollisionCache& ccache, CStateManager& mgr, float dt, CBoid& boid); + void LaunchBoid(CBoid& boid, const zeus::CVector3f& dir); + void AddParticle(const zeus::CTransform& xf); + void KillBoid(CBoid& boid, CStateManager& mgr, float deathRattleChance, float deadChance); + void UpdatePartition(); + zeus::CVector3f FindClosestCell(const zeus::CVector3f& pos) const; + void UpdateEffects(CStateManager& mgr, CAnimData& aData, int vol); + zeus::CAABox BoxForPosition(int x, int y, int z, float f) const; + void RenderParticles() const; + zeus::CColor SoftwareLight(const CStateManager& mgr, const zeus::CAABox& aabb) const; + void HardwareLight(const CStateManager& mgr, const zeus::CAABox& aabb) const; + void RenderBoid(const CBoid* boid, u32& drawMask, bool thermalHot, const CModelFlags& flags) const; public: - CWallCrawlerSwarm(TUniqueId, bool, std::string_view, const CEntityInfo&, const zeus::CVector3f&, - const zeus::CTransform&, u32, const CAnimRes&, u32, u32, u32, u32, u32, u32, const CDamageInfo&, - const CDamageInfo&, float, float, float, float, u32, u32, float, float, float, float, float, float, - float, float, float, u32, float, float, float, const CHealthInfo&, const CDamageVulnerability&, u32, - u32, const CActorParameters&); + 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); void Accept(IVisitor& visitor); + void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&); + void Think(float, CStateManager&); + void PreRender(CStateManager&, const zeus::CFrustum&); + void AddToRenderer(const zeus::CFrustum&, const CStateManager&) const; + void Render(const CStateManager&) const; + bool CanRenderUnsorted(const CStateManager&) const; + void CalculateRenderBounds(); + rstl::optional GetTouchBounds() const; + void Touch(CActor& other, CStateManager&); + zeus::CVector3f GetOrbitPosition(const CStateManager&) const; + zeus::CVector3f GetAimPosition(const CStateManager&, float) const; const zeus::CVector3f& GetLastKilledOffset() const { return x130_lastKilledOffset; } void ApplyRadiusDamage(const zeus::CVector3f& pos, const CDamageInfo& info, CStateManager& stateMgr) {} const std::vector& GetBoids() const { return x108_boids; } - int GetCurrentLockOnId() const { return x42c_lockOnId; } + int GetCurrentLockOnId() const { return x42c_lockOnIdx; } bool GetLockOnLocationValid(int id) const { return id >= 0 && id < x108_boids.size() && x108_boids[id].GetActive(); } const zeus::CVector3f& GetLockOnLocation(int id) const { return x108_boids[id].GetTranslation(); } }; diff --git a/Runtime/World/ScriptLoader.cpp b/Runtime/World/ScriptLoader.cpp index 4ac8e0cab..fd5b623f4 100644 --- a/Runtime/World/ScriptLoader.cpp +++ b/Runtime/World/ScriptLoader.cpp @@ -2466,46 +2466,51 @@ CEntity* ScriptLoader::LoadWallCrawlerSwarm(CStateManager& mgr, CInputStream& in SScaledActorHead aHead = LoadScaledActorHead(in, mgr); bool active = in.readBool(); CActorParameters aParams = LoadActorParameters(in); - u32 w1 = in.readUint32Big(); - u32 w2 = in.readUint32Big(); - u32 w3 = in.readUint32Big(); - u32 w4 = in.readUint32Big(); - u32 w5 = in.readUint32Big(); - u32 w6 = in.readUint32Big(); - u32 w7 = in.readUint32Big(); - u32 w8 = in.readUint32Big(); - u32 w9 = in.readUint32Big(); - u32 w10 = in.readUint32Big(); - CDamageInfo dInfo1(in); - float f1 = in.readFloatBig(); - CDamageInfo dInfo2(in); - float f2 = in.readFloatBig(); - float f3 = in.readFloatBig(); - float f4 = in.readFloatBig(); - float f5 = in.readFloatBig(); - u32 w11 = in.readUint32Big(); - u32 w12 = in.readUint32Big(); - float f6 = in.readFloatBig(); - float f7 = in.readFloatBig(); - float f8 = in.readFloatBig(); - float f9 = in.readFloatBig(); - float f10 = in.readFloatBig(); - float f11 = in.readFloatBig(); - float f12 = in.readFloatBig(); - float f13 = in.readFloatBig(); - u32 w13 = in.readUint32Big(); - float f14 = in.readFloatBig(); - float f15 = in.readFloatBig(); - float f16 = in.readFloatBig(); + CWallCrawlerSwarm::EFlavor flavor = CWallCrawlerSwarm::EFlavor(in.readUint32Big()); + u32 actor = in.readUint32Big(); + u32 charIdx = in.readUint32Big(); + u32 defaultAnim = in.readUint32Big(); + u32 launchAnim = in.readUint32Big(); + u32 attractAnim = in.readUint32Big(); + u32 part1 = in.readUint32Big(); + u32 part2 = in.readUint32Big(); + u32 part3 = in.readUint32Big(); + u32 part4 = in.readUint32Big(); + CDamageInfo crabDamage(in); + float crabDamageCooldown = in.readFloatBig(); + CDamageInfo scarabExplodeDamage(in); + float boidRadius = in.readFloatBig(); + float touchRadius = in.readFloatBig(); + float playerTouchRadius = in.readFloatBig(); + float animPlaybackSpeed = in.readFloatBig(); + u32 numBoids = in.readUint32Big(); + u32 maxCreatedBoids = in.readUint32Big(); + float separationRadius = in.readFloatBig(); + float cohesionMagnitude = in.readFloatBig(); + float alignmentWeight = in.readFloatBig(); + float separationMagnitude = in.readFloatBig(); + float moveToWaypointWeight = in.readFloatBig(); + float attractionMagnitude = in.readFloatBig(); + float attractionRadius = in.readFloatBig(); + float boidGenRate = in.readFloatBig(); + u32 maxLaunches = in.readUint32Big(); + float scarabBoxMargin = in.readFloatBig(); + float scarabScatterXYVelocity = in.readFloatBig(); + float scarabTimeToExplode = in.readFloatBig(); CHealthInfo hInfo(in); CDamageVulnerability dVulns(in); - u32 w14 = in.readUint32Big(); - u32 w15 = in.readUint32Big(); + u32 launchSfx = in.readUint32Big(); + u32 scatterSfx = in.readUint32Big(); return new CWallCrawlerSwarm(mgr.AllocateUniqueId(), active, aHead.x0_name, info, aHead.x40_scale, - aHead.x10_transform, w1, CAnimRes(w2, w3, zeus::CVector3f(1.5f), w4, true), w5, w6, w7, - w8, w9, w10, dInfo1, dInfo2, f1, f2, f3, f4, w11, w12, f5, f6, f7, f8, f9, f10, f11, f12, - f13, w13, f14, f15, f16, hInfo, dVulns, w14, w15, aParams); + aHead.x10_transform, flavor, + CAnimRes(actor, charIdx, zeus::CVector3f(1.5f), defaultAnim, true), + launchAnim, attractAnim, part1, part2, part3, part4, crabDamage, scarabExplodeDamage, + crabDamageCooldown, boidRadius, touchRadius, playerTouchRadius, numBoids, + maxCreatedBoids, animPlaybackSpeed, separationRadius, cohesionMagnitude, alignmentWeight, + separationMagnitude, moveToWaypointWeight, attractionMagnitude, attractionRadius, + boidGenRate, maxLaunches, scarabBoxMargin, scarabScatterXYVelocity, scarabTimeToExplode, + hInfo, dVulns, launchSfx, scatterSfx, aParams); } CEntity* ScriptLoader::LoadAiJumpPoint(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) { diff --git a/hecl b/hecl index e57b8113a..957a6c685 160000 --- a/hecl +++ b/hecl @@ -1 +1 @@ -Subproject commit e57b8113abfb6fb256da97bf0acb082acca07dfc +Subproject commit 957a6c6851ee80c413cd1ba69795f92a8f336363 diff --git a/lldb-extras/.lldbinit b/lldb-extras/.lldbinit index 5455bf504..c4c9cb3c8 100644 --- a/lldb-extras/.lldbinit +++ b/lldb-extras/.lldbinit @@ -14,7 +14,7 @@ type summary add --summary-string "(${var.mSimd.__s_.__storage_[0]}, ${var.mSimd type summary add --summary-string "${var.angle}" zeus::CRelAngle type summary add --summary-string "(${var.mSimd.__s_.__storage_[0]}, ${var.mSimd.__s_.__storage_[1]}, ${var.mSimd.__s_.__storage_[2]}, ${var.mSimd.__s_.__storage_[3]})" zeus::CQuaternion type summary add --summary-string "pos=${var.position} radius=${var.radius}" zeus::CSphere -type summary add --summary-string "norm=${var.position} d=${var.d}" zeus::CPlane +type summary add --summary-string "norm=(${var.mSimd.__s_.__storage_[0]}, ${var.mSimd.__s_.__storage_[1]}, ${var.mSimd.__s_.__storage_[2]}) d=${var.mSimd.__s_.__storage_[3]}" zeus::CPlane type summary add --summary-string "min=${var.min} max=${var.max}" zeus::CAABox type summary add --summary-string "start=${var.origin} dir=${var.dir}" zeus::CLine type summary add --summary-string "start=${var.x0_start} dir=${var.xc_dir} end=${var.x18_end}" zeus::CLineSeg diff --git a/specter b/specter index a7bd8f823..6d00c4007 160000 --- a/specter +++ b/specter @@ -1 +1 @@ -Subproject commit a7bd8f8235372b986c8793397398c49173f0c1a3 +Subproject commit 6d00c4007a9eb090b6b1e8525766cfd9c6732100