summaryrefslogtreecommitdiff
path: root/src/Game/AutoPlacement
diff options
context:
space:
mode:
authorLéo Lam <leo@leolam.fr>2026-09-28 03:39:31 +0200
committerLéo Lam <leo@leolam.fr>2026-10-06 03:19:38 +0200
commit38905c8c075ec4ed8f8fcf858aef46bc5e05f8e2 (patch)
treec1c1378ec76107a6e93bdaabe27cc80ded4d943b /src/Game/AutoPlacement
parent4a6239bbbf5b5ab78c8d7eebb7afeeb79601ff43 (diff)
AutoPlacement is game code: move it to Game/
It is created only from game code, no engine code calls it, and it hardcodes game actor names. (AI assisted using Claude Fable 5.1 and Claude Opus 5.5)
Diffstat (limited to 'src/Game/AutoPlacement')
-rw-r--r--src/Game/AutoPlacement/AutoPlacement.cpp337
-rw-r--r--src/Game/AutoPlacement/AutoPlacement.h178
-rw-r--r--src/Game/AutoPlacement/AutoPlacementFlowMgr.cpp61
-rw-r--r--src/Game/AutoPlacement/AutoPlacementFlowMgr.h41
-rw-r--r--src/Game/AutoPlacement/AutoPlacementMgr.cpp7
-rw-r--r--src/Game/AutoPlacement/AutoPlacementMgr.h41
-rw-r--r--src/Game/AutoPlacement/CMakeLists.txt8
7 files changed, 673 insertions, 0 deletions
diff --git a/src/Game/AutoPlacement/AutoPlacement.cpp b/src/Game/AutoPlacement/AutoPlacement.cpp
new file mode 100644
index 00000000..d655fb7d
--- /dev/null
+++ b/src/Game/AutoPlacement/AutoPlacement.cpp
@@ -0,0 +1,337 @@
+#include "Game/AutoPlacement/AutoPlacement.h"
+#include "Game/AutoPlacement/AutoPlacementFlowMgr.h"
+#include "Game/AutoPlacement/AutoPlacementMgr.h"
+#include "KingSystem/ActorSystem/actActor.h"
+#include "KingSystem/ActorSystem/actActorUtil.h"
+#include "KingSystem/ActorSystem/actInfoData.h"
+#include "KingSystem/ActorSystem/actPhysicsUserTag.h"
+#include "KingSystem/Ecosystem/ecoSystem.h"
+#include "KingSystem/Physics/RigidBody/physRigidBody.h"
+#include "KingSystem/Physics/System/physRayCastForRequest.h"
+#include "KingSystem/Utils/Byaml/Byaml.h"
+#include "KingSystem/World/worldWeatherMgr.h"
+
+namespace uking {
+
+AutoPlacement::AutoPlacement() = default;
+AutoPlacement::~AutoPlacement() = default;
+
+bool AutoPlacement::sub_710064CBFC(sead::Heap* heap, u8 flag, s32 num_spawn, s32 num_obj) {
+ mNearFlag = flag;
+
+ if (!mSpawnInfo.tryAllocBuffer(num_spawn, heap))
+ return false;
+
+ if (num_obj > 0 && !mObjectRefs.tryAllocBuffer(num_obj, heap))
+ return false;
+ return true;
+}
+
+u32 AutoPlacement::sub_710064CD80(u32 a, u32 b) {
+ auto calc = [](u32 x, u32 y, u32 a, u32 b) { return (1 - a + x < 3) & (1 - b + y < 3); };
+ return calc(_4, _5, a, b);
+}
+
+void ActorSpawnInfo::calcSpawnLocations() {
+ auto* mgr = ksys::act::InfoData::instance();
+ al::ByamlIter iter;
+
+ using namespace ksys::act::tags;
+
+ spawn_location.makeAllZero();
+ if (mgr == nullptr || !mgr->getActorIter(&iter, name.cstr()))
+ return;
+
+ if (mgr->hasTag(iter, CheckBirdSafetyArea))
+ spawn_location.set(SpawnLocation::CheckBirdSafetyArea);
+ if (mgr->hasTag(iter, IsSnowBall))
+ spawn_location.set(SpawnLocation::IsSnowBall);
+
+ if (mgr->hasTag(iter, CreateFromTrunkOrGlowStoneLocator))
+ spawn_location.set(SpawnLocation::TrunkOrGlowStoneLocator);
+ else if (mgr->hasTag(iter, 0x923A4962))
+ spawn_location.set(SpawnLocation::_4);
+ else if (mgr->hasTag(iter, CreateFromTrunkLocatorRandom))
+ spawn_location.set(SpawnLocation::TrunkLocatorRandom);
+ else if (mgr->hasTag(iter, 0x7881F260))
+ spawn_location.set(SpawnLocation::_10);
+ else if (mgr->hasTag(iter, CreateFromBranchLocatorRandom))
+ spawn_location.set(SpawnLocation::BranchLocatorRandom);
+ else if (mgr->hasTag(iter, CreateFromOnTreeLocator))
+ spawn_location.set(SpawnLocation::OnTreeLocator);
+ else if (mgr->hasTag(iter, CreateFromOnTreeLocatorRandom))
+ spawn_location.set(SpawnLocation::OnTreeLocatorRandom);
+}
+
+void AutoPlacement::setSpawnInfo(const sead::SafeString& name, s32 count) {
+ sub_710064CF60(false);
+ ActorSpawnInfo* info = &mSpawnInfo[0];
+ auto* flow = AutoPlacementFlowMgr::instance()->getFlow(name, mNearFlag == 0xff);
+ if (flow != nullptr) {
+ info->name = name;
+ info->flow = flow;
+ info->_19 = 0;
+ s32 value = std::min(count, 255);
+ info->_18 = value;
+ info->_1c = value * 30;
+ info->_1e = 0;
+ info->calcSpawnLocations();
+ _d = true;
+ _8 = 0;
+ } else {
+ _d = false;
+ }
+}
+
+void AutoPlacement::sub_710064DA54() {
+ sub_710064CF60(false);
+ _8 = 2;
+}
+
+ksys::eco::AreaItemType sPlacementItemTypesData[] = {
+ ksys::eco::AreaItemType::Enemy, ksys::eco::AreaItemType::Animal,
+ ksys::eco::AreaItemType::Insect, ksys::eco::AreaItemType::Fish,
+ ksys::eco::AreaItemType::Bird, ksys::eco::AreaItemType::AutoPlacementMaterial,
+};
+sead::Buffer<ksys::eco::AreaItemType> sPlacementItemTypes{sPlacementItemTypesData};
+
+// NON_MATCHING: stack
+bool AutoPlacement::sub_710064E178(const sead::SafeString& name, u32 placement_type,
+ const sead::Vector3f& pos) {
+ if (mNearFlag == 0xFE)
+ return true;
+
+ auto* eco = ksys::eco::Ecosystem::instance();
+ if (eco == nullptr)
+ return false;
+
+ s32 area = eco->getFieldMapArea(pos.x, pos.z);
+ if (area < 0)
+ return false;
+
+ ksys::eco::AreaItemSet item_set;
+ eco->getAreaItems(area, sPlacementItemTypes[placement_type], &item_set);
+
+ for (int i = 0; i < item_set.count; ++i) {
+ const auto& item = item_set.items[i];
+ if (item.num > 0.0f && name == item.name)
+ return true;
+ }
+ return false;
+}
+
+// NON_MATCHING: blocked by raycast
+bool PlacementThing::invoke() {
+ auto lock = sead::makeScopedLock(mCS);
+
+ if (AutoPlacementMgr::instance()->auto9())
+ return true;
+
+ if (mRaycast == nullptr || mState != State::Initialized)
+ return false;
+
+ mRaycast->enableLayer(ksys::phys::ContactLayer::EntityGround);
+ mRaycast->enableLayer(ksys::phys::ContactLayer::EntityGroundRough);
+ mRaycast->enableLayer(ksys::phys::ContactLayer::EntityGroundSmooth);
+ mRaycast->enableLayer(ksys::phys::ContactLayer::EntityAirWall);
+ mRaycast->enableLayer(ksys::phys::ContactLayer::EntityGroundObject);
+ mRaycast->enableLayer(ksys::phys::ContactLayer::EntityTree);
+
+ if (_8944) {
+ sead::Vector3f v1 = mVec1 + sead::Vector3f{0, 4, 30};
+ sead::Vector3f v2 = mVec1 - sead::Vector3f{0, 4, 30};
+ mRaycast->setStartAndEnd(v1, v2);
+ } else {
+ sead::Vector3f v1{mVec1.x, 2000.0f, mVec1.z};
+ sead::Vector3f v2{mVec1.x, -100.0f, mVec1.z};
+ mRaycast->setStartAndEnd(v1, v2);
+ mRaycast->setCallback(&mDelegate);
+ }
+
+ mState = State::LayersDone;
+ return true;
+}
+
+bool AutoPlacement::sub_7100650144(PlacementGroup* grp, bool check_exposure) {
+ if (grp->_1d == 0xFF)
+ return false;
+ if (grp->_20 == -1)
+ return false;
+
+ auto* object = mObjectRefs[grp->_20].obj;
+ if (object == nullptr)
+ return false;
+
+ if (object->shouldSkipSpawn()) {
+ return true;
+ }
+
+ if (object->getProc() == nullptr && object->getFlags0().isOn(ksys::map::Object::Flag0::_80))
+ return true;
+
+ if (grp->_1d != 2)
+ return false;
+
+ if (check_exposure && !ksys::world::WeatherMgr::isExposureZero())
+ return true;
+ return false;
+}
+
+bool AutoPlacement::sub_710064EF24(ActorSpawnInfo* info, const sead::Vector3f& pos) {
+ auto* mgr = AutoPlacementMgr::instance();
+ u32 placement_type = info->flow->placement_type;
+ if (mgr->auto0(pos, placement_type)) {
+ if (mNearFlag != 0xFF) {
+ return false;
+ }
+ if (info->name.findIndex("Enemy_Dragon") == -1) {
+ return false;
+ }
+ }
+
+ if ((mNearFlag != 0xFF && mgr->auto11(pos)) ||
+ (!sub_710064E178(info->name, placement_type, pos))) {
+ return false;
+ }
+
+ if (mNearFlag != 0xFE && mNearFlag != 0xFF) {
+ if (AutoPlacementMgr::instance()->auto2(info->name, pos))
+ return false;
+ }
+ return true;
+}
+
+void AutoPlacement::sub_710064F744(u8 a1, u8 a2) {
+ if (mNearFlag != 0xFF)
+ return;
+ if (mGroupIdx < 0)
+ return;
+
+ auto& grp = mGroups[mGroupIdx];
+ if (!grp._80) {
+ grp._1a = a1;
+ grp._1b = a2;
+ }
+}
+
+void AutoPlacement::incrementGroup() {
+ if (mGroupIdx < 0)
+ return;
+ mGroupIdx++;
+}
+
+s32 AutoPlacement::placeGroup(AutoPlacementFlowRes* res) {
+ if (mGroupIdx < 0) {
+ return -1;
+ }
+
+ mThing2.mRaycast = mThing1.mRaycast;
+ _6 = 0;
+ _b = 0xFF;
+
+ auto& grp = mGroups[mGroupIdx];
+ if (grp.b.isEmpty()) {
+ return -1;
+ }
+
+ _8868 = sead::SafeString::cEmptyString;
+ int tmp = 0;
+ res->start(this, grp.a, &tmp);
+ if (!_9)
+ return -1;
+ _9 = 0;
+
+ return mGroupIdx;
+}
+
+s32 AutoPlacement::sub_7100650908() {
+ if (mGroupIdx < 0) {
+ return -1;
+ }
+ return mGroups[mGroupIdx]._1c;
+}
+
+void AutoPlacement::stepAllRaycasts() {
+ mThing1.stepRaycast();
+ mThing2.stepRaycast();
+ mThing3.stepRaycast();
+}
+
+void PlacementThing::stepRaycast() {
+ auto lock = sead::makeScopedLock(mCS);
+
+ if (AutoPlacementMgr::instance()->auto9()) {
+ if (mRaycast != nullptr) {
+ mRaycast->release();
+ mRaycast = nullptr;
+ }
+ mState = State::Invalid;
+ _8880 = _8944;
+ return;
+ }
+
+ State next;
+
+ switch (mState) {
+ case State::Uninitialized:
+ mRaycast =
+ ksys::phys::RayCastForRequest::allocRequest(nullptr, ksys::phys::GroundHit::Animal);
+ next = mRaycast != nullptr ? State::Initialized : State::Invalid;
+ break;
+ case State::LayersDone:
+ mRaycast->submitRequest(ksys::phys::ContactLayerType::Entity);
+ next = State::RaycastDone;
+ break;
+ case State::PlacementDone:
+ if (mRaycast != nullptr) {
+ mRaycast->release();
+ next = State::Invalid;
+ mRaycast = nullptr;
+ } else {
+ next = State::Invalid;
+ }
+ break;
+ default:
+ return;
+ }
+ mState = next;
+}
+
+void AutoPlacement::sub_7100650C28(ksys::phys::RigidBody* rb) {
+ if (rb == nullptr)
+ return;
+
+ if (rb->getContactLayer() == ksys::phys::ContactLayer::EntityTree)
+ mNearFlag.setDirect(1);
+
+ auto* tag = sead::DynamicCast<ksys::act::PhysicsUserTag>(rb->getUserTag());
+ if (tag == nullptr)
+ return;
+
+ auto* actor = tag->getActor();
+ if (actor == nullptr)
+ return;
+
+ mNearFlag.set(ksys::act::hasTag(actor, ksys::act::tags::Tree) ||
+ ksys::act::hasTag(actor, ksys::act::tags::AutoPlacementForbidCreate));
+
+ _b.set(actor->getName() == "AirWallHorse");
+}
+
+const char* PlacementThing::getCurrentGroundMat() const {
+ if (mWaterQuery > 0.0f) {
+ return mMask2.getMaterialName();
+ } else {
+ return mMask1.getMaterialName();
+ }
+}
+
+const char* PlacementThing::getCurrentWaterSubMat() const {
+ if (mWaterQuery > 0.0f) {
+ return mMask2.getSubMaterialName();
+ } else {
+ return mMask1.getSubMaterialName();
+ }
+}
+
+} // namespace uking
diff --git a/src/Game/AutoPlacement/AutoPlacement.h b/src/Game/AutoPlacement/AutoPlacement.h
new file mode 100644
index 00000000..1b1db151
--- /dev/null
+++ b/src/Game/AutoPlacement/AutoPlacement.h
@@ -0,0 +1,178 @@
+#pragma once
+
+#include <math/seadVector.h>
+#include <prim/seadDelegate.h>
+#include <prim/seadSafeString.h>
+#include <prim/seadTypedBitFlag.h>
+#include <thread/seadCriticalSection.h>
+#include "KingSystem/Map/mapObject.h"
+#include "KingSystem/Map/mapObjectLink.h"
+#include "KingSystem/Physics/physMaterialMask.h"
+#include "KingSystem/Utils/Types.h"
+
+namespace ksys::phys {
+class RayCastForRequest;
+class RigidBody;
+} // namespace ksys::phys
+
+namespace uking {
+
+class AutoPlacement;
+struct AutoPlacementFlowRes;
+class PlacementThing;
+
+struct PlacementGroup {
+ sead::SafeString a;
+ u32 _10;
+ u32 _14;
+ u8 _18;
+ u8 _19;
+ u8 _1a;
+ u8 _1b;
+ u8 _1c;
+ u8 _1d;
+ u8 _1e;
+ u8 _1f{};
+ s16 _20;
+ u16 _22{};
+ u8 _24;
+ u8 _25{};
+ void* _28;
+ void* _30;
+ void* _38;
+ void* _40;
+ void* _48;
+ void* _50;
+ void* _58;
+ void* _60;
+ void* _68;
+ sead::SafeString b;
+ u32 _80{};
+};
+KSYS_CHECK_SIZE_NX150(PlacementGroup, 0x88);
+
+class PlacementThing {
+public:
+ enum class State : u32 {
+ Invalid,
+ Uninitialized,
+ Initialized,
+ LayersDone,
+ RaycastDone,
+ PlacementDone,
+ };
+
+ explicit PlacementThing(bool set)
+ : mDelegate{this, &PlacementThing::sub_7100650C28}, _8944(set) {}
+
+ bool invoke();
+ void stepRaycast();
+ void sub_7100650C28(ksys::phys::RigidBody*);
+ const char* getCurrentGroundMat() const;
+ const char* getCurrentWaterSubMat() const;
+
+private:
+ friend class AutoPlacement;
+
+ ksys::phys::RayCastForRequest* mRaycast{};
+ u8 _8880{};
+ u8 mUnderwater{};
+ sead::Vector3f mVec1{};
+ sead::Vector3f mQueryVec{};
+ u32 _889c{};
+ f32 _88a0 = 1.0f;
+ ksys::phys::MaterialMask mMask1;
+ ksys::phys::MaterialMask mMask2;
+ f32 mWaterQuery = -1.0f;
+ sead::Delegate1<PlacementThing, ksys::phys::RigidBody*> mDelegate;
+ sead::CriticalSection mCS{};
+ State mState;
+ u8 _8944{};
+ u8 _8945{};
+};
+
+struct ActorSpawnInfo {
+ enum class SpawnLocation : u16 {
+ CheckBirdSafetyArea = 1,
+
+ // exclusive
+ TrunkOrGlowStoneLocator = 2,
+ _4 = 4,
+ TrunkLocatorRandom = 8,
+ _10 = 0x10,
+ BranchLocatorRandom = 0x20,
+ OnTreeLocator = 0x40,
+ OnTreeLocatorRandom = 0x80,
+
+ IsSnowBall = 0x100,
+ };
+
+ void calcSpawnLocations();
+
+ sead::SafeString name{};
+ AutoPlacementFlowRes* flow;
+ u8 _18;
+ u8 _19;
+ sead::TypedBitFlag<SpawnLocation> spawn_location{};
+ u16 _1c;
+ u16 _1e;
+};
+
+class AutoPlacement {
+public:
+ AutoPlacement();
+ ~AutoPlacement();
+
+ bool sub_710064CBFC(sead::Heap* heap, u8 flag, s32 num_spawn, s32 num_obj);
+ u32 sub_710064CD80(u32, u32);
+ void sub_710064CF60(bool force);
+
+ bool sub_710064ED60(const sead::SafeString& name, const sead::Vector3f& distance);
+
+ void setSpawnInfo(const sead::SafeString& name, s32 count);
+ void sub_710064DA54();
+
+ bool sub_710064E178(const sead::SafeString& name, u32 placement_type,
+ const sead::Vector3f& pos);
+ bool sub_710064EF24(ActorSpawnInfo* info, const sead::Vector3f& pos);
+ void sub_710064F744(u8 a1, u8 a2);
+ void incrementGroup();
+ bool sub_7100650144(PlacementGroup* grp, bool check_exposure);
+ s32 placeGroup(AutoPlacementFlowRes* res);
+ s32 sub_7100650908();
+ void stepAllRaycasts();
+ void sub_7100650C28(ksys::phys::RigidBody* rb);
+
+private:
+ struct ObjectRef {
+ ksys::map::Object* obj;
+ void* _8;
+ };
+
+ s16 _0 = 0;
+ s16 mGroupIdx = -1;
+ u8 _4 = 0xFF;
+ u8 _5 = 0xFF;
+ s16 _6 = 0;
+ u8 _8 = 0;
+ u8 _9 = 2;
+ sead::BitFlag<u8> mNearFlag{};
+ sead::BitFlag<u8> _b{};
+ u8 _c = 0;
+ u8 _d = 0;
+ u8 _e = 0;
+ u8 _f = 0;
+ u8 _10 = 0;
+ s32 _14 = 0;
+ sead::Buffer<ActorSpawnInfo> mSpawnInfo;
+ sead::CriticalSection mCS{};
+ sead::SafeArray<PlacementGroup, 0x100> mGroups{};
+ sead::SafeString _8868{};
+ PlacementThing mThing1{false};
+ PlacementThing mThing2{false};
+ PlacementThing mThing3{true};
+ void* mX;
+ sead::Buffer<ObjectRef> mObjectRefs;
+};
+
+} // namespace uking
diff --git a/src/Game/AutoPlacement/AutoPlacementFlowMgr.cpp b/src/Game/AutoPlacement/AutoPlacementFlowMgr.cpp
new file mode 100644
index 00000000..10742efa
--- /dev/null
+++ b/src/Game/AutoPlacement/AutoPlacementFlowMgr.cpp
@@ -0,0 +1,61 @@
+#include "Game/AutoPlacement/AutoPlacementFlowMgr.h"
+#include <container/seadBuffer.h>
+#include <evfl/ResActor.h>
+#include "KingSystem/Resource/resLoadRequest.h"
+
+namespace uking {
+
+const char* sFlowResNamesData[] = {
+ "AutoPlacement_Animal.bfevfl",
+ "AutoPlacement_Bird.bfevfl",
+ "AutoPlacement_Enemy_Golem_Little.bfevfl",
+ "AutoPlacement_Enemy_Keese.bfevfl",
+ "AutoPlacement_Enemy_Lizalfos.bfevfl",
+ "AutoPlacement_Enemy_Octarock.bfevfl",
+ "AutoPlacement_Enemy_Wizzrobe.bfevfl",
+ "AutoPlacement_Enemy_Fish.bfevfl",
+ "AutoPlacement_Enemy_Insect.bfevfl",
+ "AutoPlacement_Enemy_Material.bfevfl",
+};
+sead::Buffer<const char*> sFlowResNames{sFlowResNamesData};
+
+const char* sFlowNearResNamesData[] = {
+ "AutoPlacementNear_Enemy_Assassin_Middle.bfevfl",
+ "AutoPlacementNear_Enemy_Assassin_Shooter_Junior.bfevfl",
+ "AutoPlacementNear_Enemy_Chuchu.bfevfl",
+ "AutoPlacementNear_Enemy_Dragon.bfevfl",
+ "AutoPlacementNear_Enemy_Lizalfos.bfevfl",
+ "AutoPlacementNear_Enemy_Octarock.bfevfl",
+ "AutoPlacementNear_Enemy_Stal.bfevfl",
+};
+sead::Buffer<const char*> sFlowNearResNames{sFlowNearResNamesData};
+
+void AutoPlacementFlowMgr::loadEventFlows() {
+ for (int i = 0; i < mFlowArray.size(); i++) {
+ auto& flow = mFlowArray[i];
+ flow.evfl_name = sFlowResNames[i];
+ flow.idx = 0xFF;
+
+ sead::FixedSafeString<128> path;
+ path.format("EventFlow/%s", flow.evfl_name.cstr());
+ ksys::res::LoadRequest req;
+ req.mRequester = "AutoPlacementFlow";
+
+ flow.handle.requestLoad(path, &req);
+ }
+
+ for (int i = 0; i < mFlowNearArray.size(); i++) {
+ auto& flow = mFlowNearArray[i];
+ flow.evfl_name = sFlowResNames[i];
+ flow.idx = 0xFF;
+
+ sead::FixedSafeString<128> path;
+ path.format("EventFlow/%s", flow.evfl_name.cstr());
+ ksys::res::LoadRequest req;
+ req.mRequester = "AutoPlacementFlow";
+
+ flow.handle.requestLoad(path, &req);
+ }
+}
+
+} // namespace uking
diff --git a/src/Game/AutoPlacement/AutoPlacementFlowMgr.h b/src/Game/AutoPlacement/AutoPlacementFlowMgr.h
new file mode 100644
index 00000000..26103f3a
--- /dev/null
+++ b/src/Game/AutoPlacement/AutoPlacementFlowMgr.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <container/seadSafeArray.h>
+#include <evfl/Flowchart.h>
+#include <heap/seadDisposer.h>
+#include <prim/seadSafeString.h>
+#include "KingSystem/Resource/resHandle.h"
+
+namespace uking {
+
+class AutoPlacement;
+
+struct AutoPlacementFlowRes {
+ void start(AutoPlacement* placement, const sead::SafeString& unit_name, int*);
+
+ ksys::res::Handle handle;
+ sead::SafeString evfl_name;
+ evfl::FlowchartContext flowchart_ctx;
+ AutoPlacement* placement;
+ sead::SafeString unit_name;
+ u8 idx;
+ s8 placement_type;
+};
+
+class AutoPlacementFlowMgr {
+ SEAD_SINGLETON_DISPOSER(AutoPlacementFlowMgr)
+public:
+ AutoPlacementFlowMgr();
+ virtual ~AutoPlacementFlowMgr();
+
+ void loadEventFlows();
+
+ AutoPlacementFlowRes* getFlow(const sead::SafeString& actor_name, bool near_flow);
+
+private:
+ sead::Heap* mHeap;
+ sead::SafeArray<AutoPlacementFlowRes, 10> mFlowArray;
+ sead::SafeArray<AutoPlacementFlowRes, 7> mFlowNearArray;
+};
+
+} // namespace uking
diff --git a/src/Game/AutoPlacement/AutoPlacementMgr.cpp b/src/Game/AutoPlacement/AutoPlacementMgr.cpp
new file mode 100644
index 00000000..c73dbfd4
--- /dev/null
+++ b/src/Game/AutoPlacement/AutoPlacementMgr.cpp
@@ -0,0 +1,7 @@
+#include "Game/AutoPlacement/AutoPlacementMgr.h"
+
+namespace uking {
+
+SEAD_SINGLETON_DISPOSER_IMPL(AutoPlacementMgr)
+
+} // namespace uking
diff --git a/src/Game/AutoPlacement/AutoPlacementMgr.h b/src/Game/AutoPlacement/AutoPlacementMgr.h
new file mode 100644
index 00000000..cad8d489
--- /dev/null
+++ b/src/Game/AutoPlacement/AutoPlacementMgr.h
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <heap/seadDisposer.h>
+#include <math/seadVector.h>
+#include <prim/seadDelegate.h>
+#include <prim/seadSafeString.h>
+
+#include "KingSystem/Utils/Types.h"
+
+namespace ksys::act {
+class Actor;
+} // namespace ksys::act
+
+namespace uking {
+
+class AutoPlacementMgr {
+ SEAD_SINGLETON_DISPOSER(AutoPlacementMgr)
+public:
+ AutoPlacementMgr();
+ virtual ~AutoPlacementMgr();
+
+ bool sub_7100659E40(ksys::act::Actor* actor, const sead::SafeString& actor_name, int count,
+ bool is_box);
+ void sub_7100659F94(ksys::act::Actor* actor);
+
+ // 0x0000007100654e44
+ bool threadFn();
+ // 0x0000007100656030
+ bool auto9();
+ // 0x0000007100656d24
+ bool auto2(const sead::SafeString& name, const sead::Vector3f& pos);
+ // 0x0000007100659188
+ bool auto0(const sead::Vector3f& pos, u32 placement_type);
+ // 0x0000007100659350
+ bool auto11(const sead::Vector3f& pos);
+
+ sead::DelegateR<AutoPlacementMgr, bool> mDelegate;
+};
+// KSYS_CHECK_SIZE_NX150(AutoPlacementMgr, 0x189E38);
+
+} // namespace uking
diff --git a/src/Game/AutoPlacement/CMakeLists.txt b/src/Game/AutoPlacement/CMakeLists.txt
new file mode 100644
index 00000000..4e98bbb6
--- /dev/null
+++ b/src/Game/AutoPlacement/CMakeLists.txt
@@ -0,0 +1,8 @@
+target_sources(uking PRIVATE
+ AutoPlacement.cpp
+ AutoPlacement.h
+ AutoPlacementFlowMgr.cpp
+ AutoPlacementFlowMgr.h
+ AutoPlacementMgr.cpp
+ AutoPlacementMgr.h
+)