Compare commits

..

2 Commits

Author SHA1 Message Date
c860152f9b Using threads more 2026-01-30 01:04:23 +03:00
da4c1fee0e Threads and tasks 2026-01-29 15:28:50 +03:00
21 changed files with 1513 additions and 558 deletions

View File

@@ -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;

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View 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 &current;
const nlohmann::json &target;
};
bool Blackboard::is_satisfied_by(const nlohmann::json &current,
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 &currentVal = 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;
}
}
}

View 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 &current,
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

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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>();
}
}

View File

@@ -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

View File

@@ -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));

View File

@@ -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")

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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

View File

@@ -2,6 +2,8 @@
#define __ITEMS_H__
#include <OgreMeshLodGenerator.h>
#include <flecs.h>
#include "Components.h"
#include "GameData.h"
namespace ECS
{
namespace Items

View File

@@ -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>();
}
}
}
}

View File

@@ -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)