summaryrefslogtreecommitdiff
path: root/src/KingSystem
diff options
context:
space:
mode:
Diffstat (limited to 'src/KingSystem')
-rw-r--r--src/KingSystem/ActorSystem/actActor.h20
-rw-r--r--src/KingSystem/ActorSystem/actActorCreator.cpp7
-rw-r--r--src/KingSystem/ActorSystem/actActorCreator.h38
-rw-r--r--src/KingSystem/ActorSystem/actActorFactory.cpp21
-rw-r--r--src/KingSystem/ActorSystem/actActorFactory.h28
-rw-r--r--src/KingSystem/ActorSystem/actActorLinkConstDataAccess.cpp0
-rw-r--r--src/KingSystem/ActorSystem/actActorLinkConstDataAccess.h21
-rw-r--r--src/KingSystem/ActorSystem/actAiAction.cpp7
-rw-r--r--src/KingSystem/ActorSystem/actAiAction.h17
-rw-r--r--src/KingSystem/ActorSystem/actAiClass.cpp8
-rw-r--r--src/KingSystem/ActorSystem/actAiClass.h44
-rw-r--r--src/KingSystem/ActorSystem/actAiParam.cpp110
-rw-r--r--src/KingSystem/ActorSystem/actAiParam.h113
-rw-r--r--src/KingSystem/ActorSystem/actBaseProc.cpp409
-rw-r--r--src/KingSystem/ActorSystem/actBaseProc.h286
-rw-r--r--src/KingSystem/ActorSystem/actBaseProcJob.cpp9
-rw-r--r--src/KingSystem/ActorSystem/actBaseProcJob.h53
-rw-r--r--src/KingSystem/ActorSystem/actBaseProcJobHandler.cpp8
-rw-r--r--src/KingSystem/ActorSystem/actBaseProcJobHandler.h23
-rw-r--r--src/KingSystem/ActorSystem/actBaseProcLinkDataMgr.cpp52
-rw-r--r--src/KingSystem/ActorSystem/actBaseProcLinkDataMgr.h43
-rw-r--r--src/KingSystem/ActorSystem/actBaseProcMap.cpp22
-rw-r--r--src/KingSystem/ActorSystem/actBaseProcMap.h61
-rw-r--r--src/KingSystem/ActorSystem/actBaseProcMgr.cpp133
-rw-r--r--src/KingSystem/ActorSystem/actBaseProcMgr.h219
-rw-r--r--src/KingSystem/ActorSystem/actBaseProcUnit.cpp0
-rw-r--r--src/KingSystem/ActorSystem/actBaseProcUnit.h28
-rw-r--r--src/KingSystem/MessageSystem/mesTransceiver.h31
-rw-r--r--src/KingSystem/Movie/.gitkeep0
-rw-r--r--src/KingSystem/Physics/NavMesh/.gitkeep0
-rw-r--r--src/KingSystem/Terrain/teraSystem.h34
-rw-r--r--src/KingSystem/Utils/StrTreeMap.h45
-rw-r--r--src/KingSystem/Utils/Types.h31
33 files changed, 1921 insertions, 0 deletions
diff --git a/src/KingSystem/ActorSystem/actActor.h b/src/KingSystem/ActorSystem/actActor.h
new file mode 100644
index 00000000..cc51e126
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actActor.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "KingSystem/ActorSystem/actBaseProc.h"
+
+namespace ksys {
+
+namespace act {
+
+class Actor : public BaseProc {
+public:
+ Actor(); // FIXME
+ ~Actor() override;
+
+ void emitBasicSigOn();
+ void emitBasicSigOff();
+};
+
+} // namespace act
+
+} // namespace ksys
diff --git a/src/KingSystem/ActorSystem/actActorCreator.cpp b/src/KingSystem/ActorSystem/actActorCreator.cpp
new file mode 100644
index 00000000..be6c43b4
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actActorCreator.cpp
@@ -0,0 +1,7 @@
+#include "KingSystem/ActorSystem/actActorCreator.h"
+
+namespace ksys::act {
+
+SEAD_SINGLETON_DISPOSER_IMPL(ActorCreator)
+
+}
diff --git a/src/KingSystem/ActorSystem/actActorCreator.h b/src/KingSystem/ActorSystem/actActorCreator.h
new file mode 100644
index 00000000..65a3494b
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actActorCreator.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <container/seadOffsetList.h>
+#include <heap/seadDisposer.h>
+#include <prim/seadDelegate.h>
+#include <thread/seadCriticalSection.h>
+
+namespace ksys::act {
+
+class Actor;
+struct ActorCreateArg;
+class ActorFactory;
+
+class ActorCreator {
+ SEAD_SINGLETON_DISPOSER(ActorCreator)
+ ActorCreator();
+ virtual ~ActorCreator();
+
+public:
+ void setActorFactory(ActorFactory* factory) { mActorFactory = factory; }
+
+private:
+ sead::Heap* forBaseProcDualHeap;
+ sead::Heap* placementMgrHeap;
+ sead::OffsetList<Actor> mActorList;
+ void* _50;
+ bool mEnableDistanceChecks;
+ bool _59;
+ bool _5a;
+ void* _60;
+ sead::Delegate1<ActorCreator, ActorCreateArg&> mCreateActorDelegate;
+ // FIXME: argument type
+ sead::Delegate1<ActorCreator, void*> mCleanUpDelegate;
+ sead::CriticalSection mActorListCS;
+ ActorFactory* mActorFactory;
+};
+
+} // namespace ksys::act
diff --git a/src/KingSystem/ActorSystem/actActorFactory.cpp b/src/KingSystem/ActorSystem/actActorFactory.cpp
new file mode 100644
index 00000000..6a835f3f
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actActorFactory.cpp
@@ -0,0 +1,21 @@
+#include "KingSystem/ActorSystem/actActorFactory.h"
+#include "KingSystem/ActorSystem/actActorCreator.h"
+
+namespace ksys::act {
+
+SEAD_SINGLETON_DISPOSER_IMPL(ActorFactoryHolder)
+
+void ActorFactory::dummy() {}
+
+void ActorFactory::dummy2() {}
+
+ActorFactoryHolder::~ActorFactoryHolder() {
+ ActorCreator::deleteInstance();
+}
+
+void ActorFactoryHolder::init(sead::Heap* heap) {
+ ActorCreator::createInstance(heap);
+ ActorCreator::instance()->setActorFactory(&mFactory);
+}
+
+} // namespace ksys::act
diff --git a/src/KingSystem/ActorSystem/actActorFactory.h b/src/KingSystem/ActorSystem/actActorFactory.h
new file mode 100644
index 00000000..77729030
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actActorFactory.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <heap/seadDisposer.h>
+
+namespace ksys::act {
+
+struct ActorCreateArg;
+class BaseProc;
+
+class ActorFactory {
+public:
+ virtual void dummy();
+ virtual BaseProc* createActor(const ActorCreateArg& arg);
+ virtual void dummy2();
+};
+
+class ActorFactoryHolder {
+ SEAD_SINGLETON_DISPOSER(ActorFactoryHolder)
+ virtual ~ActorFactoryHolder();
+
+public:
+ void init(sead::Heap* heap);
+
+private:
+ ActorFactory mFactory;
+};
+
+} // namespace ksys::act
diff --git a/src/KingSystem/ActorSystem/actActorLinkConstDataAccess.cpp b/src/KingSystem/ActorSystem/actActorLinkConstDataAccess.cpp
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actActorLinkConstDataAccess.cpp
diff --git a/src/KingSystem/ActorSystem/actActorLinkConstDataAccess.h b/src/KingSystem/ActorSystem/actActorLinkConstDataAccess.h
new file mode 100644
index 00000000..628138b1
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actActorLinkConstDataAccess.h
@@ -0,0 +1,21 @@
+#pragma once
+
+namespace ksys {
+
+namespace act {
+
+class BaseProc;
+
+/// Provides read-only access to actor data for safe, multi-threaded access.
+class ActorLinkConstDataAccess {
+public:
+ ~ActorLinkConstDataAccess();
+
+private:
+ bool mAcquired = false;
+ BaseProc* mProc = nullptr;
+};
+
+} // namespace act
+
+} // namespace ksys
diff --git a/src/KingSystem/ActorSystem/actAiAction.cpp b/src/KingSystem/ActorSystem/actAiAction.cpp
new file mode 100644
index 00000000..ad5e4c1c
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actAiAction.cpp
@@ -0,0 +1,7 @@
+#include "KingSystem/ActorSystem/actAiAction.h"
+
+namespace ksys::act::ai {
+
+Action::Action(const ClassArg& arg) : ActionBase(arg) {}
+
+} // namespace ksys::act::ai
diff --git a/src/KingSystem/ActorSystem/actAiAction.h b/src/KingSystem/ActorSystem/actAiAction.h
new file mode 100644
index 00000000..be96de6d
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actAiAction.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "KingSystem/ActorSystem/actAiClass.h"
+#include "KingSystem/Utils/Types.h"
+
+namespace ksys::act::ai {
+
+class Action : public ActionBase {
+public:
+ Action(const ClassArg& arg);
+
+ virtual void enter() {}
+ virtual void loadParams() {}
+};
+KSYS_CHECK_SIZE_NX150(Action, 0x20);
+
+} // namespace ksys::act::ai
diff --git a/src/KingSystem/ActorSystem/actAiClass.cpp b/src/KingSystem/ActorSystem/actAiClass.cpp
new file mode 100644
index 00000000..a75511d3
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actAiClass.cpp
@@ -0,0 +1,8 @@
+#include "KingSystem/ActorSystem/actAiClass.h"
+
+namespace ksys::act::ai {
+
+ActionBase::ActionBase(const ClassArg& arg)
+ : mActor{arg.actor}, mDefinitionIdx{u16(arg.definitionIdx)}, mRootIdx{u8(arg.rootIdx)} {}
+
+} // namespace ksys::act::ai
diff --git a/src/KingSystem/ActorSystem/actAiClass.h b/src/KingSystem/ActorSystem/actAiClass.h
new file mode 100644
index 00000000..d065af14
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actAiClass.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include <basis/seadTypes.h>
+
+#include "KingSystem/ActorSystem/actAiParam.h"
+#include "KingSystem/Utils/Types.h"
+
+namespace ksys::act {
+
+class Actor;
+
+namespace ai {
+
+struct ClassArg {
+ Actor* actor;
+ u32 definitionIdx;
+ u32 rootIdx;
+};
+KSYS_CHECK_SIZE_NX150(ClassArg, 0x10);
+
+class ActionBase {
+public:
+ ActionBase(const ClassArg& arg);
+ virtual ~ActionBase() = default;
+
+protected:
+ void setFinished();
+
+ template <typename T>
+ void getParamStatic(ParamRef<T>* value, const sead::SafeString& key);
+
+ Actor* mActor;
+ ParamPack mParams;
+ u16 mDefinitionIdx;
+ u8 mRootIdx;
+ u8 mStatus = 0;
+ u16 mClassIdx;
+ u16 mPrevClassIdx;
+};
+KSYS_CHECK_SIZE_NX150(ActionBase, 0x20);
+
+} // namespace ai
+
+} // namespace ksys::act
diff --git a/src/KingSystem/ActorSystem/actAiParam.cpp b/src/KingSystem/ActorSystem/actAiParam.cpp
new file mode 100644
index 00000000..5343ff07
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actAiParam.cpp
@@ -0,0 +1,110 @@
+#include "KingSystem/ActorSystem/actAiParam.h"
+
+namespace ksys::act::ai {
+
+ParamPack::ParamPack() = default;
+
+ParamPack::~ParamPack() {
+ auto* param = mParams;
+ while (param) {
+ if (param->data) {
+ switch (Param::Type(param->type)) {
+ case Param::Type::String:
+ delete static_cast<sead::SafeString*>(param->data);
+ break;
+ case Param::Type::Int:
+ delete static_cast<int*>(param->data);
+ break;
+ case Param::Type::Float:
+ delete static_cast<float*>(param->data);
+ break;
+ case Param::Type::Vec3:
+ delete static_cast<sead::Vector3f*>(param->data);
+ break;
+ case Param::Type::Bool:
+ delete static_cast<bool*>(param->data);
+ break;
+ case Param::Type::AITreeVariablePointer:
+ delete static_cast<void**>(param->data);
+ break;
+ case Param::Type::UInt:
+ delete static_cast<u32*>(param->data);
+ break;
+ case Param::Type::MesTransceiverId:
+ delete static_cast<mes::TransceiverId*>(param->data);
+ break;
+ case Param::Type::BaseProcHandle:
+ delete static_cast<BaseProcHandle**>(param->data);
+ break;
+ case Param::Type::Rail:
+ delete static_cast<Rail**>(param->data);
+ break;
+ case Param::Type::BaseProcLink:
+ delete static_cast<BaseProcLink*>(param->data);
+ break;
+ default:
+ break;
+ }
+ }
+ param = mParams->next;
+ if (mParams)
+ delete mParams;
+ mParams = param;
+ }
+}
+
+void InlineParamPack::copyToParamPack(ParamPack& pack) const {
+ const u32 n = count >= 0x1F ? 0x1F : count;
+ const InlineParam* param = params;
+ for (u32 i = 0; i < n; ++i, ++param) {
+ switch (param->type) {
+ case Param::Type::String: {
+ const sead::SafeString src = param->cstr;
+ const sead::SafeString key = param->key;
+ auto* dst = pack.getVariable<sead::BufferedSafeString>(key, Param::Type::String);
+ if (dst)
+ dst->copy(src);
+ break;
+ }
+ case Param::Type::Int:
+ pack.setVariable(param->key, Param::Type::Int, param->i);
+ break;
+ case Param::Type::Float:
+ pack.setVariable(param->key, Param::Type::Float, param->f);
+ break;
+ case Param::Type::Vec3:
+ if (auto* dst = pack.getVariable<sead::Vector3f>(param->key, Param::Type::Vec3)) {
+ dst->x = param->vec3.x;
+ dst->y = param->vec3.y;
+ dst->z = param->vec3.z;
+ }
+ break;
+ case Param::Type::Bool:
+ pack.setVariable(param->key, Param::Type::Bool, param->b);
+ break;
+ case Param::Type::UInt:
+ pack.setVariable(param->key, Param::Type::UInt, param->u);
+ break;
+ case Param::Type::BaseProcLink:
+ pack.setVariable(param->key, Param::Type::BaseProcLink, param->baseProcLink);
+ break;
+ case Param::Type::MesTransceiverId:
+ pack.setVariable(param->key, Param::Type::MesTransceiverId, param->mesTransceiverId);
+ break;
+ case Param::Type::BaseProcHandle: {
+ auto* baseProcHandle = static_cast<BaseProcHandle*>(param->ptr);
+ pack.setVariable(param->key, Param::Type::BaseProcHandle, baseProcHandle);
+ break;
+ }
+ case Param::Type::Rail: {
+ auto* rail = static_cast<Rail*>(param->ptr);
+ pack.setVariable(param->key, Param::Type::Rail, rail);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+}
+
+} // namespace ksys::act::ai
diff --git a/src/KingSystem/ActorSystem/actAiParam.h b/src/KingSystem/ActorSystem/actAiParam.h
new file mode 100644
index 00000000..b5df35c2
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actAiParam.h
@@ -0,0 +1,113 @@
+#pragma once
+
+#include <agl/Utils/aglParameter.h>
+#include <basis/seadTypes.h>
+#include <math/seadVector.h>
+#include <prim/seadSafeString.h>
+
+#include "KingSystem/ActorSystem/actBaseProc.h"
+#include "KingSystem/MessageSystem/mesTransceiver.h"
+#include "KingSystem/Utils/Types.h"
+
+namespace ksys {
+
+class Rail;
+
+namespace act::ai {
+
+struct Param {
+ enum class Type {
+ String = 0,
+ Int = 1,
+ Float = 2,
+ Vec3 = 3,
+ Bool = 4,
+ Tree = 5,
+ AITreeVariablePointer = 6,
+ UInt = 7,
+ BaseProcLink = 8,
+ MesTransceiverId = 9,
+ BaseProcHandle = 10,
+ Rail = 11,
+ Other = 12,
+ };
+
+ Param* next;
+ u32 hash;
+ const char* name;
+ void* data;
+ u16 type;
+ bool used;
+ u8 _23;
+};
+KSYS_CHECK_SIZE_NX150(Param, 0x28);
+
+class ParamPack {
+public:
+ ParamPack();
+ ~ParamPack();
+
+ template <typename T>
+ T* getVariable(const sead::SafeString& key, Param::Type type, bool a4 = true) const {
+ const u32 hash = agl::utl::ParameterBase::calcHash(key);
+ auto* param = mParams;
+ if (!param)
+ return nullptr;
+ while (param->hash != hash || Param::Type(param->type) != type) {
+ param = param->next;
+ if (!param)
+ return nullptr;
+ }
+ if (a4)
+ param->used = true;
+ return static_cast<T*>(param->data);
+ }
+
+ template <typename T>
+ void setVariable(const sead::SafeString& key, Param::Type type, const T& val) const {
+ T* variable = getVariable<T>(key, type, true);
+ if (variable)
+ *variable = val;
+ }
+
+private:
+ Param* mParams = nullptr;
+};
+
+struct InlineParam {
+ union {
+ bool b;
+ int i;
+ u32 u;
+ float f;
+ const char* cstr;
+ void* ptr;
+ };
+ BaseProcLink baseProcLink;
+ sead::Vector3f vec3;
+ mes::TransceiverId mesTransceiverId;
+ Param::Type type;
+ const char* key;
+};
+KSYS_CHECK_SIZE_NX150(InlineParam, 0x50);
+
+struct InlineParamPack {
+ void copyToParamPack(ParamPack& pack) const;
+
+ InlineParam params[32];
+ int count;
+};
+KSYS_CHECK_SIZE_NX150(InlineParamPack, 0xA08);
+
+template <typename T>
+class ParamRef {
+public:
+ const T& value() const { return *mValue; }
+ void setValuePtr(const T* ptr) { mValue = ptr; }
+
+private:
+ const T* mValue = nullptr;
+};
+} // namespace act::ai
+
+} // namespace ksys
diff --git a/src/KingSystem/ActorSystem/actBaseProc.cpp b/src/KingSystem/ActorSystem/actBaseProc.cpp
new file mode 100644
index 00000000..01ccc5d4
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actBaseProc.cpp
@@ -0,0 +1,409 @@
+#include "KingSystem/ActorSystem/actBaseProc.h"
+#include "KingSystem/ActorSystem/actBaseProcJobHandler.h"
+#include "KingSystem/ActorSystem/actBaseProcLinkDataMgr.h"
+#include "KingSystem/ActorSystem/actBaseProcMgr.h"
+#include "KingSystem/ActorSystem/actBaseProcUnit.h"
+#include "KingSystem/Terrain/teraSystem.h"
+
+namespace ksys::act {
+
+BaseProcLink::BaseProcLink() = default;
+
+BaseProc::BaseProc(const CreateArg& arg)
+ : mName(arg.actor_name), mPriority(arg.class_info->priority) {
+ BaseProcMgr* mgr = BaseProcMgr::instance();
+ mgr->generateProcId(&mId);
+ mgr->registerProc(*this);
+
+ mJobHandlers.fill(nullptr);
+
+ BaseProcLinkDataMgr::instance()->acquireLink(this);
+}
+
+BaseProcMapNode::~BaseProcMapNode() = default;
+
+BaseProc::~BaseProc() {
+ unlinkCalcChild_();
+ unlinkCalcParent_();
+
+ if (mDeleteListNode.isLinked())
+ BaseProcMgr::instance()->eraseFromUpdateStateList(*this);
+}
+
+bool BaseProc::init(sead::Heap* heap, bool sleep_after_init) {
+ InitContext context;
+ context.sleep_after_init = sleep_after_init;
+ context.result = InitResult::Skipped;
+
+ if (shouldInit_()) {
+ PrepareArg arg;
+ if (prepareInit_(heap, arg)) {
+ if (context.result != InitResult::Failed)
+ context.result = init_();
+ } else {
+ context.result = InitResult::Failed;
+ }
+ }
+
+ finalizeInit_(&context);
+ return context.result == InitResult::Ok && mStateFlags.isOff(StateFlags::RequestDelete);
+}
+
+bool BaseProc::deleteLater(DeleteReason reason) {
+ if (isDeletedOrDeleting())
+ return false;
+
+ // Debug leftovers?
+ mName.cstr();
+ mName.cstr();
+
+ BaseProcMgr* mgr = BaseProcMgr::instance();
+ const bool is_high_prio = mgr->isHighPriorityThread();
+
+ if (!is_high_prio) {
+ mgr->getProcUpdateStateListCS().lock();
+ if (isDeletedOrDeleting()) {
+ mgr->getProcUpdateStateListCS().unlock();
+ return false;
+ }
+ }
+
+ if (!BaseProcMgr::instance()->addToUpdateStateList(*this))
+ onDeleteRequested_(reason);
+
+ if (!is_high_prio)
+ mgr->getProcUpdateStateListCS().unlock();
+
+ return true;
+}
+
+BaseProc::InitResult BaseProc::init_() {
+ return InitResult::Ok;
+}
+
+bool BaseProc::shouldInit_() {
+ return true;
+}
+
+void BaseProc::finalizeInit_(InitContext* context) {
+ if (mState == State::Delete)
+ return;
+
+ if (context->result != InitResult::Ok) {
+ deleteLater(DeleteReason::_1);
+ return;
+ }
+
+ if (!mProcUnit) {
+ if (context->sleep_after_init)
+ sleep(SleepWakeReason::_0);
+ else
+ wakeUp(SleepWakeReason::_0);
+ } else {
+ sleep(SleepWakeReason::_0);
+ if (!mProcUnit->setProc(this))
+ deleteLater(DeleteReason::_2);
+ }
+}
+
+BaseProc::PreDeletePrepareResult BaseProc::prepareForPreDelete_() {
+ return PreDeletePrepareResult::Done;
+}
+
+// NON_MATCHING: branching
+bool BaseProc::startPreparingForPreDelete_() {
+ if (mDeleteListNode.isLinked())
+ return false;
+
+ return !mBaseProcLinkData || mBaseProcLinkData->refCount() <= 0 ||
+ BaseProcMgr::instance()->getUnk3() || tera::checkTeraSystemStatus();
+}
+
+void BaseProc::destruct_(int should_destruct) {
+ if (should_destruct == 1) {
+ BaseProcMgr::instance()->eraseFromPreDeleteList(*this);
+ BaseProcMgr::instance()->unregisterProc(*this);
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wundefined-bool-conversion"
+ // Yes, this check looks strange.
+ if (this)
+ delete this;
+#pragma clang diagnostic pop
+ }
+}
+
+void BaseProc::onEnterCalc_() {}
+
+void BaseProc::onDeleteRequested_(BaseProc::DeleteReason) {}
+
+void BaseProc::onSleepRequested_(BaseProc::SleepWakeReason) {}
+
+void BaseProc::onWakeUpRequested_(BaseProc::SleepWakeReason) {}
+
+void BaseProc::onEnterDelete_() {}
+
+void BaseProc::onEnterSleep_() {}
+
+void BaseProc::preDelete3_(bool*) {}
+
+bool BaseProc::prepareInit_(sead::Heap*, BaseProc::PrepareArg&) {
+ return true;
+}
+
+void BaseProc::onPreDeleteStart_(PrepareArg&) {}
+
+void BaseProc::preDelete2_(bool*) {}
+
+void BaseProc::preDelete1_() {}
+
+// NON_MATCHING: branching
+bool BaseProc::isSpecialJobType_(JobType type) {
+ return BaseProcMgr::instance()->isSpecialJobType(type) || isSpecialJobTypeForThisActor_(type);
+}
+
+bool BaseProc::canWakeUp_() {
+ return true;
+}
+
+void BaseProc::queueExtraJobPush_(JobType type) {
+ if (!isDeletedOrDeleting())
+ BaseProcMgr::instance()->queueExtraJobPush(&mJobHandlers[int(type)]->getLink());
+}
+
+bool BaseProc::hasJobType_(JobType type) {
+ return mJobHandlers[int(type)] != nullptr;
+}
+
+void BaseProc::afterUpdateState_() {
+ mFlags.reset(Flags::_80);
+ mFlags.reset(Flags::_100);
+}
+
+bool BaseProc::shouldSkipJobPush_(JobType) {
+ return false;
+}
+
+void BaseProc::onJobPush1_(JobType) {}
+
+void BaseProc::onJobPush2_(JobType) {}
+
+// NON_MATCHING: branching
+bool BaseProc::processStateUpdate(u8 counter) {
+ const bool delete_requested = mStateFlags.isOn(StateFlags::RequestDelete);
+ const bool initialized = mFlags.isOn(Flags::Initialized);
+
+ if (delete_requested) {
+ // Deletion cannot start until the reference count is 0.
+ if (initialized && mRefCount.compareExchange(0, -1)) {
+ if (mState != State::Delete) {
+ BaseProcMgr::instance()->eraseJobs(*this);
+ BaseProcMgr::instance()->addToPreDeleteList(*this);
+ startDelete_();
+ }
+ mStateFlags.makeAllZero();
+ return true;
+ }
+ // Try the deletion again later.
+ mStateFlags.setDirect(StateFlags::RequestDelete);
+ return false;
+ }
+
+ sead::TypedBitFlag<StateFlags> new_flags{mStateFlags};
+
+ if (initialized) {
+ if (new_flags.isOn(StateFlags::RequestDeleteProcUnit))
+ unlinkProcUnit_();
+
+ if (mStateFlags.isOn(StateFlags::_4000)) {
+ if (shouldClearStateFlag4000_())
+ new_flags.makeAllZero();
+ else
+ new_flags.setDirect(StateFlags::_4000);
+ } else {
+ new_flags.makeAllZero();
+ }
+
+ const auto check_calc_sleep = [&] {
+ const bool sleep_requested = mStateFlags.isOn(StateFlags::RequestSleep);
+ const State state = mState;
+ if (sleep_requested) {
+ handleSleepRequest_();
+ if (mStateFlags.isAnyOn({StateFlags::RequestWakeUp, StateFlags::_2})) {
+ new_flags.setDirect(mStateFlags.getDirect() &
+ (u32(StateFlags::RequestWakeUp) | u32(StateFlags::_2)));
+ }
+ return true;
+ }
+
+ const sead::TypedBitFlag<StateFlags> flags{mStateFlags};
+
+ if (state == State::Calc) {
+ if (flags.isOn(StateFlags::RequestChangeCalcJobPriority))
+ handleJobPriorityChangeRequest_();
+ return true;
+ }
+
+ if (!flags.isAnyOn({StateFlags::RequestWakeUp, StateFlags::_2}))
+ return true;
+
+ if (canWakeUpOrFlagsSet_()) {
+ handleWakeUpRequest_();
+ return true;
+ }
+
+ return mStateFlags.isOn(StateFlags::RequestSleep);
+ };
+
+ if (!check_calc_sleep()) {
+ new_flags.setDirect(mStateFlags.getDirect() &
+ (u32(StateFlags::RequestWakeUp) | u32(StateFlags::_2)));
+ }
+
+ if (mStateFlags.isOn(StateFlags::RequestDelete)) {
+ new_flags.set(StateFlags::RequestDelete);
+ mStateFlags.setDirect(new_flags.getDirect());
+ return false;
+ }
+
+ if (mStateFlags.isOn(StateFlags::RequestSleep))
+ handleSleepRequest_();
+
+ if (mStateFlags.isOn(StateFlags::RequestResetChild))
+ unlinkCalcChild_();
+
+ if (mStateFlags.isOn(StateFlags::RequestResetParent))
+ unlinkCalcParent_();
+
+ if (mStateFlags.isOn(StateFlags::RequestSetChild))
+ loadNewCalcChild_(counter);
+
+ if (mStateFlags.isOn(StateFlags::RequestSetParent))
+ loadNewCalcParent_(counter);
+ }
+
+ const bool ret = new_flags.isZero();
+ mStateFlags = new_flags;
+ return ret;
+}
+
+void BaseProc::processPreDelete() {
+ if (!mFlags.isOn(Flags::Initialized))
+ return;
+
+ if (mFlags.isOn(Flags::PreDeleteFailed)) {
+ // Try again.
+ if (BaseProcMgr::instance()->requestPreDelete(*this)) {
+ mFlags.set(Flags::PreDeleteStarted);
+ mFlags.reset(Flags::PreDeleteFailed);
+ }
+
+ } else if (!mFlags.isOn(Flags::PreDeleteStarted) && startPreparingForPreDelete_()) {
+ mFlags.set(Flags::PreDeleting);
+ if (prepareForPreDelete_() == PreDeletePrepareResult::Done) {
+ BaseProcLinkDataMgr::instance()->releaseLink(this);
+ PrepareArg arg;
+ onPreDeleteStart_(arg);
+
+ if (BaseProcMgr::instance()->requestPreDelete(*this))
+ mFlags.set(Flags::PreDeleteStarted);
+ else
+ mFlags.set(Flags::PreDeleteFailed);
+ }
+ }
+}
+
+void BaseProc::doPreDelete(bool* do_not_destruct_immediately) {
+ preDelete1_();
+ preDelete2_(do_not_destruct_immediately);
+ preDelete3_(do_not_destruct_immediately);
+
+ if (*do_not_destruct_immediately)
+ return;
+
+ mFlags.set(Flags::Destructed);
+ destruct_(1);
+ BaseProcMgr::instance()->decrementPendingDeletions();
+}
+
+void BaseProc::unlinkProcUnit_() {
+ if (mProcUnit) {
+ mProcUnit->unlinkProc(this);
+ mProcUnit = nullptr;
+ }
+}
+
+void BaseProc::unlinkCalcChild_() {
+ if (mConnectedCalcChild) {
+ mConnectedCalcChild->mConnectedCalcParent = nullptr;
+ mConnectedCalcChild = nullptr;
+ }
+}
+
+void BaseProc::unlinkCalcParent_() {
+ auto parent = mConnectedCalcParent;
+ if (parent && parent->mConnectedCalcChild) {
+ parent->mConnectedCalcChild->mConnectedCalcParent = nullptr;
+ parent->mConnectedCalcChild = nullptr;
+ }
+}
+
+void BaseProc::loadNewCalcChild_(u8 counter) {
+ auto child = mConnectedCalcChildNew;
+ if (child && !child->isDeletedOrDeleting() && !mConnectedCalcChild &&
+ !child->mConnectedCalcParent && !isDeletedOrDeleting() &&
+ child->mStateFlags.isOff(StateFlags::RequestDelete)) {
+ mConnectedCalcChild = child;
+ child->mConnectedCalcParent = this;
+ mCounter = counter;
+ }
+ mConnectedCalcChildNew = nullptr;
+}
+
+void BaseProc::loadNewCalcParent_(u8 counter) {
+ auto parent = mConnectedCalcParentNew;
+ if (parent && !parent->isDeletedOrDeleting() && !parent->mConnectedCalcChild &&
+ !mConnectedCalcParent && parent->mStateFlags.isOff(StateFlags::RequestDelete) &&
+ !isDeletedOrDeleting()) {
+ parent->mConnectedCalcChild = this;
+ mConnectedCalcParent = parent;
+ parent->mCounter = counter;
+ }
+ mConnectedCalcParentNew = nullptr;
+}
+
+void BaseProc::handleSleepRequest_() {
+ if (isDeletedOrDeleting() || mState == State::Sleep)
+ return;
+
+ mState = State::Sleep;
+ onEnterSleep_();
+ BaseProcMgr::instance()->eraseJobs(*this);
+}
+
+void BaseProc::handleWakeUpRequest_() {
+ if (isDeletedOrDeleting())
+ return;
+
+ onEnterCalc_();
+
+ if (!mStateFlags.isOff(StateFlags::RequestDelete))
+ return;
+
+ mState = State::Calc;
+ BaseProcMgr::instance()->pushJobs(*this);
+ unlinkProcUnit_();
+}
+
+void BaseProc::handleJobPriorityChangeRequest_() {
+ for (u32 i = 0; i < BaseProcMgr::instance()->getNumJobTypes(); ++i) {
+ if (!mJobHandlers[i] || !mJobHandlers[i]->getLink().hasPriorityChange())
+ continue;
+
+ BaseProcMgr::instance()->eraseJob(*this, JobType(i));
+ mJobHandlers[i]->getLink().loadNewPriority();
+ mJobHandlers[i]->getLink().loadNewPriority2();
+ BaseProcMgr::instance()->pushJob(*this, JobType(i));
+ }
+}
+
+} // namespace ksys::act
diff --git a/src/KingSystem/ActorSystem/actBaseProc.h b/src/KingSystem/ActorSystem/actBaseProc.h
new file mode 100644
index 00000000..12408819
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actBaseProc.h
@@ -0,0 +1,286 @@
+#pragma once
+
+#include <basis/seadTypes.h>
+#include <container/seadListImpl.h>
+#include <container/seadSafeArray.h>
+#include <container/seadTreeMap.h>
+#include <prim/seadBitFlag.h>
+#include <prim/seadRuntimeTypeInfo.h>
+#include <prim/seadSafeString.h>
+#include <prim/seadTypedBitFlag.h>
+#include <thread/seadAtomic.h>
+#include "KingSystem/ActorSystem/actBaseProcJob.h"
+#include "KingSystem/ActorSystem/actBaseProcMap.h"
+#include "KingSystem/Utils/StrTreeMap.h"
+#include "KingSystem/Utils/Types.h"
+
+namespace ksys {
+
+namespace act {
+
+class ActorLinkConstDataAccess;
+class BaseProc;
+class BaseProcLinkData;
+class BaseProcJobHandler;
+
+class BaseProcLink {
+public:
+ BaseProcLink();
+ ~BaseProcLink() { reset(); }
+ BaseProcLink& operator=(const BaseProcLink&);
+ bool operator==(const BaseProcLink&) const;
+ bool operator!=(const BaseProcLink& rhs) const { return !operator==(rhs); }
+ BaseProc* fromAccessorAndActor(ActorLinkConstDataAccess&, BaseProc*);
+ BaseProc* fromAccessor(ActorLinkConstDataAccess&);
+ void reset();
+
+private:
+ BaseProcLinkData* mData = nullptr;
+ u32 mId = -1;
+ bool mAcquired = false;
+};
+KSYS_CHECK_SIZE_NX150(BaseProcLink, 0x10);
+
+class BaseProcUnit;
+
+class BaseProcHandle {
+public:
+ BaseProcHandle();
+ ~BaseProcHandle();
+
+private:
+ BaseProcUnit* mUnit;
+ u8 mFlag;
+};
+KSYS_CHECK_SIZE_NX150(BaseProcHandle, 0x10);
+
+/// Actor base class that encapsulates all the low-level actor lifetime logic.
+class BaseProc {
+public:
+ enum class State : u8 {
+ /// The actor is constructed, but not fully initialized yet.
+ Init = 0,
+ /// The actor is active.
+ Calc = 1,
+ /// The actor is sleeping: its update jobs won't be run.
+ Sleep = 2,
+ /// The actor is scheduled for deletion.
+ Delete = 3,
+ };
+
+ enum class DeleteReason : u32 {
+ _0 = 0,
+ _1 = 1,
+ _2 = 2,
+ };
+
+ enum class SleepWakeReason : u32 {
+ _0 = 0,
+ };
+
+ struct ClassInfo {
+ sead::SafeString name;
+ u8 priority;
+ };
+ KSYS_CHECK_SIZE_NX150(ClassInfo, 0x18);
+
+ struct CreateArg {
+ const ClassInfo* class_info;
+ void* _10;
+ u32 _14;
+ u32 _18;
+ sead::SafeString actor_name;
+ };
+ KSYS_CHECK_SIZE_NX150(CreateArg, 0x28);
+
+ explicit BaseProc(const CreateArg& arg);
+ virtual ~BaseProc();
+
+ SEAD_RTTI_BASE(BaseProc)
+
+ /// @return true iff state is Calc and the actor is not being deleted.
+ bool init(sead::Heap* heap, bool sleep_after_init);
+ /// Put this actor to sleep. The request is queued.
+ void sleep(SleepWakeReason reason);
+ /// Wake up this actor. The request is queued.
+ void wakeUp(SleepWakeReason reason);
+ /// Delete this actor. The request is queued.
+ bool deleteLater(DeleteReason reason);
+
+ u8 getPriority() const { return mPriority; }
+ void setPriority(u8 priority) { mPriority = priority; }
+
+ bool isDeletedOrDeleting() const {
+ return mState == State::Delete || mStateFlags.isOn(StateFlags::RequestDelete);
+ }
+
+protected:
+ friend class BaseProcLinkDataMgr;
+ friend class BaseProcMgr;
+
+ enum class Flags : u32 {
+ Initialized = 1,
+ PreDeleteStarted = 2,
+ PreDeleteFailed = 4,
+ Destructed = 8,
+ DoNotDelete = 0x10,
+ DeleteChildOnDelete = 0x20,
+ DeleteParentOnDelete = 0x40,
+ _80 = 0x80,
+ _100 = 0x100,
+ PreDeleting = 0x200,
+ SleepWakeReason0 = 0x400,
+ };
+
+ enum class StateFlags : u32 {
+ RequestDelete = 1,
+ _2 = 2,
+ RequestSleep = 4,
+ RequestWakeUp = 8,
+ RequestChangeCalcJobPriority = 0x10,
+ _20 = 0x20,
+ _40 = 0x40,
+ _80 = 0x80,
+ RequestSetParent = 0x100,
+ RequestSetChild = 0x200,
+ RequestResetParent = 0x400,
+ RequestResetChild = 0x800,
+ _1000 = 0x1000,
+ _2000 = 0x2000,
+ _4000 = 0x4000,
+ RequestDeleteProcUnit = 0x8000,
+ };
+
+ enum class InitResult : u32 {
+ Ok = 1,
+ Failed = 2,
+ Skipped = 3,
+ };
+
+ enum class PreDeletePrepareResult : u32 {
+ NotDone = 0,
+ Done = 1,
+ };
+
+ struct InitContext {
+ InitResult result;
+ bool sleep_after_init;
+ };
+
+ struct PrepareArg {
+ bool _0 = false;
+ int _4;
+ };
+
+ /// Initialize the actor.
+ /// @return Ok to keep the actor alive, anything else to kill it?
+ virtual InitResult init_();
+ /// @return whether prepareInit_ and init_ should be called.
+ virtual bool shouldInit_();
+ /// Finalize the initialization; the actor is deleted if the result is not Ok.
+ virtual void finalizeInit_(InitContext* context);
+
+ /// Called every tick to prepare for pre-delete (after startPreparingForPreDelete_).
+ virtual PreDeletePrepareResult prepareForPreDelete_();
+ /// Called to start preparing for pre-delete. Return true to allow pre-delete to go ahead.
+ virtual bool startPreparingForPreDelete_();
+
+ /// Destructs this actor if should_destruct is 1.
+ /// @warning The actor must NOT be used after calling this function.
+ virtual void destruct_(int should_destruct);
+
+ /// Called when entering the Calc state.
+ virtual void onEnterCalc_();
+
+ /// Called when a new delete operation is queued.
+ virtual void onDeleteRequested_(DeleteReason reason);
+ /// Called when a new sleep operation is queued.
+ virtual void onSleepRequested_(SleepWakeReason reason);
+ /// Called when a new wakeup operation is queued.
+ virtual void onWakeUpRequested_(SleepWakeReason reason);
+
+ virtual bool shouldClearStateFlag4000_() { return true; }
+
+ /// Called when entering the Delete state.
+ virtual void onEnterDelete_();
+ /// Called when entering the Sleep state.
+ virtual void onEnterSleep_();
+
+ /// Called to actually pre-delete (third and final callback).
+ virtual void preDelete3_(bool* do_not_destruct_immediately);
+
+ virtual bool prepareInit_(sead::Heap* heap, PrepareArg& arg);
+
+ /// Called when pre-delete actually starts (after preparation, before requesting it).
+ virtual void onPreDeleteStart_(PrepareArg&);
+ /// Called to actually pre-delete (second callback).
+ virtual void preDelete2_(bool* do_not_destruct_immediately);
+ /// Called to actually pre-delete (first callback).
+ virtual void preDelete1_();
+
+ virtual bool isSpecialJobType_(JobType type);
+ virtual bool canWakeUp_();
+ virtual void queueExtraJobPush_(JobType type);
+ virtual bool hasJobType_(JobType type);
+ /// Called after processStateUpdate() is called for all actors in the update state list.
+ virtual void afterUpdateState_();
+
+ virtual bool shouldSkipJobPush_(JobType type);
+ /// Called before pushing a job with the specified job type (first callback).
+ virtual void onJobPush1_(JobType type);
+ /// Called before pushing a job with the specified job type (second and final callback).
+ virtual void onJobPush2_(JobType type);
+
+ bool processStateUpdate(u8 counter);
+ void processPreDelete();
+ /// Actually pre-delete the actor. Called from BaseProcDeleter.
+ void doPreDelete(bool* do_not_destruct_immediately);
+ void startDelete_();
+
+ bool isSpecialJobTypeForThisActor_(JobType type) const {
+ return mSpecialJobTypesMask.isOnBit(int(type));
+ }
+
+ sead::FixedSafeString<64> mName;
+ u32 mId = -1;
+ State mState = State::Init;
+ u8 mPriority = 0;
+ u8 mCreatePriorityState = 0;
+ u8 mCounter = 0;
+ BaseProcLinkData* mBaseProcLinkData = nullptr;
+ sead::BitFlag16 mSkippedJobTypesMask;
+ sead::BitFlag16 mSpecialJobTypesMask;
+ sead::TypedBitFlag<Flags, sead::Atomic<u32>> mFlags;
+ sead::TypedBitFlag<StateFlags, sead::Atomic<u32>> mStateFlags;
+ BaseProc* mConnectedCalcParent = nullptr;
+ BaseProc* mConnectedCalcChild = nullptr;
+ BaseProc* mConnectedCalcParentNew = nullptr;
+ BaseProc* mConnectedCalcChildNew = nullptr;
+ sead::SafeArray<BaseProcJobHandler*, 7> mJobHandlers{};
+ sead::Delegate<BaseProc> mInvoker; // TODO: is this correct?
+ sead::ListNode mPostDeleteListNode;
+ sead::ListNode mDeleteListNode;
+ BaseProcMapNode mMapNode{this};
+ BaseProcUnit* mProcUnit = nullptr;
+ sead::Atomic<u32> mRefCount = 0;
+
+private:
+ void unlinkProcUnit_();
+ void unlinkCalcChild_();
+ void unlinkCalcParent_();
+ void loadNewCalcChild_(u8 counter);
+ void loadNewCalcParent_(u8 counter);
+
+ void handleSleepRequest_();
+ void handleWakeUpRequest_();
+ void handleJobPriorityChangeRequest_();
+
+ bool canWakeUpOrFlagsSet_() {
+ return mFlags.isOn(Flags::_80) ? mFlags.isOn(Flags::_100) : canWakeUp_();
+ }
+};
+KSYS_CHECK_SIZE_NX150(BaseProc, 0x180);
+
+} // namespace act
+
+} // namespace ksys
diff --git a/src/KingSystem/ActorSystem/actBaseProcJob.cpp b/src/KingSystem/ActorSystem/actBaseProcJob.cpp
new file mode 100644
index 00000000..2b3801fa
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actBaseProcJob.cpp
@@ -0,0 +1,9 @@
+#include "KingSystem/ActorSystem/actBaseProcJob.h"
+
+namespace ksys::act {
+
+BaseProcJobLink::BaseProcJobLink(BaseProc* proc, u8 priority)
+ : TListNode(proc), mPriority(priority), mNewPriority(priority), mPriority2(3),
+ mNewPriority2(3) {}
+
+} // namespace ksys::act
diff --git a/src/KingSystem/ActorSystem/actBaseProcJob.h b/src/KingSystem/ActorSystem/actBaseProcJob.h
new file mode 100644
index 00000000..bd30b23e
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actBaseProcJob.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <container/seadTList.h>
+#include "KingSystem/Utils/Types.h"
+
+namespace ksys::act {
+
+class BaseProc;
+
+enum class Priority : u8 {
+ PlayerBefore = 0,
+ Player = 2,
+ PlayerAfter = 2,
+ AllAfter = 3,
+};
+
+enum class JobType {
+ PreCalc = 0,
+ Calc1 = 1,
+ Calc2 = 2,
+ Calc3 = 3,
+ Calc4 = 4,
+ AfterMessageDispatch = 5,
+ Calc3Alt = 6,
+ Invalid = 7,
+};
+
+class BaseProcJobLink : public sead::TListNode<BaseProc*> {
+public:
+ BaseProcJobLink(BaseProc* proc, u8 priority);
+
+ u8 getPriority() const { return mPriority; }
+ u8 getPriority2() const { return mPriority2; }
+
+ void setNewPriority(u8 priority) { mNewPriority = priority; }
+ void setNewPriority2(u8 priority) { mNewPriority2 = priority; }
+
+ void loadNewPriority() { mPriority = mNewPriority; }
+ void loadNewPriority2() { mPriority2 = mNewPriority2; }
+
+ bool hasPriorityChange() const {
+ return mPriority != mNewPriority || mPriority2 != mNewPriority2;
+ }
+
+private:
+ u8 mPriority;
+ u8 mNewPriority;
+ u8 mPriority2;
+ u8 mNewPriority2;
+};
+KSYS_CHECK_SIZE_NX150(BaseProcJobLink, 0x28);
+
+} // namespace ksys::act
diff --git a/src/KingSystem/ActorSystem/actBaseProcJobHandler.cpp b/src/KingSystem/ActorSystem/actBaseProcJobHandler.cpp
new file mode 100644
index 00000000..b74fcb1f
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actBaseProcJobHandler.cpp
@@ -0,0 +1,8 @@
+#include "KingSystem/ActorSystem/actBaseProcJobHandler.h"
+#include "KingSystem/ActorSystem/actBaseProc.h"
+
+namespace ksys::act {
+
+BaseProcJobHandler::BaseProcJobHandler(BaseProc* proc) : mLink(proc, proc->getPriority()) {}
+
+} // namespace ksys::act
diff --git a/src/KingSystem/ActorSystem/actBaseProcJobHandler.h b/src/KingSystem/ActorSystem/actBaseProcJobHandler.h
new file mode 100644
index 00000000..1df63276
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actBaseProcJobHandler.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "KingSystem/ActorSystem/actBaseProcJob.h"
+#include "KingSystem/Utils/Types.h"
+
+namespace ksys::act {
+
+class BaseProcJobHandler {
+public:
+ BaseProcJobHandler(BaseProc* proc);
+ virtual ~BaseProcJobHandler() = default;
+ virtual void invoke() = 0;
+ virtual void invokeSpecial() {}
+
+ BaseProcJobLink& getLink() { return mLink; }
+ const BaseProcJobLink& getLink() const { return mLink; }
+
+protected:
+ BaseProcJobLink mLink;
+};
+KSYS_CHECK_SIZE_NX150(BaseProcJobHandler, 0x30);
+
+} // namespace ksys::act
diff --git a/src/KingSystem/ActorSystem/actBaseProcLinkDataMgr.cpp b/src/KingSystem/ActorSystem/actBaseProcLinkDataMgr.cpp
new file mode 100644
index 00000000..cc986835
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actBaseProcLinkDataMgr.cpp
@@ -0,0 +1,52 @@
+#include "KingSystem/ActorSystem/actBaseProcLinkDataMgr.h"
+#include <prim/seadScopedLock.h>
+#include <thread/seadThread.h>
+#include "KingSystem/ActorSystem/actBaseProc.h"
+
+namespace ksys::act {
+
+SEAD_SINGLETON_DISPOSER_IMPL(BaseProcLinkDataMgr)
+
+bool BaseProcLinkDataMgr::acquireLink(BaseProc* proc) {
+ auto lock = sead::makeScopedLock(mCS);
+ s32 index = mIndex;
+ for (s32 i = 0; i < mData.size(); ++i) {
+ const s32 j = index == 0x800 ? 0 : index;
+ auto& data = mData[j];
+ if (data.mId == u32(-1)) {
+ {
+ auto data_lock = sead::makeScopedLock(data.mCS);
+ data.mProc = proc;
+ data.mId = proc->mId;
+ proc->mBaseProcLinkData = &data;
+ }
+ mIndex = j + 1;
+ return true;
+ }
+ index = j + 1;
+ }
+
+ for (s32 i = 0; i < mData.size(); ++i) {
+ auto data_lock = sead::makeScopedLock(mData[i].mCS);
+ // Debug code was probably here?
+ }
+
+ return false;
+}
+
+bool BaseProcLinkDataMgr::releaseLink(BaseProc* proc) {
+ auto* data = proc->mBaseProcLinkData;
+ if (!data)
+ return false;
+
+ const auto thread = sead::ThreadMgr::instance()->getCurrentThread();
+ thread->getPriority();
+
+ auto lock = sead::makeScopedLock(data->mCS);
+ data->mProc = nullptr;
+ data->mId = -1;
+ proc->mBaseProcLinkData = nullptr;
+ return true;
+}
+
+} // namespace ksys::act
diff --git a/src/KingSystem/ActorSystem/actBaseProcLinkDataMgr.h b/src/KingSystem/ActorSystem/actBaseProcLinkDataMgr.h
new file mode 100644
index 00000000..b7f19f81
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actBaseProcLinkDataMgr.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include <container/seadSafeArray.h>
+#include <heap/seadDisposer.h>
+#include <prim/seadScopedLock.h>
+#include <thread/seadCriticalSection.h>
+#include "KingSystem/Utils/Types.h"
+
+namespace ksys::act {
+
+class BaseProc;
+
+class BaseProcLinkData {
+public:
+ u32 id() const { return mId; }
+ BaseProc* proc() const { return mProc; }
+ s32 refCount() const { return mRefCount; }
+
+private:
+ friend class BaseProcLinkDataMgr;
+
+ sead::CriticalSection mCS;
+ u32 mId = -1;
+ BaseProc* mProc = nullptr;
+ s32 mRefCount = 0;
+};
+KSYS_CHECK_SIZE_NX150(BaseProcLinkData, 0x58);
+
+class BaseProcLinkDataMgr {
+ SEAD_SINGLETON_DISPOSER(BaseProcLinkDataMgr)
+
+public:
+ bool acquireLink(BaseProc* proc);
+ bool releaseLink(BaseProc* proc);
+
+private:
+ sead::CriticalSection mCS;
+ s32 mIndex = 0;
+ sead::SafeArray<BaseProcLinkData, 2048> mData{};
+};
+KSYS_CHECK_SIZE_NX150(BaseProcLinkDataMgr, 0x2C068);
+
+} // namespace ksys::act
diff --git a/src/KingSystem/ActorSystem/actBaseProcMap.cpp b/src/KingSystem/ActorSystem/actBaseProcMap.cpp
new file mode 100644
index 00000000..c38072e9
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actBaseProcMap.cpp
@@ -0,0 +1,22 @@
+#include "KingSystem/ActorSystem/actBaseProcMap.h"
+
+namespace ksys::act {
+
+void BaseProcMapNode::add(BaseProcMapNode* node) {
+ auto next = mNext;
+ node->mPrev = this;
+ node->mNext = next;
+ mNext = node;
+ if (next)
+ next->mPrev = node;
+}
+
+void BaseProcMap::insert(BaseProcMapNode* node) {
+ if (auto existing_node = find(node->key()))
+ existing_node->add(node);
+ else
+ StrTreeMap::insert(node);
+ node->setInserted();
+}
+
+} // namespace ksys::act
diff --git a/src/KingSystem/ActorSystem/actBaseProcMap.h b/src/KingSystem/ActorSystem/actBaseProcMap.h
new file mode 100644
index 00000000..9cbe42c9
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actBaseProcMap.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#include "KingSystem/Utils/StrTreeMap.h"
+
+namespace ksys::act {
+
+class BaseProc;
+
+class BaseProcMapNode final : public util::StrTreeMapNode {
+public:
+ explicit BaseProcMapNode(BaseProc* proc) : mProc(proc) {}
+ ~BaseProcMapNode() override;
+
+ // NON_MATCHING: addressing mode for mPrev
+ void erase_() override {
+ if (mPrev)
+ mPrev->mNext = mNext;
+
+ if (mNext)
+ mNext->mPrev = mPrev;
+
+ StrTreeMapNode::erase_();
+ mInserted = false;
+ mPrev = mNext = nullptr;
+ }
+
+ util::StrTreeMapKey& key() { return mKey; }
+ const util::StrTreeMapKey& key() const { return mKey; }
+ BaseProc* proc() const { return mProc; }
+ BaseProcMapNode* prev() const { return mPrev; }
+ BaseProcMapNode* next() const { return mNext; }
+
+ void add(BaseProcMapNode* node);
+
+ bool isInserted() const { return mInserted; }
+ void setInserted() { mInserted = true; }
+
+private:
+ BaseProc* mProc = nullptr;
+ BaseProcMapNode* mPrev = nullptr;
+ BaseProcMapNode* mNext = nullptr;
+ bool mInserted = false;
+};
+
+class BaseProcMap : public util::StrTreeMap<BaseProcMapNode> {
+public:
+ void insert(BaseProcMapNode* node);
+
+ void erase(BaseProcMapNode* node) {
+ auto prev = node->prev();
+ auto next = node->next();
+ if (prev)
+ node->erase_();
+ else if (next)
+ StrTreeMap::insert(next);
+ else
+ StrTreeMap::erase(node->key());
+ }
+};
+
+} // namespace ksys::act
diff --git a/src/KingSystem/ActorSystem/actBaseProcMgr.cpp b/src/KingSystem/ActorSystem/actBaseProcMgr.cpp
new file mode 100644
index 00000000..81ebe6a2
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actBaseProcMgr.cpp
@@ -0,0 +1,133 @@
+#include "KingSystem/ActorSystem/actBaseProcMgr.h"
+#include <prim/seadScopedLock.h>
+#include <thread/seadThread.h>
+
+namespace ksys::act {
+
+SEAD_SINGLETON_DISPOSER_IMPL(BaseProcMgr)
+
+void BaseProcMgr::generateProcId(u32* id) {
+ *id = mCreatedProcCounter.increment();
+}
+
+void BaseProcMgr::registerProc(BaseProc& proc) {
+ auto lock = sead::makeScopedLock(mProcMapCS);
+ proc.mMapNode.key().setKey(proc.mName);
+ mProcMap.insert(&proc.mMapNode);
+}
+
+void BaseProcMgr::unregisterProc(BaseProc& proc) {
+ if (!proc.mMapNode.isInserted())
+ return;
+
+ auto lock = sead::makeScopedLock(mProcMapCS);
+ mProcMap.erase(&proc.mMapNode);
+}
+
+void BaseProcMgr::addToPreDeleteList(BaseProc& proc) {
+ auto lock = sead::makeScopedLock(mProcPreDeleteListCS);
+ mProcPreDeleteList.pushBack(&proc);
+}
+
+void BaseProcMgr::doAddToUpdateStateList_(BaseProc& proc) {
+ if (mProcUpdateStateList.isNodeLinked(&proc))
+ return;
+
+ if (proc.mCreatePriorityState == 1) {
+ proc.mCreatePriorityState = 0;
+ mProcUpdateStateList.pushFront(&proc);
+ } else {
+ mProcUpdateStateList.pushBack(&proc);
+ }
+}
+
+void BaseProcMgr::eraseFromPreDeleteList(BaseProc& proc) {
+ auto lock = sead::makeScopedLock(mProcPreDeleteListCS);
+ if (mProcPreDeleteList.isNodeLinked(&proc))
+ mProcPreDeleteList.erase(&proc);
+}
+
+void BaseProcMgr::eraseFromUpdateStateList(BaseProc& proc) {
+ auto lock = sead::makeScopedLock(mProcUpdateStateListCS);
+ if (mProcUpdateStateList.isNodeLinked(&proc))
+ mProcUpdateStateList.erase(&proc);
+}
+
+void BaseProcMgr::processPreDeleteList() {
+ mStatus = Status::ProcessingPreDeleteList;
+
+ auto lock = sead::makeScopedLock(mProcPreDeleteListCS);
+ for (auto& proc : mProcPreDeleteList.robustRange())
+ proc.processPreDelete();
+
+ mStatus = Status::Idle;
+}
+
+bool BaseProcMgr::hasExtraJobLink(BaseProcJobLink* job_link, s32 idx) {
+ for (auto& ptr : mExtraJobLinkArrays[idx]) {
+ if (&ptr == job_link)
+ return true;
+ }
+ return false;
+}
+
+void BaseProcMgr::clearExtraJobArrays() {
+ mExtraJobLinkArrays[0].clear();
+ mExtraJobLinkArrays[1].clear();
+}
+
+void BaseProcMgr::setActorJobTypeAndPrio(JobType type, s32 prio, bool x) {
+ mStatus = Status::ProcessingActorJobs;
+ mJobType = type;
+ mCurrentlyProcessingPrio = prio;
+ mUnk2 = x;
+}
+
+void BaseProcMgr::goIdle() {
+ mStatus = Status::Idle;
+ mJobType = JobType::Invalid;
+ mIsPushingJobs = false;
+ mUnk2 = false;
+ mEnableExtraJobPush = false;
+ mCurrentlyProcessingPrio = 8;
+}
+
+void BaseProcMgr::clearMode() {
+ mMode = Mode::_0;
+}
+
+bool BaseProcMgr::isHighPriorityThread() const {
+ const auto current_thread = sead::ThreadMgr::instance()->getCurrentThread();
+
+ if (!current_thread)
+ return false;
+
+ const auto id = current_thread->getId();
+
+ if (!current_thread->isDefaultPriority())
+ return false;
+
+ return id == mMainThreadId || id == mHavokThreadId1 || id == mHavokThreadId2;
+}
+
+void BaseProcMgr::incrementUnk3() {
+ if (mUnk3 != 0xFF)
+ ++mUnk3;
+}
+
+void BaseProcMgr::decrementUnk3() {
+ if (mUnk3 != 0)
+ --mUnk3;
+}
+
+sead::CriticalSection* BaseProcMgr::lockProcMap() {
+ mProcMapCS.lock();
+ return &mProcMapCS;
+}
+
+void BaseProcMgr::unlockProcMap() {
+ mLastProcMapNode = nullptr;
+ mProcMapCS.unlock();
+}
+
+} // namespace ksys::act
diff --git a/src/KingSystem/ActorSystem/actBaseProcMgr.h b/src/KingSystem/ActorSystem/actBaseProcMgr.h
new file mode 100644
index 00000000..95d79f9b
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actBaseProcMgr.h
@@ -0,0 +1,219 @@
+#pragma once
+
+#include <agl/Utils/aglAtomicPtrArray.h>
+#include <container/seadOffsetList.h>
+#include <container/seadPtrArray.h>
+#include <container/seadSafeArray.h>
+#include <heap/seadDisposer.h>
+#include <prim/seadBitFlag.h>
+#include <prim/seadScopedLock.h>
+#include <thread/seadAtomic.h>
+#include <thread/seadCriticalSection.h>
+#include "KingSystem/ActorSystem/actBaseProc.h"
+#include "KingSystem/ActorSystem/actBaseProcJob.h"
+#include "KingSystem/ActorSystem/actBaseProcMap.h"
+#include "KingSystem/Utils/StrTreeMap.h"
+#include "KingSystem/Utils/Types.h"
+
+namespace sead {
+class FixedSizeJQ;
+template <typename F>
+class IFunction;
+class WorkerMgr;
+} // namespace sead
+
+namespace ksys::act {
+
+class BaseProcDeleter;
+class BaseProcInitializer;
+class BaseProcInitializerArgs;
+class BaseProcJobLists;
+class BaseProcJobQue;
+
+class BaseProcMgr {
+ SEAD_SINGLETON_DISPOSER(BaseProcMgr)
+ BaseProcMgr();
+
+public:
+ class ProcCreateRequest;
+
+ enum class Status : u8 {
+ Idle = 0,
+ _1 = 1,
+ ProcessingActorJobs = 2,
+ ProcessingPreDeleteList = 3,
+ ProcessingUpdateStateList = 4,
+ };
+
+ enum class Mode : u8 {
+ _0 = 0,
+ };
+
+ using ExtraJobLinkArray = agl::utl::FixedPtrArray<BaseProcJobLink, 512>;
+
+ static void createInstanceAndInit(sead::Heap* heap);
+ static u32 getConstant0() { return sConstant0; }
+ static u32 getConstant1() { return sConstant1; }
+ static u32 getConstant2() { return sConstant2; }
+ static u32 getConstant4() { return sConstant4; }
+
+ virtual ~BaseProcMgr();
+
+ void init(sead::Heap* heap, s32 num_job_types, u32 main_thread_id, u32 havok_thread_id1,
+ u32 havok_thread_id2, BaseProcInitializerArgs* initializer_args);
+
+ // region BaseProc management
+
+ void generateProcId(u32* id);
+ void registerProc(BaseProc& proc);
+ void unregisterProc(BaseProc& proc);
+
+ void addToPreDeleteList(BaseProc& proc);
+ bool addToUpdateStateList(BaseProc& proc);
+ void eraseFromPreDeleteList(BaseProc& proc);
+ void eraseFromUpdateStateList(BaseProc& proc);
+ void processPreDeleteList();
+
+ void forEachProc(const sead::IDelegate1<BaseProc*>& callback, u32 flags);
+ void deleteAllProcs();
+ bool hasFinishedDeletingAllProcs();
+
+ // endregion
+
+ bool requestPreDelete(BaseProc& proc);
+ void requestUnloadActorParam(void*);
+
+ // region Job processing
+
+ void pushJob(BaseProc& proc, JobType type);
+ void pushJobs(BaseProc& proc);
+ void eraseJob(BaseProc& proc, JobType type);
+ void eraseJobs(BaseProc& proc);
+
+ void processExtraJobsDirectly(JobType type, s32 prio, bool);
+ ExtraJobLinkArray& getExtraJobs();
+ void swapExtraJobArray();
+
+ void queueExtraJobPush(BaseProcJobLink* job_link);
+ void moveExtraJobsToOtherBuffer();
+ bool hasExtraJobLink(BaseProcJobLink* job_link, s32 idx);
+ void clearExtraJobArrays();
+
+ void pushJobQueues(sead::WorkerMgr* mgr, JobType type, bool);
+ bool pushExtraJobsEx(sead::FixedSizeJQ* jq, JobType type, bool, bool);
+ bool pushExtraJobsForCurrentTypeAndPrio(sead::FixedSizeJQ* jq, ExtraJobLinkArray& array);
+ bool pushPreCalcJobs(sead::FixedSizeJQ* jq, JobType type, s32 prio, bool, bool);
+
+ void setActorJobType(JobType type);
+ void setActorJobTypeAndPrio(JobType type, s32 prio, bool);
+ void goIdle();
+ void clearMode();
+ void calc();
+ void jobInvoked(BaseProcJobLink* link, s32 required_calc_rounds);
+
+ // endregion
+
+ // region Special job types
+
+ bool isSpecialJobType(JobType type) const;
+ void addSpecialJobTypes(u16 mask);
+ void removeSpecialJobTypes(u16 mask);
+
+ // endregion
+
+ /// Returns true if and only if the calling thread is the game thread or a Havok thread.
+ bool isHighPriorityThread() const;
+ /// Returns true if and only if it is safe to access the specified BaseProc.
+ bool isAccessingProcSafe(BaseProc* proc, BaseProc* other);
+
+ // region BaseProc creation
+
+ bool requestCreateProc(ProcCreateRequest& req);
+ BaseProc* createProc(ProcCreateRequest& req);
+
+ // endregion
+
+ // region Actor initializer control
+
+ void resumeThreadMaybe();
+ void stopThreads();
+ void clearMessageQueueMaybe();
+ void startActorCreateThread();
+
+ void clearInitializerMessageQueuesMaybe();
+ s32 getActorCreateInitializerQueueSize();
+ s32 getActorCreateInitializerQueueSizeEx(s32 x);
+ void invokeOnActorCreateInitializerThreadMaybe(void* delegate);
+ void setActorGenerationEnabled(bool enabled);
+
+ // endregion
+
+ auto getUnk3() const { return mUnk3; }
+ void incrementUnk3();
+ void decrementUnk3();
+
+ void writeResidentActorsCsv(const sead::SafeString& file_path);
+
+ auto& getProcUpdateStateListCS() { return mProcUpdateStateListCS; }
+
+ void incrementPendingDeletions() { mNumPendingDeletions.increment(); }
+ void decrementPendingDeletions() { mNumPendingDeletions.decrement(); }
+
+ u32 getNumJobTypes() const { return mNumJobTypes; }
+
+private:
+ void doAddToUpdateStateList_(BaseProc& proc);
+
+ sead::CriticalSection* lockProcMap();
+ void unlockProcMap();
+ void getNextActor(sead::CriticalSection* cs, BaseProc* proc, u32 flags);
+
+ static sead::BufferedSafeString* sResidentActorListStr;
+ static u32 sConstant0;
+ static u32 sConstant1;
+ static u32 sConstant2;
+ static u32 sConstant4;
+
+ Status mStatus = Status::Idle;
+ util::SizedEnum<JobType, u8> mJobType = JobType::Invalid;
+ u8 mCurrentlyProcessingPrio = 8;
+ u8 mCounter = 0;
+ sead::CriticalSection mProcMapCS;
+ sead::OffsetList<BaseProc> mProcPreDeleteList;
+ u32 mNumJobTypes = 0;
+ BaseProcJobLists* mJobLists = nullptr;
+ BaseProcMap mProcMap;
+ sead::OffsetList<BaseProc> mProcUpdateStateList;
+ sead::CriticalSection mProcUpdateStateListCS;
+ sead::CriticalSection mProcPreDeleteListCS;
+ BaseProcMapNode* mLastProcMapNode = nullptr;
+ BaseProcJobQue* mProcJobQue = nullptr;
+ sead::Atomic<u32> mCreatedProcCounter = 0;
+ sead::Atomic<u32> mNumPendingDeletions = 0;
+ BaseProcInitializer* mProcInitializer = nullptr;
+ BaseProcDeleter* mProcDeleter = nullptr;
+ bool mIsPushingJobs = false;
+ bool mPushActorJobType3InsteadOf6 = false;
+ bool mEnableExtraJobPush = false;
+ Mode mMode = Mode::_0;
+ bool mUnk2 = false;
+ bool mIsInitialisingQuestMgrMaybe = false;
+ u8 mCurrentExtraJobArrayIdx = 0;
+ u8 mUnk3 = 0;
+ sead::BitFlag16 mSpecialJobTypesMask = 0;
+ u32 mMainThreadId = 0;
+ u32 mHavokThreadId1 = 0;
+ u32 mHavokThreadId2 = 0;
+ u32 mUnk4 = 0;
+ sead::SafeArray<ExtraJobLinkArray, 2> mExtraJobLinkArrays{};
+};
+KSYS_CHECK_SIZE_NX150(BaseProcMgr, 0x21a0);
+
+inline bool BaseProcMgr::addToUpdateStateList(BaseProc& proc) {
+ auto lock = sead::makeScopedLock(mProcUpdateStateListCS);
+ doAddToUpdateStateList_(proc);
+ return (proc.mStateFlags.set(BaseProc::StateFlags::RequestDelete) &
+ u32(BaseProc::StateFlags::RequestDelete)) != 0;
+}
+
+} // namespace ksys::act
diff --git a/src/KingSystem/ActorSystem/actBaseProcUnit.cpp b/src/KingSystem/ActorSystem/actBaseProcUnit.cpp
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actBaseProcUnit.cpp
diff --git a/src/KingSystem/ActorSystem/actBaseProcUnit.h b/src/KingSystem/ActorSystem/actBaseProcUnit.h
new file mode 100644
index 00000000..7bfe7e28
--- /dev/null
+++ b/src/KingSystem/ActorSystem/actBaseProcUnit.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <basis/seadTypes.h>
+#include <thread/seadCriticalSection.h>
+
+namespace ksys::act {
+
+class BaseProc;
+class BaseProcHandle;
+
+class BaseProcUnit {
+public:
+ bool deleteProc(void*, BaseProcHandle* handle);
+ bool setProc(BaseProc* proc);
+ void unlinkProc(BaseProc* proc);
+ void cleanUp(BaseProc* proc, bool set_flag_5);
+ bool isParentHandleDefault() const;
+
+private:
+ u32 mFlags;
+ BaseProcHandle* mHandle;
+ BaseProc* mProc;
+ // FIXME:
+ // BaseProcRequest mRequest;
+ sead::CriticalSection mCS;
+};
+
+} // namespace ksys::act
diff --git a/src/KingSystem/MessageSystem/mesTransceiver.h b/src/KingSystem/MessageSystem/mesTransceiver.h
new file mode 100644
index 00000000..ea9d3f79
--- /dev/null
+++ b/src/KingSystem/MessageSystem/mesTransceiver.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <basis/seadTypes.h>
+
+#include "KingSystem/Utils/Types.h"
+
+namespace ksys {
+
+namespace mes {
+
+struct TransceiverId {
+ TransceiverId();
+
+ TransceiverId& operator=(const TransceiverId& other) {
+ _0 = other._0;
+ _4 = other._4;
+ _8 = other._8;
+ _10 = other._10;
+ return *this;
+ }
+
+ u32 _0;
+ u32 _4;
+ void* _8;
+ void* _10;
+};
+KSYS_CHECK_SIZE_NX150(TransceiverId, 0x18);
+
+} // namespace mes
+
+} // namespace ksys
diff --git a/src/KingSystem/Movie/.gitkeep b/src/KingSystem/Movie/.gitkeep
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/KingSystem/Movie/.gitkeep
diff --git a/src/KingSystem/Physics/NavMesh/.gitkeep b/src/KingSystem/Physics/NavMesh/.gitkeep
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/KingSystem/Physics/NavMesh/.gitkeep
diff --git a/src/KingSystem/Terrain/teraSystem.h b/src/KingSystem/Terrain/teraSystem.h
new file mode 100644
index 00000000..a3aab5d2
--- /dev/null
+++ b/src/KingSystem/Terrain/teraSystem.h
@@ -0,0 +1,34 @@
+#pragma once
+
+namespace ksys::tera {
+
+// TODO:
+class ApertureMaps {
+ void updateMap();
+};
+class ApertureMapsCollector {
+ void allocateImage();
+ void releaseImage();
+};
+class Core {
+ class Grass;
+ class Model;
+ class Tree;
+};
+class ImageResourceMgr {
+ void procUnloadResidualRequest();
+};
+class ResourceHolder;
+class Scene {
+ void exportFileBinary();
+};
+class System {
+ void loadScene();
+};
+class Water {
+ void setUpAttributeTable();
+};
+
+bool checkTeraSystemStatus();
+
+} // namespace ksys::tera
diff --git a/src/KingSystem/Utils/StrTreeMap.h b/src/KingSystem/Utils/StrTreeMap.h
new file mode 100644
index 00000000..d12bbcc6
--- /dev/null
+++ b/src/KingSystem/Utils/StrTreeMap.h
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <basis/seadTypes.h>
+#include <codec/seadHashCRC32.h>
+#include <container/seadTreeMap.h>
+
+namespace ksys::util {
+
+class StrTreeMapKey {
+public:
+ const sead::SafeString& key() const { return mKey; }
+
+ // NON_MATCHING: stack
+ void setKey(u32 hash, sead::SafeString key) {
+ mKeyHash = hash;
+ mKey = key;
+ }
+
+ void setKey(const sead::SafeString& key) {
+ setKey(sead::HashCRC32::calcStringHash(key.cstr()), key);
+ }
+
+ s32 compare(const StrTreeMapKey& rhs) const {
+ if (mKeyHash < rhs.mKeyHash)
+ return -1;
+ if (mKeyHash > rhs.mKeyHash)
+ return 1;
+ return mKey.compare(rhs.mKey);
+ }
+
+private:
+ u32 mKeyHash = 0;
+ sead::SafeString mKey;
+};
+
+class StrTreeMapNode : public sead::TreeMapNode<StrTreeMapKey> {
+public:
+ ~StrTreeMapNode() override { ; }
+ void erase_() override { mLeft = mRight = nullptr; }
+};
+
+template <typename Node>
+class StrTreeMap : public sead::IntrusiveTreeMap<StrTreeMapKey, Node> {};
+
+} // namespace ksys::util
diff --git a/src/KingSystem/Utils/Types.h b/src/KingSystem/Utils/Types.h
new file mode 100644
index 00000000..e29701ae
--- /dev/null
+++ b/src/KingSystem/Utils/Types.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <type_traits>
+
+#ifdef SEAD_DEBUG
+#define KSYS_CHECK_SIZE_NX150(CLASS, SIZE)
+#else
+#define KSYS_CHECK_SIZE_NX150(CLASS, SIZE) static_assert(sizeof(CLASS) == SIZE)
+#endif
+
+namespace ksys::util {
+
+/// For storing an enum with a particular storage size when specifying the underlying type of the
+/// enum is not an option.
+template <typename Enum, typename Storage>
+struct SizedEnum {
+ static_assert(std::is_enum<Enum>());
+ static_assert(!std::is_enum<Storage>());
+
+ constexpr SizedEnum() = default;
+ constexpr SizedEnum(Enum value) { *this = value; }
+ constexpr operator Enum() const { return static_cast<Enum>(mValue); }
+ constexpr SizedEnum& operator=(Enum value) {
+ mValue = static_cast<Storage>(value);
+ return *this;
+ }
+
+ Storage mValue;
+};
+
+} // namespace ksys::util