New game works almost as intended, quest system
This commit is contained in:
@@ -33,7 +33,6 @@ class ActionNodeActions {
|
||||
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);
|
||||
@@ -49,13 +48,8 @@ class ActionNodeActions {
|
||||
{
|
||||
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(
|
||||
@@ -359,9 +353,6 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
|
||||
std::lock_guard<std::mutex> lock(
|
||||
ecs_mutex);
|
||||
|
||||
alist.build();
|
||||
updateBlackboardsBits(
|
||||
town, alist, npcs, ai);
|
||||
updateBlackboards(town, alist,
|
||||
npcs, ai);
|
||||
}
|
||||
@@ -370,12 +361,12 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
|
||||
->addMainThreadTask([this, town,
|
||||
&alist]() {
|
||||
town.modified<TownAI>();
|
||||
town.modified<TownNPCs>();
|
||||
ECS::modified<
|
||||
ActionNodeList>();
|
||||
});
|
||||
});
|
||||
});
|
||||
#if 1
|
||||
ecs.system<TownAI, TownNPCs>("PlanAI")
|
||||
.kind(flecs::OnUpdate)
|
||||
.interval(0.5f)
|
||||
@@ -394,7 +385,6 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
|
||||
});
|
||||
});
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void CharacterAIModule::createAI(flecs::entity town)
|
||||
@@ -466,7 +456,6 @@ void CharacterAIModule::buildPlans(flecs::entity town, const TownNPCs &npcs,
|
||||
<< std::endl;
|
||||
ai.plans[plan_tasks.front().blackboard.index].push_back(
|
||||
plan_tasks.front().plan);
|
||||
// OgreAssert(false, "plan...");
|
||||
}
|
||||
plan_tasks.pop_front();
|
||||
} else
|
||||
@@ -504,20 +493,24 @@ void CharacterAIModule::createBlackboards(flecs::entity town,
|
||||
strength += 10;
|
||||
dexterity += 10;
|
||||
}
|
||||
// FIXME: use separate "memory" for stats
|
||||
// Do not keep actual stats in blackboard
|
||||
nlohmann::json memory;
|
||||
memory["strength"] = strength;
|
||||
memory["dexterity"] = dexterity;
|
||||
memory["health"] = health;
|
||||
memory["stamina"] = stamina;
|
||||
|
||||
memory["needs_hunger"] = 0;
|
||||
memory["needs_thirst"] = 0;
|
||||
memory["needs_toilet"] = 0;
|
||||
|
||||
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.memory[it->first] = memory;
|
||||
ai.blackboards[it->first] = Blackboard(bb);
|
||||
ai.blackboards[it->first].index = it->first;
|
||||
ai.blackboards.at(it->first).town = town;
|
||||
@@ -525,93 +518,103 @@ void CharacterAIModule::createBlackboards(flecs::entity town,
|
||||
ai.blackboards.at(it->first).actionRefResize(0);
|
||||
ai.blackboards.at(it->first).actionRefAddActions(
|
||||
ai.actions);
|
||||
ai.conditions =
|
||||
TownAI::BitEvolutionBuilder()
|
||||
.addRule(
|
||||
"healthy",
|
||||
[](const nlohmann::json &data)
|
||||
-> bool {
|
||||
return data["health"]
|
||||
.get<int>() >
|
||||
20;
|
||||
})
|
||||
->addRule(
|
||||
"hungry",
|
||||
[](const nlohmann::json &data)
|
||||
-> bool {
|
||||
return data["needs_hunger"]
|
||||
.get<int>() >
|
||||
2000;
|
||||
})
|
||||
->addRule(
|
||||
"thirsty",
|
||||
[](const nlohmann::json &data)
|
||||
-> bool {
|
||||
return data["needs_thirst"]
|
||||
.get<int>() >
|
||||
1000;
|
||||
})
|
||||
->addRule(
|
||||
"toilet",
|
||||
[](const nlohmann::json &data)
|
||||
-> bool {
|
||||
return data["needs_toilet"]
|
||||
.get<int>() >
|
||||
1500;
|
||||
})
|
||||
->build();
|
||||
ai.memoryUpdates =
|
||||
TownAI::MemoryUpdateBuilder()
|
||||
.addUpdate(
|
||||
"health",
|
||||
[](int index,
|
||||
const std::string &name,
|
||||
int value) -> int {
|
||||
if (value < 50)
|
||||
return value +
|
||||
1;
|
||||
else
|
||||
return value;
|
||||
})
|
||||
.addUpdate("needs_hunger",
|
||||
[](int index,
|
||||
const std::string &name,
|
||||
int value) -> int {
|
||||
return value + 1;
|
||||
})
|
||||
.addUpdate("needs_thirst",
|
||||
[](int index,
|
||||
const std::string &name,
|
||||
int value) -> int {
|
||||
return value + 1;
|
||||
})
|
||||
.addUpdate("needs_toilet",
|
||||
[](int index,
|
||||
const std::string &name,
|
||||
int value) -> int {
|
||||
return value + 1;
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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" },
|
||||
{ "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 &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;
|
||||
auto &bb = ai.blackboards.at(it->first);
|
||||
bb.updateBits(updateBits, it->second.props);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterAIModule::updateBlackboards(flecs::entity town,
|
||||
const ActionNodeList &alist,
|
||||
ActionNodeList &alist,
|
||||
const TownNPCs &npcs, TownAI &ai)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*ai.mutex);
|
||||
OgreAssert(town.is_valid(), "Bad town entity");
|
||||
alist.build();
|
||||
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 (auto e : ai.memoryUpdates) {
|
||||
std::string key = e.name;
|
||||
auto &memory = ai.memory.at(it->first);
|
||||
if (memory.find(key) == memory.end())
|
||||
memory[key] = 0;
|
||||
int value = memory[key].get<int>();
|
||||
int new_value = e.update(it->first, key, value);
|
||||
if (value != new_value)
|
||||
memory[key] = new_value;
|
||||
}
|
||||
auto &bb = ai.blackboards.at(it->first);
|
||||
bb.updateBits(ai, ai.memory.at(it->first), it->second.props);
|
||||
bb.query_ai();
|
||||
|
||||
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)
|
||||
@@ -628,8 +631,7 @@ void Blackboard::_actionRefResize(int count)
|
||||
}
|
||||
|
||||
Blackboard::Blackboard()
|
||||
: stats(nlohmann::json::object())
|
||||
, object(-1)
|
||||
: object(-1)
|
||||
, index(-1)
|
||||
, actionRefCount(0)
|
||||
, mutex(std::make_shared<std::mutex>())
|
||||
@@ -642,7 +644,6 @@ std::unordered_map<std::string, size_t> Blackboard::mapping;
|
||||
Blackboard::Blackboard(const nlohmann::json &stats)
|
||||
: Blackboard()
|
||||
{
|
||||
this->stats = stats;
|
||||
populate(stats, mapping);
|
||||
}
|
||||
|
||||
@@ -679,11 +680,6 @@ void Blackboard::populate(const nlohmann::json &stats,
|
||||
bool Blackboard::operator==(const Blackboard &other) const
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -695,7 +691,7 @@ bool Blackboard::operator!=(const Blackboard &other) const
|
||||
void Blackboard::apply(const Blackboard &other)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*mutex);
|
||||
stats.update(other.stats);
|
||||
// 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);
|
||||
@@ -713,8 +709,10 @@ Ogre::String Blackboard::dumpActions()
|
||||
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";
|
||||
ret += "\tprereq:\n";
|
||||
action->prereq.dump_bits();
|
||||
ret += "\teffects:\n";
|
||||
action->effects.dump_bits();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -724,23 +722,6 @@ 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);
|
||||
@@ -793,34 +774,14 @@ void Blackboard::actionRefAddActions(goap::BaseAction<Blackboard> **actions,
|
||||
}
|
||||
}
|
||||
|
||||
void Blackboard::updateBits(const std::vector<UpdateBit> &updateBits,
|
||||
void Blackboard::updateBits(const TownAI &ai, const nlohmann::json &memory,
|
||||
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;
|
||||
for (const auto &mcond : ai.conditions) {
|
||||
if (mapping.find(mcond.key) == mapping.end())
|
||||
populate(nlohmann::json::object({ { mcond.key, 0 } }),
|
||||
mapping);
|
||||
bits.set(mapping[mcond.key], mcond.condition(memory));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -869,34 +830,6 @@ 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) {
|
||||
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;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Blackboard::setPosition(const Ogre::Vector3 &position)
|
||||
@@ -922,7 +855,6 @@ void Blackboard::query_ai()
|
||||
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;
|
||||
@@ -931,10 +863,10 @@ void Blackboard::query_ai()
|
||||
if (object >= 0 && (size_t)object == point &&
|
||||
distance > radius * radius) {
|
||||
object = -1;
|
||||
stats["at_object"] = 0;
|
||||
bits.set(mapping["at_object"], false);
|
||||
} else if (object >= 0 && (size_t)object == point &&
|
||||
distance <= radius * radius)
|
||||
stats["at_object"] = 1;
|
||||
bits.set(mapping["at_object"], true);
|
||||
/* some nodes do not have usable actions */
|
||||
if (ai.nodeActions[point].size() > 0) {
|
||||
OgreAssert(ai.nodeActions[point].size() > 0,
|
||||
@@ -951,7 +883,6 @@ void Blackboard::query_ai()
|
||||
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