Compare commits
2 Commits
4cf0ea5321
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| c860152f9b | |||
| da4c1fee0e |
4
Game.cpp
4
Game.cpp
@@ -453,7 +453,9 @@ public:
|
||||
mDbgDraw->setDebugMode(mDbgDraw->getDebugMode() |
|
||||
btIDebugDraw::DBG_DrawContactPoints);
|
||||
#endif
|
||||
}
|
||||
Ogre::LogManager::getSingleton().setMinLogLevel(
|
||||
Ogre::LML_CRITICAL);
|
||||
}
|
||||
Ogre::SceneManager *getSceneManager()
|
||||
{
|
||||
return mScnMgr;
|
||||
|
||||
@@ -661,16 +661,29 @@ setup_action_handler("talk", {
|
||||
activate = function(this)
|
||||
local book = narrator.parse_file('stories.talk')
|
||||
this.story = narrator.init_story(book)
|
||||
local crash_bind = function()
|
||||
crash()
|
||||
local get_crash_bind = function(this)
|
||||
local crash_bind = function()
|
||||
print("variables")
|
||||
print(dump(this.story.variables))
|
||||
crash()
|
||||
end
|
||||
return crash_bind
|
||||
end
|
||||
this.story:bind('crash', crash_bind)
|
||||
this.story:begin()
|
||||
this:narration_update()
|
||||
this.story:bind('crash', get_crash_bind(this))
|
||||
local props = this._get_properties()
|
||||
print(props)
|
||||
local goals = this._get_goals()
|
||||
print("node activated")
|
||||
-- print(dump(goals))
|
||||
local json_data = json.decode(props)
|
||||
print(dump(json_data))
|
||||
for i, v in ipairs(goals) do
|
||||
print("Goal: ", i)
|
||||
local goal_data = json.decode(v)
|
||||
print(dump(goal_data))
|
||||
end
|
||||
this.story:begin()
|
||||
this:narration_update()
|
||||
get_crash_bind(this)()
|
||||
crash()
|
||||
end,
|
||||
event = function(this, event)
|
||||
|
||||
@@ -432,7 +432,9 @@ public:
|
||||
createContent();
|
||||
std::cout << "Setup done"
|
||||
<< "\n";
|
||||
}
|
||||
Ogre::LogManager::getSingleton().setMinLogLevel(
|
||||
Ogre::LML_TRIVIAL);
|
||||
}
|
||||
Ogre::Timer mTerrainUpd;
|
||||
// TODO: implement rough water level calculation
|
||||
float getWaterLevel(const Ogre::Vector3 &position)
|
||||
|
||||
@@ -9,7 +9,7 @@ add_library(GameData STATIC GameData.cpp CharacterModule.cpp WaterModule.cpp Sun
|
||||
GUIModule.cpp EditorGUIModule.cpp LuaData.cpp WorldMapModule.cpp BoatModule.cpp EventTriggerModule.cpp
|
||||
CharacterAnimationModule.cpp PhysicsModule.cpp EventModule.cpp CharacterManagerModule.cpp
|
||||
VehicleManagerModule.cpp AppModule.cpp StaticGeometryModule.cpp SmartObject.cpp SlotsModule.cpp
|
||||
PlayerActionModule.cpp goap.cpp)
|
||||
PlayerActionModule.cpp CharacterAIModule.cpp goap.cpp)
|
||||
target_link_libraries(GameData PUBLIC
|
||||
lua
|
||||
flecs::flecs_static
|
||||
@@ -18,5 +18,5 @@ target_link_libraries(GameData PUBLIC
|
||||
OgreBites
|
||||
OgrePaging OgreTerrain OgreOverlay OgreProcedural::OgreProcedural items
|
||||
PRIVATE sceneloader world-build physics editor)
|
||||
target_include_directories(GameData PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${BULLET_INCLUDE_DIR} ../luaaa)
|
||||
target_include_directories(GameData PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${BULLET_INCLUDE_DIR} ../luaaa ../aitoolkit/include)
|
||||
target_compile_definitions(GameData PRIVATE FLECS_CPP_NO_AUTO_REGISTRATION)
|
||||
|
||||
887
src/gamedata/CharacterAIModule.cpp
Normal file
887
src/gamedata/CharacterAIModule.cpp
Normal file
@@ -0,0 +1,887 @@
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <Ogre.h>
|
||||
#include "goap.h"
|
||||
#include "CharacterManagerModule.h"
|
||||
#include "PlayerActionModule.h"
|
||||
#include "CharacterModule.h"
|
||||
#include "items.h"
|
||||
#include "CharacterAIModule.h"
|
||||
namespace ECS
|
||||
{
|
||||
class ActionNodeActions {
|
||||
struct WalkToAction : public goap::BaseAction<Blackboard> {
|
||||
int node;
|
||||
WalkToAction(int node, int cost)
|
||||
: goap::BaseAction<Blackboard>(
|
||||
"WalkTo(" +
|
||||
Ogre::StringConverter::toString(
|
||||
node) +
|
||||
")",
|
||||
{ { { "at_object", 0 } } },
|
||||
{ { { "at_object", 1 } } }, cost)
|
||||
, node(node)
|
||||
{
|
||||
}
|
||||
bool can_run(const Blackboard &state,
|
||||
bool debug = false) override
|
||||
{
|
||||
return (state.distance_to(prereq) == 0);
|
||||
}
|
||||
void _plan_effects(Blackboard &state) override
|
||||
{
|
||||
const ActionNodeList &alist =
|
||||
ECS::get<ActionNodeList>();
|
||||
state.apply(effects);
|
||||
// node position can change in case of character
|
||||
const Ogre::Vector3 &nodePosition =
|
||||
alist.dynamicNodes[node].position;
|
||||
state.setPosition(nodePosition);
|
||||
}
|
||||
int get_cost(const Blackboard &bb) const override
|
||||
{
|
||||
int ret = m_cost;
|
||||
if (!bb.town.is_valid()) {
|
||||
ret += 1000000;
|
||||
goto out;
|
||||
}
|
||||
|
||||
{
|
||||
const ActionNodeList &alist =
|
||||
ECS::get<ActionNodeList>();
|
||||
// const TownNPCs &npcs = bb.town.get<TownNPCs>();
|
||||
// const TownAI &ai = bb.town.get<TownAI>();
|
||||
const Ogre::Vector3 &nodePosition =
|
||||
alist.dynamicNodes[node].position;
|
||||
// flecs::entity e = npcs.npcs.at(bb.index).e;
|
||||
// bool validActive = e.is_valid() &&
|
||||
// e.has<CharacterBase>();
|
||||
const Ogre::Vector3 &npcPosition =
|
||||
bb.getPosition();
|
||||
float dist = npcPosition.squaredDistance(
|
||||
nodePosition);
|
||||
ret += (int)Ogre::Math::Ceil(dist);
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
struct RunActionNode : public goap::BaseAction<Blackboard> {
|
||||
int node;
|
||||
float radius;
|
||||
Ogre::String action;
|
||||
RunActionNode(int node, const Ogre::String &action,
|
||||
const Blackboard &prereq,
|
||||
const Blackboard &effects, int cost)
|
||||
: goap::BaseAction<Blackboard>(
|
||||
"Use(" + action + "," +
|
||||
Ogre::StringConverter::toString(
|
||||
node) +
|
||||
")",
|
||||
prereq, effects, cost)
|
||||
, node(node)
|
||||
, action(action)
|
||||
{
|
||||
const ActionNodeList &alist =
|
||||
ECS::get<ActionNodeList>();
|
||||
radius = alist.dynamicNodes[node].radius;
|
||||
}
|
||||
bool can_run(const Blackboard &state,
|
||||
bool debug = false) override
|
||||
{
|
||||
bool pre = (state.distance_to(prereq) == 0);
|
||||
if (!pre)
|
||||
return pre;
|
||||
const ActionNodeList &alist =
|
||||
ECS::get<ActionNodeList>();
|
||||
const Ogre::Vector3 &nodePosition =
|
||||
alist.dynamicNodes[node].position;
|
||||
const Ogre::Vector3 &npcPosition = state.getPosition();
|
||||
return (npcPosition.squaredDistance(nodePosition) <
|
||||
radius * radius);
|
||||
}
|
||||
};
|
||||
std::vector<goap::BaseAction<Blackboard> *> m_actions;
|
||||
|
||||
public:
|
||||
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));
|
||||
nlohmann::json jactionPrereq = nlohmann::json::object();
|
||||
nlohmann::json jactionEffect = nlohmann::json::object();
|
||||
jactionPrereq["at_object"] = 1;
|
||||
const nlohmann::json props =
|
||||
ECS::get<ActionNodeList>().dynamicNodes[node].props;
|
||||
OgreAssert(!props.is_null(),
|
||||
"bad node " + Ogre::StringConverter::toString(node));
|
||||
Ogre::String prefix = "goap_prereq_";
|
||||
for (auto it = props.begin(); it != props.end(); it++) {
|
||||
if (it.key().substr(0, prefix.length()) == prefix) {
|
||||
Ogre::String key =
|
||||
it.key().substr(prefix.length());
|
||||
jactionPrereq[key] = it.value();
|
||||
}
|
||||
}
|
||||
Ogre::String prefix2 = "goap_effect_";
|
||||
for (auto it = props.begin(); it != props.end(); it++) {
|
||||
if (it.key().substr(0, prefix2.length()) == prefix2) {
|
||||
Ogre::String key =
|
||||
it.key().substr(prefix2.length());
|
||||
jactionPrereq[key] = it.value();
|
||||
}
|
||||
}
|
||||
OgreAssert(props.find("action") != props.end(),
|
||||
"bad action" + props.dump(4));
|
||||
const Ogre::String &action =
|
||||
props["action"].get<Ogre::String>();
|
||||
Ogre::String effectName = "";
|
||||
#if 0
|
||||
if (action == "sit") {
|
||||
const Ogre::String &nodeName =
|
||||
props["name"].get<Ogre::String>();
|
||||
effectName = "is_" + nodeName + "_seated";
|
||||
} else if (action == "use") {
|
||||
const Ogre::String &nodeName =
|
||||
props["name"].get<Ogre::String>();
|
||||
effectName = "is_" + nodeName + "_used";
|
||||
}
|
||||
#endif
|
||||
// const Ogre::String &nodeName =
|
||||
// props["name"].get<Ogre::String>();
|
||||
Ogre::String nodeID = Ogre::StringConverter::toString(node);
|
||||
effectName = "is_used";
|
||||
if (effectName.length() > 0) {
|
||||
jactionPrereq[effectName] = 0;
|
||||
jactionEffect[effectName] = 1;
|
||||
}
|
||||
// FIXME: add this to Blender goap_prereq_ and goap_effect_ variables
|
||||
if (action == "sit") {
|
||||
jactionPrereq["is_seated"] = 0;
|
||||
jactionEffect["is_seated"] = 1;
|
||||
}
|
||||
Blackboard actionPrereq({ jactionPrereq });
|
||||
Blackboard actionEffect({ jactionEffect });
|
||||
if (!prereq.stats.is_null())
|
||||
actionPrereq.apply(prereq);
|
||||
m_actions.push_back(OGRE_NEW RunActionNode(
|
||||
node, action, actionPrereq, actionEffect, cost));
|
||||
if (effectName == "") {
|
||||
std::cout << props.dump(4) << std::endl;
|
||||
std::cout << "Prereq" << std::endl;
|
||||
std::cout << actionPrereq.stats.dump(4) << std::endl;
|
||||
std::cout << "Effect" << std::endl;
|
||||
std::cout << actionEffect.stats.dump(4) << std::endl;
|
||||
OgreAssert(false, "action");
|
||||
}
|
||||
}
|
||||
std::vector<goap::BaseAction<Blackboard> *> getActions() const
|
||||
{
|
||||
return m_actions;
|
||||
}
|
||||
};
|
||||
CharacterAIModule::CharacterAIModule(flecs::world &ecs)
|
||||
{
|
||||
static std::mutex ecs_mutex;
|
||||
ecs.module<CharacterAIModule>();
|
||||
ecs.import <CharacterManagerModule>();
|
||||
ecs.component<Blackboard>();
|
||||
ecs.component<TownAI>().on_add([](flecs::entity e, TownAI &ai) {
|
||||
std::lock_guard<std::mutex> lock(ecs_mutex);
|
||||
ai.mutex = std::make_shared<std::mutex>();
|
||||
std::lock_guard<std::mutex> lock2(*ai.mutex);
|
||||
ai.goals.push_back(
|
||||
{ "HealthGoal",
|
||||
{ nlohmann::json::object({ { "healthy", 1 } }) } });
|
||||
ai.goals.push_back(
|
||||
{ "NotHungryGoal", { { { "hungry", 0 } } } });
|
||||
ai.goals.push_back(
|
||||
{ "NotThirstyGoal", { { { "thirsty", 0 } } } });
|
||||
ai.goals.push_back(
|
||||
{ "SatisfyToiletNeedGoal", { { { "toilet", 0 } } } });
|
||||
struct ActionData {
|
||||
Ogre::String name;
|
||||
Blackboard prereq;
|
||||
Blackboard effects;
|
||||
int cost;
|
||||
};
|
||||
struct ActionData actionData[] = {
|
||||
#if 0
|
||||
{ "WalkTo",
|
||||
{ { { "at_object", 0 } } },
|
||||
{ { { "at_object", 1 } } },
|
||||
1000 },
|
||||
{ "Sit#",
|
||||
{ { { "at_object", 1 }, { "is_seated", 0 } } },
|
||||
{ { { "is_seated", 1 } } },
|
||||
10 },
|
||||
#endif
|
||||
{ "EatFoodSeated",
|
||||
{ { { "have_food", 1 },
|
||||
{ "is_seated", 1 },
|
||||
{ "hungry", 1 } } },
|
||||
{ { { "healthy", 1 }, { "hungry", 0 } } },
|
||||
10 },
|
||||
{ "EatFood",
|
||||
{ { { "have_food", 1 }, { "hungry", 1 } } },
|
||||
{ { { "healthy", 1 }, { "hungry", 0 } } },
|
||||
2000 },
|
||||
{ "DrinkWaterSeated",
|
||||
{ { { "have_water", 1 },
|
||||
{ "is_seated", 1 },
|
||||
{ "thirsty", 1 } } },
|
||||
{ { { "thirsty", 0 } } },
|
||||
10 },
|
||||
#if 0
|
||||
{ "DrinkWater",
|
||||
{ { { "have_water", 1 }, { "thirsty", 1 } } },
|
||||
{ { { "thirsty", 0 } } },
|
||||
2000 },
|
||||
#endif
|
||||
{ "EatMedicine",
|
||||
{ { { "have_medicine", 1 }, { "healty", 0 } } },
|
||||
{ { { "healthy", 1 } } },
|
||||
100 },
|
||||
{ "UseToilet",
|
||||
{ { { "toilet", 1 } } },
|
||||
{ { { "toilet", 0 } } },
|
||||
100 },
|
||||
{ "GetFood",
|
||||
{ { { "have_food", 0 } } },
|
||||
{ { { "have_food", 1 } } },
|
||||
1000 },
|
||||
{ "GetWater",
|
||||
{ { { "have_water", 0 } } },
|
||||
{ { { "have_water", 1 } } },
|
||||
1000 },
|
||||
{ "GetMedicine",
|
||||
{ { { "have_medicine", 0 } } },
|
||||
{ { { "have_medicine", 1 } } },
|
||||
1000 },
|
||||
};
|
||||
for (const auto &adata : actionData)
|
||||
ai.actions.push_back(
|
||||
OGRE_NEW goap::BaseAction<Blackboard>(
|
||||
adata.name, adata.prereq, adata.effects,
|
||||
adata.cost));
|
||||
ai.planner = std::make_shared<goap::BasePlanner<
|
||||
Blackboard, goap::BaseAction<Blackboard> > >();
|
||||
});
|
||||
ecs.system<TownAI, TownNPCs>("CreateBlackboards")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([this](flecs::entity town, TownAI &ai,
|
||||
const TownNPCs &npcs) {
|
||||
Ogre::Root::getSingleton().getWorkQueue()->addTask(
|
||||
[this, town, npcs, &ai]() {
|
||||
Ogre::Root::getSingleton()
|
||||
.getWorkQueue()
|
||||
->addMainThreadTask([this, town,
|
||||
npcs,
|
||||
&ai]() {
|
||||
std::lock_guard<
|
||||
std::mutex>
|
||||
lock(ecs_mutex);
|
||||
createBlackboards(
|
||||
town, npcs, ai);
|
||||
});
|
||||
});
|
||||
});
|
||||
ecs.system<ActionNodeList, TownAI, TownNPCs>("UpdateDynamicActions")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([](flecs::entity e, ActionNodeList &alist, TownAI &ai,
|
||||
TownNPCs &npcs) {
|
||||
std::lock_guard<std::mutex> lock(ecs_mutex);
|
||||
if (ai.nodeActions.size() > 0)
|
||||
return;
|
||||
if (alist.dynamicNodes.size() == 0)
|
||||
ECS::get_mut<ActionNodeList>()
|
||||
.updateDynamicNodes();
|
||||
OgreAssert(alist.dynamicNodes.size() > 0,
|
||||
"bad dynamic nodes");
|
||||
int nodeIndex;
|
||||
for (nodeIndex = 0;
|
||||
nodeIndex < alist.dynamicNodes.size();
|
||||
nodeIndex++) {
|
||||
ActionNodeActions aactions(
|
||||
nodeIndex,
|
||||
Blackboard(
|
||||
{ nlohmann::json::object() }),
|
||||
10);
|
||||
ai.nodeActions[nodeIndex] =
|
||||
aactions.getActions();
|
||||
OgreAssert(ai.nodeActions[nodeIndex].size() > 0,
|
||||
"bad action count");
|
||||
}
|
||||
OgreAssert(ai.nodeActions.size() > 0,
|
||||
"no dynamic actions?");
|
||||
});
|
||||
ecs.system<ActionNodeList, TownAI, TownNPCs>("UpdateDynamicNodes")
|
||||
.kind(flecs::OnUpdate)
|
||||
.interval(0.1f)
|
||||
.each([this](flecs::entity town, ActionNodeList &alist,
|
||||
TownAI &ai, TownNPCs &npcs) {
|
||||
std::lock_guard<std::mutex> lock(ecs_mutex);
|
||||
ECS::get_mut<ActionNodeList>().updateDynamicNodes();
|
||||
});
|
||||
ecs.system<TownNPCs>("UpdateNPCPositions")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([](flecs::entity e, TownNPCs &npcs) {
|
||||
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end();
|
||||
it++) {
|
||||
auto &npc = npcs.npcs.at(it->first);
|
||||
if (npc.e.is_valid() &&
|
||||
npc.e.has<CharacterBase>())
|
||||
npc.position =
|
||||
npc.e.get<CharacterBase>()
|
||||
.mBodyNode
|
||||
->_getDerivedPosition();
|
||||
}
|
||||
});
|
||||
ecs.system<ActionNodeList, TownAI, TownNPCs>("UpdateBlackboards")
|
||||
.kind(flecs::OnUpdate)
|
||||
.interval(0.1f)
|
||||
.each([this](flecs::entity town, ActionNodeList &alist,
|
||||
TownAI &ai, const TownNPCs &npcs) {
|
||||
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end();
|
||||
it++) {
|
||||
const auto &npc = npcs.npcs.at(it->first);
|
||||
if (ai.blackboards.find(it->first) ==
|
||||
ai.blackboards.end())
|
||||
continue;
|
||||
ai.blackboards.at(it->first).setPosition(
|
||||
npc.position);
|
||||
}
|
||||
Ogre::Root::getSingleton().getWorkQueue()->addTask(
|
||||
[this, town, &alist, npcs, &ai]() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(
|
||||
ecs_mutex);
|
||||
|
||||
alist.build();
|
||||
updateBlackboardsBits(
|
||||
town, alist, npcs, ai);
|
||||
updateBlackboards(town, alist,
|
||||
npcs, ai);
|
||||
}
|
||||
Ogre::Root::getSingleton()
|
||||
.getWorkQueue()
|
||||
->addMainThreadTask([this, town,
|
||||
&alist]() {
|
||||
town.modified<TownAI>();
|
||||
ECS::modified<
|
||||
ActionNodeList>();
|
||||
});
|
||||
});
|
||||
});
|
||||
ecs.system<TownAI, TownNPCs>("PlanAI")
|
||||
.kind(flecs::OnUpdate)
|
||||
.interval(1.0f)
|
||||
.each([&](flecs::entity town, TownAI &ai,
|
||||
const TownNPCs &npcs) {
|
||||
Ogre::Root::getSingleton().getWorkQueue()->addTask(
|
||||
[this, town, npcs, &ai]() {
|
||||
std::lock_guard<std::mutex> lock(
|
||||
ecs_mutex);
|
||||
buildPlans(town, npcs, ai);
|
||||
Ogre::Root::getSingleton()
|
||||
.getWorkQueue()
|
||||
->addMainThreadTask([this,
|
||||
town]() {
|
||||
town.modified<TownAI>();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void CharacterAIModule::createAI(flecs::entity town)
|
||||
{
|
||||
town.add<TownAI>();
|
||||
}
|
||||
|
||||
void CharacterAIModule::buildPlans(flecs::entity town, const TownNPCs &npcs,
|
||||
TownAI &ai)
|
||||
{
|
||||
OgreAssert(town.is_valid(), "Bad town entity");
|
||||
std::lock_guard<std::mutex> lock(*ai.mutex);
|
||||
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) {
|
||||
if (ai.blackboards.find(it->first) == ai.blackboards.end())
|
||||
continue;
|
||||
auto &bb = ai.blackboards.at(it->first);
|
||||
/* if there are plans, skip until these get discarded */
|
||||
if (ai.plans.find(it->first) != ai.plans.end() &&
|
||||
ai.plans.at(it->first).size() > 0)
|
||||
continue;
|
||||
ai.plans[it->first] = {};
|
||||
for (const auto &goal : ai.goals) {
|
||||
if (goal.is_reached(bb))
|
||||
continue;
|
||||
#if 0
|
||||
std::cout << "blackboard: "
|
||||
<< bb.stats.dump(4)
|
||||
<< std::endl;
|
||||
std::cout << "goal: "
|
||||
<< goal.goal.stats.dump(4)
|
||||
<< std::endl;
|
||||
#endif
|
||||
std::vector<goap::BaseAction<Blackboard> *> path;
|
||||
int actionCount =
|
||||
ai.blackboards.at(it->first).getActionsCount();
|
||||
auto actionsData =
|
||||
ai.blackboards.at(it->first).getActionsData();
|
||||
path.resize(actionCount * actionCount);
|
||||
int path_length = ai.planner->plan(
|
||||
bb, goal, actionsData, actionCount, path.data(),
|
||||
path.size());
|
||||
#if 0
|
||||
std::cout << "Actions: " << std::endl;
|
||||
for (auto &action : actions) {
|
||||
std::cout << "name: "
|
||||
<< action->get_name()
|
||||
<< std::endl;
|
||||
std::cout
|
||||
<< "\tprereq:\n"
|
||||
<< action->prereq.stats
|
||||
.dump(4)
|
||||
<< std::endl;
|
||||
std::cout
|
||||
<< "\teffects:\n"
|
||||
<< action->effects.stats
|
||||
.dump(4)
|
||||
<< std::endl;
|
||||
}
|
||||
#endif
|
||||
#if 1
|
||||
std::cout << bb.index << " ";
|
||||
std::cout << "Goal: " << goal.get_name();
|
||||
std::cout << std::endl;
|
||||
std::cout << "Path: ";
|
||||
int count = 0;
|
||||
if (path_length < 0) {
|
||||
std::cout << "Bad plan " << path_length
|
||||
<< std::endl;
|
||||
}
|
||||
for (auto &action : path) {
|
||||
if (count >= path_length)
|
||||
break;
|
||||
OgreAssert(action, "No action");
|
||||
std::cout << action->get_name();
|
||||
if (count < path_length - 1)
|
||||
std::cout << ", ";
|
||||
count++;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
std::cout << path_length << std::endl;
|
||||
// OgreAssert(path_length == 0,
|
||||
// "planning");
|
||||
#endif
|
||||
if (path_length > 0) {
|
||||
struct TownAI::Plan plan;
|
||||
plan.goal = &goal;
|
||||
plan.plan.insert(plan.plan.end(), path.begin(),
|
||||
path.begin() + path_length);
|
||||
ai.plans[it->first].push_back(plan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterAIModule::createBlackboards(flecs::entity town,
|
||||
const TownNPCs &npcs, TownAI &ai)
|
||||
{
|
||||
OgreAssert(town.is_valid(), "Bad town entity");
|
||||
std::lock_guard<std::mutex> lock(*ai.mutex);
|
||||
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) {
|
||||
if (ai.blackboards.find(it->first) == ai.blackboards.end()) {
|
||||
int strength = 10;
|
||||
int dexterity = 10;
|
||||
int health = 100;
|
||||
int stamina = 100;
|
||||
int sex = it->second.props["sex"].get<int>();
|
||||
if (sex == 0) { // male
|
||||
strength += 10;
|
||||
dexterity += 10;
|
||||
}
|
||||
nlohmann::json bb;
|
||||
bb["strength"] = strength;
|
||||
bb["dexterity"] = dexterity;
|
||||
bb["health"] = health;
|
||||
bb["stamina"] = stamina;
|
||||
|
||||
bb["needs_hunger"] = 0;
|
||||
bb["needs_thirst"] = 0;
|
||||
bb["needs_toilet"] = 0;
|
||||
|
||||
bb["have_water"] = 0;
|
||||
bb["have_food"] = 0;
|
||||
bb["have_medicine"] = 0;
|
||||
bb["at_object"] = 0;
|
||||
ai.blackboards[it->first] = Blackboard(bb);
|
||||
ai.blackboards[it->first].index = it->first;
|
||||
ai.blackboards.at(it->first).town = town;
|
||||
// FIXME: do this once
|
||||
ai.blackboards.at(it->first).actionRefResize(0);
|
||||
ai.blackboards.at(it->first).actionRefAddActions(
|
||||
ai.actions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterAIModule::updateBlackboardsBits(flecs::entity town,
|
||||
ActionNodeList &alist,
|
||||
const TownNPCs &npcs, TownAI &ai)
|
||||
{
|
||||
OgreAssert(town.is_valid(), "Bad town entity");
|
||||
std::lock_guard<std::mutex> lock(*ai.mutex);
|
||||
struct UpdateBit {
|
||||
Ogre::String checkValue;
|
||||
int recover;
|
||||
int minValue;
|
||||
int maxValue;
|
||||
int threshold;
|
||||
Ogre::String writeValue;
|
||||
};
|
||||
struct UpdateBit updateBits[] = {
|
||||
{ "health", 0, 0, 100, 20, "healthy" },
|
||||
{ "needs_hunger", 1, 0, 10000, 2000, "hungry" },
|
||||
{ "needs_thirst", 1, 0, 10000, 1000, "thirsty" },
|
||||
{ "needs_toilet", 1, 0, 10000, 1500, "toilet" }
|
||||
};
|
||||
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) {
|
||||
if (ai.blackboards.find(it->first) == ai.blackboards.end())
|
||||
continue;
|
||||
auto &stats = ai.blackboards.at(it->first).stats;
|
||||
auto &object = ai.blackboards.at(it->first).object;
|
||||
ai.blackboards.at(it->first).index = it->first;
|
||||
ai.blackboards.at(it->first).town = town;
|
||||
for (const auto &bits : updateBits) {
|
||||
int value = stats[bits.checkValue].get<int>();
|
||||
int maxValue = bits.maxValue;
|
||||
int minValue = bits.minValue;
|
||||
int threshold = bits.threshold;
|
||||
if (it->second.props.find(bits.checkValue + "_max") !=
|
||||
it->second.props.end())
|
||||
maxValue =
|
||||
it->second
|
||||
.props[bits.checkValue + "_max"]
|
||||
.get<int>();
|
||||
if (it->second.props.find(bits.checkValue +
|
||||
"_threshold") !=
|
||||
it->second.props.end())
|
||||
threshold = it->second
|
||||
.props[bits.checkValue +
|
||||
"_threshold"]
|
||||
.get<int>();
|
||||
value += bits.recover;
|
||||
if (value > maxValue)
|
||||
value = maxValue;
|
||||
if (value >= threshold)
|
||||
stats[bits.writeValue] = 1;
|
||||
else
|
||||
stats[bits.writeValue] = 0;
|
||||
if (value < bits.minValue)
|
||||
value = bits.minValue;
|
||||
stats[bits.checkValue] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterAIModule::updateBlackboards(flecs::entity town,
|
||||
const ActionNodeList &alist,
|
||||
const TownNPCs &npcs, TownAI &ai)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*ai.mutex);
|
||||
OgreAssert(town.is_valid(), "Bad town entity");
|
||||
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) {
|
||||
if (ai.blackboards.find(it->first) == ai.blackboards.end())
|
||||
continue;
|
||||
auto &stats = ai.blackboards.at(it->first).stats;
|
||||
auto &object = ai.blackboards.at(it->first).object;
|
||||
ai.blackboards.at(it->first).index = it->first;
|
||||
ai.blackboards.at(it->first).town = town;
|
||||
auto &bb = ai.blackboards.at(it->first);
|
||||
bb.query_ai();
|
||||
|
||||
#if 0
|
||||
OgreAssert(nodeActionCount > 0 ||
|
||||
points.size() == 0,
|
||||
"no node actions and no points");
|
||||
if (nodeActionCount == 0) {
|
||||
std::cout << "nodes:"
|
||||
<< alist.nodes.size() << " "
|
||||
<< alist.dynamicNodes.size()
|
||||
<< std::endl;
|
||||
std::cout << "points: " << points.size()
|
||||
<< std::endl;
|
||||
std::cout << "position: " << position
|
||||
<< std::endl;
|
||||
}
|
||||
OgreAssert(nodeActionCount > 0,
|
||||
"no node actions");
|
||||
#endif
|
||||
bb.fixupBooleanKeys();
|
||||
}
|
||||
}
|
||||
|
||||
void Blackboard::_actionRefResize(int count)
|
||||
{
|
||||
if (count >= actionRef.size()) {
|
||||
int allocate = count;
|
||||
if (allocate < 1000)
|
||||
allocate = 1000;
|
||||
actionRef.resize(allocate);
|
||||
}
|
||||
OgreAssert(count < actionRef.size(), "out of memory");
|
||||
actionRefCount = count;
|
||||
actionRefPtr = count;
|
||||
}
|
||||
|
||||
Blackboard::Blackboard()
|
||||
: stats(nlohmann::json::object())
|
||||
, object(-1)
|
||||
, index(-1)
|
||||
, actionRefCount(0)
|
||||
, mutex(std::make_shared<std::mutex>())
|
||||
{
|
||||
}
|
||||
|
||||
Blackboard::Blackboard(const nlohmann::json &stats)
|
||||
: Blackboard()
|
||||
{
|
||||
this->stats = stats;
|
||||
}
|
||||
|
||||
bool Blackboard::operator==(const Blackboard &other) const
|
||||
{
|
||||
return is_satisfied_by(this->stats, other.stats);
|
||||
}
|
||||
|
||||
bool Blackboard::operator!=(const Blackboard &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
void Blackboard::apply(const Blackboard &other)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*mutex);
|
||||
stats.update(other.stats);
|
||||
}
|
||||
|
||||
Ogre::String Blackboard::dumpActions()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*mutex);
|
||||
Ogre::String ret;
|
||||
ret += "Actions:\n";
|
||||
int count = 0;
|
||||
for (count = 0; count < actionRefCount; count++) {
|
||||
auto &action = actionRef[count];
|
||||
ret += "name: " + action->get_name() + "\n";
|
||||
ret += "\tprereq:\n" + action->prereq.stats.dump(4) + "\n";
|
||||
ret += "\teffects:\n" + action->effects.stats.dump(4) + "\n";
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Blackboard::printActions()
|
||||
{
|
||||
std::cout << dumpActions() << std::endl;
|
||||
}
|
||||
|
||||
void Blackboard::fixupBooleanKeys()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*mutex);
|
||||
int count;
|
||||
for (count = 0; count < actionRefCount; count++) {
|
||||
auto &action = actionRef[count];
|
||||
const nlohmann::json &prereq = action->prereq.stats;
|
||||
const nlohmann::json &effects = action->effects.stats;
|
||||
for (auto it = prereq.begin(); it != prereq.end(); it++)
|
||||
if (stats.find(it.key()) == stats.end())
|
||||
stats[it.key()] = 0;
|
||||
for (auto it = effects.begin(); it != effects.end(); it++)
|
||||
if (stats.find(it.key()) == stats.end())
|
||||
stats[it.key()] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Blackboard::actionRefResize(int count)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*mutex);
|
||||
_actionRefResize(count);
|
||||
}
|
||||
|
||||
void Blackboard::actionRefAddAction(goap::BaseAction<Blackboard> *action)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*mutex);
|
||||
if (actionRef.size() <= actionRefCount + 16)
|
||||
actionRef.resize(actionRefCount + 32);
|
||||
OgreAssert(actionRefPtr < actionRef.size(), "out of memory");
|
||||
OgreAssert(action, "bad action");
|
||||
actionRef[actionRefPtr++] = action;
|
||||
actionRefCount++;
|
||||
}
|
||||
|
||||
void Blackboard::actionRefAddActions(
|
||||
const std::vector<goap::BaseAction<Blackboard> *> &actions)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*mutex);
|
||||
_actionRefAddActions(actions);
|
||||
}
|
||||
|
||||
void Blackboard::_actionRefAddActions(
|
||||
const std::vector<goap::BaseAction<Blackboard> *> &actions)
|
||||
{
|
||||
if (actionRef.size() <= actionRefCount + actions.size())
|
||||
actionRef.resize(actionRefCount + actions.size() * 2);
|
||||
for (const auto &action : actions) {
|
||||
OgreAssert(actionRefPtr < actionRef.size(), "out of memory");
|
||||
OgreAssert(action, "bad action");
|
||||
actionRef[actionRefPtr++] = action;
|
||||
actionRefCount++;
|
||||
}
|
||||
}
|
||||
|
||||
void Blackboard::actionRefAddActions(goap::BaseAction<Blackboard> **actions,
|
||||
int count)
|
||||
{
|
||||
int i;
|
||||
std::lock_guard<std::mutex> lock(*mutex);
|
||||
if (actionRef.size() <= actionRefCount + count)
|
||||
actionRef.resize(actionRefCount + count * 2);
|
||||
for (i = 0; i < count; i++) {
|
||||
OgreAssert(actionRefPtr < actionRef.size(), "out of memory");
|
||||
OgreAssert(actions[i], "bad action");
|
||||
actionRef[actionRefPtr++] = actions[i];
|
||||
actionRefCount++;
|
||||
}
|
||||
}
|
||||
|
||||
struct ComparePair {
|
||||
const nlohmann::json ¤t;
|
||||
const nlohmann::json ⌖
|
||||
};
|
||||
|
||||
bool Blackboard::is_satisfied_by(const nlohmann::json ¤t,
|
||||
const nlohmann::json &target, float epsilon)
|
||||
{
|
||||
std::deque<ComparePair> queue;
|
||||
queue.push_back({ current, target });
|
||||
while (!queue.empty()) {
|
||||
ComparePair pair = queue.front();
|
||||
queue.pop_front();
|
||||
const nlohmann::json &curr = pair.current;
|
||||
const nlohmann::json &tgt = pair.target;
|
||||
if (curr.type() != tgt.type() &&
|
||||
!(curr.is_number() && tgt.is_number()))
|
||||
return false;
|
||||
if (tgt.is_object())
|
||||
for (auto it = tgt.begin(); it != tgt.end(); ++it) {
|
||||
auto found = curr.find(it.key());
|
||||
if (found == curr.end())
|
||||
return false;
|
||||
queue.push_back({ *found, it.value() });
|
||||
}
|
||||
else if (tgt.is_array()) {
|
||||
if (curr.size() != tgt.size())
|
||||
return false;
|
||||
for (int i = 0; i < tgt.size(); ++i)
|
||||
queue.push_back({ curr[i], tgt[i] });
|
||||
} else if (tgt.is_number_float() || curr.is_number_float()) {
|
||||
if (std::abs(curr.get<float>() - tgt.get<float>()) >=
|
||||
epsilon)
|
||||
return false;
|
||||
} else if (curr != tgt)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int Blackboard::distance_to(const Blackboard &goal) const
|
||||
{
|
||||
int distance = 0;
|
||||
|
||||
OgreAssert(goal.stats.is_object(),
|
||||
"Not an object:\n" + goal.stats.dump(4));
|
||||
for (auto it = goal.stats.begin(); it != goal.stats.end(); ++it) {
|
||||
const std::string &key = it.key();
|
||||
const auto &goalVal = it.value();
|
||||
|
||||
// If current state doesn't have the key, treat it as a maximum difference
|
||||
if (stats.find(key) == stats.end()) {
|
||||
distance += 100; // Example: High cost for missing state
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto ¤tVal = stats[key];
|
||||
|
||||
if (goalVal.is_number() && currentVal.is_number()) {
|
||||
// Add numerical difference
|
||||
distance += std::abs(goalVal.get<float>() -
|
||||
currentVal.get<float>());
|
||||
} else {
|
||||
// Check non-numeric equality
|
||||
if (goalVal != currentVal) {
|
||||
distance += 1; // Penalty for mismatch
|
||||
}
|
||||
}
|
||||
}
|
||||
return distance;
|
||||
}
|
||||
|
||||
void Blackboard::setPosition(const Ogre::Vector3 &position)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*mutex);
|
||||
this->position = position;
|
||||
}
|
||||
|
||||
void Blackboard::query_ai()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*mutex);
|
||||
TownAI &ai = town.get_mut<TownAI>();
|
||||
const TownNPCs &npcs = town.get<TownNPCs>();
|
||||
const float distance = 10000.0f;
|
||||
Ogre::Vector3 position(0, 0, 0);
|
||||
if (npcs.npcs.at(index).e.is_valid())
|
||||
position = npcs.npcs.at(index)
|
||||
.e.get<CharacterBase>()
|
||||
.mBodyNode->_getDerivedPosition();
|
||||
else
|
||||
from_json(npcs.npcs.at(index).props["position"], position);
|
||||
this->position = position;
|
||||
ActionNodeList &alist = ECS::get_mut<ActionNodeList>();
|
||||
alist.query_ai(position, distance, points, distances);
|
||||
_actionRefResize(ai.actions.size());
|
||||
int actionRefIndex = ai.actions.size();
|
||||
int nodeActionCount = 0;
|
||||
for (size_t point : points) {
|
||||
Ogre::Vector3 &p = alist.dynamicNodes[point].position;
|
||||
float radius = alist.dynamicNodes[point].radius;
|
||||
float distance = p.squaredDistance(position);
|
||||
if (object >= 0 && (size_t)object == point &&
|
||||
distance > radius * radius) {
|
||||
object = -1;
|
||||
stats["at_object"] = 0;
|
||||
} else if (object >= 0 && (size_t)object == point &&
|
||||
distance <= radius * radius)
|
||||
stats["at_object"] = 1;
|
||||
/* some nodes do not have usable actions */
|
||||
if (ai.nodeActions[point].size() > 0) {
|
||||
OgreAssert(ai.nodeActions[point].size() > 0,
|
||||
|
||||
"bad node actions count " +
|
||||
alist.dynamicNodes[point].props.dump(
|
||||
4));
|
||||
_actionRefAddActions(ai.nodeActions[point]);
|
||||
nodeActionCount += ai.nodeActions[point].size();
|
||||
}
|
||||
nlohmann::json nodes = nlohmann::json::array();
|
||||
nlohmann::json j = alist.dynamicNodes[point].props;
|
||||
j["global_position_x"] = p.x;
|
||||
j["global_position_y"] = p.y;
|
||||
j["global_position_z"] = p.z;
|
||||
nodes.push_back(j);
|
||||
stats["nodes"] = nodes;
|
||||
}
|
||||
}
|
||||
}
|
||||
101
src/gamedata/CharacterAIModule.h
Normal file
101
src/gamedata/CharacterAIModule.h
Normal file
@@ -0,0 +1,101 @@
|
||||
#ifndef CHARACTERAIMODULE_H
|
||||
#define CHARACTERAIMODULE_H
|
||||
#include <vector>
|
||||
#include <flecs.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "goap.h"
|
||||
|
||||
namespace ECS
|
||||
{
|
||||
|
||||
struct Blackboard {
|
||||
nlohmann::json stats;
|
||||
int object;
|
||||
int index;
|
||||
flecs::entity town;
|
||||
std::shared_ptr<std::mutex> mutex;
|
||||
|
||||
private:
|
||||
std::vector<goap::BaseAction<Blackboard> *> actionRef;
|
||||
int actionRefCount;
|
||||
int actionRefPtr;
|
||||
Ogre::Vector3 position;
|
||||
std::vector<size_t> points;
|
||||
std::vector<float> distances;
|
||||
void _actionRefResize(int count);
|
||||
void _actionRefAddActions(
|
||||
const std::vector<goap::BaseAction<Blackboard> *> &actions);
|
||||
|
||||
public:
|
||||
Blackboard();
|
||||
Blackboard(const nlohmann::json &stats);
|
||||
bool operator==(const Blackboard &other) const;
|
||||
bool operator!=(const Blackboard &other) const;
|
||||
void apply(const Blackboard &other);
|
||||
Ogre::String dumpActions();
|
||||
void printActions();
|
||||
void fixupBooleanKeys();
|
||||
void actionRefResize(int count);
|
||||
void actionRefAddAction(goap::BaseAction<Blackboard> *action);
|
||||
void actionRefAddActions(
|
||||
const std::vector<goap::BaseAction<Blackboard> *> &actions);
|
||||
void actionRefAddActions(goap::BaseAction<Blackboard> **actions,
|
||||
int count);
|
||||
goap::BaseAction<Blackboard> **getActionsData()
|
||||
{
|
||||
return actionRef.data();
|
||||
}
|
||||
int getActionsCount()
|
||||
{
|
||||
return actionRefCount;
|
||||
}
|
||||
|
||||
private:
|
||||
static bool is_satisfied_by(const nlohmann::json ¤t,
|
||||
const nlohmann::json &target,
|
||||
float epsilon = 1e-4);
|
||||
|
||||
public:
|
||||
int distance_to(const Blackboard &goal) const;
|
||||
void setPosition(const Ogre::Vector3 &position);
|
||||
const Ogre::Vector3 &getPosition() const
|
||||
{
|
||||
return position;
|
||||
}
|
||||
void query_ai();
|
||||
};
|
||||
|
||||
struct TownNPCs;
|
||||
struct ActionNodeList;
|
||||
|
||||
struct TownAI {
|
||||
std::shared_ptr<std::mutex> mutex;
|
||||
std::vector<goap::BasePlanner<Blackboard,
|
||||
goap::BaseAction<Blackboard> >::BaseGoal>
|
||||
goals;
|
||||
std::vector<goap::BaseAction<Blackboard> *> actions;
|
||||
std::shared_ptr<
|
||||
goap::BasePlanner<Blackboard, goap::BaseAction<Blackboard> > >
|
||||
planner;
|
||||
std::unordered_map<int, Blackboard> blackboards;
|
||||
struct Plan {
|
||||
const goap::BasePlanner<Blackboard,
|
||||
goap::BaseAction<Blackboard> >::BaseGoal *goal;
|
||||
std::vector<goap::BaseAction<Blackboard> *> plan;
|
||||
};
|
||||
std::unordered_map<int, std::vector<struct Plan> > plans;
|
||||
std::unordered_map<int, std::vector<goap::BaseAction<Blackboard> *> > nodeActions;
|
||||
};
|
||||
|
||||
struct CharacterAIModule {
|
||||
CharacterAIModule(flecs::world &ecs);
|
||||
void createAI(flecs::entity town);
|
||||
void buildPlans(flecs::entity town, const TownNPCs &npcs, TownAI &ai);
|
||||
void createBlackboards(flecs::entity town, const TownNPCs &npcs, TownAI &ai);
|
||||
void updateBlackboardsBits(flecs::entity town, ActionNodeList &alist, const TownNPCs &npcs, TownAI &ai);
|
||||
void updateBlackboards(flecs::entity town, const ActionNodeList &alist, const TownNPCs &npcs, TownAI &ai);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CHARACTERAIMODULE_H
|
||||
@@ -12,18 +12,19 @@
|
||||
|
||||
namespace ECS
|
||||
{
|
||||
void createNPCActionNodes(flecs::entity town, flecs::entity e, int index)
|
||||
void createNPCActionNodes(flecs::entity town, int index)
|
||||
{
|
||||
NPCActionNodes &anodes = e.get_mut<NPCActionNodes>();
|
||||
const TownNPCs &npcs = town.get<TownNPCs>();
|
||||
nlohmann::json npcprops = npcs.npcs.at(index).props;
|
||||
TownNPCs &npcs = town.get_mut<TownNPCs>();
|
||||
TownNPCs::NPCData &npc = npcs.npcs.at(index);
|
||||
flecs::entity e = npc.e;
|
||||
nlohmann::json npcprops = npc.props;
|
||||
const CharacterBase &ch = e.get<CharacterBase>();
|
||||
Ogre::Vector3 characterPos = ch.mBodyNode->_getDerivedPosition();
|
||||
Ogre::Quaternion characterRot = ch.mBodyNode->_getDerivedOrientation();
|
||||
if (anodes.anodes.size() > 0) {
|
||||
if (npc.actionNodes.size() > 0) {
|
||||
int i;
|
||||
for (i = 0; i < anodes.anodes.size(); i++) {
|
||||
auto &anode = anodes.anodes[i];
|
||||
for (i = 0; i < npc.actionNodes.size(); i++) {
|
||||
auto &anode = npc.actionNodes[i];
|
||||
Ogre::Vector3 offset = Ogre::Vector3::UNIT_Z * 0.3f +
|
||||
Ogre::Vector3::UNIT_Y;
|
||||
if (i == 1)
|
||||
@@ -34,7 +35,6 @@ void createNPCActionNodes(flecs::entity town, flecs::entity e, int index)
|
||||
to_json(anode.props["position"], anode.position);
|
||||
to_json(anode.props["rotation"], anode.rotation);
|
||||
}
|
||||
e.modified<NPCActionNodes>();
|
||||
return;
|
||||
}
|
||||
{
|
||||
@@ -57,7 +57,7 @@ void createNPCActionNodes(flecs::entity town, flecs::entity e, int index)
|
||||
anode.props["town"] = town.id();
|
||||
anode.props["index"] = index;
|
||||
anode.props["npc"] = npcprops;
|
||||
anodes.anodes.push_back(anode);
|
||||
npc.actionNodes.push_back(anode);
|
||||
}
|
||||
{
|
||||
ActionNodeList::ActionNode anode;
|
||||
@@ -78,9 +78,8 @@ void createNPCActionNodes(flecs::entity town, flecs::entity e, int index)
|
||||
anode.props["town"] = town.id();
|
||||
anode.props["index"] = index;
|
||||
anode.props["npc"] = npcprops;
|
||||
anodes.anodes.push_back(anode);
|
||||
npc.actionNodes.push_back(anode);
|
||||
}
|
||||
e.modified<NPCActionNodes>();
|
||||
}
|
||||
CharacterManagerModule::CharacterManagerModule(flecs::world &ecs)
|
||||
{
|
||||
@@ -92,16 +91,10 @@ CharacterManagerModule::CharacterManagerModule(flecs::world &ecs)
|
||||
ecs.component<TownCharacterHolder>();
|
||||
ecs.component<TownNPCs>();
|
||||
ecs.component<LivesIn>();
|
||||
ecs.component<NPCActionNodes>().on_add(
|
||||
[](flecs::entity e, NPCActionNodes &anodes) {
|
||||
anodes.anodes.clear();
|
||||
});
|
||||
ecs.system<TerrainItem, TownNPCs>("UpdateCharacters")
|
||||
.immediate()
|
||||
.kind(flecs::OnUpdate)
|
||||
.interval(1.0f)
|
||||
.write<CharacterBase>()
|
||||
.write<NPCActionNodes>()
|
||||
.write<CharacterLocation>()
|
||||
.write<CharacterConf>()
|
||||
.write<Character>()
|
||||
@@ -112,73 +105,72 @@ CharacterManagerModule::CharacterManagerModule(flecs::world &ecs)
|
||||
return;
|
||||
if (!player.has<CharacterBase>())
|
||||
return;
|
||||
ECS::get().defer_suspend();
|
||||
Ogre::Vector3 cameraPos =
|
||||
player.get<CharacterBase>()
|
||||
.mBodyNode->_getDerivedPosition();
|
||||
for (auto &npc : npcs.npcs) {
|
||||
int index = npc.first;
|
||||
TownNPCs::NPCData &data = npc.second;
|
||||
Ogre::Vector3 npcPosition = data.position;
|
||||
Ogre::Quaternion npcOrientation =
|
||||
data.orientation;
|
||||
if (cameraPos.squaredDistance(npcPosition) <
|
||||
10000.0f) {
|
||||
if (!data.e.is_valid()) {
|
||||
data.e = createCharacterData(
|
||||
data.model,
|
||||
data.position,
|
||||
data.orientation);
|
||||
data.e.add<LivesIn>(town);
|
||||
break;
|
||||
|
||||
Ogre::Root::getSingleton().getWorkQueue()->addMainThreadTask([this,
|
||||
town]() {
|
||||
flecs::entity player =
|
||||
ECS::get<CharacterManagerModule>()
|
||||
.getPlayer();
|
||||
if (!player.is_valid())
|
||||
return;
|
||||
if (!player.has<CharacterBase>())
|
||||
return;
|
||||
TownNPCs &npcs = town.get_mut<TownNPCs>();
|
||||
Ogre::Vector3 cameraPos =
|
||||
player.get<CharacterBase>()
|
||||
.mBodyNode
|
||||
->_getDerivedPosition();
|
||||
for (auto &npc : npcs.npcs) {
|
||||
int index = npc.first;
|
||||
TownNPCs::NPCData &data = npc.second;
|
||||
Ogre::Vector3 npcPosition =
|
||||
data.position;
|
||||
Ogre::Quaternion npcOrientation =
|
||||
data.orientation;
|
||||
if (cameraPos.squaredDistance(
|
||||
npcPosition) < 10000.0f) {
|
||||
if (!data.e.is_valid()) {
|
||||
data.e = createCharacterData(
|
||||
data.model,
|
||||
data.position,
|
||||
data.orientation);
|
||||
data.e.add<LivesIn>(
|
||||
town);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cameraPos.squaredDistance(
|
||||
npcPosition) > 22500.0f) {
|
||||
if (data.e.is_valid()) {
|
||||
data.e.destruct();
|
||||
data.e =
|
||||
flecs::entity();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cameraPos.squaredDistance(npcPosition) >
|
||||
22500.0f) {
|
||||
if (data.e.is_valid()) {
|
||||
data.e.destruct();
|
||||
data.e = flecs::entity();
|
||||
break;
|
||||
for (auto &npc : npcs.npcs) {
|
||||
int index = npc.first;
|
||||
TownNPCs::NPCData &data = npc.second;
|
||||
Ogre::Vector3 npcPosition =
|
||||
data.position;
|
||||
Ogre::Quaternion npcOrientation =
|
||||
data.orientation;
|
||||
if (cameraPos.squaredDistance(
|
||||
npcPosition) < 10000.0f) {
|
||||
if (data.e.is_valid()) {
|
||||
if (data.e.has<
|
||||
CharacterBase>() &&
|
||||
data.e.has<LivesIn>(
|
||||
town))
|
||||
createNPCActionNodes(
|
||||
town,
|
||||
index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ECS::get().defer_resume();
|
||||
});
|
||||
ecs.system<TerrainItem, TownNPCs>("UpdateCharacters2")
|
||||
.immediate()
|
||||
.kind(flecs::OnUpdate)
|
||||
.write<CharacterBase>()
|
||||
.write<NPCActionNodes>()
|
||||
.write<CharacterLocation>()
|
||||
.write<CharacterConf>()
|
||||
.write<Character>()
|
||||
.write<LivesIn>()
|
||||
.each([this](flecs::entity town, TerrainItem &item,
|
||||
TownNPCs &npcs) {
|
||||
if (!player.is_valid())
|
||||
return;
|
||||
if (!player.has<CharacterBase>())
|
||||
return;
|
||||
Ogre::Vector3 cameraPos =
|
||||
player.get<CharacterBase>()
|
||||
.mBodyNode->_getDerivedPosition();
|
||||
for (auto &npc : npcs.npcs) {
|
||||
int index = npc.first;
|
||||
TownNPCs::NPCData &data = npc.second;
|
||||
Ogre::Vector3 npcPosition = data.position;
|
||||
Ogre::Quaternion npcOrientation =
|
||||
data.orientation;
|
||||
if (cameraPos.squaredDistance(npcPosition) <
|
||||
10000.0f) {
|
||||
if (data.e.is_valid()) {
|
||||
if (data.e.has<CharacterBase>() &&
|
||||
data.e.has<LivesIn>(town))
|
||||
createNPCActionNodes(
|
||||
town, data.e,
|
||||
index);
|
||||
}
|
||||
}
|
||||
}
|
||||
town.modified<TownNPCs>();
|
||||
});
|
||||
});
|
||||
}
|
||||
flecs::entity
|
||||
@@ -191,11 +183,10 @@ CharacterManagerModule::createPlayer(const Ogre::Vector3 &position,
|
||||
player = ECS::get().entity("player");
|
||||
OgreAssert(player.is_valid(), "Can't create player");
|
||||
std::cout << "Begin player create" << std::endl;
|
||||
player.set<CharacterLocation>({ rotation, position })
|
||||
.set<CharacterConf>({ "normal-male.glb" })
|
||||
.add<Character>()
|
||||
// .add<CharacterDisablePhysics>()
|
||||
.add<Player>();
|
||||
player.add<Player>();
|
||||
ECS::get_mut<CharacterModule>().createCharacter(
|
||||
player, position, rotation, "normal-male.glb");
|
||||
ECS::modified<CharacterModule>();
|
||||
std::cout << "End player create" << std::endl;
|
||||
count++;
|
||||
return player;
|
||||
@@ -205,13 +196,10 @@ CharacterManagerModule::createCharacterData(const Ogre::String model,
|
||||
const Ogre::Vector3 &position,
|
||||
const Ogre::Quaternion &rotation)
|
||||
{
|
||||
flecs::entity e =
|
||||
ECS::get()
|
||||
.entity()
|
||||
.set<CharacterLocation>({ rotation, position })
|
||||
.set<CharacterConf>({ model })
|
||||
.add<Character>()
|
||||
.add<NPCActionNodes>();
|
||||
flecs::entity e = ECS::get().entity();
|
||||
ECS::get_mut<CharacterModule>().createCharacter(e, position, rotation,
|
||||
model);
|
||||
ECS::modified<CharacterModule>();
|
||||
return e;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define _CHARACTER_MANAGER_MODULE_
|
||||
#include <flecs.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include "PlayerActionModule.h"
|
||||
namespace ECS
|
||||
{
|
||||
struct TownCharacterHolder{int index;};
|
||||
@@ -12,6 +13,7 @@ struct TownNPCs {
|
||||
Ogre::Vector3 position;
|
||||
Ogre::Quaternion orientation;
|
||||
Ogre::String model;
|
||||
std::vector<ActionNodeList::ActionNode> actionNodes;
|
||||
};
|
||||
|
||||
std::map<int, NPCData> npcs;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "CharacterAnimationModule.h"
|
||||
#include "CharacterManagerModule.h"
|
||||
#include "CharacterModule.h"
|
||||
#include "goap.h"
|
||||
namespace ECS
|
||||
{
|
||||
CharacterModule::CharacterModule(flecs::world &ecs)
|
||||
@@ -26,12 +25,8 @@ CharacterModule::CharacterModule(flecs::world &ecs)
|
||||
ecs.component<CharacterDisablePhysics>();
|
||||
ecs.component<CharacterUpdatePhysicsState>();
|
||||
ecs.component<CharacterInActuator>();
|
||||
ecs.component<Blackboard>();
|
||||
ecs.component<ActionTarget>();
|
||||
ecs.component<Plan>();
|
||||
ecs.component<Male>();
|
||||
ecs.component<Female>();
|
||||
ecs.component<Planner>().add(flecs::Singleton);
|
||||
ecs.import <CharacterAnimationModule>();
|
||||
ecs.import <TerrainModule>();
|
||||
ecs.import <WaterModule>();
|
||||
@@ -293,29 +288,6 @@ CharacterModule::CharacterModule(flecs::world &ecs)
|
||||
anim.configured = false;
|
||||
});
|
||||
|
||||
ecs.observer<const CharacterLocation, const CharacterConf>(
|
||||
"SetupCharacterObs")
|
||||
.event(flecs::OnSet)
|
||||
.with<Character>()
|
||||
.without<CharacterBase>()
|
||||
.without<AnimationControl>()
|
||||
.write<CharacterBase>()
|
||||
.write<AnimationControl>()
|
||||
.each([&](flecs::entity e, const CharacterLocation &loc,
|
||||
const CharacterConf &conf) {
|
||||
std::cout << "OBSERVER!!!"
|
||||
<< " " << e.id() << std::endl;
|
||||
if (e.has<CharacterBase>() || e.has<AnimationControl>())
|
||||
return;
|
||||
e.world().defer_begin();
|
||||
e.add<CharacterBase>();
|
||||
e.add<AnimationControl>();
|
||||
e.set<CharacterVelocity>(
|
||||
{ { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } });
|
||||
e.add<CharacterGravity>();
|
||||
e.add<CharacterBuoyancy>();
|
||||
e.world().defer_end();
|
||||
});
|
||||
#if 0
|
||||
ecs.system<const EngineData, const CharacterLocation,
|
||||
const CharacterConf, Body2Entity>("SetupCharacter")
|
||||
@@ -631,182 +603,22 @@ void CharacterModule::updateCameraGoal(Camera &camera, Ogre::Real deltaYaw,
|
||||
}
|
||||
}
|
||||
}
|
||||
CharacterAIModule::CharacterAIModule(flecs::world &ecs)
|
||||
{
|
||||
ecs.module<CharacterAIModule>();
|
||||
ecs.system<Blackboard>("UpdateCharacters")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([&](flecs::entity e, Blackboard &bb) {
|
||||
bb.flags &=
|
||||
~(Blackboard::LOW_HEALTH |
|
||||
Blackboard::FULL_HEALTH |
|
||||
Blackboard::LOW_STAMINA |
|
||||
Blackboard::FULL_STAMINA |
|
||||
Blackboard::LOW_LUST | Blackboard::HIGH_LUST |
|
||||
Blackboard::FULL_LUST);
|
||||
if (bb.health < 5)
|
||||
bb.flags |= Blackboard::LOW_HEALTH;
|
||||
else if (bb.health >= 100)
|
||||
bb.flags |= Blackboard::FULL_HEALTH;
|
||||
if (bb.stamina < 5)
|
||||
bb.flags |= Blackboard::LOW_STAMINA;
|
||||
if (bb.stamina >= 100)
|
||||
bb.flags |= Blackboard::FULL_STAMINA;
|
||||
if (bb.lust >= 100)
|
||||
bb.flags |= Blackboard::FULL_LUST;
|
||||
if (bb.lust > 10)
|
||||
bb.flags |= Blackboard::HIGH_LUST;
|
||||
if (bb.lust < 5)
|
||||
bb.flags |= Blackboard::LOW_LUST;
|
||||
if (bb.stamina < 0)
|
||||
bb.stamina = 0;
|
||||
else if (bb.stamina > 100)
|
||||
bb.stamina = 100;
|
||||
if (bb.lust < 0)
|
||||
bb.lust = 0;
|
||||
else if (bb.lust > 100)
|
||||
bb.lust = 100;
|
||||
});
|
||||
ecs.system<Blackboard, Plan>("UpdateCharactersPlan2")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([&](flecs::entity e, Blackboard &bb, Plan &p) {
|
||||
int i;
|
||||
bool ok_plan = true;
|
||||
|
||||
for (i = p.position; i < p.length; i++) {
|
||||
if (!p.actions[i]->can_run(bb, true)) {
|
||||
ok_plan = false;
|
||||
break;
|
||||
}
|
||||
int ret = p.actions[i]->execute(bb);
|
||||
p.position = i;
|
||||
if (ret == BaseAction::BUSY)
|
||||
break;
|
||||
else if (ret == BaseAction::ABORT) {
|
||||
ok_plan = false;
|
||||
break;
|
||||
}
|
||||
if (ret == BaseAction::OK && i == p.length - 1)
|
||||
ok_plan = false;
|
||||
// stop_this = true;
|
||||
}
|
||||
if (!ok_plan) {
|
||||
std::cout << e.name() << ": invalidated plan"
|
||||
<< " step: " << i << std::endl;
|
||||
for (i = 0; i < p.length; i++)
|
||||
p.actions[i]->stop(bb);
|
||||
e.remove<Plan>();
|
||||
}
|
||||
});
|
||||
ecs.system<Blackboard, Planner>("UpdateCharacters2")
|
||||
.kind(flecs::OnUpdate)
|
||||
.without<Plan>()
|
||||
.each([&](flecs::entity e, Blackboard &bb, Planner &planner) {
|
||||
int i;
|
||||
std::vector<BaseAction *> actions;
|
||||
actions.insert(actions.end(), planner.actions.begin(),
|
||||
planner.actions.end());
|
||||
e.world()
|
||||
.query_builder<const ActionTarget>()
|
||||
.build()
|
||||
.each([&](flecs::entity me,
|
||||
const ActionTarget &t) {
|
||||
actions.insert(actions.end(),
|
||||
t.actions.begin(),
|
||||
t.actions.end());
|
||||
#if 0
|
||||
auto it = t.actions.begin();
|
||||
while (it != t.actions.end()) {
|
||||
if (me != bb.me)
|
||||
actions.push_back(*it);
|
||||
// std::cout << (*it)->get_name()
|
||||
// << std::endl;
|
||||
it++;
|
||||
}
|
||||
#endif
|
||||
});
|
||||
#if 0
|
||||
for (i = 0; i < actions.size(); i++)
|
||||
std::cout << "action: " << i << " "
|
||||
<< actions[i]->get_name()
|
||||
<< std::endl;
|
||||
#endif
|
||||
if (actions.size() == 0)
|
||||
return;
|
||||
int len = -1;
|
||||
OgreAssert(
|
||||
bb.stamina < 100 ||
|
||||
bb.stamina >= 100 &&
|
||||
!bb.get_flag(
|
||||
Blackboard::LOW_STAMINA),
|
||||
"bad thing");
|
||||
for (i = 0; i < planner.goals.size(); i++) {
|
||||
if (planner.goals[i]->distance_to(bb) > 1000000)
|
||||
continue;
|
||||
len = planner.planner.plan(
|
||||
bb, *planner.goals[i], actions.data(),
|
||||
actions.size(), planner.path.data(),
|
||||
100);
|
||||
std::cout << bb.me.name() << ": goal: " << len
|
||||
<< " " << planner.goals[i]->get_name()
|
||||
<< std::endl;
|
||||
if (len > 0)
|
||||
break;
|
||||
}
|
||||
// std::cout << "plan length: " << len << std::endl;
|
||||
#if 0
|
||||
if (len > 0)
|
||||
stop_this = true;
|
||||
if (len < 0)
|
||||
stop_this = true;
|
||||
#endif
|
||||
if (len > 0) {
|
||||
Plan &p = e.ensure<Plan>();
|
||||
p.actions = planner.path;
|
||||
p.position = 0;
|
||||
p.length = len;
|
||||
for (i = 0; i < len; i++)
|
||||
std::cout
|
||||
<< i << " "
|
||||
<< planner.path[i]->get_name()
|
||||
<< " "
|
||||
<< planner.path[i]->get_cost(bb)
|
||||
<< std::endl;
|
||||
bool ok_plan = true;
|
||||
for (i = 0; i < len; i++) {
|
||||
if (!planner.path[i]->can_run(bb,
|
||||
true)) {
|
||||
ok_plan = false;
|
||||
break;
|
||||
}
|
||||
int ret = planner.path[i]->execute(bb);
|
||||
p.position = i;
|
||||
std::cout << "exec: " << i << " "
|
||||
<< planner.path[i]->get_name()
|
||||
<< std::endl;
|
||||
if (ret == BaseAction::BUSY)
|
||||
break;
|
||||
else if (ret == BaseAction::ABORT) {
|
||||
ok_plan = false;
|
||||
} else if (ret == BaseAction::OK)
|
||||
std::cout
|
||||
<< "exec: complete "
|
||||
<< i << " "
|
||||
<< planner.path[i]
|
||||
->get_name()
|
||||
<< std::endl;
|
||||
}
|
||||
e.modified<Plan>();
|
||||
if (!ok_plan) {
|
||||
std::cout << e.name()
|
||||
<< ": invalidate plan"
|
||||
<< " step: " << i
|
||||
<< std::endl;
|
||||
for (i = 0; i < len; i++)
|
||||
planner.path[i]->stop(bb);
|
||||
e.remove<Plan>();
|
||||
}
|
||||
}
|
||||
});
|
||||
void CharacterModule::createCharacter(flecs::entity e,
|
||||
const Ogre::Vector3 &position,
|
||||
const Ogre::Quaternion &rotation,
|
||||
const Ogre::String model)
|
||||
{
|
||||
if (e.has<CharacterBase>() || e.has<AnimationControl>())
|
||||
return;
|
||||
e.set<CharacterLocation>({ rotation, position });
|
||||
e.set<CharacterConf>({ model });
|
||||
e.add<CharacterBase>();
|
||||
e.add<AnimationControl>();
|
||||
e.set<CharacterVelocity>(
|
||||
{ { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } });
|
||||
e.add<CharacterGravity>();
|
||||
e.add<CharacterBuoyancy>();
|
||||
e.add<Character>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define CHARACTER_MODULE_H_
|
||||
#include <flecs.h>
|
||||
#include <Ogre.h>
|
||||
#include "goap.h"
|
||||
#include "Components.h"
|
||||
namespace ECS
|
||||
{
|
||||
struct Camera;
|
||||
@@ -35,30 +35,15 @@ struct CharacterConf {
|
||||
};
|
||||
struct CharacterInActuator {
|
||||
Ogre::String animationState;
|
||||
Vector3 prevMotion;
|
||||
Vector3 prevMotion;
|
||||
};
|
||||
struct ActionTarget {
|
||||
std::vector<BaseAction *> actions;
|
||||
};
|
||||
struct Plan {
|
||||
std::vector<BaseAction *> actions;
|
||||
int position;
|
||||
int length;
|
||||
};
|
||||
struct Planner {
|
||||
BasePlanner<ECS::Blackboard, BaseAction> planner;
|
||||
std::vector<BaseAction *> path;
|
||||
std::vector<BasePlanner<ECS::Blackboard, BaseAction>::BaseGoal *> goals;
|
||||
std::vector<BaseAction *> actions;
|
||||
};
|
||||
|
||||
struct CharacterModule {
|
||||
CharacterModule(flecs::world &ecs);
|
||||
void updateCameraGoal(Camera &camera, Ogre::Real deltaYaw,
|
||||
Ogre::Real deltaPitch, Ogre::Real deltaZoom);
|
||||
};
|
||||
struct CharacterAIModule {
|
||||
CharacterAIModule(flecs::world &ecs);
|
||||
void createCharacter(flecs::entity e, const Ogre::Vector3 &position,
|
||||
const Ogre::Quaternion &rotation,
|
||||
const Ogre::String model);
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -546,8 +546,10 @@ struct GUIListener : public Ogre::RenderTargetListener {
|
||||
if (list.dynamicNodes.size() > 0) {
|
||||
int j;
|
||||
Ogre::Vector3 queryPos;
|
||||
std::vector<size_t> points = list.points;
|
||||
std::vector<float> distances = list.distances;
|
||||
std::vector<size_t> points =
|
||||
list.getUIData().points;
|
||||
std::vector<float> distances =
|
||||
list.getUIData().distances;
|
||||
|
||||
Ogre::SceneNode *cameraNode =
|
||||
ECS::get<Camera>().mCameraNode;
|
||||
@@ -555,7 +557,7 @@ struct GUIListener : public Ogre::RenderTargetListener {
|
||||
cameraNode->_getDerivedPosition();
|
||||
float minDistance = 25.0f;
|
||||
int i;
|
||||
list.selected = -1;
|
||||
list.setUISelected(-1);
|
||||
for (i = 0; i < points.size(); i++) {
|
||||
size_t p = points[i];
|
||||
float distance = distances[i];
|
||||
@@ -589,9 +591,9 @@ struct GUIListener : public Ogre::RenderTargetListener {
|
||||
if (hit)
|
||||
continue;
|
||||
|
||||
if (list.selected == -1) {
|
||||
if (list.getUIData().selected == -1) {
|
||||
if (distance < actDistance)
|
||||
list.selected = p;
|
||||
list.setUISelected(p);
|
||||
}
|
||||
ImDrawList *drawList =
|
||||
ImGui::GetBackgroundDrawList();
|
||||
@@ -600,7 +602,7 @@ struct GUIListener : public Ogre::RenderTargetListener {
|
||||
float circleRadius = 8.0f;
|
||||
ImColor circleColor(
|
||||
ImVec4(0.3f, 0.3f, 0.3f, 1.0f));
|
||||
if (p == list.selected) {
|
||||
if (p == list.getUIData().selected) {
|
||||
circleRadius = 16.0f;
|
||||
circleColor = ImColor(
|
||||
ImVec4(0, 0, 1, 1));
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "VehicleManagerModule.h"
|
||||
#include "PlayerActionModule.h"
|
||||
#include "AppModule.h"
|
||||
#include "CharacterAIModule.h"
|
||||
#include "world-build.h"
|
||||
|
||||
namespace ECS
|
||||
@@ -62,6 +63,7 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
|
||||
ecs.import <WorldMapModule>();
|
||||
ecs.import <CharacterAnimationModule>();
|
||||
ecs.import <PlayerActionModule>();
|
||||
ecs.import <CharacterAIModule>();
|
||||
ecs.add<ActionNodeList>();
|
||||
|
||||
ecs.system<EngineData>("UpdateDelta")
|
||||
|
||||
@@ -656,6 +656,8 @@ PhysicsModule::PhysicsModule(flecs::world &ecs)
|
||||
}
|
||||
// gr.velocity.y = 0.0f;
|
||||
// v.y = 0.0f;
|
||||
OgreAssert(v.squaredLength() < 1000.0f,
|
||||
"shitty velocity setting");
|
||||
ch->SetLinearVelocity(
|
||||
JoltPhysics::convert<JPH::Vec3>(v));
|
||||
gr.velocity = Ogre::Vector3::ZERO;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <iostream>
|
||||
#include <flecs.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <lua.hpp>
|
||||
#include "Components.h"
|
||||
#include "GameData.h"
|
||||
#include "CharacterManagerModule.h"
|
||||
@@ -181,6 +182,33 @@ struct LuaNarrationHandler : GUI::NarrationHandler {
|
||||
},
|
||||
1);
|
||||
lua_setfield(L, -2, "_get_properties");
|
||||
lua_pushlightuserdata(L, this);
|
||||
lua_pushcclosure(
|
||||
L,
|
||||
[](lua_State *L) {
|
||||
LuaNarrationHandler *handler =
|
||||
static_cast<LuaNarrationHandler *>(
|
||||
lua_touserdata(
|
||||
L,
|
||||
lua_upvalueindex(1)));
|
||||
lua_pushstring(
|
||||
L, handler->getProperties().c_str());
|
||||
const std::vector<ActionNodeList::ActionNode>
|
||||
&nodes = ECS::get<ActionNodeList>()
|
||||
.dynamicNodes;
|
||||
lua_newtable(L);
|
||||
int i;
|
||||
for (i = 0; i < nodes.size(); i++) {
|
||||
lua_pushinteger(L, i + 1);
|
||||
lua_pushstring(
|
||||
L,
|
||||
nodes[i].props.dump().c_str());
|
||||
lua_settable(L, -3);
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
1);
|
||||
lua_setfield(L, -2, "_get_goals");
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
void finish() override
|
||||
@@ -231,7 +259,7 @@ struct SimpleWordHandler : PlayerActionModule::ActionWordHandler {
|
||||
const TownNPCs::NPCData &npc =
|
||||
town.get<TownNPCs>().npcs.at(index);
|
||||
flecs::entity e = npc.e;
|
||||
for (const auto &anode : e.get<NPCActionNodes>().anodes) {
|
||||
for (const auto &anode : npc.actionNodes) {
|
||||
if (anode.action == word) {
|
||||
nlohmann::json props = anode.props;
|
||||
props["initiator"] =
|
||||
@@ -254,17 +282,18 @@ PlayerActionModule::PlayerActionModule(flecs::world &ecs)
|
||||
ecs.import <CharacterManagerModule>();
|
||||
ecs.component<ActionNodeList>()
|
||||
.on_add([](flecs::entity e, ActionNodeList &alist) {
|
||||
alist.dirty = true;
|
||||
alist.nodeMutex = std::make_shared<std::mutex>();
|
||||
alist.setDirty();
|
||||
alist.nodes.reserve(1000);
|
||||
alist.dynamicNodes.reserve(1000);
|
||||
alist.selected = -1;
|
||||
alist.busy = false;
|
||||
alist.setUISelected(-1);
|
||||
alist.setReady();
|
||||
})
|
||||
.add(flecs::Singleton);
|
||||
ecs.system<ActionNodeList>("updateNodeList")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([](ActionNodeList &list) {
|
||||
if (list.busy)
|
||||
if (list.isBusy())
|
||||
return;
|
||||
if (list.nodes.size() > 0) {
|
||||
Ogre::SceneNode *cameraNode =
|
||||
@@ -279,49 +308,55 @@ PlayerActionModule::PlayerActionModule(flecs::world &ecs)
|
||||
player.get<CharacterBase>()
|
||||
.mBodyNode
|
||||
->_getDerivedPosition();
|
||||
list.query(playerPos, list.points,
|
||||
list.distances);
|
||||
list.UIquery(playerPos);
|
||||
} else {
|
||||
list.query(cameraPos, list.points,
|
||||
list.distances);
|
||||
list.UIquery(cameraPos);
|
||||
}
|
||||
}
|
||||
});
|
||||
ecs.system<ActionNodeList, const Input>("ActivateActionNode")
|
||||
.kind(flecs::OnUpdate)
|
||||
.each([this](ActionNodeList &list, const Input &input) {
|
||||
std::lock_guard<std::mutex> lock(*list.nodeMutex);
|
||||
if (input.control & 32)
|
||||
std::cout << "act pressed" << std::endl;
|
||||
if (list.busy)
|
||||
if (list.isBusy())
|
||||
return;
|
||||
if (input.act_pressed && list.selected >= 0) {
|
||||
std::cout << list.dynamicNodes[list.selected]
|
||||
.props.dump(4)
|
||||
<< std::endl;
|
||||
if (input.act_pressed &&
|
||||
list.getUIData().selected >= 0) {
|
||||
std::cout
|
||||
<< list.dynamicNodes[list.getUIData()
|
||||
.selected]
|
||||
.props.dump(4)
|
||||
<< std::endl;
|
||||
flecs::entity_t townid =
|
||||
list.dynamicNodes[list.selected]
|
||||
list.dynamicNodes[list.getUIData()
|
||||
.selected]
|
||||
.props["town"]
|
||||
.get<flecs::entity_t>();
|
||||
flecs::entity town = ECS::get().entity(townid);
|
||||
int index = list.dynamicNodes[list.selected]
|
||||
int index = list.dynamicNodes[list.getUIData()
|
||||
.selected]
|
||||
.props["index"]
|
||||
.get<int>();
|
||||
for (auto it = actionWords.begin();
|
||||
it != actionWords.end(); it++) {
|
||||
if (it->first ==
|
||||
list.dynamicNodes[list.selected]
|
||||
list.dynamicNodes[list.getUIData()
|
||||
.selected]
|
||||
.action) {
|
||||
(*it->second)(
|
||||
town, index,
|
||||
list.dynamicNodes
|
||||
[list.selected]
|
||||
[list.getUIData()
|
||||
.selected]
|
||||
.action);
|
||||
list.busy = true;
|
||||
list.setBusy();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!ECS::get<GUI>().enabled)
|
||||
list.busy = false;
|
||||
list.setReady();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -367,8 +402,7 @@ struct LuaWordHandler : PlayerActionModule::ActionWordHandler {
|
||||
const TownNPCs::NPCData &npc =
|
||||
town.get<TownNPCs>().npcs.at(index);
|
||||
flecs::entity e = npc.e;
|
||||
for (const auto &anode :
|
||||
e.get<NPCActionNodes>().anodes) {
|
||||
for (const auto &anode : npc.actionNodes) {
|
||||
if (anode.action == word) {
|
||||
nlohmann::json props = anode.props;
|
||||
props["initiator"] =
|
||||
@@ -426,27 +460,41 @@ int PlayerActionModule::setupLuaActionHandler(lua_State *L)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ActionNodeList::updateDynamicNodes()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*nodeMutex);
|
||||
if (dynamicNodes.size() > nodes.size())
|
||||
dynamicNodes.resize(nodes.size());
|
||||
else {
|
||||
dynamicNodes.clear();
|
||||
dynamicNodes.insert(dynamicNodes.end(), nodes.begin(),
|
||||
nodes.end());
|
||||
}
|
||||
ECS::get().query_builder<const TownNPCs>().each(
|
||||
[this](flecs::entity town, const TownNPCs &npcs) {
|
||||
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end();
|
||||
it++) {
|
||||
dynamicNodes.insert(
|
||||
dynamicNodes.end(),
|
||||
it->second.actionNodes.begin(),
|
||||
it->second.actionNodes.end());
|
||||
}
|
||||
});
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void ActionNodeList::build()
|
||||
{
|
||||
dynamicNodes.clear();
|
||||
dynamicNodes.insert(dynamicNodes.end(), nodes.begin(), nodes.end());
|
||||
ECS::get().query_builder<const NPCActionNodes>().each(
|
||||
[&](flecs::entity e, const NPCActionNodes &anodes) {
|
||||
dynamicNodes.insert(dynamicNodes.end(),
|
||||
anodes.anodes.begin(),
|
||||
anodes.anodes.end());
|
||||
});
|
||||
|
||||
std::lock_guard<std::mutex> lock(*nodeMutex);
|
||||
indexObj = std::make_shared<ActionNodeList::indexObject>(dynamicNodes);
|
||||
indexObj->index.buildIndex();
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
bool ActionNodeList::query(const Ogre::Vector3 &position,
|
||||
std::vector<size_t> &points,
|
||||
std::vector<float> &distances)
|
||||
bool ActionNodeList::_query(const Ogre::Vector3 &position,
|
||||
std::vector<size_t> &points,
|
||||
std::vector<float> &distances)
|
||||
{
|
||||
build();
|
||||
std::vector<size_t> tmppoints;
|
||||
std::vector<float> tmpdistances;
|
||||
points.clear();
|
||||
@@ -455,6 +503,10 @@ bool ActionNodeList::query(const Ogre::Vector3 &position,
|
||||
distances.reserve(4);
|
||||
tmppoints.resize(4);
|
||||
tmpdistances.resize(4);
|
||||
if (!indexObj) {
|
||||
dirty = true;
|
||||
return false;
|
||||
}
|
||||
nanoflann::KNNResultSet<float> resultSet(4);
|
||||
resultSet.init(tmppoints.data(), tmpdistances.data());
|
||||
bool ret = indexObj->index.findNeighbors(resultSet, &position.x,
|
||||
@@ -467,4 +519,108 @@ bool ActionNodeList::query(const Ogre::Vector3 &position,
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool ActionNodeList::query_ai(const Ogre::Vector3 &position, float distance,
|
||||
std::vector<size_t> &points,
|
||||
std::vector<float> &distances)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*nodeMutex);
|
||||
std::vector<size_t> tmppoints;
|
||||
std::vector<float> tmpdistances;
|
||||
points.clear();
|
||||
points.reserve(100);
|
||||
distances.clear();
|
||||
distances.reserve(100);
|
||||
tmppoints.resize(100);
|
||||
tmpdistances.resize(100);
|
||||
if (!indexObj) {
|
||||
dirty = true;
|
||||
return false;
|
||||
}
|
||||
nanoflann::KNNResultSet<float> resultSet(100);
|
||||
resultSet.init(tmppoints.data(), tmpdistances.data());
|
||||
bool ret = indexObj->index.findNeighbors(resultSet, &position.x,
|
||||
nanoflann::SearchParameters());
|
||||
int i;
|
||||
for (i = 0; i < resultSet.size(); i++)
|
||||
if (tmpdistances[i] < distance) {
|
||||
points.push_back(tmppoints[i]);
|
||||
distances.push_back(tmpdistances[i]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ActionNodeList::addNode(ActionNode &node)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*nodeMutex);
|
||||
int index = nodes.size();
|
||||
nodes.push_back(node);
|
||||
dirty = true;
|
||||
return index;
|
||||
}
|
||||
|
||||
void ActionNodeList::removeNode(int index)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*nodeMutex);
|
||||
nodes.erase(nodes.begin() + index);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
const ActionNodeList::UIData &ActionNodeList::getUIData()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*uidata.mutex);
|
||||
return uidata;
|
||||
}
|
||||
|
||||
void ActionNodeList::setUISelected(int selected)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*uidata.mutex);
|
||||
|
||||
uidata.selected = selected;
|
||||
}
|
||||
|
||||
void ActionNodeList::setUIPoints(const std::vector<size_t> &points,
|
||||
const std::vector<float> &distances)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*uidata.mutex);
|
||||
uidata.points = points;
|
||||
uidata.distances = distances;
|
||||
}
|
||||
|
||||
void ActionNodeList::UIquery(const Ogre::Vector3 &position)
|
||||
{
|
||||
bool needBuild = false;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*nodeMutex);
|
||||
if (dirty || !indexObj)
|
||||
needBuild = true;
|
||||
}
|
||||
if (needBuild)
|
||||
build();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*uidata.mutex);
|
||||
_query(position, uidata.points, uidata.distances);
|
||||
}
|
||||
}
|
||||
|
||||
void ActionNodeList::setDirty()
|
||||
{
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
void ActionNodeList::setReady()
|
||||
{
|
||||
busy = false;
|
||||
}
|
||||
|
||||
void ActionNodeList::setBusy()
|
||||
{
|
||||
busy = true;
|
||||
}
|
||||
|
||||
bool ActionNodeList::isBusy()
|
||||
{
|
||||
return busy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,29 +18,45 @@ struct ActionNodeList {
|
||||
float radius;
|
||||
nlohmann::json props;
|
||||
};
|
||||
std::vector<ActionNode> nodes, dynamicNodes;
|
||||
std::shared_ptr<indexObject> indexObj;
|
||||
std::vector<size_t> points;
|
||||
std::vector<float> distances;
|
||||
int selected;
|
||||
struct UIData {
|
||||
std::shared_ptr<std::mutex> mutex;
|
||||
int selected;
|
||||
std::vector<size_t> points;
|
||||
std::vector<float> distances;
|
||||
UIData()
|
||||
: mutex(std::make_shared<std::mutex>())
|
||||
, selected(-1)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
bool dirty;
|
||||
bool busy;
|
||||
std::shared_ptr<indexObject> indexObj;
|
||||
struct UIData uidata;
|
||||
bool _query(const Ogre::Vector3 &position, std::vector<size_t> &points,
|
||||
std::vector<float> &distances);
|
||||
|
||||
public:
|
||||
std::shared_ptr<std::mutex> nodeMutex;
|
||||
std::vector<ActionNode> nodes, dynamicNodes;
|
||||
void updateDynamicNodes();
|
||||
void build();
|
||||
bool query(const Ogre::Vector3 &position, std::vector<size_t> &points, std::vector<float> &distances);
|
||||
int addNode(struct ActionNodeList::ActionNode &node)
|
||||
{
|
||||
int index = nodes.size();
|
||||
nodes.push_back(node);
|
||||
dirty = true;
|
||||
return index;
|
||||
}
|
||||
void removeNode(int index)
|
||||
{
|
||||
nodes.erase(nodes.begin() + index);
|
||||
}
|
||||
};
|
||||
struct NPCActionNodes {
|
||||
std::vector<ActionNodeList::ActionNode> anodes;
|
||||
bool query_ai(const Ogre::Vector3 &position, float distance,
|
||||
std::vector<size_t> &points,
|
||||
std::vector<float> &distances);
|
||||
int addNode(struct ActionNodeList::ActionNode &node);
|
||||
void removeNode(int index);
|
||||
const UIData &getUIData();
|
||||
void setUISelected(int selected);
|
||||
void setUIPoints(const std::vector<size_t> &points,
|
||||
const std::vector<float> &distances);
|
||||
void UIquery(const Ogre::Vector3 &position);
|
||||
void setDirty(); // node was added or removed
|
||||
void setReady();
|
||||
void setBusy();
|
||||
bool isBusy();
|
||||
};
|
||||
struct PlayerActionModule {
|
||||
struct ActionWordHandler {
|
||||
|
||||
@@ -522,9 +522,6 @@ TerrainModule::TerrainModule(flecs::world &ecs)
|
||||
OgreAssert(terrain.mTerrainGlobals,
|
||||
"Failed to allocate global options");
|
||||
|
||||
Ogre::LogManager::getSingleton().setMinLogLevel(
|
||||
Ogre::LML_TRIVIAL);
|
||||
|
||||
terrain.mTerrainGroup =
|
||||
OGRE_NEW Ogre::TerrainGroup(
|
||||
eng.mScnMgr,
|
||||
|
||||
@@ -1,38 +1,5 @@
|
||||
#include <set>
|
||||
#include "goap.h"
|
||||
bool BaseAction::can_run(const ECS::Blackboard &state, bool debug)
|
||||
namespace goap
|
||||
{
|
||||
return m_exec->_can_run(state, debug);
|
||||
}
|
||||
void BaseAction::_plan_effects(ECS::Blackboard &state)
|
||||
{
|
||||
state.clear_flag(m_exec->m_clear_bits);
|
||||
state.set_flag(m_exec->m_set_bits);
|
||||
}
|
||||
bool BaseAction::is_active(const ECS::Blackboard &state)
|
||||
{
|
||||
return m_exec->is_active(state);
|
||||
}
|
||||
bool BaseAction::stop(ECS::Blackboard &state)
|
||||
{
|
||||
if (!is_active(state))
|
||||
return false;
|
||||
m_exec->_exit(state);
|
||||
state._active.erase(m_exec.get());
|
||||
return true;
|
||||
}
|
||||
int BaseAction::execute(ECS::Blackboard &state)
|
||||
{
|
||||
if (!is_active(state)) {
|
||||
state._active.insert(m_exec.get());
|
||||
m_exec->_enter(state);
|
||||
}
|
||||
int ret = m_exec->_execute(state);
|
||||
if (ret == OK)
|
||||
stop(state);
|
||||
return ret;
|
||||
}
|
||||
int BaseAction::get_cost(const ECS::Blackboard &state) const
|
||||
{
|
||||
return m_exec->_get_cost(state);
|
||||
}
|
||||
|
||||
@@ -1,77 +1,35 @@
|
||||
#ifndef H_GOAP_H_
|
||||
#define H_GOAP_H_
|
||||
#undef NDEBUG
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <flecs.h>
|
||||
namespace ECS
|
||||
#include <Ogre.h>
|
||||
namespace goap
|
||||
{
|
||||
struct Blackboard;
|
||||
}
|
||||
struct BaseAction;
|
||||
struct BaseActionExec;
|
||||
namespace ECS
|
||||
{
|
||||
struct Blackboard {
|
||||
enum {
|
||||
HIGH_LUST = (1 << 0),
|
||||
LOW_LUST = (1 << 1),
|
||||
FULL_LUST = (1 << 2),
|
||||
LOW_HEALTH = (1 << 3),
|
||||
FULL_HEALTH = (1 << 4),
|
||||
HAS_TARGET = (1 << 5),
|
||||
LOW_STAMINA = (1 << 6),
|
||||
FULL_STAMINA = (1 << 7),
|
||||
REACHED_TARGET = (1 << 8),
|
||||
};
|
||||
flecs::entity me;
|
||||
int health;
|
||||
int stamina;
|
||||
int lust;
|
||||
uint32_t flags;
|
||||
std::set<BaseActionExec *> _active;
|
||||
bool operator==(const Blackboard &other)
|
||||
{
|
||||
return flags == other.flags;
|
||||
}
|
||||
void set_flag(int flag)
|
||||
{
|
||||
flags |= flag;
|
||||
}
|
||||
void clear_flag(int flag)
|
||||
{
|
||||
flags &= ~flag;
|
||||
}
|
||||
bool get_flag(int flag) const
|
||||
{
|
||||
return flags & flag;
|
||||
}
|
||||
bool check_flag(int flag) const
|
||||
{
|
||||
return (flags & flag) == flag;
|
||||
}
|
||||
};
|
||||
}
|
||||
struct BaseAction {
|
||||
template <typename State> struct BaseAction {
|
||||
std::string m_name;
|
||||
State prereq;
|
||||
State effects;
|
||||
int m_cost;
|
||||
|
||||
public:
|
||||
enum { OK = 0, BUSY, ABORT };
|
||||
|
||||
private:
|
||||
std::unique_ptr<BaseActionExec> m_exec;
|
||||
|
||||
public:
|
||||
BaseAction(const std::string &name, BaseActionExec *exec)
|
||||
: m_name(name)
|
||||
, m_exec(exec)
|
||||
BaseAction(const std::string &name, const State &prereq, const State &effects, int cost)
|
||||
: m_name(name)
|
||||
, prereq(prereq)
|
||||
, effects(effects)
|
||||
, m_cost(cost)
|
||||
{
|
||||
}
|
||||
const std::string &get_name() const
|
||||
virtual const std::string &get_name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
void plan_effects(ECS::Blackboard &state)
|
||||
void plan_effects(State &state)
|
||||
{
|
||||
// std::cout << m_name << " pre: " << &state << " " << state.flags
|
||||
// << std::endl;
|
||||
@@ -79,42 +37,20 @@ public:
|
||||
// std::cout << m_name << " post: " << &state << " " << state.flags
|
||||
// << std::endl;
|
||||
}
|
||||
virtual bool can_run(const ECS::Blackboard &state, bool debug = false);
|
||||
virtual void _plan_effects(ECS::Blackboard &state);
|
||||
bool is_active(const ECS::Blackboard &state);
|
||||
bool stop(ECS::Blackboard &state);
|
||||
int execute(ECS::Blackboard &state);
|
||||
virtual int get_cost(const ECS::Blackboard &state) const;
|
||||
virtual bool can_run(const State &state, bool debug = false)
|
||||
{
|
||||
return state.distance_to(prereq) == 0;
|
||||
}
|
||||
virtual void _plan_effects(State &state)
|
||||
{
|
||||
state.apply(effects);
|
||||
}
|
||||
// constant cost
|
||||
virtual int get_cost(const State &state) const
|
||||
{
|
||||
return m_cost;
|
||||
}
|
||||
};
|
||||
struct BaseActionExec {
|
||||
enum {
|
||||
OK = BaseAction::OK,
|
||||
BUSY = BaseAction::BUSY,
|
||||
ABORT = BaseAction::ABORT
|
||||
};
|
||||
BaseActionExec(int set_bits, int clear_bits)
|
||||
: m_set_bits(set_bits)
|
||||
, m_clear_bits(clear_bits)
|
||||
{
|
||||
}
|
||||
bool is_active(const ECS::Blackboard &state)
|
||||
{
|
||||
return state._active.find(this) != state._active.end();
|
||||
}
|
||||
virtual int _execute(ECS::Blackboard &state) = 0;
|
||||
virtual void _enter(ECS::Blackboard &state) = 0;
|
||||
virtual void _exit(ECS::Blackboard &state) = 0;
|
||||
virtual int _get_cost(const ECS::Blackboard &state) const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
virtual bool _can_run(const ECS::Blackboard &state, bool debug = false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
int m_set_bits, m_clear_bits;
|
||||
};
|
||||
|
||||
template <typename State, typename Act, int N = 100> class BasePlanner {
|
||||
struct VisitedState {
|
||||
int priority;
|
||||
@@ -190,20 +126,24 @@ template <typename State, typename Act, int N = 100> class BasePlanner {
|
||||
public:
|
||||
struct BaseGoal {
|
||||
std::string m_name;
|
||||
State goal;
|
||||
|
||||
public:
|
||||
BaseGoal(const std::string &name)
|
||||
: m_name(name)
|
||||
BaseGoal(const std::string &name, const State &goal)
|
||||
: m_name(name), goal(goal)
|
||||
{
|
||||
}
|
||||
/** Checks if the goal is reached for the given state. */
|
||||
virtual bool is_reached(const State &state) const
|
||||
{
|
||||
return distance_to(state) == 0;
|
||||
return distance_to(state) == 0;
|
||||
}
|
||||
|
||||
/** Computes the distance from state to goal. */
|
||||
virtual int distance_to(const State &state) const = 0;
|
||||
virtual int distance_to(const State &state) const
|
||||
{
|
||||
return state.distance_to(goal);
|
||||
}
|
||||
|
||||
virtual ~BaseGoal() = default;
|
||||
const std::string &get_name() const
|
||||
@@ -216,7 +156,7 @@ public:
|
||||
*
|
||||
* If path is given, then the found path is stored there.
|
||||
*/
|
||||
int plan(const State &state, BaseGoal &goal, Act *actions[],
|
||||
int plan(const State &state, const BaseGoal &goal, Act *actions[],
|
||||
unsigned action_count, Act **path = nullptr, int path_len = 10)
|
||||
{
|
||||
visited_states_array_to_list(nodes, N);
|
||||
@@ -244,7 +184,8 @@ public:
|
||||
}
|
||||
|
||||
if (len > path_len) {
|
||||
return -1;
|
||||
OgreAssert(len <= path_len, "Out of plan length");
|
||||
return -3;
|
||||
}
|
||||
|
||||
if (path) {
|
||||
@@ -255,6 +196,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
OgreAssert(len >= 0, "Bad plan length");
|
||||
return len;
|
||||
}
|
||||
|
||||
@@ -274,6 +216,7 @@ public:
|
||||
}
|
||||
|
||||
if (!gc) {
|
||||
OgreAssert(gc, "Out of memory");
|
||||
return -2 /* OOM */;
|
||||
}
|
||||
|
||||
@@ -337,14 +280,16 @@ public:
|
||||
return -1 /* No path */;
|
||||
}
|
||||
};
|
||||
template <class RunnerType> struct DeclareAction : public BaseAction {
|
||||
template <class RunnerType, class State>
|
||||
struct DeclareAction : public BaseAction<State> {
|
||||
RunnerType runner;
|
||||
template <class... Args>
|
||||
DeclareAction(const std::string &name, Args... args)
|
||||
: runner(args...)
|
||||
, BaseAction(name, &runner)
|
||||
, BaseAction<State>(name, &runner)
|
||||
{
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#define __ITEMS_H__
|
||||
#include <OgreMeshLodGenerator.h>
|
||||
#include <flecs.h>
|
||||
#include "Components.h"
|
||||
#include "GameData.h"
|
||||
namespace ECS
|
||||
{
|
||||
namespace Items
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "LuaData.h"
|
||||
#include "PlayerActionModule.h"
|
||||
#include "CharacterManagerModule.h"
|
||||
#include "CharacterAIModule.h"
|
||||
#include "items.h"
|
||||
#include "town.h"
|
||||
|
||||
@@ -2095,7 +2096,69 @@ bool editNPCs(nlohmann::json &npcs)
|
||||
ImGui::Text("NPC");
|
||||
int id = 0;
|
||||
for (auto &npc : npcs) {
|
||||
ImGui::Text("%s", npc["lastName"].get<Ogre::String>().c_str());
|
||||
static char firstName[64] = { 0 };
|
||||
static char lastName[64] = { 0 };
|
||||
static char nickName[64] = { 0 };
|
||||
static char tags[256] = { 0 };
|
||||
if (npc.find("firstName") == npc.end())
|
||||
npc["firstName"] = "";
|
||||
if (npc.find("lastName") == npc.end())
|
||||
npc["lastName"] = "";
|
||||
if (npc.find("nickName") == npc.end())
|
||||
npc["nickName"] = "";
|
||||
if (npc.find("tags") == npc.end())
|
||||
npc["tags"] = "";
|
||||
if (npc.find("sex") == npc.end())
|
||||
npc["sex"] = 1;
|
||||
|
||||
strncpy(firstName, npc["firstName"].get<Ogre::String>().c_str(),
|
||||
sizeof(firstName));
|
||||
strncpy(lastName, npc["lastName"].get<Ogre::String>().c_str(),
|
||||
sizeof(lastName));
|
||||
strncpy(nickName, npc["nickName"].get<Ogre::String>().c_str(),
|
||||
sizeof(lastName));
|
||||
strncpy(tags, npc["tags"].get<Ogre::String>().c_str(),
|
||||
sizeof(tags));
|
||||
|
||||
ImGui::InputText(
|
||||
("Last name##" + Ogre::StringConverter::toString(id))
|
||||
.c_str(),
|
||||
lastName, sizeof(lastName));
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
npc["lastName"] = Ogre::String(lastName);
|
||||
changed = true;
|
||||
}
|
||||
ImGui::InputText(
|
||||
("First name##" + Ogre::StringConverter::toString(id))
|
||||
.c_str(),
|
||||
firstName, sizeof(firstName));
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
npc["firstName"] = Ogre::String(firstName);
|
||||
changed = true;
|
||||
}
|
||||
ImGui::InputText(
|
||||
("Nickname##" + Ogre::StringConverter::toString(id))
|
||||
.c_str(),
|
||||
nickName, sizeof(nickName));
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
npc["nickName"] = Ogre::String(nickName);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui::InputText(
|
||||
("Tags##" + Ogre::StringConverter::toString(id)).c_str(),
|
||||
tags, sizeof(tags));
|
||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||
npc["tags"] = Ogre::String(tags);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
int selection = npc["sex"].get<int>();
|
||||
const char *items[] = { "Male", "Female" };
|
||||
if (ImGui::Combo(("Sex##" + Ogre::StringConverter::toString(id))
|
||||
.c_str(),
|
||||
&selection, items, 2))
|
||||
npc["sex"] = selection;
|
||||
if (ImGui::SmallButton(
|
||||
("Spawn##" + Ogre::StringConverter::toString(id))
|
||||
.c_str())) {
|
||||
@@ -2121,33 +2184,36 @@ bool editNPCs(nlohmann::json &npcs)
|
||||
}
|
||||
id++;
|
||||
}
|
||||
ImGui::Text("New NPC");
|
||||
static char lastName[64] = { 0 };
|
||||
ImGui::InputText("Last name", lastName, sizeof(lastName));
|
||||
static char firstName[64] = { 0 };
|
||||
ImGui::InputText("First name", firstName, sizeof(firstName));
|
||||
static char tags[256] = { 0 };
|
||||
ImGui::InputText("Tags", tags, sizeof(firstName));
|
||||
static int selection = 0;
|
||||
const char *items[] = { "Male", "Female" };
|
||||
ImGui::Combo("Sex", &selection, items, 2);
|
||||
if (ImGui::SmallButton("Add NPC")) {
|
||||
nlohmann::json npc;
|
||||
npc["lastName"] = Ogre::String(lastName);
|
||||
npc["firstName"] = Ogre::String(firstName);
|
||||
npc["tags"] = Ogre::String(tags);
|
||||
Ogre::Vector3 npcPosition =
|
||||
ECS::get<EditorGizmo>().sceneNode->_getDerivedPosition();
|
||||
Ogre::Quaternion npcOrientation =
|
||||
ECS::get<EditorGizmo>()
|
||||
.sceneNode->_getDerivedOrientation();
|
||||
to_json(npc["position"], npcPosition);
|
||||
to_json(npc["orientation"], npcOrientation);
|
||||
npc["sex"] = selection;
|
||||
npc["health"] = 100;
|
||||
npc["stamina"] = 100;
|
||||
npcs.push_back(npc);
|
||||
changed = true;
|
||||
{
|
||||
ImGui::Text("New NPC");
|
||||
static char lastName[64] = { 0 };
|
||||
ImGui::InputText("Last name", lastName, sizeof(lastName));
|
||||
static char firstName[64] = { 0 };
|
||||
ImGui::InputText("First name", firstName, sizeof(firstName));
|
||||
static char tags[256] = { 0 };
|
||||
ImGui::InputText("Tags", tags, sizeof(tags));
|
||||
static int selection = 0;
|
||||
const char *items[] = { "Male", "Female" };
|
||||
ImGui::Combo("Sex", &selection, items, 2);
|
||||
if (ImGui::SmallButton("Add NPC")) {
|
||||
nlohmann::json npc;
|
||||
npc["lastName"] = Ogre::String(lastName);
|
||||
npc["firstName"] = Ogre::String(firstName);
|
||||
npc["tags"] = Ogre::String(tags);
|
||||
Ogre::Vector3 npcPosition =
|
||||
ECS::get<EditorGizmo>()
|
||||
.sceneNode->_getDerivedPosition();
|
||||
Ogre::Quaternion npcOrientation =
|
||||
ECS::get<EditorGizmo>()
|
||||
.sceneNode->_getDerivedOrientation();
|
||||
to_json(npc["position"], npcPosition);
|
||||
to_json(npc["orientation"], npcOrientation);
|
||||
npc["sex"] = selection;
|
||||
npc["health"] = 100;
|
||||
npc["stamina"] = 100;
|
||||
npcs.push_back(npc);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
ImGui::Separator();
|
||||
return changed;
|
||||
@@ -5509,6 +5575,8 @@ struct TownDecorateFurniture : TownTask {
|
||||
Ogre::Vector3::
|
||||
UNIT_Y));
|
||||
node->attachObject(ent);
|
||||
ent->setRenderingDistance(
|
||||
60.0f);
|
||||
addStaticBodyMesh(
|
||||
e, mesh,
|
||||
worldCenterPosition +
|
||||
@@ -5857,6 +5925,11 @@ void createTown(flecs::entity e, Ogre::SceneNode *sceneNode,
|
||||
});
|
||||
});
|
||||
registerTownNPCs(e);
|
||||
if (ECS::get().entity<CharacterAIModule>().is_valid())
|
||||
if (ECS::get().has<CharacterAIModule>()) {
|
||||
ECS::get_mut<CharacterAIModule>().createAI(e);
|
||||
ECS::modified<CharacterAIModule>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,14 @@ add_library(world-build STATIC world-build.cpp)
|
||||
target_link_libraries(world-build PRIVATE GameData)
|
||||
target_include_directories(world-build PUBLIC .)
|
||||
|
||||
add_executable(test test.cpp)
|
||||
target_link_libraries(test PRIVATE action world-build lua GameData OgreMain)
|
||||
#add_executable(test test.cpp)
|
||||
#target_link_libraries(test PRIVATE action world-build lua GameData OgreMain)
|
||||
|
||||
add_executable(test2 test2.cpp)
|
||||
target_link_libraries(test2 PRIVATE action world-build lua GameData OgreMain)
|
||||
#add_executable(test2 test2.cpp)
|
||||
#target_link_libraries(test2 PRIVATE action world-build lua GameData OgreMain)
|
||||
|
||||
add_executable(mark_harbors mark_harbors.cpp)
|
||||
target_link_libraries(mark_harbors PRIVATE lua OgreMain OgreRTShaderSystem)
|
||||
#add_executable(mark_harbors mark_harbors.cpp)
|
||||
#target_link_libraries(mark_harbors PRIVATE lua OgreMain OgreRTShaderSystem)
|
||||
|
||||
#add_custom_target(world ALL DEPENDS test test2)
|
||||
|
||||
add_custom_target(world ALL DEPENDS test test2)
|
||||
|
||||
Reference in New Issue
Block a user