Threads and tasks

This commit is contained in:
2026-01-29 15:28:50 +03:00
parent 4cf0ea5321
commit da4c1fee0e
21 changed files with 1464 additions and 558 deletions

View File

@@ -0,0 +1,838 @@
#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
{
#if 1
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)
{
}
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.nodes[node].position;
flecs::entity e = npcs.npcs.at(bb.index).e;
bool validActive = e.is_valid() &&
e.has<CharacterBase>();
const Ogre::Vector3 &npcPosition =
validActive ?
npcs.npcs.at(bb.index)
.e.get<CharacterBase>()
.mBodyNode
->_getDerivedPosition() :
npcs.npcs.at(bb.index).position;
float dist = nodePosition.squaredDistance(
nodePosition);
ret += (int)Ogre::Math::Ceil(dist);
}
out:
return ret;
}
};
struct RunActionNode : public goap::BaseAction<Blackboard> {
int node;
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)
{
}
};
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;
}
};
#endif
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<ActionNodeList, TownAI, TownNPCs>("UpdateBlackboards")
.kind(flecs::OnUpdate)
.interval(0.1f)
.each([this](flecs::entity town, ActionNodeList &alist,
TownAI &ai, const TownNPCs &npcs) {
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;
}
}
}