From d139e77969b7279495678f751d14ad67e8dcc1a4 Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Sat, 7 Feb 2026 17:03:12 +0300 Subject: [PATCH] Continued working on GOAP action executor --- Game.cpp | 13 +- src/gamedata/CharacterAIModule.cpp | 333 +++++++++++++++++++++-------- src/gamedata/GameData.cpp | 15 +- src/gamedata/PhysicsModule.cpp | 5 - src/physics/physics.cpp | 9 +- 5 files changed, 269 insertions(+), 106 deletions(-) diff --git a/Game.cpp b/Game.cpp index 1a4656c..6afc0f2 100644 --- a/Game.cpp +++ b/Game.cpp @@ -18,6 +18,8 @@ #include "GUIModuleCommon.h" #include "AppModule.h" #include "GUIModule.h" +#include "PhysicsModule.h" +#include "physics.h" #include "sound.h" class App; class SkyRenderer : public Ogre::SceneManager::Listener { @@ -626,10 +628,13 @@ end: void setupInput() { } + JoltPhysicsWrapper *mJolt; void createContent() { int i; - sky = new SkyBoxRenderer(getSceneManager()); + mJolt = new JoltPhysicsWrapper(mScnMgr, mCameraNode); + + sky = new SkyBoxRenderer(getSceneManager()); bool drawFirst = true; uint8_t renderQueue = drawFirst ? Ogre::RENDER_QUEUE_SKIES_EARLY : @@ -651,6 +656,12 @@ end: ECS::setupExteriorScene(mScnMgr, /*mDynWorld.get(), */ mCameraNode, mCamera, getRenderWindow()); + + ECS::get().import (); + ECS::Physics &ph = ECS::get().ensure(); + ph.physics = mJolt; + ECS::modified(); + ECS::get() .observer("UpdateInputListener") .event(flecs::OnSet) diff --git a/src/gamedata/CharacterAIModule.cpp b/src/gamedata/CharacterAIModule.cpp index 4806c21..693924d 100644 --- a/src/gamedata/CharacterAIModule.cpp +++ b/src/gamedata/CharacterAIModule.cpp @@ -9,7 +9,89 @@ #include "CharacterAIModule.h" namespace ECS { -class ActionNodeActions { +struct ActionExec { + struct PlanExecData { + TownNPCs::NPCData &npc; + Blackboard &bb; + nlohmann::json &memory; + }; + enum { OK = 0, BUSY, ERROR }; + TownNPCs::NPCData &npc; + Blackboard &bb; + nlohmann::json &memory; + bool complete; + bool active; + const goap::BaseAction *action; + ActionExec(PlanExecData &data, + const goap::BaseAction *action) + : npc(data.npc) + , bb(data.bb) + , memory(data.memory) + , complete(false) + , active(false) + , action(action) + { + } + ActionExec(const ActionExec &other) + : npc(other.npc) + , bb(other.bb) + , memory(other.memory) + , complete(other.complete) + , active(other.active) + , action(other.action) + { + } + ActionExec &operator=(const ActionExec &other) + { + npc = other.npc; + bb = other.bb; + memory = other.memory; + complete = other.complete; + active = other.active; + action = other.action; + return *this; + } + +private: + virtual int update(float delta) = 0; + virtual void finish(int result) = 0; + virtual void activate() = 0; + +public: + int operator()(float delta) + { + if (!active) + activate(); + int ret = update(delta); + if (ret != BUSY) + finish(ret); + return ret; + } +}; +struct PlanExec { + enum { OK = 0, BUSY, ERROR }; + std::vector action_exec; + int index; + PlanExec() + : index(-1) + { + } + int operator()(float delta) + { + if (index == -1) + index = 0; + int rc = (*action_exec[index])(delta); + if (rc == ActionExec::ERROR) + return ERROR; + if (action_exec[index]->complete) + index++; + if (index >= action_exec.size()) + return OK; + return BUSY; + } +}; + +struct ActionNodeActions { struct WalkToAction : public goap::BaseAction { int node; WalkToAction(int node, int cost) @@ -98,12 +180,103 @@ out: std::vector *> m_actions; public: + struct ActionExecWalk : ActionExec { + private: + Ogre::Vector3 targetPosition; + float radius; + int update(float delta) override + { + if (npc.e.is_valid()) { + Ogre::Vector3 position = + npc.e.get() + .mBodyNode + ->_getDerivedPosition(); + if (position.squaredDistance(targetPosition) >= + radius * radius) { + if (npc.e.is_valid()) + npc.e.get() + .mBodyNode + ->_setDerivedPosition( + targetPosition); + npc.position = targetPosition; + return BUSY; + } + } else { + if (npc.position.squaredDistance( + targetPosition) >= + radius * radius) { + npc.position = targetPosition; + return BUSY; + } + } + return OK; + } + void finish(int rc) override + { + if (rc == OK) + bb.apply(action->effects); + } + void activate() override + { + const ActionNodeActions::WalkToAction *wtaction = + static_cast< + const ActionNodeActions::WalkToAction *>( + action); + radius = ECS::get() + .dynamicNodes[wtaction->node] + .radius; + targetPosition = ECS::get() + .dynamicNodes[wtaction->node] + .position; + Ogre::Vector3 direction = + (targetPosition - npc.position).normalisedCopy(); + targetPosition -= direction * radius; + std::cout << action->get_name(); + } + + public: + ActionExecWalk(ActionExec::PlanExecData &data, + goap::BaseAction *action) + : ActionExec(data, action) + { + } + }; + struct ActionExecUse : ActionExec { + private: + int update(float delta) override + { + return OK; + } + void finish(int rc) override + { + if (rc == OK) + bb.apply(action->effects); + } + void activate() override + { + std::cout << action->get_name(); + const ActionNodeActions::RunActionNode *runaction = + static_cast(action); + if (runaction) + std::cout << "Is Run" << std::endl; + OgreAssert(false, "activate"); + } + + public: + ActionExecUse(ActionExec::PlanExecData &data, + goap::BaseAction *action) + : ActionExec(data, action) + { + } + }; ActionNodeActions(int node, const Blackboard &prereq, int cost) { OgreAssert( node < ECS::get().dynamicNodes.size(), "bad node " + Ogre::StringConverter::toString(node)); - m_actions.push_back(OGRE_NEW WalkToAction(node, 10000)); + auto paction = OGRE_NEW WalkToAction(node, 10000); + m_actions.push_back(paction); nlohmann::json jactionPrereq = nlohmann::json::object(); nlohmann::json jactionEffect = nlohmann::json::object(); jactionPrereq["at_object"] = 1; @@ -176,6 +349,32 @@ public: return m_actions; } }; +struct ActionExecCommon : ActionExec { +private: + int update(float delta) override + { + std::cout << "running: " << action->get_name() << std::endl; + return OK; + } + void finish(int rc) override + { + if (rc == OK) + bb.apply(action->effects); + std::cout << "finish: " << action->get_name() << std::endl; + } + void activate() override + { + std::cout << action->get_name(); + std::cout << "!"; + } + +public: + ActionExecCommon(ActionExec::PlanExecData &data, + goap::BaseAction *action) + : ActionExec(data, action) + { + } +}; CharacterAIModule::CharacterAIModule(flecs::world &ecs) { static std::mutex ecs_mutex; @@ -376,92 +575,6 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs) }); }); }); - struct ActionExec { - enum { OK = 0, BUSY, ERROR }; - TownNPCs::NPCData &npc; - Blackboard &bb; - nlohmann::json &memory; - bool complete; - bool active; - const goap::BaseAction *action; - ActionExec(TownNPCs::NPCData &npc, Blackboard &bb, - nlohmann::json &memory, - const goap::BaseAction *action) - : npc(npc) - , bb(bb) - , memory(memory) - , complete(false) - , active(false) - , action(action) - { - } - ActionExec(const ActionExec &other) - : npc(other.npc) - , bb(other.bb) - , memory(other.memory) - , complete(other.complete) - , active(other.active) - , action(other.action) - { - } - ActionExec &operator=(const ActionExec &other) - { - npc = other.npc; - bb = other.bb; - memory = other.memory; - complete = other.complete; - active = other.active; - action = other.action; - return *this; - } - - private: - int update(float delta) - { - return OK; - } - void finish(int result) - { - } - void activate() - { - std::cout << action->get_name(); - OgreAssert(false, "activate"); - } - - public: - int operator()(float delta) - { - if (!active) - activate(); - int ret = update(delta); - if (ret != BUSY) - finish(ret); - return ret; - } - }; - struct PlanExec { - enum { OK = 0, BUSY, ERROR }; - std::vector action_exec; - int index; - PlanExec() - : index(-1) - { - } - int operator()(float delta) - { - if (index == -1) - index = 0; - int rc = action_exec[index](delta); - if (rc == ActionExec::ERROR) - return ERROR; - if (action_exec[index].complete) - index++; - if (index >= action_exec.size()) - return OK; - return BUSY; - } - }; static std::unordered_map plan_exec; ecs.system("RunPLAN") .kind(flecs::OnUpdate) @@ -470,7 +583,14 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs) for (const auto &plans : ai.plans) { if (plan_exec.find(plans.first) != plan_exec.end()) { - plan_exec[plans.first](eng.delta); + int rc = plan_exec[plans.first]( + eng.delta); + if (rc != PlanExec::BUSY) { + plan_exec.erase(plans.first); + ai.plans[plans.first].erase( + ai.plans[plans.first] + .begin()); + } continue; } std::cout << "NPC: " << plans.first; @@ -482,14 +602,45 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs) std::cout << " Goal: "; plan.goal->goal.dump_bits(); for (const auto &action : plan.plan) { - pexec.action_exec.emplace_back( + ActionExec::PlanExecData data({ npcs.npcs.at( plans.first), ai.blackboards.at( plans.first), ai.memory.at( plans.first), - action); + + }); + // TODO: executor factory is needed + if (action->get_name().substr( + 0, 4) == "Walk") { + ActionExec *e = OGRE_NEW + ActionNodeActions::ActionExecWalk( + data, + action); + pexec.action_exec + .push_back(e); + } else if (action->get_name() + .substr(0, + 4) == + "Use(") { + ActionExec *e = OGRE_NEW + ActionNodeActions::ActionExecUse( + data, + action); + pexec.action_exec + .push_back(e); + } else { + std::cout + << action->get_name() + << " "; + ActionExec *e = OGRE_NEW + ActionExecCommon( + data, + action); + pexec.action_exec + .push_back(e); + } std::cout << action->get_name() << " "; } diff --git a/src/gamedata/GameData.cpp b/src/gamedata/GameData.cpp index 1167068..7c1a581 100644 --- a/src/gamedata/GameData.cpp +++ b/src/gamedata/GameData.cpp @@ -27,6 +27,7 @@ #include "CharacterAIModule.h" #include "QuestModule.h" #include "world-build.h" +#include "physics.h" namespace ECS { @@ -50,9 +51,11 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode, Ogre::Camera *camera, Ogre::RenderWindow *window) { std::cout << "Setup GameData\n"; - setup_minimal(); + setup_minimal(); + + std::cout << "Setup Editor\n"; ecs.component().add(flecs::Singleton); - ecs.import (); + ecs.import (); ecs.import (); ecs.import (); ecs.import (); @@ -208,9 +211,11 @@ void setupInventoryScene(Ogre::SceneManager *scnMgr, void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode, Ogre::Camera *camera, Ogre::RenderWindow *window) { - std::cout << "Setup Editor\n"; - setup_minimal(); - ecs.component().add(flecs::Singleton); + setup_minimal(); + Physics &ph = ECS::get().ensure(); + ph.physics = new JoltPhysicsWrapper(scnMgr, cameraNode); + ECS::modified(); + ecs.component().add(flecs::Singleton); ecs.component().add(flecs::Singleton); ecs.import (); ecs.import (); diff --git a/src/gamedata/PhysicsModule.cpp b/src/gamedata/PhysicsModule.cpp index 2dc3755..50aa030 100644 --- a/src/gamedata/PhysicsModule.cpp +++ b/src/gamedata/PhysicsModule.cpp @@ -627,11 +627,6 @@ void PhysicsModule::setDebugDraw(bool enable) void PhysicsModule::configurePhysics() { - Physics &ph = ECS::get().ensure(); - const EngineData &e = ECS::get(); - const Camera &c = ECS::get(); - ph.physics = new JoltPhysicsWrapper(e.mScnMgr, c.mCameraNode); - ECS::modified(); } bool WaterBody::isInWater(const JPH::BodyID &id) const { diff --git a/src/physics/physics.cpp b/src/physics/physics.cpp index c6d5084..33780f7 100644 --- a/src/physics/physics.cpp +++ b/src/physics/physics.cpp @@ -579,9 +579,6 @@ public: static int instanceCount = 0; OgreAssert(instanceCount == 0, "Bad initialisation"); instanceCount++; - // Create a factory, this class is responsible for creating instances of classes based on their name or hash and is mainly used for deserialization of saved data. - // It is not directly used in this example but still required. - JPH::Factory::sInstance = new JPH::Factory(); // Register all physics types with the factory and install their collision handlers with the CollisionDispatch class. // If you have your own custom shape types you probably need to register their handlers with the CollisionDispatch before calling this function. @@ -1530,7 +1527,11 @@ JoltPhysicsWrapper::JoltPhysicsWrapper(Ogre::SceneManager *scnMgr, // This needs to be done before any other Jolt function is called. JPH::RegisterDefaultAllocator(); - // Install trace and assert callbacks + // Create a factory, this class is responsible for creating instances of classes based on their name or hash and is mainly used for deserialization of saved data. + // It is not directly used in this example but still required. + JPH::Factory::sInstance = new JPH::Factory(); + + // Install trace and assert callbacks JPH::Trace = TraceImpl; JPH_IF_ENABLE_ASSERTS(JPH::AssertFailed = AssertFailedImpl;)