Threads and tasks
This commit is contained in:
838
src/gamedata/CharacterAIModule.cpp
Normal file
838
src/gamedata/CharacterAIModule.cpp
Normal 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 ¤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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user