diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 000000000..a3c98d4fa --- /dev/null +++ b/.clang-tidy @@ -0,0 +1 @@ +Checks: '*,-misc-unused-parameters,-modernize-use-trailing-return-type,-readability-named-parameter,-readability-convert-member-functions-to-static,-readability-uppercase-literal-suffix,-readability-magic-numbers,-hicpp-uppercase-literal-suffix,-hicpp-signed-bitwise,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-pro-type-static-cast-downcast,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-owning-memory,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-non-private-member-variables-in-classes,-fuchsia-*,-google-runtime-references' diff --git a/Runtime/Character/CharacterCommon.hpp b/Runtime/Character/CharacterCommon.hpp index 7b0e628c7..51a3d710e 100644 --- a/Runtime/Character/CharacterCommon.hpp +++ b/Runtime/Character/CharacterCommon.hpp @@ -68,7 +68,7 @@ enum class EFallState { Invalid = -1, Zero, One , Two}; enum class EReactionType { Invalid = -1, Zero, One, Two, Three }; -enum class EAdditiveReactionType { Invalid = -1, Electrocution, One, Two, IceBreakout }; +enum class EAdditiveReactionType { Invalid = -1, Electrocution, One, Two, IceBreakout, Four, Five, Six, Seven }; enum class EJumpType { Normal, One, Ambush }; @@ -96,7 +96,7 @@ enum class ELoopState { Invalid = -1, Begin, Loop, End }; enum class ELoopAttackType { Invalid = -1 }; -enum class EGenerateType { Invalid = -1, Zero, One, Two, Three, Four }; +enum class EGenerateType { Invalid = -1, Zero, One, Two, Three, Four, Five }; enum class ESlideType { Invalid = -1, Zero = 0 }; diff --git a/Runtime/MP1/World/CBouncyGrenade.cpp b/Runtime/MP1/World/CBouncyGrenade.cpp new file mode 100644 index 000000000..05fe40b0f --- /dev/null +++ b/Runtime/MP1/World/CBouncyGrenade.cpp @@ -0,0 +1,208 @@ +#include "Runtime/MP1/World/CBouncyGrenade.hpp" + +#include "Runtime/CPlayerState.hpp" +#include "Runtime/CSimplePool.hpp" +#include "Runtime/CStateManager.hpp" +#include "Runtime/GameGlobalObjects.hpp" +#include "Runtime/Graphics/CBooRenderer.hpp" +#include "Runtime/World/CPlayer.hpp" +#include "Runtime/Collision/CCollisionActor.hpp" + +namespace urde::MP1 { +CBouncyGrenade::CBouncyGrenade(TUniqueId uid, std::string_view name, const CEntityInfo& info, + const zeus::CTransform& xf, CModelData&& mData, const CActorParameters& actParams, + TUniqueId parentId, const SBouncyGrenadeData& data, float velocity, + float explodePlayerDistance) +: CPhysicsActor(uid, true, name, info, xf, std::move(mData), {EMaterialTypes::Projectile, EMaterialTypes::Solid}, + mData.GetBounds(), SMoverData{data.GetUnkStruct().GetMass()}, actParams, 0.3f, 0.1f) +, x258_data(data) +, x294_numBounces(data.GetNumBounces()) +, x298_parentId(parentId) +, x2a0_elementGen1(std::make_unique(g_SimplePool->GetObj({'PART', data.GetElementGenId1()}))) +, x2a4_elementGen2(std::make_unique(g_SimplePool->GetObj({'PART', data.GetElementGenId2()}))) +, x2a8_elementGen3(std::make_unique(g_SimplePool->GetObj({'PART', data.GetElementGenId3()}))) +, x2ac_elementGen4(std::make_unique(g_SimplePool->GetObj({'PART', data.GetElementGenId4()}))) +, x2b0_explodePlayerDistance(explodePlayerDistance) +, x2b4_24_exploded(false) +, x2b4_25_(false) { + SetMomentumWR({0.f, 0.f, -GravityConstant() * GetMass()}); + SetVelocityWR(velocity * xf.frontVector()); + x2a0_elementGen1->SetParticleEmission(false); + x2a4_elementGen2->SetParticleEmission(false); + x2a8_elementGen3->SetParticleEmission(false); + x2ac_elementGen4->SetParticleEmission(true); + CMaterialFilter filter = GetMaterialFilter(); + filter.ExcludeList().Add(EMaterialTypes::Character); + SetMaterialFilter(CMaterialFilter::MakeIncludeExclude(filter.IncludeList(), filter.ExcludeList())); +} + +void CBouncyGrenade::AddToRenderer(const zeus::CFrustum& frustum, const CStateManager& mgr) const { + CActor::AddToRenderer(frustum, mgr); + if (!x2b4_24_exploded) { + g_Renderer->AddParticleGen(*x2ac_elementGen4); + return; + } + const auto visor = mgr.GetPlayerState()->GetActiveVisor(mgr); + if (visor == CPlayerState::EPlayerVisor::Combat || visor == CPlayerState::EPlayerVisor::Scan) { + g_Renderer->AddParticleGen(*x2a0_elementGen1); + } else if (visor == CPlayerState::EPlayerVisor::XRay || visor == CPlayerState::EPlayerVisor::Thermal) { + g_Renderer->AddParticleGen(*x2a8_elementGen3); + } +} + +void CBouncyGrenade::CollidedWith(TUniqueId id, const CCollisionInfoList& list, CStateManager& mgr) { + constexpr auto matList = CMaterialList{ + EMaterialTypes::Solid, + EMaterialTypes::Ceiling, + EMaterialTypes::Floor, + EMaterialTypes::Character, + }; + + bool shouldExplode = false; + if (x298_parentId != id) { + const CEntity* const entity = mgr.GetObjectById(id); + if (entity != nullptr) { + if (TCastToConstPtr actor = entity) { + shouldExplode = actor->GetOwnerId() != x298_parentId; + } else { + shouldExplode = true; + } + } + } + if (shouldExplode) { + Explode(mgr, id); + } else { + for (const auto& info : list) { + if (info.GetMaterialLeft().SharesMaterials(matList)) { + if (x294_numBounces == 0) { + Explode(mgr, kInvalidUniqueId); + } else { + const zeus::CVector3f* normal = &info.GetNormalLeft(); + if (GetVelocity().dot(info.GetNormalLeft()) > 0.f) { + normal = &info.GetNormalRight(); + } + const zeus::CVector3f impulse = + (x258_data.GetUnkStruct().GetSpeed() * GetConstantForce().magnitude()) * *normal; + const zeus::CVector3f angle = -x258_data.GetUnkStruct().GetSpeed() * GetAngularMomentum(); + ApplyImpulseWR(impulse, angle); + CSfxManager::AddEmitter(x258_data.GetBounceSfx(), GetTranslation(), zeus::skUp, false, false, 0x7f, + GetAreaIdAlways()); + --x294_numBounces; + } + break; + } + } + } + CPhysicsActor::CollidedWith(id, list, mgr); +} + +std::optional CBouncyGrenade::GetTouchBounds() const { return GetModelData()->GetBounds(GetTransform()); } + +void CBouncyGrenade::Render(const CStateManager& mgr) const { + if (!x2b4_24_exploded) { + GetModelData()->Render(mgr, GetTransform(), nullptr, {0, 0, 3, zeus::skWhite}); + } else if (mgr.GetPlayerState()->GetActiveVisor(mgr) == CPlayerState::EPlayerVisor::XRay) { + CGraphics::SetFog(ERglFogMode::PerspLin, 0.f, 75.f, zeus::skBlack); + x2a4_elementGen2->Render(); + mgr.SetupFogForArea(GetAreaIdAlways()); + } +} + +void CBouncyGrenade::Think(float dt, CStateManager& mgr) { + if (GetActive()) { + const zeus::CTransform& orientation = GetTransform().getRotation(); + const zeus::CVector3f& translation = GetTranslation(); + const zeus::CVector3f& scale = GetModelData()->GetScale(); + auto UpdateElementGen = [ orientation, translation, scale, dt ](CElementGen & gen) constexpr { + gen.SetOrientation(orientation); + gen.SetGlobalTranslation(translation); + gen.SetGlobalScale(scale); + gen.Update(dt); + }; + if (x2b4_24_exploded) { + Stop(); + UpdateElementGen(*x2a0_elementGen1); + UpdateElementGen(*x2a4_elementGen2); + UpdateElementGen(*x2a8_elementGen3); + } else { + UpdateElementGen(*x2ac_elementGen4); + } + x29c_ += dt; + if (x29c_ > 0.3f) { + x2b4_25_ = true; + } + const zeus::CVector3f& playerDistance = mgr.GetPlayer().GetTranslation() + + zeus::CVector3f{0.f, 0.f, 0.5f * mgr.GetPlayer().GetEyeHeight()} - + translation; + if (playerDistance.magSquared() < x2b0_explodePlayerDistance * x2b0_explodePlayerDistance) { + Explode(mgr, kInvalidUniqueId); + } + } + if (x2a0_elementGen1->IsSystemDeletable() && x2a4_elementGen2->IsSystemDeletable() && + x2a8_elementGen3->IsSystemDeletable()) { + mgr.FreeScriptObject(GetUniqueId()); + } +} + +void CBouncyGrenade::Touch(CActor& act, CStateManager& mgr) { CActor::Touch(act, mgr); } + +void CBouncyGrenade::Explode(CStateManager& mgr, TUniqueId uid) { + if (x2b4_24_exploded) { + return; + } + + x2b4_24_exploded = true; + CSfxManager::AddEmitter(x258_data.GetExplodeSfx(), GetTranslation(), zeus::skUp, false, false, 0x7f, + GetAreaIdAlways()); + x2a0_elementGen1->SetParticleEmission(true); + x2a4_elementGen2->SetParticleEmission(true); + x2a8_elementGen3->SetParticleEmission(true); + x2ac_elementGen4->SetParticleEmission(false); + + const CDamageInfo& dInfo = x258_data.GetDamageInfo(); + { + bool isParent = x298_parentId == uid; + if (TCastToConstPtr actor = mgr.GetObjectById(uid)) { + isParent = x298_parentId == actor->GetOwnerId(); + } + if (uid != kInvalidUniqueId && !isParent) { + mgr.ApplyDamage(GetUniqueId(), uid, GetUniqueId(), dInfo, CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), + zeus::skZero3f); + } + } + + const float radius = dInfo.GetRadius(); + if (radius > 1.f) { + const zeus::CVector3f& pos = GetTranslation(); + const CMaterialFilter filter = CMaterialFilter::MakeInclude({EMaterialTypes::Player, EMaterialTypes::Character}); + rstl::reserved_vector nearList; + mgr.BuildNearList(nearList, {pos - radius, pos + radius}, filter, nullptr); + + for (const auto& id : nearList) { + bool isParent = x298_parentId == id; + if (TCastToConstPtr cActor = mgr.GetObjectById(id)) { + isParent = x298_parentId == cActor->GetOwnerId(); + } + if (isParent) { + continue; + } + + const auto* actor = static_cast(mgr.GetObjectById(id)); + if (actor == nullptr) { + continue; + } + + const float magnitude = (actor->GetTranslation() - GetTranslation()).magnitude(); + if (radius <= magnitude) { + continue; + } + + float scale = (radius - magnitude) / radius; + const CDamageInfo info{dInfo.GetWeaponMode(), scale * dInfo.GetDamage(), radius, + scale * dInfo.GetKnockBackPower()}; + mgr.ApplyDamage(GetUniqueId(), id, GetUniqueId(), info, CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), + zeus::skZero3f); + } + } +} +} // namespace urde::MP1 diff --git a/Runtime/MP1/World/CBouncyGrenade.hpp b/Runtime/MP1/World/CBouncyGrenade.hpp new file mode 100644 index 000000000..1ea0f9533 --- /dev/null +++ b/Runtime/MP1/World/CBouncyGrenade.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include "Runtime/World/CPhysicsActor.hpp" +#include "Runtime/World/CDamageInfo.hpp" +#include "Runtime/Particle/CElementGen.hpp" + +#include "TCastTo.hpp" // Generated file, do not modify include path + +#include + +namespace urde::MP1 { +struct SGrenadeUnknownStruct { +private: + float x0_mass; + float x4_speed; // wrong name probably + +public: + explicit SGrenadeUnknownStruct(CInputStream& in) : x0_mass(in.readFloatBig()), x4_speed(in.readFloatBig()) {} + + [[nodiscard]] float GetMass() const { return x0_mass; } + [[nodiscard]] float GetSpeed() const { return x4_speed; } +}; + +struct SBouncyGrenadeData { +private: + SGrenadeUnknownStruct x0_; + CDamageInfo x8_damageInfo; + CAssetId x24_elementGenId1; + CAssetId x28_elementGenId2; + CAssetId x2c_elementGenId3; + CAssetId x30_elementGenId4; + u32 x34_numBounces; + u16 x38_bounceSfx; + u16 x3a_explodeSfx; + +public: + SBouncyGrenadeData(const SGrenadeUnknownStruct& unkStruct, const CDamageInfo& damageInfo, CAssetId w1, CAssetId w2, + CAssetId w3, CAssetId w4, u32 w5, u16 s1, u16 s2) + : x0_(unkStruct) + , x8_damageInfo(damageInfo) + , x24_elementGenId1(w1) + , x28_elementGenId2(w2) + , x2c_elementGenId3(w3) + , x30_elementGenId4(w4) + , x34_numBounces(w5) + , x38_bounceSfx(s1) + , x3a_explodeSfx(s2){}; + + [[nodiscard]] const SGrenadeUnknownStruct& GetUnkStruct() const { return x0_; } + [[nodiscard]] const CDamageInfo& GetDamageInfo() const { return x8_damageInfo; } + [[nodiscard]] CAssetId GetElementGenId1() const { return x24_elementGenId1; } + [[nodiscard]] CAssetId GetElementGenId2() const { return x28_elementGenId2; } + [[nodiscard]] CAssetId GetElementGenId3() const { return x2c_elementGenId3; } + [[nodiscard]] CAssetId GetElementGenId4() const { return x30_elementGenId4; } + [[nodiscard]] u32 GetNumBounces() const { return x34_numBounces; } + [[nodiscard]] u16 GetBounceSfx() const { return x38_bounceSfx; } + [[nodiscard]] u16 GetExplodeSfx() const { return x3a_explodeSfx; } +}; + +class CBouncyGrenade : public CPhysicsActor { +private: + SBouncyGrenadeData x258_data; + u32 x294_numBounces; + TUniqueId x298_parentId; + float x29c_ = 0.f; + std::unique_ptr x2a0_elementGen1; + std::unique_ptr x2a4_elementGen2; + std::unique_ptr x2a8_elementGen3; + std::unique_ptr x2ac_elementGen4; + float x2b0_explodePlayerDistance; + bool x2b4_24_exploded : 1; + bool x2b4_25_ : 1; + +public: + CBouncyGrenade(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, + CModelData&& mData, const CActorParameters& actParams, TUniqueId parentId, + const SBouncyGrenadeData& data, float velocity, float explodePlayerDistance); + + void Accept(IVisitor& visitor) override { visitor.Visit(this); } + void AddToRenderer(const zeus::CFrustum& frustum, const CStateManager& mgr) const override; + void CollidedWith(TUniqueId id, const CCollisionInfoList& list, CStateManager& mgr) override; + [[nodiscard]] std::optional GetTouchBounds() const override; + void Render(const CStateManager& mgr) const override; + void Think(float dt, CStateManager& mgr) override; + void Touch(CActor& act, CStateManager& mgr) override; + +private: + void Explode(CStateManager& mgr, TUniqueId uid); +}; +} // namespace urde::MP1 diff --git a/Runtime/MP1/World/CElitePirate.cpp b/Runtime/MP1/World/CElitePirate.cpp index d0316d5e5..a7cc4272f 100644 --- a/Runtime/MP1/World/CElitePirate.cpp +++ b/Runtime/MP1/World/CElitePirate.cpp @@ -1,48 +1,1214 @@ #include "Runtime/MP1/World/CElitePirate.hpp" +#include "Runtime/Collision/CCollisionActor.hpp" +#include "Runtime/Collision/CCollisionActorManager.hpp" +#include "Runtime/CSimplePool.hpp" +#include "Runtime/CStateManager.hpp" +#include "Runtime/GameGlobalObjects.hpp" +#include "Runtime/MP1/World/CGrenadeLauncher.hpp" +#include "Runtime/Weapon/CGameProjectile.hpp" +#include "Runtime/World/CExplosion.hpp" +#include "Runtime/World/CPatternedInfo.hpp" +#include "Runtime/World/CPlayer.hpp" +#include "Runtime/World/CWorld.hpp" #include "Runtime/World/ScriptLoader.hpp" +#include "TCastTo.hpp" // Generated file, do not modify include path + namespace urde::MP1 { +namespace { +constexpr std::array skLeftArmJointList{{ + {"L_shoulder", "L_elbow", 1.f, 1.5f}, + {"L_wrist", "L_elbow", 0.9f, 1.3f}, + {"L_knee", "L_ankle", 0.9f, 1.3f}, +}}; + +constexpr std::array skRightArmJointList{{ + {"R_shoulder", "R_elbow", 1.f, 1.5f}, + {"R_wrist", "R_elbow", 0.9f, 1.3f}, + {"R_knee", "R_ankle", 0.9f, 1.3f}, +}}; + +constexpr std::array skSphereJointList{{ + {"Head_1", 1.2f}, + {"L_Palm_LCTR", 1.5f}, + {"R_Palm_LCTR", 1.5f}, + {"Spine_1", 1.5f}, + {"Collar", 1.2f}, + {"L_Ball", 0.8f}, + {"R_Ball", 0.8f}, +}}; +} // namespace + CElitePirateData::CElitePirateData(CInputStream& in, u32 propCount) -: x0_(in.readFloatBig()) -, x4_(in.readFloatBig()) +: x0_tauntInterval(in.readFloatBig()) +, x4_tauntVariance(in.readFloatBig()) , x8_(in.readFloatBig()) , xc_(in.readFloatBig()) -, x10_(in.readFloatBig()) -, x14_(in.readFloatBig()) -, x18_(in.readFloatBig()) +, x10_attackChance(in.readFloatBig()) +, x14_shotAtTime(in.readFloatBig()) +, x18_shotAtTimeVariance(in.readFloatBig()) , x1c_(in.readFloatBig()) , x20_(in) -, x24_(CSfxManager::TranslateSFXID(in.readUint32Big())) -, x28_(ScriptLoader::LoadActorParameters(in)) -, x90_(ScriptLoader::LoadAnimationParameters(in)) +, x24_sfxAbsorb(CSfxManager::TranslateSFXID(in.readUint32Big())) +, x28_launcherActParams(ScriptLoader::LoadActorParameters(in)) +, x90_launcherAnimParams(ScriptLoader::LoadAnimationParameters(in)) , x9c_(in) , xa0_(CSfxManager::TranslateSFXID(in.readUint32Big())) , xa4_(in) , xa8_(in) -, xc4_(in.readFloatBig()) +, xc4_launcherHp(in.readFloatBig()) , xc8_(in) , xcc_(in) , xd0_(in) , xd4_(in) -, xd8_(in.readFloatBig()) -, xdc_(in.readFloatBig()) -, xe0_(in.readFloatBig()) -, xe4_(in.readFloatBig()) -, xe8_(zeus::degToRad(in.readFloatBig())) -, xec_(zeus::degToRad(in.readFloatBig())) -, xf0_(in.readUint32Big()) +, xd8_(in) +, xe0_trajectoryInfo(in) +, xf0_grenadeNumBounces(in.readUint32Big()) , xf4_(CSfxManager::TranslateSFXID(in.readUint32Big())) +, xf6_(CSfxManager::TranslateSFXID(in.readUint32Big())) , xf8_(in) , xfc_(in) , x118_(in) , x11c_(CSfxManager::TranslateSFXID(in.readUint32Big())) , x11e_(in.readBool()) -, x11f_(propCount < 24 ? true : in.readBool()) {} +, x11f_(propCount < 42 ? true : in.readBool()) {} CElitePirate::CElitePirate(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms, - const CElitePirateData& eliteData) + CElitePirateData data) : CPatterned(ECharacter::ElitePirate, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo, - EMovementType::Ground, EColliderType::One, EBodyType::BiPedal, actParms, EKnockBackVariant::Large) {} + EMovementType::Ground, EColliderType::One, EBodyType::BiPedal, actParms, EKnockBackVariant::Large) +, x56c_vulnerability(pInfo.GetDamageVulnerability()) +, x5d8_data(std::move(data)) +, x6f8_boneTracking(*GetModelData()->GetAnimationData(), "Head_1", zeus::degToRad(80.f), zeus::degToRad(180.f), + EBoneTrackingFlags::None) +, x738_collisionAabb(GetBoundingBox(), GetMaterialList()) +, x7a0_initialSpeed(x3b4_speed) +, x7d0_pathFindSearch(nullptr, 1, pInfo.GetPathfindingIndex(), 1.f, 1.f) +, x8c0_(5.f) +, x988_24_damageOn(false) +, x988_25_attackingRightClaw(false) +, x988_26_attackingLeftClaw(false) +, x988_27_shotAt(false) +, x988_28_alert(false) +, x988_29_shockWaveAnim(false) +, x988_30_calledForBackup(false) +, x988_31_running(false) +, x989_24_onPath(false) { + if (x5d8_data.GetX20().IsValid()) { + x760_energyAbsorbDesc = g_SimplePool->GetObj({SBIG('PART'), x5d8_data.GetX20()}); + } + + x460_knockBackController.SetEnableFreeze(false); + x460_knockBackController.SetAutoResetImpulse(false); + x460_knockBackController.SetEnableBurn(false); + x460_knockBackController.SetEnableExplodeDeath(false); + x460_knockBackController.SetEnableLaggedBurnDeath(false); + SetupPathFindSearch(); +} + +void CElitePirate::Accept(IVisitor& visitor) { visitor.Visit(this); } + +void CElitePirate::Think(float dt, CStateManager& mgr) { + if (GetActive()) { + CPatterned::Think(dt, mgr); + x6f8_boneTracking.Update(dt); + if (HasWeakPointHead()) { + x730_collisionActorMgrHead->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace); + } + x5d4_collisionActorMgr->Update(dt, mgr, CCollisionActorManager::EUpdateOptions::ObjectSpace); + if (IsAttractingEnergy() && x5d8_data.GetX11F()) { + x3b4_speed = 2.f * x7a0_initialSpeed; + } else { + x3b4_speed = x7a0_initialSpeed; + } + UpdateTimers(dt); + UpdatePositionHistory(); + UpdateActorTransform(mgr, x772_launcherId, "grenadeLauncher_LCTR"sv); + UpdateHealthInfo(mgr); + x328_31_energyAttractor = IsAttractingEnergy(); + } +} + +void CElitePirate::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) { + bool shouldPass = true; + + switch (msg) { + case EScriptObjectMessage::Activate: { + if (HasWeakPointHead()) { + x730_collisionActorMgrHead->SetActive(mgr, true); + } + if (CEntity* ent = mgr.ObjectById(x772_launcherId)) { + ent->SetActive(true); + } + break; + } + case EScriptObjectMessage::Deactivate: { + if (HasWeakPointHead()) { + x730_collisionActorMgrHead->SetActive(mgr, false); + } + x5d4_collisionActorMgr->SetActive(mgr, false); + if (CEntity* ent = mgr.ObjectById(x772_launcherId)) { + ent->SetActive(false); + } + break; + } + case EScriptObjectMessage::Alert: + x988_28_alert = true; + break; + case EScriptObjectMessage::Touched: { + if (HealthInfo(mgr)->GetHP() <= 0.f) { + break; + } + TCastToPtr actor = mgr.ObjectById(uid); + if (!actor) { + if (uid == x772_launcherId && x772_launcherId != kInvalidUniqueId) { + SetShotAt(true, mgr); + } + break; + } + const TUniqueId& touchedUid = actor->GetLastTouchedObject(); + if (touchedUid != mgr.GetPlayer().GetUniqueId()) { + if (TCastToPtr(mgr.ObjectById(touchedUid))) { + SetShotAt(true, mgr); + } + break; + } + if (!x988_24_damageOn) { + if (x420_curDamageRemTime <= 0.f) { + CDamageInfo info = GetContactDamage(); + info.SetDamage(0.5f * info.GetDamage()); + mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), info, + CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), zeus::skZero3f); + x420_curDamageRemTime = x424_damageWaitTime; + } + break; + } + if ((!x988_25_attackingRightClaw || !IsArmClawCollider(uid, x774_collisionRJointIds)) && + (!x988_26_attackingLeftClaw || !IsArmClawCollider(uid, x788_collisionLJointIds))) { + break; + } + mgr.ApplyDamage(GetUniqueId(), mgr.GetPlayer().GetUniqueId(), GetUniqueId(), GetContactDamage(), + CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), zeus::skZero3f); + x420_curDamageRemTime = x424_damageWaitTime; + x988_24_damageOn = false; + break; + } + case EScriptObjectMessage::Registered: { + x450_bodyController->Activate(mgr); + SetupCollisionManager(mgr); + x772_launcherId = mgr.AllocateUniqueId(); + CreateGrenadeLauncher(mgr, x772_launcherId); + const auto& bodyStateInfo = x450_bodyController->GetBodyStateInfo(); + if (bodyStateInfo.GetMaxSpeed() > 0.f) { + x7a4_steeringSpeed = (0.99f * bodyStateInfo.GetLocomotionSpeed(pas::ELocomotionAnim::Walk)) / bodyStateInfo.GetMaxSpeed(); + } + x450_bodyController->GetCommandMgr().SetSteeringBlendMode(ESteeringBlendMode::FullSpeed); + x450_bodyController->GetCommandMgr().SetSteeringSpeedRange(x7a4_steeringSpeed, x7a4_steeringSpeed); + break; + } + case EScriptObjectMessage::Deleted: + if (HasWeakPointHead()) { + x730_collisionActorMgrHead->Destroy(mgr); + } + x5d4_collisionActorMgr->Destroy(mgr); + mgr.FreeScriptObject(x772_launcherId); + break; + case EScriptObjectMessage::InitializedInArea: + x7d0_pathFindSearch.SetArea(mgr.GetWorld()->GetAreaAlways(GetAreaIdAlways())->GetPostConstructed()->x10bc_pathArea); + break; + case EScriptObjectMessage::Damage: + shouldPass = false; + if (TCastToPtr actor = mgr.ObjectById(uid)) { + if (TCastToPtr projectile = mgr.ObjectById(actor->GetLastTouchedObject())) { + if (uid == x770_collisionHeadId) { + x428_damageCooldownTimer = 0.33f; + const auto& damageInfo = projectile->GetDamageInfo(); + KnockBack(projectile->GetTranslation() - projectile->GetPreviousPos(), mgr, damageInfo, + EKnockBackType::Radius, false, damageInfo.GetKnockBackPower()); + CPatterned::AcceptScriptMsg(msg, uid, mgr); + } else if (uid == x79c_ && x760_energyAbsorbDesc->IsLoaded()) { + CreateEnergyAbsorb(mgr, projectile->GetTransform()); + } + SetShotAt(true, mgr); + } + } else if (uid == x772_launcherId && x772_launcherId != kInvalidUniqueId) { + x450_bodyController->GetCommandMgr().DeliverCmd( + CBCKnockBackCmd(GetTransform().frontVector(), pas::ESeverity::Eight)); + } else { + ApplyDamageToHead(mgr, uid); + } + break; + case EScriptObjectMessage::InvulnDamage: { + SetShotAt(true, mgr); + if (!TCastToPtr(mgr.ObjectById(uid))) { + ApplyDamageToHead(mgr, uid); + } + break; + } + default: + break; + } + + if (shouldPass) { + CPatterned::AcceptScriptMsg(msg, uid, mgr); + } +} + +void CElitePirate::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) { + CPatterned::PreRender(mgr, frustum); + auto modelData = GetModelData(); + x6f8_boneTracking.PreRender(mgr, *modelData->GetAnimationData(), GetTransform(), modelData->GetScale(), + *x450_bodyController); + auto numMaterialSets = modelData->GetNumMaterialSets(); + xb4_drawFlags.x1_matSetIdx = + numMaterialSets - 1 < x7cc_activeMaterialSet ? numMaterialSets - 1 : x7cc_activeMaterialSet; +} + +const CDamageVulnerability* CElitePirate::GetDamageVulnerability() const { + return &CDamageVulnerability::PassThroughVulnerabilty(); +} + +const CDamageVulnerability* CElitePirate::GetDamageVulnerability(const zeus::CVector3f& pos, const zeus::CVector3f& dir, + const CDamageInfo& dInfo) const { + return &CDamageVulnerability::PassThroughVulnerabilty(); +} + +zeus::CVector3f CElitePirate::GetOrbitPosition(const CStateManager& mgr) const { + if (x772_launcherId != kInvalidUniqueId && + mgr.GetPlayerState()->GetCurrentVisor() == CPlayerState::EPlayerVisor::Thermal) { + if (const auto* actor = static_cast(mgr.GetObjectById(x772_launcherId))) { + return GetLockOnPosition(actor); + } + } + if (HasWeakPointHead()) { + if (TCastToConstPtr actor = mgr.GetObjectById(x770_collisionHeadId)) { + return actor->GetTranslation(); + } + } + return GetLctrTransform("lockon_target_LCTR").origin; +} + +zeus::CVector3f CElitePirate::GetAimPosition(const CStateManager& mgr, float) const { + const std::shared_ptr& playerState = mgr.GetPlayerState(); + if (x5d4_collisionActorMgr->GetActive() && playerState->IsFiringComboBeam() && + playerState->GetCurrentBeam() == CPlayerState::EBeamId::Wave) { + if (TCastToConstPtr actor = mgr.GetObjectById(x79c_)) { + return actor->GetTranslation(); + } + } + return GetOrbitPosition(mgr); +} + +void CElitePirate::DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) { + bool handled = false; + switch (type) { + case EUserEventType::Projectile: + if (x772_launcherId != kInvalidUniqueId) { + CEntity* launcher = mgr.ObjectById(x772_launcherId); + mgr.SendScriptMsg(launcher, GetUniqueId(), EScriptObjectMessage::Action); + } + handled = true; + break; + case EUserEventType::DamageOn: + handled = true; + x988_24_damageOn = true; + break; + case EUserEventType::DamageOff: + handled = true; + x988_24_damageOn = false; + break; + case EUserEventType::ScreenShake: + HasWeakPointHead(); + handled = true; + break; + case EUserEventType::BeginAction: { + const zeus::CVector3f& origin = GetTranslation(); + const zeus::CVector3f& front = GetTransform().frontVector(); + float dot = (GetLctrTransform(node.GetLocatorName()).origin - origin).dot(front); + const zeus::CTransform& xf = zeus::CTransform::Translate({ + origin.x() + dot * front.x(), + origin.y() + dot * front.y(), + origin.z(), + }); + mgr.AddObject(new CShockWave(mgr.AllocateUniqueId(), "Shock Wave", {GetAreaIdAlways(), CEntity::NullConnectionList}, + xf, GetUniqueId(), GetShockWaveData(), IsElitePirate() ? 2.f : 1.3f, + IsElitePirate() ? 0.4f : 0.5f)); + handled = true; + break; + } + case EUserEventType::BecomeShootThrough: + if (HasWeakPointHead()) { + u32 numCollisionActors = x730_collisionActorMgrHead->GetNumCollisionActors(); + for (u32 i = 0; i < numCollisionActors; ++i) { + const auto& description = x730_collisionActorMgrHead->GetCollisionDescFromIndex(i); + if (TCastToPtr actor = mgr.ObjectById(description.GetCollisionActorId())) { + actor->AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr); + } + } + } + handled = true; + break; + default: + break; + } + if (!handled) { + CPatterned::DoUserAnimEvent(mgr, node, type, dt); + } +} + +const CCollisionPrimitive* CElitePirate::GetCollisionPrimitive() const { return &x738_collisionAabb; } + +void CElitePirate::KnockBack(const zeus::CVector3f& pos, CStateManager& mgr, const CDamageInfo& info, + EKnockBackType type, bool inDeferred, float magnitude) { + if (!CanKnockBack(info)) { + return; + } + CPatterned::KnockBack(pos, mgr, info, type, inDeferred, magnitude); + if (info.GetWeaponMode().IsComboed() && info.GetWeaponMode().GetType() == EWeaponType::Ice) { + Freeze(mgr, zeus::skZero3f, GetTransform().transposeRotate(pos), 1.5f); + } +} + +void CElitePirate::TakeDamage(const zeus::CVector3f& pos, float) {} + +void CElitePirate::Patrol(CStateManager& mgr, EStateMsg msg, float dt) { + if (msg == EStateMsg::Activate) { + x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed); + x400_24_hitByPlayerProjectile = false; + x989_24_onPath = false; + } + CPatterned::Patrol(mgr, msg, dt); +} + +void CElitePirate::PathFind(CStateManager& mgr, EStateMsg msg, float dt) { + if (msg == EStateMsg::Activate) { + x989_24_onPath = true; + x988_28_alert = false; + x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed); + x6f8_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId()); + x6f8_boneTracking.SetActive(true); + UpdateDestPos(mgr); + CPatterned::PathFind(mgr, msg, dt); + x7bc_tauntTimer = x5d8_data.GetTauntVariance() * mgr.GetActiveRandom()->Float() + x5d8_data.GetTauntInterval(); + if (TooClose(mgr, 0.f)) { + x450_bodyController->GetCommandMgr().ClearLocomotionCmds(); + } + } else if (msg == EStateMsg::Update) { + if (x7bc_tauntTimer > 0.f) { + x7bc_tauntTimer -= dt; + } + if (!TooClose(mgr, 0.f) && !PathShagged(mgr, 0.f)) { + CPatterned::PathFind(mgr, msg, dt); + } else if (PathShagged(mgr, 0.f)) { + const zeus::CVector3f& move = x8c0_.GetValue(GetTranslation(), GetTransform().frontVector()); + if (move != zeus::skZero3f) { + x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, zeus::skZero3f, 1.f)); + } + } else if (ShouldTurn(mgr, 0.f)) { + const zeus::CVector3f& aim = + mgr.GetPlayer().GetAimPosition(mgr, 0.5f * GetModelData()->GetAnimationData()->GetSpeedScale()); + const zeus::CVector3f& face = aim - GetTranslation(); + if (face.canBeNormalized()) { + x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(zeus::skZero3f, face.normalized(), 1.f)); + } + } + } else if (msg == EStateMsg::Deactivate) { + x6f8_boneTracking.SetActive(false); + } +} + +void CElitePirate::TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) { + if (msg == EStateMsg::Activate) { + x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed); + if (HasPatrolPath(mgr, 0.f)) { + CPatterned::Patrol(mgr, msg, dt); + UpdateDest(mgr); + } else { + SetDestPos(x3a0_latestLeashPosition); + } + x8b4_targetDestPos = x2e0_destPos; + if (GetSearchPath() != nullptr) { + CPatterned::PathFind(mgr, msg, dt); + } + } else if (msg == EStateMsg::Update) { + if (PathShagged(mgr, 0.f)) { + const zeus::CVector3f& move = x45c_steeringBehaviors.Arrival(*this, x8b4_targetDestPos, 25.f); + x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, zeus::skZero3f, 1.f)); + } else { + CPatterned::PathFind(mgr, msg, dt); + } + } else if (msg == EStateMsg::Deactivate) { + x988_28_alert = false; + } +} + +void CElitePirate::Halt(CStateManager& mgr, EStateMsg msg, float) { + if (msg == EStateMsg::Activate) { + x450_bodyController->SetLocomotionType(pas::ELocomotionType::Lurk); + x989_24_onPath = false; + CMaterialFilter filter = GetMaterialFilter(); + filter.ExcludeList().Add( + {EMaterialTypes::Wall, EMaterialTypes::Ceiling, EMaterialTypes::AIBlock, EMaterialTypes::Character}); + SetMaterialFilter(filter); + } else if (msg == EStateMsg::Deactivate) { + CMaterialFilter filter = GetMaterialFilter(); + filter.ExcludeList().Remove( + {EMaterialTypes::Wall, EMaterialTypes::Ceiling, EMaterialTypes::AIBlock, EMaterialTypes::Character}); + SetMaterialFilter(filter); + } +} + +void CElitePirate::Run(CStateManager& mgr, EStateMsg msg, float dt) { + if (msg == EStateMsg::Activate) { + x988_31_running = true; + x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed); + x450_bodyController->GetCommandMgr().SetSteeringSpeedRange(1.f, 1.f); + x6f8_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId()); + x6f8_boneTracking.SetActive(true); + UpdateDestPos(mgr); + CPatterned::PathFind(mgr, msg, dt); + } else if (msg == EStateMsg::Update) { + if (PathShagged(mgr, 0.f)) { + auto move = x8c0_.GetValue(GetTranslation(), GetTransform().frontVector()); + if (move != zeus::skZero3f) { + x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, zeus::skZero3f, 1.f)); + } else if (ShouldTurn(mgr, 0.f)) { + const zeus::CVector3f& aim = + mgr.GetPlayer().GetAimPosition(mgr, 0.5f * GetModelData()->GetAnimationData()->GetSpeedScale()); + auto face = aim - GetTranslation(); + if (face.canBeNormalized()) { + x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(zeus::skZero3f, face.normalized(), 1.f)); + } + } + } else { + CPatterned::PathFind(mgr, msg, dt); + } + } else if (msg == EStateMsg::Deactivate) { + x988_31_running = false; + x6f8_boneTracking.SetActive(false); + x450_bodyController->GetCommandMgr().SetSteeringSpeedRange(x7a4_steeringSpeed, x7a4_steeringSpeed); + } +} + +void CElitePirate::Generate(CStateManager& mgr, EStateMsg msg, float) { + if (msg == EStateMsg::Activate) { + x568_state = EState::One; + } else if (msg == EStateMsg::Update) { + if (x568_state == EState::Zero) { + if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Generate) { + x568_state = EState::Two; + } else { + x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Zero)); + } + } else if (x568_state == EState::One) { + if (ShouldTurn(mgr, 0.f)) { + const auto& face = mgr.GetPlayer().GetTranslation() - GetTranslation(); + if (face.canBeNormalized()) { + x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(zeus::skZero3f, face.normalized(), 1.f)); + } + } else { + x568_state = EState::Zero; + } + } else if (x568_state == EState::Two && x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Generate) { + x568_state = EState::Over; + } + } else if (msg == EStateMsg::Deactivate) { + SetShotAt(false, mgr); + SetLaunchersActive(mgr, true); + } +} + +void CElitePirate::Attack(CStateManager& mgr, EStateMsg msg, float) { + if (msg == EStateMsg::Activate) { + x568_state = EState::Zero; + ExtendTouchBounds(mgr, x774_collisionRJointIds, zeus::CVector3f(2.f)); + if (x64_modelData->GetNumMaterialSets() > 1) { + x7cc_activeMaterialSet = 1; + } + } else if (msg == EStateMsg::Update) { + if (x568_state == EState::Zero) { + if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::MeleeAttack) { + x568_state = EState::One; + x988_25_attackingRightClaw = true; + x7c8_currAnimId = x450_bodyController->GetCurrentAnimId(); + } else { + x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::One)); + } + } else if (x568_state == EState::One) { + if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::MeleeAttack) { + if (x7c8_currAnimId == x450_bodyController->GetCurrentAnimId()) { + x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation()); + if (ShouldAttack(mgr, 0.f)) { + x450_bodyController->GetCommandMgr().DeliverCmd(CBCMeleeAttackCmd(pas::ESeverity::Two)); + } + } else { + x568_state = EState::Two; + x988_25_attackingRightClaw = false; + x988_26_attackingLeftClaw = true; + ExtendTouchBounds(mgr, x774_collisionRJointIds, zeus::skZero3f); + ExtendTouchBounds(mgr, x788_collisionLJointIds, zeus::CVector3f(2.f)); + } + } else { + x568_state = EState::Over; + } + } else if (x568_state == EState::Two) { + if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::MeleeAttack) { + x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation() - GetTranslation()); + } else { + x568_state = EState::Over; + } + } + } else if (msg == EStateMsg::Deactivate) { + CheckAttackChance(mgr); + x988_24_damageOn = false; + x988_26_attackingLeftClaw = false; + x988_25_attackingRightClaw = false; + x7c8_currAnimId = -1; + ExtendTouchBounds(mgr, x774_collisionRJointIds, zeus::skZero3f); + ExtendTouchBounds(mgr, x788_collisionLJointIds, zeus::skZero3f); + x7cc_activeMaterialSet = 0; + } +} + +void CElitePirate::Taunt(CStateManager& mgr, EStateMsg msg, float dt) { CAi::Taunt(mgr, msg, dt); } + +void CElitePirate::ProjectileAttack(CStateManager& mgr, EStateMsg msg, float) { + if (msg == EStateMsg::Activate) { + x568_state = EState::Zero; + } else if (msg == EStateMsg::Update) { + const zeus::CVector3f& playerPos = mgr.GetPlayer().GetTranslation(); + if (x568_state == EState::Zero) { + if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) { + x568_state = EState::Two; + } else { + x450_bodyController->GetCommandMgr().DeliverCmd(CBCProjectileAttackCmd(pas::ESeverity::One, playerPos, false)); + } + } else if (x568_state == EState::Two) { + if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) { + x450_bodyController->GetCommandMgr().DeliverTargetVector(playerPos - GetTranslation()); + } else { + x568_state = EState::Over; + } + } + } else if (msg == EStateMsg::Deactivate) { + CheckAttackChance(mgr); + } +} + +void CElitePirate::SpecialAttack(CStateManager& mgr, EStateMsg msg, float) { + if (msg == EStateMsg::Activate) { + x568_state = EState::Zero; + } else if (msg == EStateMsg::Update) { + if (x568_state == EState::Zero) { + if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) { + x568_state = EState::Two; + x988_29_shockWaveAnim = true; + } else { + x450_bodyController->GetCommandMgr().DeliverCmd( + CBCProjectileAttackCmd(pas::ESeverity::Two, mgr.GetPlayer().GetTranslation(), false)); + } + } else if (x568_state == EState::Two) { + if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::ProjectileAttack) { + x450_bodyController->GetCommandMgr().DeliverTargetVector(mgr.GetPlayer().GetTranslation()); + } else { + x568_state = EState::Over; + } + } + } else if (msg == EStateMsg::Deactivate) { + CheckAttackChance(mgr); + x988_29_shockWaveAnim = false; + } +} + +void CElitePirate::CallForBackup(CStateManager& mgr, EStateMsg msg, float) { + if (msg == EStateMsg::Activate) { + x568_state = EState::Zero; + x988_30_calledForBackup = true; + SetShotAt(false, mgr); + } else if (msg == EStateMsg::Update) { + if (x568_state == EState::Zero) { + if (x450_bodyController->GetCurrentStateId() == pas::EAnimationState::Generate) { + x568_state = EState::Two; + } else { + x450_bodyController->GetCommandMgr().DeliverCmd(CBCGenerateCmd(pas::EGenerateType::Five, zeus::skZero3f)); + } + } else if (x568_state == EState::Two) { + if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Generate) { + x568_state = EState::Over; + } + } + } else if (msg == EStateMsg::Deactivate) { + SendScriptMsgs(EScriptObjectState::Zero, mgr, EScriptObjectMessage::None); + } +} + +void CElitePirate::Cover(CStateManager& mgr, EStateMsg msg, float dt) { + if (msg == EStateMsg::Activate) { + x450_bodyController->SetLocomotionType(pas::ELocomotionType::Crouch); + if (HasWeakPointHead()) { + if (TCastToPtr actor = mgr.ObjectById(x770_collisionHeadId)) { + actor->SetDamageVulnerability(CDamageVulnerability::ImmuneVulnerabilty()); + } + } + x5d4_collisionActorMgr->SetActive(mgr, true); + x6f8_boneTracking.SetTarget(mgr.GetPlayer().GetUniqueId()); + x6f8_boneTracking.SetActive(true); + UpdateDestPos(mgr); + CPatterned::PathFind(mgr, msg, dt); + if (TooClose(mgr, 0.f)) { + x450_bodyController->GetCommandMgr().ClearLocomotionCmds(); + } + } else if (msg == EStateMsg::Update) { + if (x988_27_shotAt) { + x7c0_shotAtTimer -= dt; + if (x7c0_shotAtTimer <= 0.f) { + x988_27_shotAt = false; + } + } + x7a8_pathShaggedTime = PathShagged(mgr, 0.f) ? x7a8_pathShaggedTime + dt : 0.f; + if (!TooClose(mgr, 0.f) && !PathShagged(mgr, 0.f)) { + CPatterned::PathFind(mgr, msg, dt); + } else if (PathShagged(mgr, 0.f)) { + const zeus::CVector3f& move = x8c0_.GetValue(GetTranslation(), GetTransform().frontVector()); + if (move != zeus::skZero3f) { + x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(move, zeus::skZero3f, 1.f)); + } + } else if (ShouldTurn(mgr, 0.f)) { + const zeus::CVector3f& aim = + mgr.GetPlayer().GetAimPosition(mgr, 0.5f * GetModelData()->GetAnimationData()->GetSpeedScale()); + const zeus::CVector3f& face = aim - GetTranslation(); + if (face.canBeNormalized()) { + x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(zeus::skZero3f, face.normalized(), 1.f)); + } + } + AttractProjectiles(mgr); + UpdateAbsorbBodyState(mgr, dt); + } else if (msg == EStateMsg::Deactivate) { + x450_bodyController->SetLocomotionType(pas::ELocomotionType::Relaxed); + x6f8_boneTracking.SetActive(false); + if (HasWeakPointHead()) { + if (TCastToPtr actor = mgr.ObjectById(x770_collisionHeadId)) { + actor->SetDamageVulnerability(x56c_vulnerability); + } + } + x5d4_collisionActorMgr->SetActive(mgr, false); + } +} + +bool CElitePirate::TooClose(CStateManager& mgr, float) { + return x2fc_minAttackRange * x2fc_minAttackRange > (GetTranslation() - mgr.GetPlayer().GetTranslation()).magSquared(); +} + +bool CElitePirate::InDetectionRange(CStateManager& mgr, float arg) { + return x988_28_alert ? true : CPatterned::InDetectionRange(mgr, arg); +} + +bool CElitePirate::SpotPlayer(CStateManager& mgr, float arg) { + return x988_28_alert ? true : CPatterned::SpotPlayer(mgr, arg); +} + +bool CElitePirate::AnimOver(CStateManager& mgr, float) { return x568_state == EState::Over; } + +bool CElitePirate::ShouldAttack(CStateManager& mgr, float) { + if ((mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() > x2fc_minAttackRange * x2fc_minAttackRange) { + return false; + } + return !ShouldTurn(mgr, 0.f); +} + +bool CElitePirate::InPosition(CStateManager& mgr, float) { + return (x8b4_targetDestPos - GetTranslation()).magSquared() < 25.f; +} + +bool CElitePirate::ShouldTurn(CStateManager& mgr, float) { + return zeus::CVector2f::getAngleDiff((mgr.GetPlayer().GetTranslation() - GetTranslation()).toVec2f(), + GetTransform().frontVector().toVec2f()) > zeus::degToRad(15.f); +} + +bool CElitePirate::AggressionCheck(CStateManager& mgr, float arg) { + if (x772_launcherId == kInvalidUniqueId && !PathShagged(mgr, arg)) { + if (x988_31_running) { + return true; + } + return 4.f * x300_maxAttackRange * x300_maxAttackRange < + (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared(); + } + return false; +} + +bool CElitePirate::ShouldTaunt(CStateManager& mgr, float) { return x7bc_tauntTimer <= 0.f; } + +bool CElitePirate::ShouldFire(CStateManager& mgr, float) { return ShouldFireFromLauncher(mgr, x772_launcherId); } + +bool CElitePirate::ShotAt(CStateManager& mgr, float) { return x988_27_shotAt; } + +bool CElitePirate::ShouldSpecialAttack(CStateManager& mgr, float) { + if (x7b8_attackTimer <= 0.f && GetAreaIdAlways() == mgr.GetPlayer().GetAreaIdAlways()) { + const zeus::CVector3f& dist = mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetTranslation(); + float magSquared = dist.magSquared(); + if (x2fc_minAttackRange * x2fc_minAttackRange <= magSquared && + magSquared <= x300_maxAttackRange * x300_maxAttackRange) { + return std::abs(dist.z()) < 3.f; + } + } + return false; +} + +bool CElitePirate::ShouldCallForBackup(CStateManager& mgr, float) { + return ShouldCallForBackupFromLauncher(mgr, x772_launcherId); +} + +CPathFindSearch* CElitePirate::GetSearchPath() { return &x7d0_pathFindSearch; } + +void CElitePirate::SetupHealthInfo(CStateManager& mgr) { + const CHealthInfo* const health = HealthInfo(mgr); + x7b4_hp = health->GetHP(); + if (HasWeakPointHead()) { + if (TCastToPtr actor = mgr.ObjectById(x770_collisionHeadId)) { + auto actHealth = actor->HealthInfo(mgr); + actHealth->SetHP(health->GetHP()); + actHealth->SetKnockbackResistance(health->GetKnockbackResistance()); + actor->SetDamageVulnerability(x56c_vulnerability); + } + } + SetupLauncherHealthInfo(mgr, x772_launcherId); +} + +void CElitePirate::SetLaunchersActive(CStateManager& mgr, bool val) { SetLauncherActive(mgr, val, x772_launcherId); } + +void CElitePirate::SetupPathFindSearch() { + float scale = 1.5f * GetModelData()->GetScale().y(); + float fVar1 = IsElitePirate() ? 5.f : 1.f; + zeus::CAABox box{{-scale, -scale, 0.f}, {scale, scale, fVar1 * scale}}; + SetBoundingBox(box); + x738_collisionAabb.SetBox(box); + x7d0_pathFindSearch.SetCharacterRadius(scale); + x7d0_pathFindSearch.SetCharacterHeight(3.f * scale); +} + +void CElitePirate::SetShotAt(bool val, CStateManager& mgr) { + if (!IsElitePirate() || x7b4_hp <= 0.f || !val) { + x988_27_shotAt = val; + } else if (HealthInfo(mgr)->GetHP() / x7b4_hp <= x7b0_) { + x7b0_ -= 0.2f; + x988_27_shotAt = true; + } + if (x988_27_shotAt) { + x7c0_shotAtTimer = mgr.GetActiveRandom()->Float() * x5d8_data.GetShotAtTimeVariance() + x5d8_data.GetShotAtTime(); + } else { + x7c0_shotAtTimer = 0.f; + } +} + +bool CElitePirate::IsArmClawCollider(TUniqueId uid, const rstl::reserved_vector& vec) const { + return std::find(vec.begin(), vec.end(), uid) != vec.end(); +} + +void CElitePirate::AddCollisionList(const SJointInfo* joints, size_t count, + std::vector& outJoints) const { + const CAnimData* animData = GetModelData()->GetAnimationData(); + for (size_t i = 0; i < count; ++i) { + const auto& joint = joints[i]; + const CSegId from = animData->GetLocatorSegId(joint.from); + const CSegId to = animData->GetLocatorSegId(joint.to); + if (to.IsInvalid() || from.IsInvalid()) { + continue; + } + outJoints.emplace_back(CJointCollisionDescription::SphereSubdivideCollision( + to, from, joint.radius, joint.separation, CJointCollisionDescription::EOrientationType::One, joint.from, 10.f)); + } +} + +void CElitePirate::AddSphereCollisionList(const SSphereJointInfo* joints, size_t count, + std::vector& outJoints) const { + const CAnimData* animData = GetModelData()->GetAnimationData(); + for (size_t i = 0; i < count; ++i) { + const auto& joint = joints[i]; + const CSegId seg = animData->GetLocatorSegId(joint.name); + if (seg.IsInvalid()) { + continue; + } + outJoints.emplace_back(CJointCollisionDescription::SphereCollision(seg, joint.radius, joint.name, 10.f)); + } +} + +void CElitePirate::SetupCollisionManager(CStateManager& mgr) { + constexpr size_t jointInfoCount = skLeftArmJointList.size() + skRightArmJointList.size() + skSphereJointList.size(); + std::vector joints; + joints.reserve(jointInfoCount); + AddCollisionList(skLeftArmJointList.data(), skLeftArmJointList.size(), joints); + AddCollisionList(skRightArmJointList.data(), skLeftArmJointList.size(), joints); + AddSphereCollisionList(skSphereJointList.data(), skSphereJointList.size(), joints); + if (HasWeakPointHead()) { + x730_collisionActorMgrHead = + std::make_unique(mgr, GetUniqueId(), GetAreaIdAlways(), joints, true); + x730_collisionActorMgrHead->SetActive(mgr, GetActive()); + } + x774_collisionRJointIds.clear(); + x788_collisionLJointIds.clear(); + + const CAnimData* animData = GetModelData()->GetAnimationData(); + constexpr zeus::CVector3f bounds{4.f, 4.f, 2.f}; + joints.emplace_back(CJointCollisionDescription::OBBCollision(animData->GetLocatorSegId("L_Palm_LCTR"sv), bounds, + zeus::skZero3f, "Shield"sv, 10.f)); + x5d4_collisionActorMgr = + std::make_unique(mgr, GetUniqueId(), GetAreaIdAlways(), joints, false); + + SetupCollisionActorInfo(mgr); + SetupHealthInfo(mgr); + + SetMaterialFilter(CMaterialFilter::MakeIncludeExclude( + {EMaterialTypes::Solid}, + {EMaterialTypes::CollisionActor, EMaterialTypes::AIPassthrough, EMaterialTypes::Player})); + AddMaterial(EMaterialTypes::ProjectilePassthrough, mgr); +} + +void CElitePirate::SetupCollisionActorInfo(CStateManager& mgr) { + if (HasWeakPointHead()) { + for (size_t i = 0; i < x730_collisionActorMgrHead->GetNumCollisionActors(); ++i) { + const auto& colDesc = x730_collisionActorMgrHead->GetCollisionDescFromIndex(i); + const TUniqueId& uid = colDesc.GetCollisionActorId(); + if (TCastToPtr act = mgr.ObjectById(uid)) { + if (colDesc.GetName() == "Head_1"sv) { + x770_collisionHeadId = uid; + } else if (IsArmClawCollider(colDesc.GetName(), "R_Palm_LCTR"sv, skRightArmJointList.data(), + skRightArmJointList.size())) { + x774_collisionRJointIds.push_back(uid); + } else if (IsArmClawCollider(colDesc.GetName(), "L_Palm_LCTR"sv, skLeftArmJointList.data(), + skLeftArmJointList.size())) { + x788_collisionLJointIds.push_back(uid); + } + if (uid != x770_collisionHeadId) { + act->SetDamageVulnerability(CDamageVulnerability::ReflectVulnerabilty()); + } + } + } + x730_collisionActorMgrHead->AddMaterial( + mgr, {EMaterialTypes::AIJoint, EMaterialTypes::CameraPassthrough, EMaterialTypes::Immovable}); + } + + const CJointCollisionDescription& description = x5d4_collisionActorMgr->GetCollisionDescFromIndex(0); + x79c_ = description.GetCollisionActorId(); + if (TCastToPtr act = mgr.ObjectById(x79c_)) { + act->SetWeaponCollisionResponseType(EWeaponCollisionResponseTypes::None); + } + x5d4_collisionActorMgr->AddMaterial(mgr, {EMaterialTypes::AIJoint, EMaterialTypes::CameraPassthrough}); +} + +bool CElitePirate::IsArmClawCollider(std::string_view name, std::string_view locator, const SJointInfo* info, + size_t infoCount) { + if (name == locator) { + return true; + } + for (size_t i = 0; i < infoCount; ++i) { + if (name == info[i].from) { + return true; + } + } + return false; +} + +void CElitePirate::CreateGrenadeLauncher(CStateManager& mgr, TUniqueId uid) { + const CAnimationParameters& params = x5d8_data.GetLauncherAnimParams(); + if (!params.GetACSFile().IsValid()) { + return; + } + CModelData mData(CAnimRes(params.GetACSFile(), params.GetCharacter(), GetModelData()->GetScale(), + params.GetInitialAnimation(), true)); + const zeus::CAABox bounds = mData.GetBounds(GetTransform().getRotation()); + mgr.AddObject( + new CGrenadeLauncher(uid, "Grenade Launcher", {GetAreaIdAlways(), CEntity::NullConnectionList}, GetTransform(), + std::move(mData), bounds, CHealthInfo(x5d8_data.GetLauncherHP(), 10.f), x56c_vulnerability, + x5d8_data.GetLauncherActParams(), GetUniqueId(), x5d8_data.GetGrenadeLauncherData(), 0.f)); +} + +void CElitePirate::ApplyDamageToHead(CStateManager& mgr, TUniqueId uid) { + if (!HasWeakPointHead()) { + return; + } + if (TCastToPtr weapon = mgr.ObjectById(uid)) { + CDamageInfo damageInfo = weapon->GetDamageInfo(); + damageInfo.SetRadius(0.f); + mgr.ApplyDamage(uid, x770_collisionHeadId, weapon->GetOwnerId(), damageInfo, + CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), zeus::skZero3f); + } +} + +void CElitePirate::CreateEnergyAbsorb(CStateManager& mgr, const zeus::CTransform& xf) { + if (x7ac_energyAbsorbCooldown > 0.f) { + return; + } + mgr.AddObject(new CExplosion(*x760_energyAbsorbDesc, mgr.AllocateUniqueId(), true, + {GetAreaIdAlways(), CEntity::NullConnectionList}, "Absorb energy Fx", xf, 0, + GetModelData()->GetScale(), zeus::skWhite)); + CSfxManager::AddEmitter(x5d8_data.GetSFXAbsorb(), GetTranslation(), zeus::skUp, false, false, 0x7f, + GetAreaIdAlways()); + x7ac_energyAbsorbCooldown = 0.25f; +} + +void CElitePirate::SetupLauncherHealthInfo(CStateManager& mgr, TUniqueId uid) { + const CHealthInfo* const health = HealthInfo(mgr); + if (uid != kInvalidUniqueId) { + if (TCastToPtr actor = mgr.ObjectById(uid)) { + auto actHealth = actor->HealthInfo(mgr); + actHealth->SetHP(x5d8_data.GetLauncherHP()); + actHealth->SetKnockbackResistance(health->GetKnockbackResistance()); + actor->SetDamageVulnerability(x56c_vulnerability); + } + } +} + +void CElitePirate::SetLauncherActive(CStateManager& mgr, bool val, TUniqueId uid) { + if (uid == kInvalidUniqueId) { + return; + } + if (auto entity = mgr.ObjectById(uid)) { + mgr.SendScriptMsg(entity, GetUniqueId(), val ? EScriptObjectMessage::Start : EScriptObjectMessage::Stop); + } +} + +zeus::CVector3f CElitePirate::GetLockOnPosition(const CActor* actor) const { + const zeus::CTransform& targetTransform = actor->GetLocatorTransform("lockon_target_LCTR"sv); + return actor->GetTranslation() + actor->GetTransform().rotate(targetTransform.origin); +} + +bool CElitePirate::CanKnockBack(const CDamageInfo& info) const { + return !x400_25_alive || info.GetWeaponMode().IsComboed() || info.GetWeaponMode().GetType() != EWeaponType::Plasma; +} + +void CElitePirate::UpdateDestPos(CStateManager& mgr) { + x8b4_targetDestPos = GetTranslation(); + const zeus::CVector3f& playerPos = mgr.GetPlayer().GetTranslation(); + const zeus::CVector3f& dist = GetTranslation() - playerPos; + if (dist.canBeNormalized() && dist.magSquared() > x2fc_minAttackRange * x2fc_minAttackRange) { + x2e0_destPos = playerPos + (x2fc_minAttackRange * dist.normalized()); + x8b4_targetDestPos = x2e0_destPos; + } +} + +void CElitePirate::CheckAttackChance(CStateManager& mgr) { + if (mgr.GetActiveRandom()->Float() > x5d8_data.GetAttackChance()) { + x7b8_attackTimer = x308_attackTimeVariation * mgr.GetActiveRandom()->Float() + x304_averageAttackTime; + } +} + +void CElitePirate::AttractProjectiles(CStateManager& mgr) { + if (!IsAlive()) { + return; + } + TCastToConstPtr actor = mgr.GetObjectById(x79c_); + if (!actor) { + return; + } + + float radius = x5d8_data.GetX1C(); + const zeus::CVector3f& actorPos = actor->GetTranslation(); + const zeus::CVector3f& pos = GetTranslation(); + rstl::reserved_vector projNearList; + zeus::CAABox aabb{pos - radius, pos + radius}; + mgr.BuildNearList(projNearList, aabb, CMaterialFilter::MakeInclude({EMaterialTypes::Projectile}), nullptr); + if (projNearList.empty()) { + return; + } + + rstl::reserved_vector charNearList; + mgr.BuildNearList(charNearList, aabb, CMaterialFilter::MakeInclude({EMaterialTypes::Character}), nullptr); + for (const TUniqueId projId : projNearList) { + TCastToPtr projectile = mgr.ObjectById(projId); + if (!projectile || projectile->GetType() == EWeaponType::Missile || + projectile->GetOwnerId() != mgr.GetPlayer().GetUniqueId() || + projectile->GetAreaIdAlways() != GetAreaIdAlways()) { + continue; + } + + const zeus::CVector3f& projectilePos = projectile->GetTranslation(); + const zeus::CVector3f actorProjDist = actorPos - projectilePos; + if (GetTransform().frontVector().dot(actorProjDist) < 0.f) { + const zeus::CVector3f projectileDir = projectilePos - projectile->GetPreviousPos(); + if (projectileDir.canBeNormalized() && IsClosestEnergyAttractor(mgr, charNearList, projectilePos)) { + float actorProjMag = actorProjDist.magnitude(); + const zeus::CVector3f b = projectilePos + ((0.5f * actorProjMag) * projectileDir.normalized()); + const zeus::CVector3f c = actorPos + zeus::CVector3f{0.f, 0.f, 0.4f * 0.4f * actorProjMag}; + const zeus::CVector3f p1 = zeus::getBezierPoint(projectilePos, b, c, actorPos, 0.333f); + const zeus::CVector3f p2 = zeus::getBezierPoint(projectilePos, b, c, actorPos, 0.666f); + + float magAdd = (p2 - p1).magnitude() + (p1 - projectilePos).magnitude() + (actorPos - p2).magnitude(); + const zeus::CVector3f p3 = + zeus::getBezierPoint(projectilePos, b, c, actorPos, projectileDir.magnitude() / magAdd); + + const zeus::CVector3f look = p3 - projectilePos; + if (look.canBeNormalized()) { + zeus::CTransform xf = zeus::lookAt(zeus::skZero3f, look); + xf.orthonormalize(); + CProjectileWeapon& weapon = projectile->ProjectileWeapon(); + weapon.SetWorldSpaceOrientation(xf); + const zeus::CVector3f scaledVelocity = 0.8f * weapon.GetVelocity().normalized(); + weapon.SetVelocity(weapon.GetVelocity() * 0.39999998f + (scaledVelocity * 0.6f)); + } + } + } + SetShotAt(true, mgr); + } +} + +void CElitePirate::UpdateAbsorbBodyState(CStateManager& mgr, float dt) { + if (!x988_27_shotAt || x450_bodyController->IsFrozen()) { + return; + } + x7c4_absorbUpdateTimer += dt; + if (x7c4_absorbUpdateTimer < 3.f) { + return; + } + if (x450_bodyController->GetCurrentStateId() != pas::EAnimationState::Turn && + x450_bodyController->GetBodyStateInfo().GetCurrentState()->IsMoving()) { + x450_bodyController->GetCommandMgr().DeliverCmd( + CBCAdditiveReactionCmd(pas::EAdditiveReactionType::Six, 1.f, false)); + } else { + bool b = false; + if (HasWeakPointHead()) { + if (TCastToConstPtr actor = mgr.GetObjectById(x770_collisionHeadId)) { + float z = actor->GetTranslation().z(); + b = z - 0.5f * (z - GetTranslation().z()) <= mgr.GetPlayer().GetTranslation().z(); + } + } + b = b || TooClose(mgr, 0.f); + x450_bodyController->GetCommandMgr().DeliverCmd( + CBCAdditiveReactionCmd(b ? pas::EAdditiveReactionType::Seven : pas::EAdditiveReactionType::Five, 1.f, false)); + } + x7c4_absorbUpdateTimer = 0.f; +} + +bool CElitePirate::IsAttractingEnergy() { + if (x450_bodyController->GetLocomotionType() == pas::ELocomotionType::Crouch) { + const auto state = x450_bodyController->GetCurrentStateId(); + return state == pas::EAnimationState::Locomotion || state == pas::EAnimationState::Turn; + } + return false; +} + +void CElitePirate::UpdateTimers(float dt) { + if (x7b8_attackTimer > 0.f) { + x7b8_attackTimer -= dt; + } + if (x7ac_energyAbsorbCooldown > 0.f) { + x7ac_energyAbsorbCooldown -= dt; + } +} + +void CElitePirate::UpdatePositionHistory() { + const zeus::CVector3f& pos = GetTranslation(); + if (x7d0_pathFindSearch.OnPath(pos) == CPathFindSearch::EResult::Success) { + x8c0_.Clear(); + } + x8c0_.AddValue(pos); +} + +void CElitePirate::UpdateActorTransform(CStateManager& mgr, TUniqueId& uid, std::string_view name) { + if (uid == kInvalidUniqueId) { + return; + } + auto* actor = static_cast(mgr.ObjectById(uid)); + if (actor == nullptr) { + uid = kInvalidUniqueId; + return; + } + actor->SetTransform(GetLctrTransform(name)); +} + +void CElitePirate::UpdateHealthInfo(CStateManager& mgr) { + float hp = HealthInfo(mgr)->GetHP(); + if (HasWeakPointHead()) { + if (TCastToPtr actor = mgr.ObjectById(x770_collisionHeadId)) { + float headHp = actor->HealthInfo(mgr)->GetHP(); + HealthInfo(mgr)->SetHP(hp - (hp - headHp)); + *actor->HealthInfo(mgr) = *HealthInfo(mgr); // TODO does this work? + } + } + if (HealthInfo(mgr)->GetHP() <= 0.f) { + Death(mgr, zeus::skZero3f, EScriptObjectState::DeathRattle); + RemoveMaterial(EMaterialTypes::Orbit, EMaterialTypes::Target, mgr); + } +} + +void CElitePirate::ExtendTouchBounds(const CStateManager& mgr, const rstl::reserved_vector& uids, + const zeus::CVector3f& vec) const { + for (const TUniqueId uid : uids) { + if (TCastToPtr actor = mgr.ObjectById(uid)) { + actor->SetExtendedTouchBounds(vec); + } + } +} + +bool CElitePirate::ShouldFireFromLauncher(CStateManager& mgr, TUniqueId launcherId) { + if (x7b8_attackTimer > 0.f || launcherId == kInvalidUniqueId) { + return false; + } + const auto* launcher = static_cast(mgr.GetObjectById(launcherId)); + if (launcher == nullptr) { + return false; + } + const zeus::CVector3f& aim = mgr.GetPlayer().GetAimPosition(mgr, 0.f); + if (x300_maxAttackRange * x300_maxAttackRange > (aim - GetTranslation()).magSquared() || ShouldTurn(mgr, 0.f)) { + return false; + } + const zeus::CVector3f& origin = GetLockOnPosition(launcher); + if (IsPatternObstructed(mgr, origin, aim)) { + return false; + } + const zeus::CVector3f& target = CGrenadeLauncher::GrenadeTarget(mgr); + float angleOut = x5d8_data.GetGrenadeTrajectoryInfo().GetAngleMin(); + float velocityOut = x5d8_data.GetGrenadeTrajectoryInfo().GetVelocityMin(); + CGrenadeLauncher::CalculateGrenadeTrajectory(target, origin, x5d8_data.GetGrenadeTrajectoryInfo(), angleOut, + velocityOut); + const zeus::CVector3f& rot = GetTransform().rotate({0.f, std::cos(angleOut), std::sin(angleOut)}); + return !CPatterned::IsPatternObstructed(mgr, target, target + (7.5f * rot)); +} + +bool CElitePirate::ShouldCallForBackupFromLauncher(const CStateManager& mgr, TUniqueId uid) const { + if (x988_30_calledForBackup || uid != kInvalidUniqueId || !x5d8_data.GetX11E()) { + return false; + } + return x7a8_pathShaggedTime >= 3.f; +} + +bool CElitePirate::IsClosestEnergyAttractor(const CStateManager& mgr, + const rstl::reserved_vector& charNearList, + const zeus::CVector3f& projectilePos) const { + float distance = (projectilePos - GetTranslation()).magSquared(); + for (const auto id : charNearList) { + if (TCastToConstPtr actor = mgr.GetObjectById(id)) { + if (actor->GetUniqueId() != GetUniqueId() && actor->IsEnergyAttractor() && + (projectilePos - actor->GetTranslation()).magSquared() < distance) { + return false; + } + } + } + return true; +} + +zeus::CVector3f CElitePirate::SUnknownStruct::GetValue(const zeus::CVector3f& v1, const zeus::CVector3f& v2) { + while (!x4_.empty()) { + const zeus::CVector3f v = x4_[x4_.size() - 1] - v1; + if (v.dot(v2) > 0.f && v.isMagnitudeSafe()) { + return v.normalized(); + } + x4_.pop_back(); + } + return zeus::skZero3f; +} + +void CElitePirate::SUnknownStruct::AddValue(const zeus::CVector3f& vec) { + if (x4_.size() > 15) { + return; + } + if (x4_.empty()) { + x4_.emplace_back(vec); + return; + } + if (x4_[x4_.size() - 1].magSquared() > x0_) { + x4_.emplace_back(vec); + } +} } // namespace urde::MP1 diff --git a/Runtime/MP1/World/CElitePirate.hpp b/Runtime/MP1/World/CElitePirate.hpp index 0f7d31de0..4761a68b3 100644 --- a/Runtime/MP1/World/CElitePirate.hpp +++ b/Runtime/MP1/World/CElitePirate.hpp @@ -1,59 +1,229 @@ #pragma once -#include - -#include "Runtime/RetroTypes.hpp" +#include "Runtime/Character/CBoneTracking.hpp" +#include "Runtime/Collision/CCollisionActorManager.hpp" +#include "Runtime/Collision/CJointCollisionDescription.hpp" +#include "Runtime/MP1/World/CGrenadeLauncher.hpp" +#include "Runtime/MP1/World/CShockWave.hpp" #include "Runtime/World/CActorParameters.hpp" #include "Runtime/World/CAnimationParameters.hpp" +#include "Runtime/World/CPathFindSearch.hpp" #include "Runtime/World/CPatterned.hpp" namespace urde::MP1 { class CElitePirateData { - float x0_; - float x4_; +private: + float x0_tauntInterval; + float x4_tauntVariance; float x8_; float xc_; - float x10_; - float x14_; - float x18_; + float x10_attackChance; + float x14_shotAtTime; + float x18_shotAtTimeVariance; float x1c_; CAssetId x20_; - s16 x24_; - CActorParameters x28_; - CAnimationParameters x90_; + u16 x24_sfxAbsorb; + CActorParameters x28_launcherActParams; + CAnimationParameters x90_launcherAnimParams; CAssetId x9c_; - s16 xa0_; + u16 xa0_; CAssetId xa4_; CDamageInfo xa8_; - float xc4_; + float xc4_launcherHp; CAssetId xc8_; CAssetId xcc_; CAssetId xd0_; CAssetId xd4_; - float xd8_; - float xdc_; - float xe0_; - float xe4_; - float xe8_; - float xec_; - u32 xf0_; - u32 xf4_; + SGrenadeUnknownStruct xd8_; + SGrenadeTrajectoryInfo xe0_trajectoryInfo; + u32 xf0_grenadeNumBounces; + u16 xf4_; + u16 xf6_; CAssetId xf8_; CDamageInfo xfc_; CAssetId x118_; - s16 x11c_; + u16 x11c_; bool x11e_; bool x11f_; public: CElitePirateData(CInputStream&, u32 propCount); + + [[nodiscard]] float GetTauntInterval() const { return x0_tauntInterval; } + [[nodiscard]] float GetTauntVariance() const { return x4_tauntVariance; } + [[nodiscard]] float GetAttackChance() const { return x10_attackChance; } + [[nodiscard]] float GetShotAtTime() const { return x14_shotAtTime; } + [[nodiscard]] float GetShotAtTimeVariance() const { return x18_shotAtTimeVariance; } + [[nodiscard]] float GetX1C() const { return x1c_; } + [[nodiscard]] CAssetId GetX20() const { return x20_; } + [[nodiscard]] u16 GetSFXAbsorb() const { return x24_sfxAbsorb; } + [[nodiscard]] const CActorParameters& GetLauncherActParams() const { return x28_launcherActParams; } + [[nodiscard]] const CAnimationParameters& GetLauncherAnimParams() const { return x90_launcherAnimParams; } + [[nodiscard]] float GetLauncherHP() const { return xc4_launcherHp; } + [[nodiscard]] const SGrenadeTrajectoryInfo& GetGrenadeTrajectoryInfo() const { return xe0_trajectoryInfo; } + [[nodiscard]] CAssetId GetXF8() const { return xf8_; } + [[nodiscard]] const CDamageInfo& GetXFC() const { return xfc_; } + [[nodiscard]] CAssetId GetX118() const { return x118_; } + [[nodiscard]] u16 GetX11C() const { return x11c_; } + [[nodiscard]] bool GetX11E() const { return x11e_; } + [[nodiscard]] bool GetX11F() const { return x11f_; } + + [[nodiscard]] SBouncyGrenadeData GetBouncyGrenadeData() const { + return {xd8_, xa8_, xc8_, xcc_, xd0_, xd4_, xf0_grenadeNumBounces, xf4_, xf6_}; + } + [[nodiscard]] SGrenadeLauncherData GetGrenadeLauncherData() const { + return {GetBouncyGrenadeData(), xa4_, x9c_, xa0_, xe0_trajectoryInfo}; + } }; class CElitePirate : public CPatterned { +private: + struct SUnknownStruct { + private: + float x0_; + rstl::reserved_vector x4_; + + public: + explicit SUnknownStruct(float f) : x0_(f * f) {} + zeus::CVector3f GetValue(const zeus::CVector3f& v1, const zeus::CVector3f& v2); + void AddValue(const zeus::CVector3f& vec); + void Clear() { x4_.clear(); } + }; + + enum class EState { + Invalid = -1, + Zero = 0, + One = 1, + Two = 2, + Over = 3, + }; + + EState x568_state = EState::Invalid; + CDamageVulnerability x56c_vulnerability; + std::unique_ptr x5d4_collisionActorMgr; + CElitePirateData x5d8_data; + CBoneTracking x6f8_boneTracking; + std::unique_ptr x730_collisionActorMgrHead; + // s32 x734_; + CCollidableAABox x738_collisionAabb; + std::optional> x760_energyAbsorbDesc; + TUniqueId x770_collisionHeadId = kInvalidUniqueId; + TUniqueId x772_launcherId = kInvalidUniqueId; + rstl::reserved_vector x774_collisionRJointIds; + rstl::reserved_vector x788_collisionLJointIds; + TUniqueId x79c_ = kInvalidUniqueId; + float x7a0_initialSpeed; + float x7a4_steeringSpeed = 1.f; + float x7a8_pathShaggedTime = 0.f; + float x7ac_energyAbsorbCooldown = 0.f; + float x7b0_ = 1.f; + float x7b4_hp = 0.f; + float x7b8_attackTimer = 0.f; + float x7bc_tauntTimer = 0.f; + float x7c0_shotAtTimer = 0.f; + float x7c4_absorbUpdateTimer = 0.f; + s32 x7c8_currAnimId = -1; + u32 x7cc_activeMaterialSet = 0; + CPathFindSearch x7d0_pathFindSearch; + zeus::CVector3f x8b4_targetDestPos; + SUnknownStruct x8c0_; + bool x988_24_damageOn : 1; + bool x988_25_attackingRightClaw : 1; + bool x988_26_attackingLeftClaw : 1; + bool x988_27_shotAt : 1; + bool x988_28_alert : 1; + bool x988_29_shockWaveAnim : 1; + bool x988_30_calledForBackup : 1; + bool x988_31_running : 1; + bool x989_24_onPath : 1; + public: DEFINE_PATTERNED(ElitePirate) - CElitePirate(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&, - const CPatternedInfo&, const CActorParameters&, const CElitePirateData&); + CElitePirate(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, + CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms, + CElitePirateData data); + + void Accept(IVisitor& visitor) override; + void Think(float dt, CStateManager& mgr) override; + void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override; + void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override; + const CDamageVulnerability* GetDamageVulnerability() const override; + const CDamageVulnerability* GetDamageVulnerability(const zeus::CVector3f& pos, const zeus::CVector3f& dir, + const CDamageInfo& dInfo) const override; + zeus::CVector3f GetOrbitPosition(const CStateManager& mgr) const override; + zeus::CVector3f GetAimPosition(const CStateManager& mgr, float) const override; + void DoUserAnimEvent(CStateManager& mgr, const CInt32POINode& node, EUserEventType type, float dt) override; + const CCollisionPrimitive* GetCollisionPrimitive() const override; + void KnockBack(const zeus::CVector3f&, CStateManager& mgr, const CDamageInfo& info, EKnockBackType type, + bool inDeferred, float magnitude) override; + void TakeDamage(const zeus::CVector3f&, float arg) override; + void Patrol(CStateManager& mgr, EStateMsg msg, float dt) override; + void PathFind(CStateManager& mgr, EStateMsg msg, float dt) override; + void TargetPatrol(CStateManager& mgr, EStateMsg msg, float dt) override; + void Halt(CStateManager& mgr, EStateMsg msg, float dt) override; + void Run(CStateManager& mgr, EStateMsg msg, float dt) override; + void Generate(CStateManager& mgr, EStateMsg msg, float dt) override; + void Attack(CStateManager& mgr, EStateMsg msg, float dt) override; + void Taunt(CStateManager& mgr, EStateMsg msg, float dt) override; + void ProjectileAttack(CStateManager& mgr, EStateMsg msg, float dt) override; + void Cover(CStateManager& mgr, EStateMsg msg, float dt) override; + void SpecialAttack(CStateManager& mgr, EStateMsg msg, float dt) override; + void CallForBackup(CStateManager& mgr, EStateMsg msg, float dt) override; + bool TooClose(CStateManager& mgr, float arg) override; + bool InDetectionRange(CStateManager& mgr, float arg) override; + bool SpotPlayer(CStateManager& mgr, float arg) override; + bool AnimOver(CStateManager& mgr, float arg) override; + bool ShouldAttack(CStateManager& mgr, float arg) override; + bool InPosition(CStateManager& mgr, float arg) override; + bool ShouldTurn(CStateManager& mgr, float arg) override; + bool AggressionCheck(CStateManager& mgr, float arg) override; + bool ShouldTaunt(CStateManager& mgr, float arg) override; + bool ShouldFire(CStateManager& mgr, float arg) override; + bool ShotAt(CStateManager& mgr, float arg) override; + bool ShouldSpecialAttack(CStateManager& mgr, float arg) override; + bool ShouldCallForBackup(CStateManager& mgr, float arg) override; + CPathFindSearch* GetSearchPath() override; + virtual bool HasWeakPointHead() const { return true; } + virtual bool IsElitePirate() const { return true; } + virtual void SetupHealthInfo(CStateManager& mgr); + virtual void SetLaunchersActive(CStateManager& mgr, bool val); + virtual SShockWaveData GetShockWaveData() const { + return {x5d8_data.GetXF8(), x5d8_data.GetXFC(), x5d8_data.GetX118(), x5d8_data.GetX11C()}; + } + +private: + void SetupPathFindSearch(); + void SetShotAt(bool val, CStateManager& mgr); + bool IsArmClawCollider(TUniqueId uid, const rstl::reserved_vector& vec) const; + void AddSphereCollisionList(const SSphereJointInfo* joints, size_t count, + std::vector& outJoints) const; + void AddCollisionList(const SJointInfo* joints, size_t count, + std::vector& outJoints) const; + void SetupCollisionManager(CStateManager& mgr); + void SetupCollisionActorInfo(CStateManager& mgr); + bool IsArmClawCollider(std::string_view name, std::string_view locator, const SJointInfo* info, size_t infoCount); + void CreateGrenadeLauncher(CStateManager& mgr, TUniqueId uid); + void ApplyDamageToHead(CStateManager& mgr, TUniqueId uid); + void CreateEnergyAbsorb(CStateManager& mgr, const zeus::CTransform& xf); + void SetupLauncherHealthInfo(CStateManager& mgr, TUniqueId uid); + void SetLauncherActive(CStateManager& mgr, bool val, TUniqueId uid); + zeus::CVector3f GetLockOnPosition(const CActor* actor) const; + bool CanKnockBack(const CDamageInfo& info) const; + void UpdateDestPos(CStateManager& mgr); + void CheckAttackChance(CStateManager& mgr); + void AttractProjectiles(CStateManager& mgr); + void UpdateAbsorbBodyState(CStateManager& mgr, float dt); + bool IsAttractingEnergy(); + void UpdateTimers(float dt); + void UpdatePositionHistory(); + void UpdateActorTransform(CStateManager& mgr, TUniqueId& uid, std::string_view name); + void UpdateHealthInfo(CStateManager& mgr); + void ExtendTouchBounds(const CStateManager& mgr, const rstl::reserved_vector& uids, + const zeus::CVector3f& vec) const; + bool ShouldFireFromLauncher(CStateManager& mgr, TUniqueId launcherId); + bool ShouldCallForBackupFromLauncher(const CStateManager& mgr, TUniqueId uid) const; + bool IsClosestEnergyAttractor(const CStateManager& mgr, const rstl::reserved_vector& charNearList, + const zeus::CVector3f& projectilePos) const; }; -} // namespace urde::MP1 \ No newline at end of file +} // namespace urde diff --git a/Runtime/MP1/World/CFlyingPirate.cpp b/Runtime/MP1/World/CFlyingPirate.cpp index 615bb13b1..ba5aa6359 100644 --- a/Runtime/MP1/World/CFlyingPirate.cpp +++ b/Runtime/MP1/World/CFlyingPirate.cpp @@ -21,7 +21,7 @@ namespace urde::MP1 { namespace { -constexpr std::array skBurst1{{ +constexpr std::array skBurstsFlying{{ {4, {3, 4, 11, 12, -1, 0, 0, 0}, 0.1f, 0.05f}, {20, {2, 3, 4, 5, -1, 0, 0, 0}, 0.1f, 0.05f}, {20, {10, 11, 12, 13, -1, 0, 0, 0}, 0.1f, 0.05f}, @@ -30,7 +30,7 @@ constexpr std::array skBurst1{{ {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000}, }}; -constexpr std::array skBurst2{{ +constexpr std::array skBurstsFlyingOutOfView{{ {5, {3, 4, 8, 12, -1, 0, 0, 0}, 0.1f, 0.05f}, {10, {2, 3, 4, 5, -1, 0, 0, 0}, 0.1f, 0.05f}, {10, {10, 11, 12, 13, -1, 0, 0, 0}, 0.1f, 0.05f}, @@ -39,7 +39,7 @@ constexpr std::array skBurst2{{ {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000}, }}; -constexpr std::array skBurst3{{ +constexpr std::array skBurstsLanded{{ {30, {3, 4, 5, 11, 12, 4, -1, 0}, 0.1f, 0.05f}, {20, {2, 3, 4, 5, 4, 3, -1, 0}, 0.1f, 0.05f}, {20, {5, 4, 3, 13, 12, 11, -1, 0}, 0.1f, 0.05f}, @@ -47,7 +47,7 @@ constexpr std::array skBurst3{{ {0, {0, 0, 0, 0, 0, 0, 0, 0}, 0.000000, 0.000000}, }}; -constexpr std::array skBurst4{{ +constexpr std::array skBurstsLandedOutOfView{{ {10, {6, 5, 4, 14, 13, 12, -1, 0}, 0.1f, 0.05f}, {20, {14, 13, 12, 11, 10, 9, -1, 0}, 0.1f, 0.05f}, {20, {14, 15, 16, 11, 10, 9, -1, 0}, 0.1f, 0.05f}, @@ -56,7 +56,11 @@ constexpr std::array skBurst4{{ }}; constexpr std::array skBursts{ - skBurst1.data(), skBurst2.data(), skBurst3.data(), skBurst4.data(), nullptr, + skBurstsFlying.data(), + skBurstsFlyingOutOfView.data(), + skBurstsLanded.data(), + skBurstsLandedOutOfView.data(), + nullptr, }; constexpr std::array skParts{ diff --git a/Runtime/MP1/World/CGrenadeLauncher.cpp b/Runtime/MP1/World/CGrenadeLauncher.cpp new file mode 100644 index 000000000..6f88fbd3e --- /dev/null +++ b/Runtime/MP1/World/CGrenadeLauncher.cpp @@ -0,0 +1,335 @@ +#include "Runtime/MP1/World/CGrenadeLauncher.hpp" + +#include "Runtime/Character/CPASAnimParm.hpp" +#include "Runtime/Character/CPASAnimParmData.hpp" +#include "Runtime/CSimplePool.hpp" +#include "Runtime/CStateManager.hpp" +#include "Runtime/GameGlobalObjects.hpp" +#include "Runtime/Weapon/CGameProjectile.hpp" +#include "Runtime/World/CExplosion.hpp" +#include "Runtime/World/CPatterned.hpp" +#include "Runtime/World/CPlayer.hpp" + +namespace urde::MP1 { +CGrenadeLauncher::CGrenadeLauncher(TUniqueId uid, std::string_view name, const CEntityInfo& info, + const zeus::CTransform& xf, CModelData&& mData, const zeus::CAABox& bounds, + const CHealthInfo& healthInfo, const CDamageVulnerability& vulnerability, + const CActorParameters& actParams, TUniqueId parentId, + const SGrenadeLauncherData& data, float f1) +: CPhysicsActor(uid, true, name, info, xf, std::move(mData), {EMaterialTypes::Character, EMaterialTypes::Solid}, bounds, + SMoverData{1000.f}, actParams, 0.3f, 0.1f) +, x25c_healthInfo(healthInfo) +, x264_vulnerability(vulnerability) +, x2cc_parentId(parentId) +, x2d0_data(data) +, x328_cSphere({{}, mData.GetScale().z()}, {EMaterialTypes::Character, EMaterialTypes::Solid}) +, x350_grenadeActorParams(actParams) +, x3e8_thermalMag(actParams.GetThermalMag()) +, x3f8_explodePlayerDistance(f1) { + if (data.GetExplosionGenDescId().IsValid()) { + x3b8_particleGenDesc = g_SimplePool->GetObj({SBIG('PART'), data.GetExplosionGenDescId()}); + } + GetModelData()->EnableLooping(true); + const CPASDatabase& pasDatabase = GetModelData()->GetAnimationData()->GetCharacterInfo().GetPASDatabase(); + for (int i = 0; i < x3c8_animIds.size(); ++i) { + const auto result = pasDatabase.FindBestAnimation(CPASAnimParmData{22, CPASAnimParm::FromEnum(i)}, -1); + x3c8_animIds[i] = result.second; + } +} + +zeus::CVector3f CGrenadeLauncher::GrenadeTarget(const CStateManager& mgr) { + const zeus::CVector3f& aim = mgr.GetPlayer().GetAimPosition(mgr, 1.f); + if (mgr.GetPlayer().GetMorphballTransitionState() == CPlayer::EPlayerMorphBallState::Unmorphed) { + return aim - zeus::CVector3f{0.f, 0.f, 0.5f * mgr.GetPlayer().GetEyeHeight()}; + } + return aim; +} + +void CGrenadeLauncher::CalculateGrenadeTrajectory(const zeus::CVector3f& target, const zeus::CVector3f& origin, + const SGrenadeTrajectoryInfo& info, float& angleOut, + float& velocityOut) { + float angle = info.GetAngleMin(); + float velocity = info.GetVelocityMin(); + float delta = std::max(0.01f, 0.1f * (info.GetAngleMax() - info.GetAngleMin())); + zeus::CVector3f dist = target - origin; + float distXYMag = dist.toVec2f().magnitude(); + float velocityMinSq = info.GetVelocityMin() * info.GetVelocityMin(); + float velocityMaxSq = info.GetVelocityMax() * info.GetVelocityMax(); + float gravAdj = distXYMag * ((0.5f * CPhysicsActor::GravityConstant()) * distXYMag); + float currAngle = info.GetAngleMin(); + float leastResult = FLT_MAX; + while (info.GetAngleMax() >= currAngle) { + float cos = std::cos(currAngle); + float sin = std::sin(currAngle); + float result = (distXYMag * (cos * sin) - (dist.z() * (cos * cos))); + if (result > FLT_EPSILON) { + float div = gravAdj / result; + if (velocityMinSq <= result && result <= velocityMaxSq) { + angle = currAngle; + velocity = std::sqrt(div); + break; + } + if (result <= velocityMaxSq) { + result = velocityMinSq - result; + } else { + result = result - velocityMaxSq; + } + if (result < leastResult) { + angle = currAngle; + velocity = std::sqrt(div); + leastResult = result; + } + } + currAngle += delta; + } + angleOut = angle; + velocityOut = velocity; +} + +void CGrenadeLauncher::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) { + CActor::AcceptScriptMsg(msg, uid, mgr); + switch (msg) { + case EScriptObjectMessage::Start: + if (x2cc_parentId == uid && x258_started != 1) { + x258_started = 1; + sub_80230438(); + } + break; + case EScriptObjectMessage::Stop: + if (x2cc_parentId == uid && x258_started != 0) { + x258_started = 0; + sub_80230438(); + } + break; + case EScriptObjectMessage::Action: + if (x2cc_parentId == uid && x258_started == 1) { + x3fc_launchGrenade = true; + } + break; + case EScriptObjectMessage::Registered: + sub_80230438(); + break; + case EScriptObjectMessage::Damage: + x3ec_damageTimer = 0.33f; + break; + default: + break; + } +} + +void CGrenadeLauncher::AddToRenderer(const zeus::CFrustum& frustum, const CStateManager& mgr) const { + CActor::AddToRenderer(frustum, mgr); +} + +std::optional CGrenadeLauncher::GetTouchBounds() const { + return x328_cSphere.CalculateAABox(GetTransform()); +} + +void CGrenadeLauncher::PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) { + if (x3f4_color3.a() == 1.f) { + xb4_drawFlags = CModelFlags{2, 0, 3, zeus::skWhite}; + // Original code redundantly sets a() = 1.f + xb4_drawFlags.addColor = x3f4_color3; + } else { + xb4_drawFlags = CModelFlags{5, 0, 3, zeus::skWhite}; + xb4_drawFlags.addColor = x3f4_color3; + } + CActor::PreRender(mgr, frustum); +} + +void CGrenadeLauncher::Render(const CStateManager& mgr) const { + if (x3fd_visible) { + CPhysicsActor::Render(mgr); + } +} + +void CGrenadeLauncher::Think(float dt, CStateManager& mgr) { + if (GetActive()) { + if (x3fc_launchGrenade) { + LaunchGrenade(mgr); + x3fc_launchGrenade = false; + } + + UpdateCollision(); + UpdateColor(dt); + sub_8022f9e0(mgr, dt); + UpdateDamageTime(dt); + + const SAdvancementDeltas& deltas = CActor::UpdateAnimation(dt, mgr, true); + MoveToOR(deltas.x0_posDelta, dt); + RotateToOR(deltas.xc_rotDelta, dt); + + TCastToPtr parent = mgr.ObjectById(x2cc_parentId); + if (parent == nullptr || !parent->IsAlive() || parent->HealthInfo(mgr)->GetHP() <= 0.f) { + mgr.SendScriptMsg(parent, GetUniqueId(), EScriptObjectMessage::Damage); + CreateExplosion(mgr); + mgr.FreeScriptObject(GetUniqueId()); + } + } +} + +void CGrenadeLauncher::Touch(CActor& act, CStateManager& mgr) { + if (TCastToPtr projectile = act) { + if (projectile->GetOwnerId() == mgr.GetPlayer().GetUniqueId() && + GetDamageVulnerability()->WeaponHurts(CWeaponMode{projectile->GetType()}, false)) { + x348_shotTimer = 0.5f; + CEntity* parent = mgr.ObjectById(x2cc_parentId); + if (parent != nullptr) { + mgr.SendScriptMsg(parent, GetUniqueId(), EScriptObjectMessage::Touched); + } + } + } +} + +void CGrenadeLauncher::UpdateCollision() { + x328_cSphere.SetSphereCenter(GetLocatorTransform("lockon_target_LCTR"sv).origin); +} + +void CGrenadeLauncher::UpdateColor(float arg) { + if (x348_shotTimer > 0.f) { + x348_shotTimer = std::max(0.f, x348_shotTimer - arg); + x34c_color1 = zeus::CColor::lerp(zeus::skWhite, zeus::skRed, x348_shotTimer); + } +} + +void CGrenadeLauncher::UpdateDamageTime(float arg) { + if (x3ec_damageTimer <= 0.f) { + xd0_damageMag = x3e8_thermalMag; + } else { + x3ec_damageTimer = std::max(0.f, x3ec_damageTimer - arg); + x3f4_color3 = zeus::CColor::lerp(zeus::skBlack, x3f0_color2, std::clamp(x3ec_damageTimer / 0.33f, 0.f, 1.f)); + xd0_damageMag = 5.f * x3ec_damageTimer + x3e8_thermalMag; + } +} + +void CGrenadeLauncher::CreateExplosion(CStateManager& mgr) { + if (!x3b8_particleGenDesc) { + return; + } + mgr.AddObject(new CExplosion(*x3b8_particleGenDesc, mgr.AllocateUniqueId(), true, + {GetAreaIdAlways(), CEntity::NullConnectionList}, "Grenade Launcher Explode Fx"sv, + GetTransform(), 0, GetModelData()->GetScale(), zeus::skWhite)); + CSfxManager::SfxStart(x2d0_data.GetExplosionSfx(), 1.f, 1.f, false, 0x7f, false, kInvalidAreaId); +} + +void CGrenadeLauncher::sub_8022f9e0(CStateManager& mgr, float dt) { + CModelData* modelData = GetModelData(); + CAnimData* animData = nullptr; + + if (modelData != nullptr && (animData = modelData->GetAnimationData()) != nullptr && x258_started == 1 && x3fe_) { + const zeus::CVector3f& target = mgr.GetPlayer().GetAimPosition(mgr, 0.f) - GetTranslation(); + const zeus::CVector3f& rot = GetTransform().rotate({target.x(), target.y(), 0.f}); // TODO double check + + if (rot.canBeNormalized()) { + constexpr float p36d = zeus::degToRad(36.476f); + constexpr float n45d = zeus::degToRad(-45.f); + constexpr float p45d = zeus::degToRad(45.f); + + float l84 = p36d * std::clamp(std::atan2(rot.x(), rot.y()), n45d, p45d); + float l88 = std::clamp((0.25f * (l84 - x3d8_)) / dt, -3.f, 3.f); + float l8c = std::clamp((l88 - x3dc_) / dt, -10.f, 10.f); + x3dc_ += dt * l8c; + + float l90 = p36d * std::clamp(std::atan2(rot.z(), rot.toVec2f().magnitude()), n45d, p45d); + l88 = std::clamp((0.25f * (l90 - x3e0_)) / dt, -3.f, 3.f); + l8c = std::clamp((l88 - x3e4_) / dt, -10.f, 10.f); + x3e4_ += dt * l8c; + + float dVar7 = std::clamp(dt * x3dc_ + x3d8_, -0.5f, 0.5f); + float dVar8 = std::clamp(dt * x3e4_ + x3e0_, -0.5f, 0.5f); + + if (dVar7 != x3d8_) { + if (std::abs(x3d8_) > 0.f && x3d8_ * dVar7 <= 0.f) { + animData->DelAdditiveAnimation(x3c8_animIds[x3d8_ >= 0.f ? 1 : 0]); + } + float weight = std::abs(dVar7); + if (weight > 0.f) { + animData->AddAdditiveAnimation(x3c8_animIds[dVar7 >= 0.f ? 1 : 0], weight, false, false); + } + } + if (dVar8 != x3e0_) { + if (std::abs(x3e0_) > 0.f && x3e0_ * dVar8 <= 0.f) { + animData->DelAdditiveAnimation(x3c8_animIds[x3e0_ <= 0.f ? 3 : 2]); + } + float weight = std::abs(dVar8); + if (weight > 0.f) { + animData->AddAdditiveAnimation(x3c8_animIds[dVar8 <= 0.f ? 3 : 2], weight, false, false); + } + } + x3d8_ = dVar7; + x3e0_ = dVar8; + } + } else { + if (x3d8_ != 0.f) { + if (animData != nullptr) { // Original code does not check + animData->DelAdditiveAnimation(x3c8_animIds[x3d8_ >= 0.f ? 1 : 0]); + } + x3d8_ = 0.f; + } + if (x3e0_ != 0.f) { + if (animData != nullptr) { // Original code does not check + animData->DelAdditiveAnimation(x3c8_animIds[x3e0_ <= 0.f ? 3 : 2]); + } + x3e0_ = 0.f; + } + } +} + +void CGrenadeLauncher::sub_80230438() { + CModelData* modelData = GetModelData(); + CAnimData* animData; + if (modelData == nullptr || (animData = modelData->GetAnimationData()) == nullptr || x258_started <= -1 || + x258_started >= 2) { + return; + } + + constexpr std::array arr = {0, 3}; + const auto& anim = animData->GetCharacterInfo().GetPASDatabase().FindBestAnimation( + CPASAnimParmData{5, CPASAnimParm::FromEnum(0), CPASAnimParm::FromEnum(arr[x258_started])}, -1); + if (anim.first > 0.f) { + animData->SetAnimation({anim.second, -1, 1.f, true}, false); + modelData->EnableLooping(true); + } +} + +void CGrenadeLauncher::LaunchGrenade(CStateManager& mgr) { + CModelData* modelData = GetModelData(); + CAnimData* animData; + if (modelData == nullptr || (animData = modelData->GetAnimationData()) == nullptr) { + return; + } + + const auto& anim = animData->GetCharacterInfo().GetPASDatabase().FindBestAnimation(CPASAnimParmData{23}, -1); + if (anim.first > 0.f) { + animData->AddAdditiveAnimation(anim.second, 1.f, false, true); + const zeus::CVector3f& origin = + GetTranslation() + GetTransform().rotate(GetLocatorTransform("grenade_LCTR"sv).origin); + const zeus::CVector3f& target = GrenadeTarget(mgr); + float angleOut = x2d0_data.GetGrenadeTrajectoryInfo().GetAngleMin(); + float velocityOut = x2d0_data.GetGrenadeTrajectoryInfo().GetVelocityMin(); + CalculateGrenadeTrajectory(target, origin, x2d0_data.GetGrenadeTrajectoryInfo(), angleOut, velocityOut); + + zeus::CVector3f dist = target - origin; + dist.z() = 0.f; + const zeus::CVector3f& front = GetTransform().frontVector(); + if (dist.canBeNormalized()) { + dist.normalize(); + } else { + dist = front; + } + + constexpr float maxAngle = zeus::degToRad(45.f); + if (zeus::CVector3f::getAngleDiff(front, dist) > maxAngle) { + dist = zeus::CVector3f::slerp(front, dist, maxAngle); + } + + const zeus::CVector3f& look = zeus::CVector3f::slerp(dist, zeus::skUp, angleOut); + const zeus::CTransform& xf = zeus::lookAt(origin, origin + look, zeus::skUp); + CModelData mData{CStaticRes{x2d0_data.GetGrenadeModelId(), GetModelData()->GetScale()}}; + mgr.AddObject(new CBouncyGrenade(mgr.AllocateUniqueId(), "Bouncy Grenade"sv, + {GetAreaIdAlways(), CEntity::NullConnectionList}, xf, std::move(mData), + x350_grenadeActorParams, x2cc_parentId, x2d0_data.GetGrenadeData(), velocityOut, + x3f8_explodePlayerDistance)); + } +} +} // namespace urde::MP1 diff --git a/Runtime/MP1/World/CGrenadeLauncher.hpp b/Runtime/MP1/World/CGrenadeLauncher.hpp new file mode 100644 index 000000000..ad865e7f2 --- /dev/null +++ b/Runtime/MP1/World/CGrenadeLauncher.hpp @@ -0,0 +1,124 @@ +#pragma once + +#include "Runtime/Character/CModelData.hpp" +#include "Runtime/Collision/CCollidableSphere.hpp" +#include "Runtime/MP1/World/CBouncyGrenade.hpp" +#include "Runtime/Particle/CGenDescription.hpp" +#include "Runtime/World/CActorParameters.hpp" +#include "Runtime/World/CDamageInfo.hpp" +#include "Runtime/World/CDamageVulnerability.hpp" +#include "Runtime/World/CEntityInfo.hpp" +#include "Runtime/World/CHealthInfo.hpp" +#include "Runtime/World/CPhysicsActor.hpp" + +#include "TCastTo.hpp" // Generated file, do not modify include path + +#include +#include +#include +#include +#include + +namespace urde::MP1 { +struct SGrenadeTrajectoryInfo { +private: + float x0_velocityMin; + float x4_velocityMax; + float x8_angleMin; + float xc_angleMax; + +public: + explicit SGrenadeTrajectoryInfo(CInputStream& in) + : x0_velocityMin(in.readFloatBig()) + , x4_velocityMax(in.readFloatBig()) + , x8_angleMin(zeus::degToRad(in.readFloatBig())) + , xc_angleMax(zeus::degToRad(in.readFloatBig())) {} + + [[nodiscard]] float GetVelocityMin() const { return x0_velocityMin; } + [[nodiscard]] float GetVelocityMax() const { return x4_velocityMax; } + [[nodiscard]] float GetAngleMin() const { return x8_angleMin; } + [[nodiscard]] float GetAngleMax() const { return xc_angleMax; } +}; + +struct SGrenadeLauncherData { +private: + SBouncyGrenadeData x0_grenadeData; + CAssetId x3c_grenadeCmdl; + CAssetId x40_launcherExplodeGenDesc; + u16 x44_launcherExplodeSfx; + SGrenadeTrajectoryInfo x48_trajectoryInfo; + +public: + SGrenadeLauncherData(const SBouncyGrenadeData& data, CAssetId w1, CAssetId w2, u16 sfx, + const SGrenadeTrajectoryInfo& trajectoryInfo) + : x0_grenadeData(data) + , x3c_grenadeCmdl(w1) + , x40_launcherExplodeGenDesc(w2) + , x44_launcherExplodeSfx(sfx) + , x48_trajectoryInfo(trajectoryInfo){}; + + [[nodiscard]] const SBouncyGrenadeData& GetGrenadeData() const { return x0_grenadeData; } + [[nodiscard]] CAssetId GetGrenadeModelId() const { return x3c_grenadeCmdl; } + [[nodiscard]] CAssetId GetExplosionGenDescId() const { return x40_launcherExplodeGenDesc; } + [[nodiscard]] u16 GetExplosionSfx() const { return x44_launcherExplodeSfx; } + [[nodiscard]] const SGrenadeTrajectoryInfo& GetGrenadeTrajectoryInfo() const { return x48_trajectoryInfo; } +}; + +class CGrenadeLauncher : public CPhysicsActor { +private: + int x258_started = 0; + CHealthInfo x25c_healthInfo; + CDamageVulnerability x264_vulnerability; + TUniqueId x2cc_parentId; + SGrenadeLauncherData x2d0_data; + CCollidableSphere x328_cSphere; + float x348_shotTimer = -1.f; + zeus::CColor x34c_color1{1.f}; + CActorParameters x350_grenadeActorParams; + std::optional> x3b8_particleGenDesc; + std::array x3c8_animIds{}; + float x3d8_ = 0.f; + float x3dc_ = 0.f; + float x3e0_ = 0.f; + float x3e4_ = 0.f; + float x3e8_thermalMag; + float x3ec_damageTimer = 0.f; + zeus::CColor x3f0_color2{0.5f, 0.f, 0.f}; + zeus::CColor x3f4_color3{0.f}; + float x3f8_explodePlayerDistance; + bool x3fc_launchGrenade = false; + bool x3fd_visible = true; + bool x3fe_ = true; + +public: + CGrenadeLauncher(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, + CModelData&& mData, const zeus::CAABox& bounds, const CHealthInfo& healthInfo, + const CDamageVulnerability& vulnerability, const CActorParameters& actParams, TUniqueId parentId, + const SGrenadeLauncherData& data, float f1); + + void Accept(IVisitor& visitor) override { visitor.Visit(this); } + void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override; + void AddToRenderer(const zeus::CFrustum& frustum, const CStateManager& mgr) const override; + [[nodiscard]] const CCollisionPrimitive* GetCollisionPrimitive() const override { return &x328_cSphere; } + [[nodiscard]] const CDamageVulnerability* GetDamageVulnerability() const override { return &x264_vulnerability; } + [[nodiscard]] std::optional GetTouchBounds() const override; + CHealthInfo* HealthInfo(CStateManager& mgr) override { return &x25c_healthInfo; } + void PreRender(CStateManager& mgr, const zeus::CFrustum& frustum) override; + void Render(const CStateManager& mgr) const override; + void Think(float dt, CStateManager& mgr) override; + void Touch(CActor& act, CStateManager& mgr) override; + + static zeus::CVector3f GrenadeTarget(const CStateManager& mgr); + static void CalculateGrenadeTrajectory(const zeus::CVector3f& target, const zeus::CVector3f& origin, + const SGrenadeTrajectoryInfo& info, float& angleOut, float& velocityOut); + +private: + void UpdateCollision(); + void UpdateColor(float arg); + void UpdateDamageTime(float arg); + void CreateExplosion(CStateManager& mgr); + void sub_8022f9e0(CStateManager& mgr, float dt); + void sub_80230438(); + void LaunchGrenade(CStateManager& mgr); +}; +} // namespace urde::MP1 diff --git a/Runtime/MP1/World/CMakeLists.txt b/Runtime/MP1/World/CMakeLists.txt index 4ac6f702a..e0c88ce2e 100644 --- a/Runtime/MP1/World/CMakeLists.txt +++ b/Runtime/MP1/World/CMakeLists.txt @@ -1,42 +1,47 @@ set(MP1_WORLD_SOURCES - CNewIntroBoss.hpp CNewIntroBoss.cpp - CBeetle.hpp CBeetle.cpp - CWarWasp.hpp CWarWasp.cpp - CElitePirate.hpp CElitePirate.cpp - CBloodFlower.hpp CBloodFlower.cpp - CChozoGhost.hpp CChozoGhost.cpp - CSpacePirate.hpp CSpacePirate.cpp - CParasite.hpp CParasite.cpp - CBabygoth.hpp CBabygoth.cpp - CTryclops.hpp CTryclops.cpp - CFireFlea.hpp CFireFlea.cpp - CEnergyBall.hpp CEnergyBall.cpp - CEyeball.hpp CEyeball.cpp + CActorContraption.hpp CActorContraption.cpp CAtomicAlpha.hpp CAtomicAlpha.cpp CAtomicBeta.hpp CAtomicBeta.cpp + CBabygoth.hpp CBabygoth.cpp + CBeetle.hpp CBeetle.cpp + CBloodFlower.hpp CBloodFlower.cpp + CBouncyGrenade.hpp CBouncyGrenade.cpp + CBurrower.hpp CBurrower.cpp + CChozoGhost.hpp CChozoGhost.cpp + CElitePirate.hpp CElitePirate.cpp + CEnergyBall.hpp CEnergyBall.cpp + CEyeball.hpp CEyeball.cpp + CFireFlea.hpp CFireFlea.cpp + CFlaahgra.hpp CFlaahgra.cpp + CFlaahgraProjectile.hpp CFlaahgraProjectile.cpp + CFlaahgraTentacle.hpp CFlaahgraTentacle.cpp CFlickerBat.hpp CFlickerBat.cpp - CJellyZap.hpp CJellyZap.cpp CFlyingPirate.hpp CFlyingPirate.cpp - CMetroidPrimeRelay.hpp CMetroidPrimeRelay.cpp + CGrenadeLauncher.hpp CGrenadeLauncher.cpp + CJellyZap.hpp CJellyZap.cpp + CMagdolite.hpp CMagdolite.cpp + CMetaree.hpp CMetaree.cpp + CMetroid.hpp CMetroid.cpp + CMetroidBeta.hpp CMetroidBeta.cpp CMetroidPrimeExo.hpp CMetroidPrimeExo.cpp CMetroidPrimeProjectile.hpp CMetroidPrimeProjectile.cpp - CActorContraption.hpp CActorContraption.cpp - CThardusRockProjectile.hpp CThardusRockProjectile.cpp - CMetroidBeta.hpp CMetroidBeta.cpp - CMetroid.hpp CMetroid.cpp - CMetaree.hpp CMetaree.cpp - CBurrower.hpp CBurrower.cpp - CPuffer.hpp CPuffer.cpp - CMagdolite.hpp CMagdolite.cpp - CSeedling.hpp CSeedling.cpp - CRidley.hpp CRidley.cpp - CPuddleToadGamma.hpp CPuddleToadGamma.cpp - CFlaahgra.hpp CFlaahgra.cpp - CFlaahgraTentacle.hpp CFlaahgraTentacle.cpp - CFlaahgraProjectile.hpp CFlaahgraProjectile.cpp - CSpankWeed.hpp CSpankWeed.cpp + CMetroidPrimeRelay.hpp CMetroidPrimeRelay.cpp + CNewIntroBoss.hpp CNewIntroBoss.cpp + COmegaPirate.hpp COmegaPirate.cpp + CParasite.hpp CParasite.cpp CPuddleSpore.hpp CPuddleSpore.cpp + CPuddleToadGamma.hpp CPuddleToadGamma.cpp + CPuffer.hpp CPuffer.cpp + CRidley.hpp CRidley.cpp CRipper.hpp CRipper.cpp - CThardus.hpp CThardus.cpp) + CSeedling.hpp CSeedling.cpp + CShockWave.hpp CShockWave.cpp + CSpacePirate.hpp CSpacePirate.cpp + CSpankWeed.hpp CSpankWeed.cpp + CThardus.hpp CThardus.cpp + CThardusRockProjectile.hpp CThardusRockProjectile.cpp + CTryclops.hpp CTryclops.cpp + CWarWasp.hpp CWarWasp.cpp +) runtime_add_list(World MP1_WORLD_SOURCES) diff --git a/Runtime/MP1/World/COmegaPirate.cpp b/Runtime/MP1/World/COmegaPirate.cpp new file mode 100644 index 000000000..16f462ec6 --- /dev/null +++ b/Runtime/MP1/World/COmegaPirate.cpp @@ -0,0 +1,12 @@ +#include "Runtime/MP1/World/COmegaPirate.hpp" + +namespace urde::MP1 { +COmegaPirate::CFlash::CFlash(TUniqueId uid, const CEntityInfo& info, const zeus::CVector3f& pos, CToken& p4, float p5) +: CActor(uid, true, "Omega Pirate Flash", info, zeus::CTransform::Translate(pos), CModelData::CModelDataNull(), {}, + CActorParameters::None(), kInvalidUniqueId) {} + +COmegaPirate::COmegaPirate(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, + CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms, + CElitePirateData data, CAssetId w1, CAssetId w2, CAssetId w3) +: CElitePirate(uid, name, info, xf, std::move(mData), pInfo, actParms, data) {} +} // namespace urde::MP1 diff --git a/Runtime/MP1/World/COmegaPirate.hpp b/Runtime/MP1/World/COmegaPirate.hpp new file mode 100644 index 000000000..369393aa5 --- /dev/null +++ b/Runtime/MP1/World/COmegaPirate.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "Runtime/MP1/World/CElitePirate.hpp" + +namespace urde::MP1 { +class COmegaPirate : public CElitePirate { +private: + class CFlash : public CActor { + private: + CToken xe8_; + int xf0_; + float xf4_; + float xf8_; + float xfc_; + + CFlash(TUniqueId uid, const CEntityInfo& info, const zeus::CVector3f& pos, CToken& p4, float p5); + }; + +public: + COmegaPirate(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, + CModelData&& mData, const CPatternedInfo& pInfo, const CActorParameters& actParms, CElitePirateData data, + CAssetId w1, CAssetId w2, CAssetId w3); +}; +} // namespace urde::MP1 diff --git a/Runtime/MP1/World/CShockWave.cpp b/Runtime/MP1/World/CShockWave.cpp new file mode 100644 index 000000000..a61cbd53a --- /dev/null +++ b/Runtime/MP1/World/CShockWave.cpp @@ -0,0 +1,177 @@ +#include "Runtime/MP1/World/CShockWave.hpp" + +#include "Runtime/Collision/CCollisionActor.hpp" +#include "Runtime/CSimplePool.hpp" +#include "Runtime/CStateManager.hpp" +#include "Runtime/GameGlobalObjects.hpp" +#include "Runtime/Graphics/CBooRenderer.hpp" +#include "Runtime/World/CActorParameters.hpp" +#include "Runtime/World/CGameLight.hpp" +#include "Runtime/World/CHUDBillboardEffect.hpp" +#include "Runtime/World/CPlayer.hpp" + +#include "TCastTo.hpp" // Generated file, do not modify include path + +namespace urde::MP1 { +CShockWave::CShockWave(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, + TUniqueId parent, const SShockWaveData& data, float minActiveTime, float knockback) +: CActor(uid, true, name, info, xf, CModelData::CModelDataNull(), {EMaterialTypes::Projectile}, + CActorParameters::None(), kInvalidUniqueId) +, xe8_id1(parent) +, xec_damageInfo(data.GetDamageInfo()) +, x108_elementGenDesc(g_SimplePool->GetObj({SBIG('PART'), data.GetParticleDescId()})) +, x110_elementGen(std::make_unique(x108_elementGenDesc)) +, x114_data(data) +, x150_(data.GetX24()) +, x154_(data.GetX2C()) +, x15c_minActiveTime(minActiveTime) +, x160_knockback(knockback) { + if (data.GetWeaponDescId().IsValid()) { + x974_electricDesc = g_SimplePool->GetObj({SBIG('ELSC'), data.GetWeaponDescId()}); + } + x110_elementGen->SetParticleEmission(true); + x110_elementGen->SetOrientation(GetTransform().getRotation()); + x110_elementGen->SetGlobalTranslation(GetTranslation()); + xe6_27_thermalVisorFlags = 2; +} + +void CShockWave::Accept(IVisitor& visitor) { visitor.Visit(this); } + +void CShockWave::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) { + if (msg == EScriptObjectMessage::Registered) { + if (x110_elementGen->SystemHasLight()) { + x980_id2 = mgr.AllocateUniqueId(); + mgr.AddObject(new CGameLight(x980_id2, GetAreaIdAlways(), GetActive(), "ShockWaveLight_" + x10_name, + GetTransform(), GetUniqueId(), x110_elementGen->GetLight(), + x114_data.GetParticleDescId().Value(), 1, 0.f)); + } + } else if (msg == EScriptObjectMessage::Deleted) { + mgr.FreeScriptObject(x980_id2); + x980_id2 = kInvalidUniqueId; + } + CActor::AcceptScriptMsg(msg, uid, mgr); + mgr.SendScriptMsgAlways(x980_id2, uid, msg); +} + +void CShockWave::AddToRenderer(const zeus::CFrustum& frustum, const CStateManager& mgr) const { + CActor::AddToRenderer(frustum, mgr); + g_Renderer->AddParticleGen(*x110_elementGen); +} + +std::optional CShockWave::GetTouchBounds() const { + if (x150_ <= 0.f) { + return std::nullopt; + } + return zeus::CAABox({-x150_, -x150_, 0.f}, {x150_, x150_, 1.f}).getTransformedAABox(GetTransform()); +} + +void CShockWave::Render(const CStateManager& mgr) const { + CActor::Render(mgr); + x110_elementGen->Render(); +} + +void CShockWave::Think(float dt, CStateManager& mgr) { + if (GetActive()) { + x110_elementGen->Update(dt); + x158_activeTime += dt; + x150_ += x154_ * dt; + x154_ += dt * x114_data.GetX30(); + x110_elementGen->SetExternalVar(0, x150_); + for (int i = 0; i < x110_elementGen->GetNumActiveChildParticles(); ++i) { + auto& particle = static_cast(x110_elementGen->GetActiveChildParticle(i)); + if (particle.Get4CharId() == SBIG('PART')) { + particle.SetExternalVar(0, x150_); + } + } + if (x16c_hitPlayerInAir) { + x164_timeSinceHitPlayerInAir += dt; + x16c_hitPlayerInAir = false; + } + if (x16d_hitPlayer) { + x168_timeSinceHitPlayer += dt; + x16d_hitPlayer = false; + } + } + if (x110_elementGen->IsSystemDeletable() && x15c_minActiveTime > 0.f && x158_activeTime >= x15c_minActiveTime) { + mgr.FreeScriptObject(GetUniqueId()); + } else if (x980_id2 != kInvalidUniqueId) { + if (TCastToPtr light = mgr.ObjectById(x980_id2)) { + if (light->GetActive()) { + light->SetLight(x110_elementGen->GetLight()); + } + } + } +} + +void CShockWave::Touch(CActor& actor, CStateManager& mgr) { + if (x158_activeTime >= x15c_minActiveTime) { + return; + } + + bool isParent = xe8_id1 == actor.GetUniqueId(); + if (TCastToConstPtr cactor = mgr.GetObjectById(actor.GetUniqueId())) { + isParent = xe8_id1 == cactor->GetOwnerId(); + } + if (isParent) { + return; + } + + float mmax = x150_ * x150_; + float mmin = mmax * x114_data.GetX28() * x114_data.GetX28(); + zeus::CVector3f dist = actor.GetTranslation() - GetTranslation(); + CDamageInfo damageInfo = xec_damageInfo; + float knockBackScale = std::max(0.f, 1.f - x160_knockback * x158_activeTime); + bool isPlayer = mgr.GetPlayer().GetUniqueId() == actor.GetUniqueId(); + bool isPlayerInAir = isPlayer && mgr.GetPlayer().GetPlayerMovementState() != CPlayer::EPlayerMovementState::OnGround; + float distXYMag = dist.toVec2f().magSquared(); + if (distXYMag < mmin || distXYMag > mmax) { + return; + } + + if (isPlayer) { + if (mgr.GetPlayer().GetPlayerMovementState() == CPlayer::EPlayerMovementState::OnGround) { + const zeus::CTransform& playerTransform = mgr.GetPlayer().GetTransform(); + zeus::CVector3f playerDir = GetTranslation() - playerTransform.origin; + if (playerDir.canBeNormalized()) { + playerDir.normalize(); + float dot = std::abs(playerDir.dot(playerTransform.frontVector())); + knockBackScale = std::max(0.12f, 0.88f * dot * dot); + } + } + if (mgr.GetPlayer().GetVelocity().magnitude() > 40.f) { + x168_timeSinceHitPlayer = 0.2666f; + } + } + damageInfo.SetKnockBackPower(knockBackScale * damageInfo.GetKnockBackPower()); + + if (isPlayer && (x164_timeSinceHitPlayerInAir >= 0.1333f || x168_timeSinceHitPlayer >= 0.2666f)) { + return; + } + if (!IsHit(actor.GetUniqueId())) { + mgr.ApplyDamage(GetUniqueId(), actor.GetUniqueId(), GetUniqueId(), damageInfo, + CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), zeus::skZero3f); + if (isPlayer && x974_electricDesc) { + mgr.AddObject(new CHUDBillboardEffect(std::nullopt, x974_electricDesc, mgr.AllocateUniqueId(), true, + "VisorElectricFx", CHUDBillboardEffect::GetNearClipDistance(mgr), + CHUDBillboardEffect::GetScaleForPOV(mgr), zeus::skWhite, zeus::skOne3f, + zeus::skZero3f)); + CSfxManager::SfxStart(x114_data.GetElectrocuteSfx(), 1.f, 1.f, false, 0x7f, false, kInvalidAreaId); + } + x170_hitIds.push_back(actor.GetUniqueId()); + } else { + damageInfo.SetDamage(0.f); + mgr.ApplyDamage(GetUniqueId(), actor.GetUniqueId(), GetUniqueId(), damageInfo, + CMaterialFilter::MakeInclude({EMaterialTypes::Solid}), zeus::skZero3f); + } + if (isPlayerInAir) { + x16c_hitPlayerInAir = true; + } + if (isPlayer) { + x16d_hitPlayer = true; + } +} + +bool CShockWave::IsHit(TUniqueId id) const { + return std::find(x170_hitIds.begin(), x170_hitIds.end(), id) != x170_hitIds.end(); +} +} // namespace urde::MP1 diff --git a/Runtime/MP1/World/CShockWave.hpp b/Runtime/MP1/World/CShockWave.hpp new file mode 100644 index 000000000..6947cbf73 --- /dev/null +++ b/Runtime/MP1/World/CShockWave.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include "Runtime/World/CActor.hpp" +#include "Runtime/World/CDamageInfo.hpp" +#include "Runtime/Particle/CElementGen.hpp" + +namespace urde::MP1 { +struct SShockWaveData { +private: + u32 x0_ = 8; + CAssetId x4_particleDesc; + CDamageInfo x8_damageInfo; + float x24_ = 0.f; + float x28_ = 0.5f; + float x2c_ = 16.5217f; + float x30_ = 0.f; + CAssetId x34_weaponDesc; + u16 x38_electrocuteSfx; + +public: + SShockWaveData(CAssetId part, const CDamageInfo& dInfo, CAssetId weapon, u16 sfx) + : x4_particleDesc(part), x8_damageInfo(dInfo), x34_weaponDesc(weapon), x38_electrocuteSfx(sfx) {} + + [[nodiscard]] CAssetId GetParticleDescId() const { return x4_particleDesc; } + [[nodiscard]] const CDamageInfo& GetDamageInfo() const { return x8_damageInfo; } + [[nodiscard]] float GetX24() const { return x24_; } + [[nodiscard]] float GetX28() const { return x28_; } + [[nodiscard]] float GetX2C() const { return x2c_; } + [[nodiscard]] float GetX30() const { return x30_; } + [[nodiscard]] CAssetId GetWeaponDescId() const { return x34_weaponDesc; } + [[nodiscard]] u16 GetElectrocuteSfx() const { return x38_electrocuteSfx; } +}; + +class CShockWave : public CActor { +private: + TUniqueId xe8_id1; + CDamageInfo xec_damageInfo; + TToken x108_elementGenDesc; + std::unique_ptr x110_elementGen; + SShockWaveData x114_data; + float x150_; + float x154_; + float x158_activeTime = 0.f; + float x15c_minActiveTime; + float x160_knockback; + float x164_timeSinceHitPlayerInAir = 0.f; + float x168_timeSinceHitPlayer = 0.f; + bool x16c_hitPlayerInAir = false; + bool x16d_hitPlayer = false; + rstl::reserved_vector x170_hitIds; + std::optional> x974_electricDesc; + TUniqueId x980_id2 = kInvalidUniqueId; + +public: + CShockWave(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, + TUniqueId parent, const SShockWaveData& data, float minActiveTime, float knockback); + + void Accept(IVisitor& visitor) override; + void AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) override; + void AddToRenderer(const zeus::CFrustum& frustum, const CStateManager& mgr) const override; + [[nodiscard]] std::optional GetTouchBounds() const override; + void Render(const CStateManager& mgr) const override; + void Think(float dt, CStateManager& mgr) override; + void Touch(CActor& actor, CStateManager& mgr) override; + +private: + [[nodiscard]] bool IsHit(TUniqueId id) const; +}; +} // namespace urde::MP1 diff --git a/Runtime/Weapon/CGameProjectile.cpp b/Runtime/Weapon/CGameProjectile.cpp index 62a8affdd..851054cef 100644 --- a/Runtime/Weapon/CGameProjectile.cpp +++ b/Runtime/Weapon/CGameProjectile.cpp @@ -25,7 +25,8 @@ CGameProjectile::CGameProjectile(bool active, const TToken& CMaterialFilter::MakeIncludeExclude( {EMaterialTypes::Solid, EMaterialTypes::NonSolidDamageable}, {EMaterialTypes::Projectile, EMaterialTypes::ProjectilePassthrough, excludeMat}), - CMaterialList(), dInfo, attribs | GetBeamAttribType(wType), CModelData::CModelDataNull()) + CMaterialList(EMaterialTypes::Projectile), dInfo, attribs | GetBeamAttribType(wType), + CModelData::CModelDataNull()) , x158_visorParticle(visorParticle) , x168_visorSfx(visorSfx) , x170_projectile(wDesc, xf.origin, xf.basis, scale, diff --git a/Runtime/World/CDamageVulnerability.cpp b/Runtime/World/CDamageVulnerability.cpp index c0c367c52..cb48efc37 100644 --- a/Runtime/World/CDamageVulnerability.cpp +++ b/Runtime/World/CDamageVulnerability.cpp @@ -17,14 +17,14 @@ const CDamageVulnerability CDamageVulnerability::sImmuneVulnerability( EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EDeflectType::None); -/* LOL, thanks retro */ + const CDamageVulnerability CDamageVulnerability::sReflectVulnerability( EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, - EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EDeflectType::None); + EVulnerability::Deflect, EVulnerability::Deflect, EVulnerability::Deflect, EDeflectType::One); const CDamageVulnerability CDamageVulnerability::sPassThroughVulnerability( EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough, EVulnerability::PassThrough, diff --git a/Runtime/World/CPathFindSearch.hpp b/Runtime/World/CPathFindSearch.hpp index 62adc4133..ce5a37d6e 100644 --- a/Runtime/World/CPathFindSearch.hpp +++ b/Runtime/World/CPathFindSearch.hpp @@ -14,6 +14,7 @@ class CPathFindSearch; class CPathFindVisualizer { CLineRenderer m_spline = {CLineRenderer::EPrimitiveMode::LineStrip, 16, {}, true}; + public: void Draw(const CPathFindSearch& path); }; @@ -56,6 +57,8 @@ public: void SetArea(CPFArea* area) { x0_area = area; } float GetCharacterHeight() const { return xd0_chHeight; } void SetCharacterHeight(float h) { xd0_chHeight = h; } + float GetCharacterRadius() const { return xd4_chRadius; } + void SetCharacterRadius(float r) { xd4_chRadius = r; } void SetPadding(float padding) { xd8_padding = padding; } float RemainingPathDistance(const zeus::CVector3f& pos) const; void DebugDraw() const; diff --git a/Runtime/World/CPatterned.hpp b/Runtime/World/CPatterned.hpp index 1c16b9221..2190af4e5 100644 --- a/Runtime/World/CPatterned.hpp +++ b/Runtime/World/CPatterned.hpp @@ -134,7 +134,7 @@ protected: bool x328_28_prevOnGround : 1; bool x328_29_noPatternShagging : 1; bool x328_30_lookAtDeathDir : 1; - bool x328_31_ : 1; + bool x328_31_energyAttractor : 1; bool x329_24_ : 1; }; u32 _dummy = 0; @@ -367,6 +367,7 @@ public: } float GetDamageDuration() const { return x504_damageDur; } zeus::CVector3f GetGunEyePos() const; + bool IsEnergyAttractor() const { return x328_31_energyAttractor; } bool IsAlive() const { return x400_25_alive; } void BuildBodyController(EBodyType); diff --git a/Runtime/World/CPhysicsActor.hpp b/Runtime/World/CPhysicsActor.hpp index c6d54dbff..905aadc5f 100644 --- a/Runtime/World/CPhysicsActor.hpp +++ b/Runtime/World/CPhysicsActor.hpp @@ -161,6 +161,7 @@ public: void SetMomentumWR(const zeus::CVector3f& momentum) { x150_momentum = momentum; } const zeus::CVector3f& GetConstantForce() const { return xfc_constantForce; } void SetConstantForce(const zeus::CVector3f& force) { xfc_constantForce = force; } + const zeus::CVector3f& GetAngularMomentum() const { return x108_angularMomentum; } void SetAngularMomentum(const zeus::CAxisAngle& momentum) { x108_angularMomentum = momentum; } const zeus::CVector3f& GetMomentum() const { return x150_momentum; } const zeus::CVector3f& GetVelocity() const { return x138_velocity; } diff --git a/Runtime/World/ScriptLoader.cpp b/Runtime/World/ScriptLoader.cpp index 1a67936e5..1d2a3707e 100644 --- a/Runtime/World/ScriptLoader.cpp +++ b/Runtime/World/ScriptLoader.cpp @@ -31,6 +31,7 @@ #include "Runtime/MP1/World/CMetroidBeta.hpp" #include "Runtime/MP1/World/CMetroidPrimeRelay.hpp" #include "Runtime/MP1/World/CNewIntroBoss.hpp" +#include "Runtime/MP1/World/COmegaPirate.hpp" #include "Runtime/MP1/World/CParasite.hpp" #include "Runtime/MP1/World/CPuddleSpore.hpp" #include "Runtime/MP1/World/CPuddleToadGamma.hpp" @@ -127,6 +128,8 @@ static logvisor::Module Log("urde::ScriptLoader"); constexpr SObjectTag MorphballDoorANCS = {FOURCC('ANCS'), 0x1F9DA858}; +constexpr int skElitePiratePropCount = 41; + static bool EnsurePropertyCount(int count, int expected, const char* structName) { if (count < expected) { Log.report(logvisor::Warning, fmt("Insufficient number of props ({}/{}) for {} entity"), count, expected, @@ -1394,7 +1397,7 @@ CEntity* ScriptLoader::LoadFlyingPirate(CStateManager& mgr, CInputStream& in, in } CEntity* ScriptLoader::LoadElitePirate(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) { - if (!EnsurePropertyCount(propCount, 41, "ElitePirate")) + if (!EnsurePropertyCount(propCount, skElitePiratePropCount, "ElitePirate")) return nullptr; SScaledActorHead actHead = LoadScaledActorHead(in, mgr); @@ -3638,7 +3641,33 @@ CEntity* ScriptLoader::LoadMazeNode(CStateManager& mgr, CInputStream& in, int pr } CEntity* ScriptLoader::LoadOmegaPirate(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) { + if (!EnsurePropertyCount(propCount, skElitePiratePropCount + 1, "OmegaPirate")) { + return nullptr; + } + +#if 0 + SScaledActorHead actHead = LoadScaledActorHead(in, mgr); + auto pair = CPatternedInfo::HasCorrectParameterCount(in); + if (!pair.first) { + return nullptr; + } + + CPatternedInfo pInfo(in, pair.second); + CActorParameters actParms = LoadActorParameters(in); + MP1::CElitePirateData elitePirateData(in, propCount); + + if (!pInfo.GetAnimationParameters().GetACSFile().IsValid()) { + return nullptr; + } + + CModelData mData(CAnimRes(pInfo.GetAnimationParameters().GetACSFile(), pInfo.GetAnimationParameters().GetCharacter(), + actHead.x40_scale, pInfo.GetAnimationParameters().GetInitialAnimation(), true)); + + return new MP1::COmegaPirate(mgr.AllocateUniqueId(), actHead.x0_name, info, actHead.x10_transform, std::move(mData), + pInfo, actParms, elitePirateData, CAssetId(in), CAssetId(in), CAssetId(in)); +#else return nullptr; +#endif } CEntity* ScriptLoader::LoadPhazonPool(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {