diff --git a/configure.py b/configure.py index b63cc7f4..f111a6d1 100755 --- a/configure.py +++ b/configure.py @@ -259,7 +259,7 @@ LIBS = [ "MetroidPrime/CCollisionActor", "MetroidPrime/ScriptObjects/CScriptPlayerActor", "MetroidPrime/Tweaks/CTweakPlayerRes", - "MetroidPrime/Enemies/CBurstFire", + ["MetroidPrime/Enemies/CBurstFire", True], "MetroidPrime/Enemies/CFlaahgra", "MetroidPrime/Player/CPlayerEnergyDrain", "MetroidPrime/CFlameWarp", diff --git a/include/MetroidPrime/Enemies/CBurstFire.hpp b/include/MetroidPrime/Enemies/CBurstFire.hpp new file mode 100644 index 00000000..3afc0f54 --- /dev/null +++ b/include/MetroidPrime/Enemies/CBurstFire.hpp @@ -0,0 +1,40 @@ +#ifndef _CBURSTFIRE +#define _CBURSTFIRE + +#include "Kyoto/Math/CVector3f.hpp" + +#include "rstl/reserved_vector.hpp" + +class CStateManager; + +struct SBurst { + int x0_randomSelectionWeight; + int x4_shotAngles[8]; + float x24_timeToNextShot; + float x28_timeToNextShotVariance; +}; + +class CBurstFire { + int x0_burstType; + int x4_angleIdx; + float x8_timeToNextShot; + int xc_firstBurstIdx; + int x10_firstBurstCounter; + bool x14_24_shouldFire : 1; + bool x14_25_avoidAccuracy : 1; + const SBurst* x18_curBursts; + rstl::reserved_vector x1c_burstDefs; + +public: + CBurstFire(const SBurst** burstDefs, int firstBurstCount); + + void Start(CStateManager& mgr); + void Update(CStateManager& mgr, float dt); + + CVector3f GetError(float xMag, float zMag) const; + CVector3f GetDistanceCompensatedError(float dist, float maxErrDist) const; + float GetMaxXError() const; + float GetMaxZError() const; +}; + +#endif // _CBURSTFIRE diff --git a/obj_files.mk b/obj_files.mk index 9c916f7e..95ff563f 100644 --- a/obj_files.mk +++ b/obj_files.mk @@ -226,7 +226,7 @@ METROIDPRIME :=\ $(BUILD_DIR)/asm/MetroidPrime/CCollisionActor.o\ $(BUILD_DIR)/asm/MetroidPrime/ScriptObjects/CScriptPlayerActor.o\ $(BUILD_DIR)/asm/MetroidPrime/Tweaks/CTweakPlayerRes.o\ - $(BUILD_DIR)/asm/MetroidPrime/Enemies/CBurstFire.o\ + $(BUILD_DIR)/src/MetroidPrime/Enemies/CBurstFire.o\ $(BUILD_DIR)/asm/MetroidPrime/Enemies/CFlaahgra.o\ $(BUILD_DIR)/asm/MetroidPrime/Player/CPlayerEnergyDrain.o\ $(BUILD_DIR)/asm/MetroidPrime/CFlameWarp.o\ diff --git a/src/MetroidPrime/Enemies/CBurstFire.cpp b/src/MetroidPrime/Enemies/CBurstFire.cpp new file mode 100644 index 00000000..70c46f38 --- /dev/null +++ b/src/MetroidPrime/Enemies/CBurstFire.cpp @@ -0,0 +1,102 @@ +#include "MetroidPrime/Enemies/CBurstFire.hpp" + +#include "MetroidPrime/CStateManager.hpp" +#include "MetroidPrime/Tweaks/CTweakPlayer.hpp" + +#include "rstl/math.hpp" + +CBurstFire::CBurstFire(const SBurst** burstDefs, int firstBurstCount) +: x0_burstType(-1) +, x4_angleIdx(-1) +, x8_timeToNextShot(0.f) +, xc_firstBurstIdx(0) +, x10_firstBurstCounter(firstBurstCount) +, x14_24_shouldFire(false) +, x14_25_avoidAccuracy(false) +, x18_curBursts(nullptr) { + while (*burstDefs) { + x1c_burstDefs.push_back(*burstDefs); + ++burstDefs; + } +} + +void CBurstFire::Start(CStateManager& mgr) { + const SBurst* bursts = x1c_burstDefs[x0_burstType]; + int burstIdx = -1; + + if (x10_firstBurstCounter-- > 0) { + burstIdx = xc_firstBurstIdx >= 0 ? xc_firstBurstIdx : 0; + + } else { + int random = mgr.GetActiveRandom()->Range(0, 100); + int advanceAccum = 0; + do { + burstIdx += 1; + int advanceWeight = bursts[burstIdx].x0_randomSelectionWeight; + if (advanceWeight == 0) { + advanceAccum = 100; + burstIdx -= 1; + } + advanceAccum += advanceWeight; + } while (random > advanceAccum); + } + x18_curBursts = &bursts[burstIdx]; + x4_angleIdx = -1; + x8_timeToNextShot = 0.f; + x14_24_shouldFire = false; +} + +void CBurstFire::Update(CStateManager& mgr, float dt) { + x14_24_shouldFire = false; + if (!x18_curBursts) { + return; + } + + x8_timeToNextShot -= dt; + if (x8_timeToNextShot < 0.f) { + x4_angleIdx += 1; + if (x18_curBursts->x4_shotAngles[x4_angleIdx] > 0) { + x14_24_shouldFire = true; + x8_timeToNextShot = x18_curBursts->x24_timeToNextShot; + x8_timeToNextShot += + (mgr.GetActiveRandom()->Float() - 0.5f) * x18_curBursts->x28_timeToNextShotVariance; + } else { + x18_curBursts = nullptr; + } + } +} + +CVector3f CBurstFire::GetError(float xMag, float zMag) const { + CVector3f result = CVector3f::Zero(); + + if (x14_24_shouldFire && x18_curBursts) { + + int r0 = x18_curBursts->x4_shotAngles[x4_angleIdx]; + if (x14_25_avoidAccuracy && (r0 == 4 || r0 == 12)) { + r0 = x18_curBursts->x4_shotAngles[x4_angleIdx > 0 ? x4_angleIdx - 1 : x4_angleIdx + 1]; + } + + if (r0 > 0) { + float angle = r0 * (-22.5f * (M_PIF / 180.f)); + result.SetX(CMath::FastCosR(angle) * xMag); + result.SetZ(CMath::FastSinR(angle) * zMag); + } + } + + return result; +} + +CVector3f CBurstFire::GetDistanceCompensatedError(float dist, float maxErrDist) const { + float xErr = GetMaxXError(); + float zErr = GetMaxZError(); + float div = dist / maxErrDist; + xErr = rstl::min_val(div * xErr, xErr); + zErr = rstl::min_val(div * zErr, zErr); + return GetError(xErr, zErr); +} + +float CBurstFire::GetMaxXError() const { + return gpTweakPlayer->GetPlayerXYHalfExtent() * 3.625f + 0.2f; +} + +float CBurstFire::GetMaxZError() const { return gpTweakPlayer->GetEyeOffset(); }