From bc08792523b2742a4607c725e3c7aa8f2c11e4c2 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 30 Mar 2020 03:05:16 -0400 Subject: [PATCH] CBouncyGrenade implementation --- Runtime/MP1/World/CBouncyGrenade.cpp | 203 +++++++++++++++++++++++++ Runtime/MP1/World/CBouncyGrenade.hpp | 73 +++++++++ Runtime/MP1/World/CElitePirate.cpp | 4 +- Runtime/MP1/World/CElitePirate.hpp | 2 +- Runtime/MP1/World/CGrenadeLauncher.cpp | 38 ++++- Runtime/MP1/World/CGrenadeLauncher.hpp | 46 ++---- Runtime/MP1/World/CMakeLists.txt | 1 + Runtime/World/CPhysicsActor.hpp | 1 + 8 files changed, 324 insertions(+), 44 deletions(-) create mode 100644 Runtime/MP1/World/CBouncyGrenade.cpp create mode 100644 Runtime/MP1/World/CBouncyGrenade.hpp diff --git a/Runtime/MP1/World/CBouncyGrenade.cpp b/Runtime/MP1/World/CBouncyGrenade.cpp new file mode 100644 index 000000000..6448858de --- /dev/null +++ b/Runtime/MP1/World/CBouncyGrenade.cpp @@ -0,0 +1,203 @@ +#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}, {}, + {data.x0_.x0_mass}, actParams, 0.3f, 0.1f) +, x258_data(data) +, x294_numBounces(data.x34_numBounces) +, x298_parentId(parentId) +, x2a0_elementGen1(std::make_unique(g_SimplePool->GetObj({'PART', data.x24_elementGenId1}))) +, x2a4_elementGen2(std::make_unique(g_SimplePool->GetObj({'PART', data.x28_elementGenId2}))) +, x2a8_elementGen3(std::make_unique(g_SimplePool->GetObj({'PART', data.x2c_elementGenId3}))) +, x2ac_elementGen4(std::make_unique(g_SimplePool->GetObj({'PART', data.x30_elementGenId4}))) +, 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.x0_.x4_ * GetConstantForce().magnitude()) * *normal; + const zeus::CVector3f angle = -x258_data.x0_.x4_ * GetAngularMomentum(); + ApplyImpulseWR(impulse, angle); + CSfxManager::AddEmitter(x258_data.x38_bounceSfx, 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.x3a_explodeSfx, 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.x8_damageInfo; + { + 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 CActor* 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..0fd2e1cd7 --- /dev/null +++ b/Runtime/MP1/World/CBouncyGrenade.hpp @@ -0,0 +1,73 @@ +#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 { + float x0_mass; + float x4_; // speed? + + SGrenadeUnknownStruct(CInputStream& in) : x0_mass(in.readFloatBig()), x4_(in.readFloatBig()) {} +}; + +struct SBouncyGrenadeData { + 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; + + 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){}; +}; + +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; + 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 0fc0e7b6f..ae2ab3f5a 100644 --- a/Runtime/MP1/World/CElitePirate.cpp +++ b/Runtime/MP1/World/CElitePirate.cpp @@ -64,7 +64,7 @@ CElitePirateData::CElitePirateData(CInputStream& in, u32 propCount) , xd4_(in) , xd8_(in) , xe0_trajectoryInfo(in) -, xf0_(in) +, xf0_grenadeNumBounces(in.readUint32Big()) , xf4_(CSfxManager::TranslateSFXID(in.readUint32Big())) , xf6_(CSfxManager::TranslateSFXID(in.readUint32Big())) , xf8_(in) @@ -892,7 +892,7 @@ void CElitePirate::CreateGrenadeLauncher(CStateManager& mgr, TUniqueId uid) { CModelData mData(CAnimRes(params.GetACSFile(), params.GetCharacter(), GetModelData()->GetScale(), params.GetInitialAnimation(), true)); SBouncyGrenadeData grenadeData{x5d8_data.xd8_, x5d8_data.xa8_, x5d8_data.xc8_, x5d8_data.xcc_, x5d8_data.xd0_, - x5d8_data.xd4_, x5d8_data.xf0_, x5d8_data.xf4_, x5d8_data.xf6_}; + x5d8_data.xd4_, x5d8_data.xf0_grenadeNumBounces, x5d8_data.xf4_, x5d8_data.xf6_}; CGrenadeLauncherData launcherData{grenadeData, x5d8_data.xa4_, x5d8_data.x9c_, x5d8_data.xa0_, x5d8_data.xe0_trajectoryInfo}; mgr.AddObject(new CGrenadeLauncher(uid, "Grenade Launcher", {GetAreaIdAlways(), CEntity::NullConnectionList}, diff --git a/Runtime/MP1/World/CElitePirate.hpp b/Runtime/MP1/World/CElitePirate.hpp index dabc59e2d..12b2be580 100644 --- a/Runtime/MP1/World/CElitePirate.hpp +++ b/Runtime/MP1/World/CElitePirate.hpp @@ -38,7 +38,7 @@ public: CAssetId xd4_; SGrenadeUnknownStruct xd8_; SGrenadeTrajectoryInfo xe0_trajectoryInfo; - CAssetId xf0_; + u32 xf0_grenadeNumBounces; u16 xf4_; u16 xf6_; CAssetId xf8_; diff --git a/Runtime/MP1/World/CGrenadeLauncher.cpp b/Runtime/MP1/World/CGrenadeLauncher.cpp index 19bb10589..5bff3f2cc 100644 --- a/Runtime/MP1/World/CGrenadeLauncher.cpp +++ b/Runtime/MP1/World/CGrenadeLauncher.cpp @@ -10,8 +10,7 @@ #include "Runtime/World/CPatterned.hpp" #include "Runtime/World/CPlayer.hpp" -namespace urde { -namespace MP1 { +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, @@ -24,9 +23,9 @@ CGrenadeLauncher::CGrenadeLauncher(TUniqueId uid, std::string_view name, const C , x2cc_parentId(parentId) , x2d0_data(data) , x328_cSphere({{}, mData.GetScale().z()}, {EMaterialTypes::Character, EMaterialTypes::Solid}) -, x350_actParms(actParams) +, x350_grenadeActorParams(actParams) , x3e8_thermalMag(actParams.GetThermalMag()) -, x3f8_(f1) { +, x3f8_explodePlayerDistance(f1) { if (data.x40_.IsValid()) { x3b8_particleGenDesc = g_SimplePool->GetObj({SBIG('PART'), data.x40_}); } @@ -293,8 +292,33 @@ void CGrenadeLauncher::LaunchGrenade(CStateManager& mgr) { const auto& anim = animData->GetCharacterInfo().GetPASDatabase().FindBestAnimation({24}, -1); if (anim.first > 0.f) { animData->AddAdditiveAnimation(anim.second, 1.f, false, true); - // TODO + const zeus::CVector3f& origin = + GetTranslation() + GetTransform().rotate(GetLocatorTransform("grenade_LCTR"sv).origin); + const zeus::CVector3f& target = GrenadeTarget(mgr); + float angleOut = x2d0_data.x48_trajectoryInfo.x8_angleMin, velocityOut = x2d0_data.x48_trajectoryInfo.x0_; + CalculateGrenadeTrajectory(target, origin, x2d0_data.x48_trajectoryInfo, 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.x3c_grenadeCmdl, GetModelData()->GetScale()}}; + mgr.AddObject(new CBouncyGrenade(mgr.AllocateUniqueId(), "Bouncy Grenade"sv, + {GetAreaIdAlways(), CEntity::NullConnectionList}, xf, std::move(mData), + x350_grenadeActorParams, x2cc_parentId, x2d0_data.x0_grenadeData, velocityOut, + x3f8_explodePlayerDistance)); } } -} // namespace MP1 -} // namespace urde +} // namespace urde::MP1 diff --git a/Runtime/MP1/World/CGrenadeLauncher.hpp b/Runtime/MP1/World/CGrenadeLauncher.hpp index b961a393f..11f3bf4b1 100644 --- a/Runtime/MP1/World/CGrenadeLauncher.hpp +++ b/Runtime/MP1/World/CGrenadeLauncher.hpp @@ -2,15 +2,14 @@ #include "Runtime/Character/CModelData.hpp" #include "Runtime/Collision/CCollidableSphere.hpp" +#include "Runtime/MP1/World/CBouncyGrenade.hpp" #include "Runtime/Particle/CGenDescription.hpp" -#include "Runtime/RetroTypes.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 "Runtime/World/CPhysicsActor.hpp" #include "TCastTo.hpp" // Generated file, do not modify include path @@ -20,8 +19,7 @@ #include #include -namespace urde { -namespace MP1 { +namespace urde::MP1 { struct SGrenadeTrajectoryInfo { float x0_; float x4_; @@ -35,40 +33,21 @@ struct SGrenadeTrajectoryInfo { , xc_angleMax(zeus::degToRad(in.readFloatBig())) {} }; -struct SGrenadeUnknownStruct { - float x0_mass; - float x4_; - - SGrenadeUnknownStruct(CInputStream& in) : x0_mass(in.readFloatBig()), x4_(in.readFloatBig()) {} -}; - -struct SBouncyGrenadeData { - SGrenadeUnknownStruct x0_; - CDamageInfo x8_damageInfo; - CAssetId x24_; - CAssetId x28_; - CAssetId x2c_; - CAssetId x30_; - CAssetId x34_; - u16 x38_; - u16 x3a_; - - SBouncyGrenadeData(const SGrenadeUnknownStruct& unkStruct, const CDamageInfo& damageInfo, CAssetId w1, CAssetId w2, - CAssetId w3, CAssetId w4, CAssetId w5, u16 s1, u16 s2) - : x0_(unkStruct), x8_damageInfo(damageInfo), x24_(w1), x28_(w2), x2c_(w3), x30_(w4), x34_(w5), x38_(s1), x3a_(s2){}; -}; - class CGrenadeLauncherData { public: - SBouncyGrenadeData x0_; - CAssetId x3c_; + SBouncyGrenadeData x0_grenadeData; + CAssetId x3c_grenadeCmdl; CAssetId x40_; u16 x44_launcherExplodeSfx; SGrenadeTrajectoryInfo x48_trajectoryInfo; CGrenadeLauncherData(const SBouncyGrenadeData& data, CAssetId w1, CAssetId w2, u16 sfx, const SGrenadeTrajectoryInfo& trajectoryInfo) - : x0_(data), x3c_(w1), x40_(w2), x44_launcherExplodeSfx(sfx), x48_trajectoryInfo(trajectoryInfo){}; + : x0_grenadeData(data) + , x3c_grenadeCmdl(w1) + , x40_(w2) + , x44_launcherExplodeSfx(sfx) + , x48_trajectoryInfo(trajectoryInfo){}; }; class CGrenadeLauncher : public CPhysicsActor { @@ -81,7 +60,7 @@ public: CCollidableSphere x328_cSphere; float x348_shotTimer = -1.f; zeus::CColor x34c_color1{1.f}; - CActorParameters x350_actParms; + CActorParameters x350_grenadeActorParams; std::optional> x3b8_particleGenDesc; std::array x3c8_animIds; float x3d8_ = 0.f; @@ -92,7 +71,7 @@ public: float x3ec_damageTimer = 0.f; zeus::CColor x3f0_color2{0.5f, 0.f, 0.f}; zeus::CColor x3f4_color3{0.f}; - float x3f8_; + float x3f8_explodePlayerDistance; bool x3fc_launchGrenade = false; bool x3fd_visible = true; bool x3fe_ = true; @@ -127,5 +106,4 @@ protected: void sub_80230438(); void LaunchGrenade(CStateManager& mgr); }; -} // namespace MP1 -} // namespace urde +} // namespace urde::MP1 diff --git a/Runtime/MP1/World/CMakeLists.txt b/Runtime/MP1/World/CMakeLists.txt index 53e5ac55e..782deee69 100644 --- a/Runtime/MP1/World/CMakeLists.txt +++ b/Runtime/MP1/World/CMakeLists.txt @@ -5,6 +5,7 @@ set(MP1_WORLD_SOURCES 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 diff --git a/Runtime/World/CPhysicsActor.hpp b/Runtime/World/CPhysicsActor.hpp index fd683477c..fab806ccf 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; }