diff --git a/configure.py b/configure.py index e32987a4..a487ceb9 100755 --- a/configure.py +++ b/configure.py @@ -92,7 +92,7 @@ LIBS = [ "MetroidPrime/ScriptObjects/CScriptWaypoint", "MetroidPrime/Enemies/CPatterned", "MetroidPrime/ScriptObjects/CScriptDoor", - "MetroidPrime/Enemies/CStateMachine", + ["MetroidPrime/Enemies/CStateMachine", False], "MetroidPrime/CMapArea", ["MetroidPrime/Cameras/CBallCamera", False], "MetroidPrime/ScriptObjects/CScriptEffect", diff --git a/include/Kyoto/Streams/CInputStream.hpp b/include/Kyoto/Streams/CInputStream.hpp index ef84ab15..7f87e562 100644 --- a/include/Kyoto/Streams/CInputStream.hpp +++ b/include/Kyoto/Streams/CInputStream.hpp @@ -64,6 +64,11 @@ template <> inline bool cinput_stream_helper(const TType< bool >& type, CInputStream& in) { return in.ReadBool(); } +template <> +inline char cinput_stream_helper(const TType< char >& type, CInputStream& in) { + return in.ReadChar(); +} + template <> inline int cinput_stream_helper(const TType< int >& type, CInputStream& in) { return in.ReadLong(); diff --git a/include/MetroidPrime/Enemies/CAi.hpp b/include/MetroidPrime/Enemies/CAi.hpp index 25260e6f..a55b6be0 100644 --- a/include/MetroidPrime/Enemies/CAi.hpp +++ b/include/MetroidPrime/Enemies/CAi.hpp @@ -16,8 +16,8 @@ class CAiFuncMap; class CAi : public CPhysicsActor { public: // static void CreateFuncLookup(CAiFuncMap* funcMap); - // static CAiStateFunc GetStateFunc(std::string_view func); - // static CAiTriggerFunc GetTriggerFunc(std::string_view func); + static CAiStateFunc GetStateFunc(const char* func); + static CAiTriggerFunc GetTriggerFunc(const char* func); // const CStateMachine* GetStateMachine() const; ~CAi(); diff --git a/include/MetroidPrime/Enemies/CAiFuncMap.hpp b/include/MetroidPrime/Enemies/CAiFuncMap.hpp index dfff8700..ce2ff64a 100644 --- a/include/MetroidPrime/Enemies/CAiFuncMap.hpp +++ b/include/MetroidPrime/Enemies/CAiFuncMap.hpp @@ -9,6 +9,11 @@ enum EStateMsg { kStateMsg_Deactivate = 2, }; +class CAi; +class CStateManager; +typedef void (CAi::*CAiStateFunc)(CStateManager& mgr, EStateMsg msg, float arg); +typedef bool (CAi::*CAiTriggerFunc)(CStateManager& mgr, float arg); + class CAiFuncMap { public: CAiFuncMap(); diff --git a/include/MetroidPrime/Enemies/CStateMachine.hpp b/include/MetroidPrime/Enemies/CStateMachine.hpp new file mode 100644 index 00000000..a2bff2a9 --- /dev/null +++ b/include/MetroidPrime/Enemies/CStateMachine.hpp @@ -0,0 +1,120 @@ +#ifndef _CSTATEMACHINE +#define _CSTATEMACHINE + +#include "rstl/string.hpp" +#include "rstl/vector.hpp" +#include "string.h" + +#include "MetroidPrime/Enemies/CAiFuncMap.hpp" + +class CAi; +class CStateManager; +class CInputStream; + +class CAiState; +class CAiTrigger { +public: + CAiTrigger() + : x0_func(nullptr), xc_arg(0.f), x10_andTrigger(nullptr), x14_state(nullptr), x18_lNot(false) {} + CAiTrigger* GetAnd() const { return x10_andTrigger; } + CAiState* GetState() const { return x14_state; } + + bool CallFunc(CStateManager& mgr, CAi& ai) { + bool ret = true; + if (x0_func) { + ret = (ai.*x0_func)(mgr, xc_arg); + if (x18_lNot) { + ret = !ret; + } + } + + return ret; + } + + void Setup(CAiTriggerFunc func, bool lnot, float arg, CAiTrigger* andTrig) { + x0_func = func; + x18_lNot = lnot; + xc_arg = arg; + x10_andTrigger = andTrig; + } + + void Setup(CAiTriggerFunc func, bool lnot, float arg, CAiState* state) { + x0_func = func; + x18_lNot = lnot; + xc_arg = arg; + x14_state = state; + } + +private: + CAiTriggerFunc x0_func; + float xc_arg; + CAiTrigger* x10_andTrigger; + CAiState* x14_state; + bool x18_lNot; +}; + +class CAiState { +public: + CAiState(CAiStateFunc func, const char* name) { + x0_func = func; + strncpy(xc_name, name, 31); + } + + CAiTrigger* GetTrig(int idx) { return &x30_firstTrigger[idx]; } + const char* GetName() const { return xc_name; } + void SetTriggers(CAiTrigger* triggers) { x30_firstTrigger = triggers; } + void SetNumTriggers(int numTriggers) { x2c_numTriggers = numTriggers; } + int GetNumTriggers() const { return x2c_numTriggers; } + + void CallFunc(CStateManager& mgr, CAi& ai, EStateMsg msg, float arg) const { + if (x0_func) { + (ai.*x0_func)(mgr, msg, arg); + } + } + +private: + CAiStateFunc x0_func; + char xc_name[32]; + uint x2c_numTriggers; + CAiTrigger* x30_firstTrigger; +}; + +class CStateMachine { +public: + explicit CStateMachine(CInputStream& in); + + int GetStateIndex(const rstl::string& state) const; + const rstl::vector< CAiState >& GetStateVector() const { return x0_states; } + +private: + rstl::vector< CAiState > x0_states; + rstl::vector< CAiTrigger > x10_triggers; +}; + +class CStateMachineState { +public: + CStateMachineState(); + + void Update(CStateManager& mgr, CAi& ai, float delta); + void SetState(CStateManager& mgr, CAi& ai, int state); + void SetState(CStateManager& mgr, CAi&, const CStateMachine* machine, const rstl::string& state); + const rstl::vector< CAiState >& GetStateVector() const { return x0_machine->GetStateVector(); } + void Setup(const CStateMachine* machine); + const char* GetName() const; + void SetDelay(float delay) { x10_delay = delay; } + float GetTime() const { return x8_time; } + float GetRandom() const { return xc_random; } + float GetDelay() const { return x10_delay; } + void SetCodeTrigger() { x18_24_codeTrigger = true; } + +private: + const CStateMachine* x0_machine; + CAiState* x4_state; + float x8_time; + float xc_random; + float x10_delay; + float x14_; + bool x18_24_codeTrigger : 1; +}; + +#endif // _CSTATEMACHINE diff --git a/include/rstl/string.hpp b/include/rstl/string.hpp index 80b5cbb5..1db7b884 100644 --- a/include/rstl/string.hpp +++ b/include/rstl/string.hpp @@ -97,6 +97,7 @@ public: ~basic_string() { internal_dereference(); } + size_t size() const { return x8_size; } void assign(const basic_string&); basic_string& operator=(const basic_string&); basic_string operator+(const basic_string&); diff --git a/src/MetroidPrime/Enemies/CStateMachine.cpp b/src/MetroidPrime/Enemies/CStateMachine.cpp new file mode 100644 index 00000000..60f6abd5 --- /dev/null +++ b/src/MetroidPrime/Enemies/CStateMachine.cpp @@ -0,0 +1,166 @@ +#include "MetroidPrime/Enemies/CStateMachine.hpp" + +#include "MetroidPrime/Enemies/CAi.hpp" + +#include "Kyoto/Streams/CInputStream.hpp" + +void CStateMachineState::Update(CStateManager& mgr, CAi& ai, float delta) { + if (x4_state) { + x8_time += delta; + x4_state->CallFunc(mgr, ai, kStateMsg_Update, delta); + for (int i = 0; i < x4_state->GetNumTriggers(); ++i) { + CAiTrigger* trig = x4_state->GetTrig(i); + CAiState* state = nullptr; + bool andPassed = true; + while (andPassed && trig) { + andPassed = false; + if (trig->CallFunc(mgr, ai)) { + andPassed = true; + state = trig->GetState(); + trig = trig->GetAnd(); + } + } + + if (andPassed && state != nullptr) { + x4_state->CallFunc(mgr, ai, kStateMsg_Deactivate, 0.f); + x4_state = state; + x8_time = 0.f; + x18_24_codeTrigger = false; + xc_random = mgr.Random()->Float(); + x4_state->CallFunc(mgr, ai, kStateMsg_Activate, 0.f); + return; + } + } + } +} + +void CStateMachineState::SetState(CStateManager& mgr, CAi& ai, int idx) { + const CAiState* state = &x0_machine->GetStateVector()[idx]; + if (x4_state == state) { + return; + } + if (x4_state) { + x4_state->CallFunc(mgr, ai, kStateMsg_Deactivate, 0.f); + } + + x4_state = const_cast< CAiState* >(state); + x8_time = 0.f; + xc_random = mgr.Random()->Float(); + x18_24_codeTrigger = false; + x4_state->CallFunc(mgr, ai, kStateMsg_Activate, 0.f); +} + +void CStateMachineState::SetState(CStateManager& mgr, CAi& ai, const CStateMachine* machine, + const rstl::string& state) { + if (!machine) + return; + + if (!x0_machine) + Setup(machine); + + s32 idx = machine->GetStateIndex(state); + SetState(mgr, ai, idx); +} + +CStateMachineState::CStateMachineState() +: x0_machine(nullptr) +, x4_state(nullptr) +, x8_time(0.f) +, xc_random(0.f) +, x10_delay(0.f) +, x18_24_codeTrigger(false) {} + +void CStateMachineState::Setup(const CStateMachine* machine) { + x0_machine = machine; + x4_state = nullptr; + x8_time = 0.f; + xc_random = 0.f; + x10_delay = 0.f; +} + +const char* CStateMachineState::GetName() const { + if (x4_state != nullptr) { + return x4_state->GetName(); + } + + return nullptr; +} + +CStateMachine::CStateMachine(CInputStream& in) { + CAiTrigger* lastTrig = nullptr; + int stateCount = in.ReadLong(); + + x0_states.reserve(stateCount); + + for (int i = 0; i < stateCount; ++i) { + char name[32]; + int nameLen = 0; + for (; nameLen < 31; ++nameLen) { + name[nameLen] = in.Get< char >(); + if (name[nameLen] == '\0') { + break; + } + } + name[nameLen] = '\0'; + CAiStateFunc func = CAi::GetStateFunc(name); + x0_states.push_back(CAiState(func, name)); + } + + x10_triggers.reserve(in.Get< int >()); + + for (int i = 0; i < stateCount; ++i) { + x0_states[i].SetNumTriggers(in.Get< int >()); + + if (x0_states[i].GetNumTriggers() == 0) { + continue; + } + + CAiTrigger* firstTrig = x10_triggers.data() + x10_triggers.size(); + x0_states[i].SetTriggers(firstTrig); + + for (int j = 0; j < x0_states[i].GetNumTriggers(); ++j) { + const int triggerCount = in.Get< int >(); + const int lastTriggerIdx = triggerCount - 1; + + for (int k = 0; k < triggerCount; ++k) { + char name[32]; + int nameLen = 0; + for (; nameLen < 31; ++nameLen) { + name[nameLen] = in.Get< char >(); + + if (name[nameLen] == '\0') { + break; + } + } + + const bool isNot = name[0] == '!'; + const CAiTriggerFunc func = CAi::GetTriggerFunc(isNot ? name + 1 : name); + const float arg = in.Get< float >(); + CAiTrigger* newTrig; + if (k < lastTriggerIdx) { + x10_triggers.push_back(CAiTrigger()); + newTrig = &x10_triggers.back(); + } else { + newTrig = &firstTrig[j]; + } + + if (k == 0) { + newTrig->Setup(func, isNot, arg, &x0_states[in.Get< int >()]); + } else { + newTrig->Setup(func, isNot, arg, lastTrig); + } + lastTrig = newTrig; + } + } + } +} + +int CStateMachine::GetStateIndex(const rstl::string& state) const { + for (int i = 0; i < x0_states.size(); ++i) { + if (strncmp(x0_states[i].GetName(), state.data(), 31) == 0) { + return i; + } + } + + return 0; +}