diff --git a/Runtime/Character/CBodyController.hpp b/Runtime/Character/CBodyController.hpp index 1a217be95..79a648e2a 100644 --- a/Runtime/Character/CBodyController.hpp +++ b/Runtime/Character/CBodyController.hpp @@ -103,6 +103,7 @@ public: EBodyType GetBodyType() const { return x2f4_bodyType; } bool HasBeenFrozen() const { return x300_27_hasBeenFrozen; } float GetRestrictedFlyerMoveSpeed() const { return x330_restrictedFlyerMoveSpeed; } + void SetRestrictedFlyerMoveSpeed(float s) { x330_restrictedFlyerMoveSpeed = s; } bool GetActive() const { return x300_25_active; } }; } // namespace urde diff --git a/Runtime/Collision/CCollisionResponseData.hpp b/Runtime/Collision/CCollisionResponseData.hpp index a45424875..62acf9a16 100644 --- a/Runtime/Collision/CCollisionResponseData.hpp +++ b/Runtime/Collision/CCollisionResponseData.hpp @@ -55,7 +55,7 @@ enum class EWeaponCollisionResponseTypes { Unknown39, Unknown40, Unknown41, - Unknown42, + AtomicBeta, AtomicAlpha, Unknown44, Unknown45, @@ -105,7 +105,7 @@ enum class EWeaponCollisionResponseTypes { Unknown89, Unknown90, Unknown91, - Unknown92, + AtomicBetaReflect, AtomicAlphaReflect }; diff --git a/Runtime/MP1/World/CAtomicBeta.cpp b/Runtime/MP1/World/CAtomicBeta.cpp new file mode 100644 index 000000000..c0da833d9 --- /dev/null +++ b/Runtime/MP1/World/CAtomicBeta.cpp @@ -0,0 +1,166 @@ +#include "CAtomicBeta.hpp" +#include "Particle/CWeaponDescription.hpp" +#include "Weapon/CPlayerGun.hpp" +#include "Weapon/CElectricBeamProjectile.hpp" +#include "World/CPlayer.hpp" +#include "GameGlobalObjects.hpp" +#include "CStateManager.hpp" +#include "CSimplePool.hpp" + +namespace urde::MP1 { +const std::string_view CAtomicBeta::skBombLocators[3] = { + "bomb2_LCTR"sv, + "bomb3_LCTR"sv, + "bomb4_LCTR"sv +}; + +CAtomicBeta::CAtomicBeta(TUniqueId uid, std::string_view name, const CEntityInfo& info, const zeus::CTransform& xf, + CModelData&& mData, const CActorParameters& actParms, const CPatternedInfo& pInfo, + CAssetId electricId, CAssetId weaponId, const CDamageInfo& dInfo, CAssetId particleId, + float f1, float beamRadius, float f3, const CDamageVulnerability& dVuln, float f4, float f5, float f6, + s16 sId1, s16 sId2, s16 sId3, float f7) +: CPatterned(ECharacter::AtomicBeta, uid, name, EFlavorType::Zero, info, xf, std::move(mData), pInfo, + EMovementType::Flyer, EColliderType::One, EBodyType::RestrictedFlyer, actParms, EKnockBackVariant::Small) +, x578_(f5) +, x57c_(f6) +, x580_(f7) +, x584_(x578_) +, x588_frozenDamage(dVuln) +, x5f0_(f4) +, x5f4_(xf.basis[1]) +, x600_electricWeapon(g_SimplePool->GetObj({SBIG('ELSC'), electricId})) +, x608_(g_SimplePool->GetObj({SBIG('WPSC'), weaponId})) +, x610_projectileDamage(dInfo) +, x62c_beamParticle(particleId) +, x630_(f1) +, x634_beamRadius(beamRadius) +, x638_(f3) +, x644_(CSfxManager::TranslateSFXID(sId1)) +, x646_(CSfxManager::TranslateSFXID(sId2)) +, x648_(CSfxManager::TranslateSFXID(sId3)) { + x460_knockBackController.SetAutoResetImpulse(false); + x460_knockBackController.SetEnableFreeze(false); + x460_knockBackController.SetX82_24(false); +} + +void CAtomicBeta::CreateBeams(CStateManager& mgr) { + const SElectricBeamInfo beamInfo{x600_electricWeapon, 50.f, x634_beamRadius, 10.f, x62c_beamParticle, x630_, x638_}; + + for (u32 i = 0; i < kBombCount; ++i) { + x568_projectileIds.push_back(mgr.AllocateUniqueId()); + mgr.AddObject(new CElectricBeamProjectile(x608_, EWeaponType::AI, beamInfo, {}, EMaterialTypes::Character, + x610_projectileDamage, x568_projectileIds[i], GetAreaIdAlways(), + GetUniqueId(), EProjectileAttrib::None)); + } +} + +void CAtomicBeta::UpdateBeams(CStateManager& mgr, bool fireBeam) { + if (x574_beamFired == fireBeam) + return; + + for (u32 i = 0; i < kBombCount; ++i) { + zeus::CTransform xf = GetTransform() * GetScaledLocatorTransform(skBombLocators[i]); + zeus::CTransform newXf = zeus::lookAt(xf.origin, xf.origin + xf.basis[1], zeus::CVector3f::skUp); + if (CElectricBeamProjectile* proj = static_cast(mgr.ObjectById(x568_projectileIds[i]))) { + if (fireBeam) + proj->Fire(GetTransform() * GetScaledLocatorTransform(skBombLocators[i]), mgr, false); + else + proj->ResetBeam(mgr, false); + } + } + x574_beamFired = fireBeam; +} + +void CAtomicBeta::FreeBeams(CStateManager& mgr) { + for (TUniqueId uid : x568_projectileIds) { + mgr.FreeScriptObject(uid); + } + x568_projectileIds.clear(); +} + +void CAtomicBeta::UpdateOrCreateEmitter(CSfxHandle& handle, u16 id, const zeus::CVector3f& pos, float maxVol) { + if (handle) + CSfxManager::UpdateEmitter(handle, pos, {}, maxVol); + else + handle = CSfxManager::AddEmitter(id, pos, {}, maxVol, true, true, 0x7F, GetAreaIdAlways()); +} + +void CAtomicBeta::DestroyEmitter(CSfxHandle& handle) { + if (handle) { + CSfxManager::RemoveEmitter(handle); + handle.reset(); + } +} + +void CAtomicBeta::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) { + if (msg == EScriptObjectMessage::Registered) { + x450_bodyController->Activate(mgr); + CreateBeams(mgr); + } else if (msg == EScriptObjectMessage::Deactivate) { + UpdateBeams(mgr, false); + DestroyEmitter(x650_); + DestroyEmitter(x654_); + DestroyEmitter(x64c_); + } else if (msg == EScriptObjectMessage::Deleted) { + FreeBeams(mgr); + } + CPatterned::AcceptScriptMsg(msg, uid, mgr); +} + +void CAtomicBeta::Think(float dt, CStateManager& mgr) { + CPatterned::Think(dt, mgr); + zeus::CVector3f movementVec = x450_bodyController->GetCommandMgr().GetMoveVector(); + x450_bodyController->GetCommandMgr().ClearLocomotionCmds(); + if (!movementVec.isZero()) + x450_bodyController->GetCommandMgr().DeliverCmd(CBCLocomotionCmd(movementVec, x5f4_, 1.f)); + + float mag = + x63c_ * std::max(1.f - (mgr.GetPlayer().GetTranslation() - GetTranslation()).magSquared() / (x640_ / x640_), 0.f); + if (!zeus::close_enough(mag, 0.f)) + mgr.GetPlayerState()->GetStaticInterference().AddSource(GetUniqueId(), mag, 0.5f); + + if (InMaxRange(mgr, dt)) { + UpdateBeams(mgr, true); + UpdateOrCreateEmitter(x650_, x646_, GetTranslation(), 96 / 127.f); + UpdateOrCreateEmitter(x654_, x648_, GetTranslation(), 96 / 127.f); + DestroyEmitter(x64c_); + } else { + DestroyEmitter(x650_); + DestroyEmitter(x654_); + UpdateOrCreateEmitter(x64c_, x644_, GetTranslation(), 96 / 127.f); + } + + for (u32 i = 0; i < kBombCount; ++i) { + if (CElectricBeamProjectile* proj = static_cast(mgr.ObjectById(x568_projectileIds[i]))) { + if (!proj->GetActive()) + continue; + zeus::CTransform xf = GetTransform() * GetScaledLocatorTransform(skBombLocators[i]); + proj->UpdateFx(zeus::lookAt(xf.origin, xf.origin + xf.basis[1], zeus::CVector3f::skUp), dt, mgr); + } + } + + float speed = x580_ * (dt * (IsPlayerBeamChargedEnough(mgr) ? 1.f : -1.f)) + x584_; + x584_ = zeus::clamp(x578_, speed, x57c_); + x3b4_speed = x584_; + x450_bodyController->SetRestrictedFlyerMoveSpeed(x5f0_ * x584_); +} + +const CDamageVulnerability* CAtomicBeta::GetDamageVulnerability() const { + if (zeus::close_enough(x450_bodyController->GetPercentageFrozen(), 0.f)) + return CPatterned::GetDamageVulnerability(); + return &x588_frozenDamage; +} + +void CAtomicBeta::Death(CStateManager& mgr, const zeus::CVector3f& dir, EScriptObjectState state) { + UpdateBeams(mgr, false); + DestroyEmitter(x650_); + DestroyEmitter(x654_); + DestroyEmitter(x64c_); + CPatterned::Death(mgr, dir, state); +} + +bool CAtomicBeta::IsPlayerBeamChargedEnough(const CStateManager& mgr) { + const CPlayerGun* gun = mgr.GetPlayer().GetPlayerGun(); + return (gun->IsCharging() ? gun->GetChargeBeamFactor() : 0.f) > .1f; +} +} // namespace urde::MP1 \ No newline at end of file diff --git a/Runtime/MP1/World/CAtomicBeta.hpp b/Runtime/MP1/World/CAtomicBeta.hpp new file mode 100644 index 000000000..230364320 --- /dev/null +++ b/Runtime/MP1/World/CAtomicBeta.hpp @@ -0,0 +1,61 @@ +#pragma once +#include "World/CPatterned.hpp" + +namespace urde { +class CWeaponDescription; +} + +namespace urde::MP1 { +class CAtomicBeta final : public CPatterned { + static const std::string_view skBombLocators[3]; + static constexpr u32 kBombCount = 3; + rstl::reserved_vector x568_projectileIds; + bool x574_beamFired = false; + float x578_; + float x57c_; + float x580_; + float x584_; + CDamageVulnerability x588_frozenDamage; + float x5f0_; + zeus::CVector3f x5f4_; + TToken x600_electricWeapon; + TToken x608_; + CDamageInfo x610_projectileDamage; + CAssetId x62c_beamParticle; + float x630_; + float x634_beamRadius; + float x638_; + float x63c_ = 1.f; + float x640_ = 10.f; + u16 x644_; + u16 x646_; + u16 x648_; + CSfxHandle x64c_; + CSfxHandle x650_; + CSfxHandle x654_; + + void CreateBeams(CStateManager&); + void UpdateBeams(CStateManager&, bool); + void FreeBeams(CStateManager&); + void UpdateOrCreateEmitter(CSfxHandle&, u16, const zeus::CVector3f&, float); + void DestroyEmitter(CSfxHandle&); + +public: + DEFINE_PATTERNED(AtomicBeta) + CAtomicBeta(TUniqueId, std::string_view, const CEntityInfo&, const zeus::CTransform&, CModelData&&, + const CActorParameters&, const CPatternedInfo&, CAssetId, CAssetId, const CDamageInfo&, CAssetId, float, + float, float, const CDamageVulnerability&, float, float, float, s16, s16, s16, float); + + void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&); + + void Think(float, CStateManager&); + const CDamageVulnerability* GetDamageVulnerability() const; + EWeaponCollisionResponseTypes GetCollisionResponseType(const zeus::CVector3f&, const zeus::CVector3f&, + const CWeaponMode& mode) const { + return GetDamageVulnerability()->WeaponHits(mode, false) ? EWeaponCollisionResponseTypes::AtomicBeta + : EWeaponCollisionResponseTypes::AtomicBetaReflect; + } + void Death(CStateManager&, const zeus::CVector3f&, EScriptObjectState); + static bool IsPlayerBeamChargedEnough(const CStateManager& mgr); +}; +} // namespace urde::MP1 \ No newline at end of file diff --git a/Runtime/MP1/World/CMakeLists.txt b/Runtime/MP1/World/CMakeLists.txt index 0e26acd38..5c2cfe4f4 100644 --- a/Runtime/MP1/World/CMakeLists.txt +++ b/Runtime/MP1/World/CMakeLists.txt @@ -12,6 +12,7 @@ set(MP1_WORLD_SOURCES CFireFlea.hpp CFireFlea.cpp CEyeball.hpp CEyeball.cpp CAtomicAlpha.hpp CAtomicAlpha.cpp + CAtomicBeta.hpp CAtomicBeta.cpp CFlickerBat.hpp CFlickerBat.cpp CFlyingPirate.hpp CFlyingPirate.cpp CMetroidPrimeRelay.hpp CMetroidPrimeRelay.cpp diff --git a/Runtime/Weapon/CElectricBeamProjectile.cpp b/Runtime/Weapon/CElectricBeamProjectile.cpp new file mode 100644 index 000000000..ec0892799 --- /dev/null +++ b/Runtime/Weapon/CElectricBeamProjectile.cpp @@ -0,0 +1,81 @@ +#include "CElectricBeamProjectile.hpp" +#include "Graphics/CBooRenderer.hpp" +#include "Particle/CElectricDescription.hpp" +#include "Particle/CParticleElectric.hpp" +#include "Particle/CElementGen.hpp" +#include "GameGlobalObjects.hpp" +#include "CSimplePool.hpp" +#include "CStateManager.hpp" +#include "TCastTo.hpp" + +namespace urde { +CElectricBeamProjectile::CElectricBeamProjectile(const TToken& wDesc, EWeaponType wType, + const SElectricBeamInfo& elec, const zeus::CTransform& xf, + EMaterialTypes matTypes, const CDamageInfo& dInfo, TUniqueId uid, + TAreaId areaId, TUniqueId owner, EProjectileAttrib attribs) +: CBeamProjectile(wDesc, "ElectricBeamProjectile"sv, wType, xf, u32(elec.x8_maxLength), elec.xc_radius, + elec.x10_travelSpeed, matTypes, dInfo, uid, areaId, owner, attribs, false) +, x468_electric(new CParticleElectric(elec.x0_electricDescription)) +, x46c_genDescription(g_SimplePool->GetObj({SBIG('PART'), elec.x14_particleId})) +, x478_elementGen(new CElementGen(x46c_genDescription)) +, x47c_(elec.x18_) +, x488_(elec.x1c_) { + x478_elementGen->SetParticleEmission(false); + x468_electric->SetParticleEmission(false); +} + +void CElectricBeamProjectile::Accept(IVisitor& visitor) { visitor.Visit(this); } + +void CElectricBeamProjectile::AcceptScriptMsg(EScriptObjectMessage msg, TUniqueId uid, CStateManager& mgr) { + if (msg == EScriptObjectMessage::Registered) { + mgr.AddWeaponId(xec_ownerId, xf0_weaponType); + CauseDamage(true); + } else if (msg == EScriptObjectMessage::Deleted) { + DeleteProjectileLight(mgr); + } + CGameProjectile::AcceptScriptMsg(msg, uid, mgr); +} + +void CElectricBeamProjectile::PreRender(CStateManager&, const zeus::CFrustum&) { + if (!GetActive()) + return; + + g_Renderer->AddParticleGen(*x478_elementGen.get()); + g_Renderer->AddParticleGen(*x468_electric.get()); +} + +void CElectricBeamProjectile::UpdateFx(const zeus::CTransform& xf, float dt, CStateManager& mgr) { + if (!GetActive()) + return; + + if (x484_ <= 0.f) + CauseDamage(true); + + if (GetDamageType() == EDamageType::Actor) { + x484_ = x488_; + CauseDamage(false); + } + + x484_ -= dt; + float f2 = zeus::close_enough(x47c_, 0.f) && x48c_ != 0 ? 1.f : -1.f; + /* TODO: Finish */ +} + +void CElectricBeamProjectile::ResetBeam(CStateManager& mgr, bool b) +{ + if (b) { + SetActive(false); + x478_elementGen->SetParticleEmission(false); + x468_electric->SetParticleEmission(false); + CBeamProjectile::ResetBeam(mgr, true); + } else { + x48c_ = false; + } +} + +void CElectricBeamProjectile::Fire(const zeus::CTransform&, CStateManager&, bool) { + x48c_ = true; + SetActive(true); + x480_ = 0.f; +} +} // namespace urde \ No newline at end of file diff --git a/Runtime/Weapon/CElectricBeamProjectile.hpp b/Runtime/Weapon/CElectricBeamProjectile.hpp new file mode 100644 index 000000000..5e364679c --- /dev/null +++ b/Runtime/Weapon/CElectricBeamProjectile.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "CBeamProjectile.hpp" + +namespace urde { +struct SElectricBeamInfo { + TToken x0_electricDescription; + float x8_maxLength; + float xc_radius; + float x10_travelSpeed; + CAssetId x14_particleId; + float x18_; + float x1c_; +}; +class CElectricBeamProjectile : public CBeamProjectile { + std::unique_ptr x468_electric; + TToken x46c_genDescription; + std::unique_ptr x478_elementGen; + float x47c_; + float x480_; + float x484_ = 0.f; + float x488_; + bool x48c_ = false; + +public: + CElectricBeamProjectile(const TToken&, EWeaponType, const SElectricBeamInfo&, + const zeus::CTransform&, EMaterialTypes, const CDamageInfo&, TUniqueId, TAreaId, TUniqueId, + EProjectileAttrib); + + void Accept(IVisitor&); + void AcceptScriptMsg(EScriptObjectMessage, TUniqueId, CStateManager&); + void PreRender(CStateManager&, const zeus::CFrustum&); + void Touch(CActor&, CStateManager&){}; + void UpdateFx(const zeus::CTransform&, float, CStateManager&); + void ResetBeam(CStateManager&, bool); + void Fire(const zeus::CTransform&, CStateManager&, bool); +}; +} // namespace urde \ No newline at end of file diff --git a/Runtime/Weapon/CMakeLists.txt b/Runtime/Weapon/CMakeLists.txt index 323153a7c..3e308fd64 100644 --- a/Runtime/Weapon/CMakeLists.txt +++ b/Runtime/Weapon/CMakeLists.txt @@ -20,6 +20,7 @@ set(WEAPON_SOURCES CWeapon.hpp CWeapon.cpp CGameProjectile.hpp CGameProjectile.cpp CBeamProjectile.hpp CBeamProjectile.cpp + CElectricBeamProjectile.hpp CElectricBeamProjectile.cpp CTargetableProjectile.hpp CTargetableProjectile.cpp CBeamInfo.hpp CPlasmaProjectile.hpp CPlasmaProjectile.cpp diff --git a/Runtime/World/ScriptLoader.cpp b/Runtime/World/ScriptLoader.cpp index b79edbf43..3bbf1dcdd 100644 --- a/Runtime/World/ScriptLoader.cpp +++ b/Runtime/World/ScriptLoader.cpp @@ -91,6 +91,7 @@ #include "MP1/World/CPuddleToadGamma.hpp" #include "CScriptSpindleCamera.hpp" #include "MP1/World/CAtomicAlpha.hpp" +#include "MP1/World/CAtomicBeta.hpp" #include "CSimplePool.hpp" #include "CStateManager.hpp" #include "CWallCrawlerSwarm.hpp" @@ -3044,7 +3045,35 @@ CEntity* ScriptLoader::LoadAmbientAI(CStateManager& mgr, CInputStream& in, int p } CEntity* ScriptLoader::LoadAtomicBeta(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) { - return nullptr; + if (!EnsurePropertyCount(propCount, 21, "AtomicBeta")) + return nullptr; + SScaledActorHead aHead = LoadScaledActorHead(in, mgr); + auto pair = CPatternedInfo::HasCorrectParameterCount(in); + if (!pair.first) + return nullptr; + CPatternedInfo pInfo(in, pair.second); + CActorParameters actParms = LoadActorParameters(in); + CAssetId electricId(in); + CAssetId weaponId(in); + CDamageInfo dInfo(in); + CAssetId particleId(in); + float f1 = in.readFloatBig(); + float f2 = in.readFloatBig(); + float f3 = in.readFloatBig(); + CDamageVulnerability dVuln(in); + float f4 = in.readFloatBig(); + float f5 = in.readFloatBig(); + float f6 = in.readFloatBig(); + s16 sId1 = s16(in.readInt32Big() & 0xFFFF); + s16 sId2 = s16(in.readInt32Big() & 0xFFFF); + s16 sId3 = s16(in.readInt32Big() & 0xFFFF); + float f7 = in.readFloatBig(); + const CAnimationParameters& animParms = pInfo.GetAnimationParameters(); + CModelData mData(CAnimRes(animParms.GetACSFile(), animParms.GetCharacter(), aHead.x40_scale, + animParms.GetInitialAnimation(), true)); + return new MP1::CAtomicBeta(mgr.AllocateUniqueId(), aHead.x0_name, info, aHead.x10_transform, std::move(mData), + actParms, pInfo, electricId, weaponId, dInfo, particleId, f1, f2, f3, dVuln, f4, f5, f6, + sId1, sId2, sId3, f7); } CEntity* ScriptLoader::LoadIceZoomer(CStateManager& mgr, CInputStream& in, int propCount, const CEntityInfo& info) {