Compare commits

...

3 Commits

Author SHA1 Message Date
d139e77969 Continued working on GOAP action executor 2026-02-07 17:03:12 +03:00
685b15933a Physics initialization update 2026-02-07 08:31:07 +03:00
a54b042e49 Starting to implement GOAP plan 2026-02-06 22:14:48 +03:00
6 changed files with 314 additions and 22 deletions

View File

@@ -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::PhysicsModule>();
ECS::Physics &ph = ECS::get().ensure<ECS::Physics>();
ph.physics = mJolt;
ECS::modified<ECS::Physics>();
ECS::get()
.observer<ECS::App>("UpdateInputListener")
.event(flecs::OnSet)

View File

@@ -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<Blackboard> *action;
ActionExec(PlanExecData &data,
const goap::BaseAction<Blackboard> *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<struct ActionExec *> 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<Blackboard> {
int node;
WalkToAction(int node, int cost)
@@ -98,12 +180,103 @@ out:
std::vector<goap::BaseAction<Blackboard> *> 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<CharacterBase>()
.mBodyNode
->_getDerivedPosition();
if (position.squaredDistance(targetPosition) >=
radius * radius) {
if (npc.e.is_valid())
npc.e.get<CharacterBase>()
.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<ActionNodeList>()
.dynamicNodes[wtaction->node]
.radius;
targetPosition = ECS::get<ActionNodeList>()
.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<Blackboard> *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<const ActionNodeActions::RunActionNode
*>(action);
if (runaction)
std::cout << "Is Run" << std::endl;
OgreAssert(false, "activate");
}
public:
ActionExecUse(ActionExec::PlanExecData &data,
goap::BaseAction<Blackboard> *action)
: ActionExec(data, action)
{
}
};
ActionNodeActions(int node, const Blackboard &prereq, int cost)
{
OgreAssert(
node < ECS::get<ActionNodeList>().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<Blackboard> *action)
: ActionExec(data, action)
{
}
};
CharacterAIModule::CharacterAIModule(flecs::world &ecs)
{
static std::mutex ecs_mutex;
@@ -376,6 +575,81 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
});
});
});
static std::unordered_map<int, struct PlanExec> plan_exec;
ecs.system<const EngineData, TownNPCs, TownAI>("RunPLAN")
.kind(flecs::OnUpdate)
.each([&](flecs::entity town, const EngineData &eng,
TownNPCs &npcs, TownAI &ai) {
for (const auto &plans : ai.plans) {
if (plan_exec.find(plans.first) !=
plan_exec.end()) {
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;
std::cout << " Plans: " << plans.second.size();
for (const auto &plan : plans.second) {
struct PlanExec pexec;
if (plan.plan.size() == 0)
continue;
std::cout << " Goal: ";
plan.goal->goal.dump_bits();
for (const auto &action : plan.plan) {
ActionExec::PlanExecData data({
npcs.npcs.at(
plans.first),
ai.blackboards.at(
plans.first),
ai.memory.at(
plans.first),
});
// 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()
<< " ";
}
plan_exec[plans.first] = pexec;
break;
}
std::cout << std::endl;
}
});
}
void CharacterAIModule::createAI(flecs::entity town)

View File

@@ -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<GameState>().add(flecs::Singleton);
ecs.import <CharacterModule>();
ecs.import <CharacterModule>();
ecs.import <BoatModule>();
ecs.import <PhysicsModule>();
ecs.import <WaterModule>();
@@ -104,6 +107,7 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
ecs.set<EngineData>({ scnMgr, 0.0f, 5.0f, (int)window->getWidth(),
(int)window->getHeight(), false });
ecs.set<Camera>({ cameraNode, camera, false });
PhysicsModule::configurePhysics();
ecs.add<GameData>();
ecs.add<Input>();
ecs.observer<GameState>("Game_Start_Scen_Startup")
@@ -207,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<RenderWindow>().add(flecs::Singleton);
setup_minimal();
Physics &ph = ECS::get().ensure<Physics>();
ph.physics = new JoltPhysicsWrapper(scnMgr, cameraNode);
ECS::modified<Physics>();
ecs.component<RenderWindow>().add(flecs::Singleton);
ecs.component<EditorSceneSwitch>().add(flecs::Singleton);
ecs.import <CharacterModule>();
ecs.import <BoatModule>();
@@ -261,7 +267,8 @@ void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
#if 0
ecs.set<EditorSceneSwitch>({ 0 });
#endif
ecs.add<GameData>();
PhysicsModule::configurePhysics();
ecs.add<GameData>();
ecs.add<Input>();
ecs.add<WaterSurface>();
ecs.set<Sun>({ nullptr, nullptr, nullptr, nullptr });

View File

@@ -61,15 +61,6 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
ecs.component<CachedMass>();
ecs.import <TerrainModule>();
ecs.import <WaterModule>();
ecs.system<const EngineData, const Camera>("physics_init")
.kind(PhysicsPreUpdate)
.without<Physics>()
.each([&](const EngineData &e, const Camera &c) {
Physics &ph = ECS::get().ensure<Physics>();
ph.physics = new JoltPhysicsWrapper(e.mScnMgr,
c.mCameraNode);
ECS::modified<Physics>();
});
ecs.system<EngineData, Physics>("physics_update")
.kind(PhysicsUpdate)
.each([&](EngineData &e, Physics &ph) {
@@ -633,6 +624,10 @@ void PhysicsModule::setDebugDraw(bool enable)
{
JoltPhysicsWrapper::getSingleton().setDebugDraw(enable);
}
void PhysicsModule::configurePhysics()
{
}
bool WaterBody::isInWater(const JPH::BodyID &id) const
{
flecs::entity e =

View File

@@ -61,6 +61,7 @@ struct PhysicsModule {
const Ogre::Vector3 &endPos,
Ogre::Vector3 &position, JPH::BodyID &id);
static void setDebugDraw(bool enable);
static void configurePhysics();
};
}
#endif

View File

@@ -576,9 +576,9 @@ public:
, object_vs_object_layer_filter{}
, debugDraw(false)
{
// 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();
static int instanceCount = 0;
OgreAssert(instanceCount == 0, "Bad initialisation");
instanceCount++;
// 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.
@@ -1527,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;)