Fixed up GOAP support
This commit is contained in:
@@ -164,16 +164,16 @@ public:
|
||||
}
|
||||
Blackboard actionPrereq({ jactionPrereq });
|
||||
Blackboard actionEffect({ jactionEffect });
|
||||
if (!prereq.stats.is_null())
|
||||
if (!prereq.is_valid())
|
||||
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;
|
||||
actionPrereq.dump_bits();
|
||||
std::cout << "Effect" << std::endl;
|
||||
std::cout << actionEffect.stats.dump(4) << std::endl;
|
||||
actionEffect.dump_bits();
|
||||
OgreAssert(false, "action");
|
||||
}
|
||||
}
|
||||
@@ -241,7 +241,7 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
|
||||
2000 },
|
||||
#endif
|
||||
{ "EatMedicine",
|
||||
{ { { "have_medicine", 1 }, { "healty", 0 } } },
|
||||
{ { { "have_medicine", 1 }, { "healthy", 0 } } },
|
||||
{ { { "healthy", 1 } } },
|
||||
100 },
|
||||
{ "UseToilet",
|
||||
@@ -402,91 +402,90 @@ void CharacterAIModule::createAI(flecs::entity town)
|
||||
town.add<TownAI>();
|
||||
}
|
||||
|
||||
struct PlanTask {
|
||||
Blackboard blackboard;
|
||||
TownAI::goal_t goal;
|
||||
TownAI::Plan plan;
|
||||
TownAI::planner_t *planner;
|
||||
bool operator()()
|
||||
{
|
||||
auto buildPlan = [this](Blackboard &blackboard,
|
||||
const TownAI::goal_t &goal,
|
||||
TownAI::Plan &plan) -> bool {
|
||||
if (goal.is_reached(blackboard))
|
||||
return false;
|
||||
plan.goal = &goal;
|
||||
std::vector<goap::BaseAction<Blackboard> *> path;
|
||||
int actionCount = blackboard.getActionsCount();
|
||||
auto actionsData = blackboard.getActionsData();
|
||||
path.resize(actionCount * actionCount);
|
||||
int path_length = planner->plan(
|
||||
blackboard, goal, actionsData, actionCount,
|
||||
path.data(), path.size());
|
||||
if (path_length > 0) {
|
||||
plan.goal = &goal;
|
||||
plan.plan.insert(plan.plan.end(), path.begin(),
|
||||
path.begin() + path_length);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
return buildPlan(blackboard, goal, plan);
|
||||
}
|
||||
PlanTask(Blackboard &blackboard, const TownAI::goal_t &goal,
|
||||
TownAI::planner_t *planner)
|
||||
: blackboard(blackboard)
|
||||
, goal(goal)
|
||||
, planner(planner)
|
||||
{
|
||||
}
|
||||
};
|
||||
static std::deque<PlanTask> plan_tasks;
|
||||
|
||||
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);
|
||||
auto planner = ai.planner;
|
||||
auto buildPlan = [planner](Blackboard &blackboard,
|
||||
const TownAI::goal_t &goal,
|
||||
TownAI::Plan &plan) -> bool {
|
||||
if (goal.is_reached(blackboard))
|
||||
return false;
|
||||
plan.goal = &goal;
|
||||
std::vector<goap::BaseAction<Blackboard> *> path;
|
||||
int actionCount = blackboard.getActionsCount();
|
||||
auto actionsData = blackboard.getActionsData();
|
||||
path.resize(actionCount * actionCount);
|
||||
int path_length = planner->plan(blackboard, goal, actionsData,
|
||||
actionCount, path.data(),
|
||||
path.size());
|
||||
if (path_length > 0) {
|
||||
plan.goal = &goal;
|
||||
plan.plan.insert(plan.plan.end(), path.begin(),
|
||||
path.begin() + path_length);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
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;
|
||||
const auto &npc = npcs.npcs.at(it->first);
|
||||
int index = it->first;
|
||||
ai.plans[index] = {};
|
||||
for (const auto &goal : ai.goals) {
|
||||
struct TownAI::Plan plan;
|
||||
bool created = buildPlan(bb, goal, plan);
|
||||
#if 0
|
||||
std::cout << "blackboard: "
|
||||
<< bb.stats.dump(4)
|
||||
<< std::endl;
|
||||
std::cout << "goal: "
|
||||
<< goal.goal.stats.dump(4)
|
||||
<< std::endl;
|
||||
#endif
|
||||
#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
|
||||
if (created) {
|
||||
std::cout << bb.index << " ";
|
||||
std::cout << "Goal: " << goal.get_name();
|
||||
std::cout << std::endl;
|
||||
std::cout << "Path: ";
|
||||
for (auto &action : plan.plan) {
|
||||
OgreAssert(action, "No action");
|
||||
std::cout << action->get_name() + " ";
|
||||
}
|
||||
std::cout << " size: " << plan.plan.size()
|
||||
<< std::endl;
|
||||
ai.plans[it->first].push_back(plan);
|
||||
OgreAssert(false, "plan");
|
||||
if (plan_tasks.size() > 0) {
|
||||
bool created = (plan_tasks.front())();
|
||||
if (created) {
|
||||
std::cout << plan_tasks.front().blackboard.index << " ";
|
||||
std::cout << "Goal: "
|
||||
<< plan_tasks.front().goal.get_name();
|
||||
plan_tasks.front().goal.goal.dump_bits();
|
||||
std::cout << std::endl;
|
||||
std::cout << "Path: ";
|
||||
for (auto &action : plan_tasks.front().plan.plan) {
|
||||
OgreAssert(action, "No action");
|
||||
std::cout << action->get_name() + " ";
|
||||
}
|
||||
#endif
|
||||
std::cout << " size: "
|
||||
<< plan_tasks.front().plan.plan.size()
|
||||
<< std::endl;
|
||||
ai.plans[plan_tasks.front().blackboard.index].push_back(
|
||||
plan_tasks.front().plan);
|
||||
// OgreAssert(false, "plan...");
|
||||
}
|
||||
plan_tasks.pop_front();
|
||||
} else
|
||||
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;
|
||||
const auto &npc = npcs.npcs.at(it->first);
|
||||
int index = it->first;
|
||||
ai.plans[index] = {};
|
||||
for (const auto &goal : ai.goals)
|
||||
plan_tasks.emplace_back(bb, goal,
|
||||
ai.planner.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterAIModule::createBlackboards(flecs::entity town,
|
||||
@@ -536,15 +535,7 @@ void CharacterAIModule::updateBlackboardsBits(flecs::entity town,
|
||||
{
|
||||
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[] = {
|
||||
std::vector<struct Blackboard::UpdateBit> updateBits = {
|
||||
{ "health", 0, 0, 100, 20, "healthy" },
|
||||
{ "needs_hunger", 1, 0, 10000, 2000, "hungry" },
|
||||
{ "needs_thirst", 1, 0, 10000, 1000, "thirsty" },
|
||||
@@ -553,39 +544,16 @@ void CharacterAIModule::updateBlackboardsBits(flecs::entity town,
|
||||
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;
|
||||
auto &bb = ai.blackboards.at(it->first);
|
||||
bb.commit();
|
||||
}
|
||||
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) {
|
||||
if (ai.blackboards.find(it->first) == ai.blackboards.end())
|
||||
continue;
|
||||
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;
|
||||
}
|
||||
auto &bb = ai.blackboards.at(it->first);
|
||||
bb.updateBits(updateBits, it->second.props);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,32 +566,52 @@ void CharacterAIModule::updateBlackboards(flecs::entity town,
|
||||
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();
|
||||
}
|
||||
prepareActions(ai);
|
||||
}
|
||||
|
||||
void CharacterAIModule::prepareActions(TownAI &ai)
|
||||
{
|
||||
// baking actions
|
||||
for (auto &action : ai.actions) {
|
||||
action->effects.commit();
|
||||
action->prereq.commit();
|
||||
}
|
||||
for (auto &naction : ai.nodeActions)
|
||||
for (auto &action : naction.second) {
|
||||
action->effects.commit();
|
||||
action->prereq.commit();
|
||||
}
|
||||
#if 0
|
||||
for (const auto &action : ai.actions) {
|
||||
std::cout << action->get_name() << std::endl;
|
||||
std::cout << "effects: " << std::endl;
|
||||
action->effects.dump_bits();
|
||||
std::cout << "prereq: " << std::endl;
|
||||
action->prereq.dump_bits();
|
||||
}
|
||||
for (auto &naction : ai.nodeActions)
|
||||
for (const auto &action : naction.second) {
|
||||
std::cout << action->get_name() << std::endl;
|
||||
std::cout << action->get_name() << std::endl;
|
||||
std::cout << "effects: " << std::endl;
|
||||
action->effects.dump_bits();
|
||||
std::cout << "prereq: " << std::endl;
|
||||
action->prereq.dump_bits();
|
||||
}
|
||||
std::cout << "end dump" << std::endl;
|
||||
std::cout << "dump" << std::endl;
|
||||
for (auto &m : Blackboard::mapping)
|
||||
std::cout << m.first << ": " << m.second << std::endl;
|
||||
std::cout << "end dump" << std::endl;
|
||||
// OgreAssert(false, "baking actions");
|
||||
#endif
|
||||
}
|
||||
|
||||
void Blackboard::_actionRefResize(int count)
|
||||
@@ -645,18 +633,58 @@ Blackboard::Blackboard()
|
||||
, index(-1)
|
||||
, actionRefCount(0)
|
||||
, mutex(std::make_shared<std::mutex>())
|
||||
, bits(0)
|
||||
, mask(0)
|
||||
{
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, size_t> Blackboard::mapping;
|
||||
Blackboard::Blackboard(const nlohmann::json &stats)
|
||||
: Blackboard()
|
||||
{
|
||||
this->stats = stats;
|
||||
populate(stats, mapping);
|
||||
}
|
||||
|
||||
void Blackboard::populate(const nlohmann::json &stats,
|
||||
std::unordered_map<std::string, size_t> &mapping)
|
||||
{
|
||||
if (stats.empty())
|
||||
return;
|
||||
|
||||
for (auto &[key, value] : stats.items()) {
|
||||
if (value.is_number_integer()) {
|
||||
int val = value.get<int>();
|
||||
if (val == 0 || val == 1) {
|
||||
// Update mapping if key is new
|
||||
if (mapping.find(key) == mapping.end()) {
|
||||
size_t next_bit = mapping.size();
|
||||
if (next_bit < 64) {
|
||||
mapping[key] = next_bit;
|
||||
} else {
|
||||
OgreAssert(false,
|
||||
"Out of bits");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
size_t bit_idx = mapping[key];
|
||||
bits.set(bit_idx, val == 1);
|
||||
mask.set(bit_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Blackboard::operator==(const Blackboard &other) const
|
||||
{
|
||||
return is_satisfied_by(this->stats, other.stats);
|
||||
OgreAssert(mask != 0 && other.mask != 0, "blackboard not prepared");
|
||||
#if 0
|
||||
if (mask == 0 || other.mask == 0)
|
||||
return is_satisfied_by(this->stats, other.stats);
|
||||
else
|
||||
#endif
|
||||
return (bits & other.mask) == (other.bits & other.mask);
|
||||
}
|
||||
|
||||
bool Blackboard::operator!=(const Blackboard &other) const
|
||||
@@ -668,6 +696,12 @@ void Blackboard::apply(const Blackboard &other)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*mutex);
|
||||
stats.update(other.stats);
|
||||
// 1. Clear bits in 'values' that are about to be overwritten (where effect mask is 1)
|
||||
// 2. OR the result with the effect values (filtered by the effect mask)
|
||||
bits = (bits & ~other.mask) | (other.bits & other.mask);
|
||||
|
||||
// 3. Update our mask to include any new state definitions introduced by the effect
|
||||
mask |= other.mask;
|
||||
}
|
||||
|
||||
Ogre::String Blackboard::dumpActions()
|
||||
@@ -759,6 +793,37 @@ void Blackboard::actionRefAddActions(goap::BaseAction<Blackboard> **actions,
|
||||
}
|
||||
}
|
||||
|
||||
void Blackboard::updateBits(const std::vector<UpdateBit> &updateBits,
|
||||
const nlohmann::json &props)
|
||||
{
|
||||
for (const auto &mbits : updateBits) {
|
||||
// OgreAssert(mapping.find(mbits.checkValue) != mapping.end(),
|
||||
// "No key " + mbits.checkValue);
|
||||
OgreAssert(mapping.find(mbits.writeValue) != mapping.end(),
|
||||
"No key " + mbits.writeValue);
|
||||
|
||||
int value = stats[mbits.checkValue].get<int>();
|
||||
int maxValue = mbits.maxValue;
|
||||
int minValue = mbits.minValue;
|
||||
int threshold = mbits.threshold;
|
||||
if (props.find(mbits.checkValue + "_max") != props.end())
|
||||
maxValue = props[mbits.checkValue + "_max"].get<int>();
|
||||
if (props.find(mbits.checkValue + "_threshold") != props.end())
|
||||
threshold = props[mbits.checkValue + "_threshold"]
|
||||
.get<int>();
|
||||
value += mbits.recover;
|
||||
if (value > maxValue)
|
||||
value = maxValue;
|
||||
if (value >= threshold)
|
||||
stats[mbits.writeValue] = 1;
|
||||
else
|
||||
stats[mbits.writeValue] = 0;
|
||||
if (value < mbits.minValue)
|
||||
value = mbits.minValue;
|
||||
stats[mbits.checkValue] = value;
|
||||
}
|
||||
}
|
||||
|
||||
struct ComparePair {
|
||||
const nlohmann::json ¤t;
|
||||
const nlohmann::json ⌖
|
||||
@@ -802,7 +867,9 @@ bool Blackboard::is_satisfied_by(const nlohmann::json ¤t,
|
||||
int Blackboard::distance_to(const Blackboard &goal) const
|
||||
{
|
||||
int distance = 0;
|
||||
|
||||
OgreAssert(mask != 0 && goal.mask != 0, "blackboard not prepared");
|
||||
return ((bits ^ goal.bits) & goal.mask).count();
|
||||
#if 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) {
|
||||
@@ -829,6 +896,7 @@ int Blackboard::distance_to(const Blackboard &goal) const
|
||||
}
|
||||
}
|
||||
return distance;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Blackboard::setPosition(const Ogre::Vector3 &position)
|
||||
|
||||
@@ -9,13 +9,23 @@ namespace ECS
|
||||
{
|
||||
|
||||
struct Blackboard {
|
||||
nlohmann::json stats;
|
||||
struct UpdateBit {
|
||||
Ogre::String checkValue;
|
||||
int recover;
|
||||
int minValue;
|
||||
int maxValue;
|
||||
int threshold;
|
||||
Ogre::String writeValue;
|
||||
};
|
||||
int object;
|
||||
int index;
|
||||
flecs::entity town;
|
||||
std::shared_ptr<std::mutex> mutex;
|
||||
static std::unordered_map<std::string, size_t> mapping;
|
||||
|
||||
private:
|
||||
nlohmann::json stats;
|
||||
std::bitset<64> bits, mask;
|
||||
std::vector<goap::BaseAction<Blackboard> *> actionRef;
|
||||
int actionRefCount;
|
||||
int actionRefPtr;
|
||||
@@ -25,6 +35,7 @@ private:
|
||||
void _actionRefResize(int count);
|
||||
void _actionRefAddActions(
|
||||
const std::vector<goap::BaseAction<Blackboard> *> &actions);
|
||||
void populate(const nlohmann::json &stats, std::unordered_map<std::string, size_t>& mapping);
|
||||
|
||||
public:
|
||||
Blackboard();
|
||||
@@ -41,14 +52,32 @@ public:
|
||||
const std::vector<goap::BaseAction<Blackboard> *> &actions);
|
||||
void actionRefAddActions(goap::BaseAction<Blackboard> **actions,
|
||||
int count);
|
||||
const goap::BaseAction<Blackboard> *const *getActionsData() const
|
||||
{
|
||||
return actionRef.data();
|
||||
}
|
||||
goap::BaseAction<Blackboard> **getActionsData()
|
||||
{
|
||||
return actionRef.data();
|
||||
}
|
||||
int getActionsCount()
|
||||
int getActionsCount() const
|
||||
{
|
||||
return actionRefCount;
|
||||
}
|
||||
void commit()
|
||||
{
|
||||
populate(stats, mapping);
|
||||
}
|
||||
void dump_bits() const
|
||||
{
|
||||
std::cout << "bits: " << bits << std::endl;
|
||||
std::cout << "mask: " << mask << std::endl;
|
||||
}
|
||||
bool is_valid() const
|
||||
{
|
||||
return !stats.is_null() && mask != 0;
|
||||
}
|
||||
void updateBits(const std::vector<UpdateBit> &updateBits, const nlohmann::json &props);
|
||||
|
||||
private:
|
||||
static bool is_satisfied_by(const nlohmann::json ¤t,
|
||||
@@ -79,6 +108,7 @@ struct TownAI {
|
||||
planner;
|
||||
std::unordered_map<int, Blackboard> blackboards;
|
||||
typedef goap::BasePlanner<Blackboard, goap::BaseAction<Blackboard> >::BaseGoal goal_t;
|
||||
typedef goap::BasePlanner<Blackboard, goap::BaseAction<Blackboard> > planner_t;
|
||||
struct Plan {
|
||||
const goal_t *goal;
|
||||
std::vector<goap::BaseAction<Blackboard> *> plan;
|
||||
@@ -94,6 +124,7 @@ struct CharacterAIModule {
|
||||
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);
|
||||
void prepareActions(TownAI &ai);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user