diff --git a/Game.cpp b/Game.cpp index 1a672f0..660484b 100644 --- a/Game.cpp +++ b/Game.cpp @@ -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; diff --git a/lua-scripts/data.lua b/lua-scripts/data.lua index 5b1a258..2b6d718 100644 --- a/lua-scripts/data.lua +++ b/lua-scripts/data.lua @@ -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) diff --git a/src/editor/main.cpp b/src/editor/main.cpp index c9742c3..75f8a58 100644 --- a/src/editor/main.cpp +++ b/src/editor/main.cpp @@ -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) diff --git a/src/gamedata/CMakeLists.txt b/src/gamedata/CMakeLists.txt index ecf659c..f0cb781 100644 --- a/src/gamedata/CMakeLists.txt +++ b/src/gamedata/CMakeLists.txt @@ -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) diff --git a/src/gamedata/CharacterAIModule.cpp b/src/gamedata/CharacterAIModule.cpp new file mode 100644 index 0000000..0fa3961 --- /dev/null +++ b/src/gamedata/CharacterAIModule.cpp @@ -0,0 +1,838 @@ +#include +#include +#include +#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 { + int node; + WalkToAction(int node, int cost) + : goap::BaseAction( + "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(); + const TownNPCs &npcs = bb.town.get(); + const TownAI &ai = bb.town.get(); + const Ogre::Vector3 &nodePosition = + alist.nodes[node].position; + flecs::entity e = npcs.npcs.at(bb.index).e; + bool validActive = e.is_valid() && + e.has(); + const Ogre::Vector3 &npcPosition = + validActive ? + npcs.npcs.at(bb.index) + .e.get() + .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 { + int node; + Ogre::String action; + RunActionNode(int node, const Ogre::String &action, + const Blackboard &prereq, + const Blackboard &effects, int cost) + : goap::BaseAction( + "Use(" + action + "," + + Ogre::StringConverter::toString( + node) + + ")", + prereq, effects, cost) + , node(node) + , action(action) + { + } + }; + std::vector *> m_actions; + +public: + ActionNodeActions(int node, const Blackboard &prereq, int cost) + { + OgreAssert( + node < ECS::get().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().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 effectName = ""; +#if 0 + if (action == "sit") { + const Ogre::String &nodeName = + props["name"].get(); + effectName = "is_" + nodeName + "_seated"; + } else if (action == "use") { + const Ogre::String &nodeName = + props["name"].get(); + effectName = "is_" + nodeName + "_used"; + } +#endif + // const Ogre::String &nodeName = + // props["name"].get(); + 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 *> getActions() const + { + return m_actions; + } +}; +#endif +CharacterAIModule::CharacterAIModule(flecs::world &ecs) +{ + static std::mutex ecs_mutex; + ecs.module(); + ecs.import (); + ecs.component(); + ecs.component().on_add([](flecs::entity e, TownAI &ai) { + std::lock_guard lock(ecs_mutex); + ai.mutex = std::make_shared(); + std::lock_guard 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( + adata.name, adata.prereq, adata.effects, + adata.cost)); + ai.planner = std::make_shared > >(); + }); + ecs.system("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("UpdateDynamicActions") + .kind(flecs::OnUpdate) + .each([](flecs::entity e, ActionNodeList &alist, TownAI &ai, + TownNPCs &npcs) { + std::lock_guard lock(ecs_mutex); + if (ai.nodeActions.size() > 0) + return; + if (alist.dynamicNodes.size() == 0) + ECS::get_mut() + .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("UpdateDynamicNodes") + .kind(flecs::OnUpdate) + .interval(0.1f) + .each([this](flecs::entity town, ActionNodeList &alist, + TownAI &ai, TownNPCs &npcs) { + std::lock_guard lock(ecs_mutex); + ECS::get_mut().updateDynamicNodes(); + }); + ecs.system("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 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(); + ECS::modified< + ActionNodeList>(); + }); + }); + }); + ecs.system("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 lock( + ecs_mutex); + buildPlans(town, npcs, ai); + Ogre::Root::getSingleton() + .getWorkQueue() + ->addMainThreadTask([this, + town]() { + town.modified(); + }); + }); + }); +} + +void CharacterAIModule::createAI(flecs::entity town) +{ + town.add(); +} + +void CharacterAIModule::buildPlans(flecs::entity town, const TownNPCs &npcs, + TownAI &ai) +{ + OgreAssert(town.is_valid(), "Bad town entity"); + std::lock_guard 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 *> 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 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(); + 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 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 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(); + if (it->second.props.find(bits.checkValue + + "_threshold") != + it->second.props.end()) + threshold = it->second + .props[bits.checkValue + + "_threshold"] + .get(); + 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 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()) +{ +} + +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 lock(*mutex); + stats.update(other.stats); +} + +Ogre::String Blackboard::dumpActions() +{ + std::lock_guard 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 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 lock(*mutex); + _actionRefResize(count); +} + +void Blackboard::actionRefAddAction(goap::BaseAction *action) +{ + std::lock_guard 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 *> &actions) +{ + std::lock_guard lock(*mutex); + _actionRefAddActions(actions); +} + +void Blackboard::_actionRefAddActions( + const std::vector *> &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 **actions, + int count) +{ + int i; + std::lock_guard 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 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() - tgt.get()) >= + 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() - + currentVal.get()); + } 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 lock(*mutex); + this->position = position; +} + +void Blackboard::query_ai() +{ + std::lock_guard lock(*mutex); + TownAI &ai = town.get_mut(); + const TownNPCs &npcs = town.get(); + 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() + .mBodyNode->_getDerivedPosition(); + else + from_json(npcs.npcs.at(index).props["position"], position); + this->position = position; + ActionNodeList &alist = ECS::get_mut(); + 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; + } +} +} diff --git a/src/gamedata/CharacterAIModule.h b/src/gamedata/CharacterAIModule.h new file mode 100644 index 0000000..c92a13b --- /dev/null +++ b/src/gamedata/CharacterAIModule.h @@ -0,0 +1,101 @@ +#ifndef CHARACTERAIMODULE_H +#define CHARACTERAIMODULE_H +#include +#include +#include +#include "goap.h" + +namespace ECS +{ + +struct Blackboard { + nlohmann::json stats; + int object; + int index; + flecs::entity town; + std::shared_ptr mutex; + +private: + std::vector *> actionRef; + int actionRefCount; + int actionRefPtr; + Ogre::Vector3 position; + std::vector points; + std::vector distances; + void _actionRefResize(int count); + void _actionRefAddActions( + const std::vector *> &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 *action); + void actionRefAddActions( + const std::vector *> &actions); + void actionRefAddActions(goap::BaseAction **actions, + int count); + goap::BaseAction **getActionsData() + { + return actionRef.data(); + } + int getActionsCount() + { + return actionRefCount; + } + +private: + static bool is_satisfied_by(const nlohmann::json ¤t, + 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 mutex; + std::vector >::BaseGoal> + goals; + std::vector *> actions; + std::shared_ptr< + goap::BasePlanner > > + planner; + std::unordered_map blackboards; + struct Plan { + const goap::BasePlanner >::BaseGoal *goal; + std::vector *> plan; + }; + std::unordered_map > plans; + std::unordered_map *> > 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 diff --git a/src/gamedata/CharacterManagerModule.cpp b/src/gamedata/CharacterManagerModule.cpp index af97015..a6d39db 100644 --- a/src/gamedata/CharacterManagerModule.cpp +++ b/src/gamedata/CharacterManagerModule.cpp @@ -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(); - const TownNPCs &npcs = town.get(); - nlohmann::json npcprops = npcs.npcs.at(index).props; + TownNPCs &npcs = town.get_mut(); + TownNPCs::NPCData &npc = npcs.npcs.at(index); + flecs::entity e = npc.e; + nlohmann::json npcprops = npc.props; const CharacterBase &ch = e.get(); 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(); 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(); } CharacterManagerModule::CharacterManagerModule(flecs::world &ecs) { @@ -92,16 +91,10 @@ CharacterManagerModule::CharacterManagerModule(flecs::world &ecs) ecs.component(); ecs.component(); ecs.component(); - ecs.component().on_add( - [](flecs::entity e, NPCActionNodes &anodes) { - anodes.anodes.clear(); - }); ecs.system("UpdateCharacters") - .immediate() .kind(flecs::OnUpdate) .interval(1.0f) .write() - .write() .write() .write() .write() @@ -112,73 +105,72 @@ CharacterManagerModule::CharacterManagerModule(flecs::world &ecs) return; if (!player.has()) return; - ECS::get().defer_suspend(); - Ogre::Vector3 cameraPos = - player.get() - .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(town); - break; + + Ogre::Root::getSingleton().getWorkQueue()->addMainThreadTask([this, + town]() { + flecs::entity player = + ECS::get() + .getPlayer(); + if (!player.is_valid()) + return; + if (!player.has()) + return; + TownNPCs &npcs = town.get_mut(); + Ogre::Vector3 cameraPos = + player.get() + .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( + 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( + town)) + createNPCActionNodes( + town, + index); + } } } - } - ECS::get().defer_resume(); - }); - ecs.system("UpdateCharacters2") - .immediate() - .kind(flecs::OnUpdate) - .write() - .write() - .write() - .write() - .write() - .write() - .each([this](flecs::entity town, TerrainItem &item, - TownNPCs &npcs) { - if (!player.is_valid()) - return; - if (!player.has()) - return; - Ogre::Vector3 cameraPos = - player.get() - .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() && - data.e.has(town)) - createNPCActionNodes( - town, data.e, - index); - } - } - } + town.modified(); + }); }); } 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({ rotation, position }) - .set({ "normal-male.glb" }) - .add() - // .add() - .add(); + player.add(); + ECS::get_mut().createCharacter( + player, position, rotation, "normal-male.glb"); + ECS::modified(); 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({ rotation, position }) - .set({ model }) - .add() - .add(); + flecs::entity e = ECS::get().entity(); + ECS::get_mut().createCharacter(e, position, rotation, + model); + ECS::modified(); return e; } diff --git a/src/gamedata/CharacterManagerModule.h b/src/gamedata/CharacterManagerModule.h index 016d5e1..77aab35 100644 --- a/src/gamedata/CharacterManagerModule.h +++ b/src/gamedata/CharacterManagerModule.h @@ -2,6 +2,7 @@ #define _CHARACTER_MANAGER_MODULE_ #include #include +#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 actionNodes; }; std::map npcs; diff --git a/src/gamedata/CharacterModule.cpp b/src/gamedata/CharacterModule.cpp index 3e47c17..3b70aa3 100644 --- a/src/gamedata/CharacterModule.cpp +++ b/src/gamedata/CharacterModule.cpp @@ -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(); ecs.component(); ecs.component(); - ecs.component(); - ecs.component(); - ecs.component(); ecs.component(); ecs.component(); - ecs.component().add(flecs::Singleton); ecs.import (); ecs.import (); ecs.import (); @@ -293,29 +288,6 @@ CharacterModule::CharacterModule(flecs::world &ecs) anim.configured = false; }); - ecs.observer( - "SetupCharacterObs") - .event(flecs::OnSet) - .with() - .without() - .without() - .write() - .write() - .each([&](flecs::entity e, const CharacterLocation &loc, - const CharacterConf &conf) { - std::cout << "OBSERVER!!!" - << " " << e.id() << std::endl; - if (e.has() || e.has()) - return; - e.world().defer_begin(); - e.add(); - e.add(); - e.set( - { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } }); - e.add(); - e.add(); - e.world().defer_end(); - }); #if 0 ecs.system("SetupCharacter") @@ -631,182 +603,22 @@ void CharacterModule::updateCameraGoal(Camera &camera, Ogre::Real deltaYaw, } } } -CharacterAIModule::CharacterAIModule(flecs::world &ecs) -{ - ecs.module(); - ecs.system("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("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(); - } - }); - ecs.system("UpdateCharacters2") - .kind(flecs::OnUpdate) - .without() - .each([&](flecs::entity e, Blackboard &bb, Planner &planner) { - int i; - std::vector actions; - actions.insert(actions.end(), planner.actions.begin(), - planner.actions.end()); - e.world() - .query_builder() - .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(); - 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(); - 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(); - } - } - }); +void CharacterModule::createCharacter(flecs::entity e, + const Ogre::Vector3 &position, + const Ogre::Quaternion &rotation, + const Ogre::String model) +{ + if (e.has() || e.has()) + return; + e.set({ rotation, position }); + e.set({ model }); + e.add(); + e.add(); + e.set( + { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } }); + e.add(); + e.add(); + e.add(); } } diff --git a/src/gamedata/CharacterModule.h b/src/gamedata/CharacterModule.h index dcafff0..3ddc2ae 100644 --- a/src/gamedata/CharacterModule.h +++ b/src/gamedata/CharacterModule.h @@ -2,7 +2,7 @@ #define CHARACTER_MODULE_H_ #include #include -#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 actions; -}; -struct Plan { - std::vector actions; - int position; - int length; -}; -struct Planner { - BasePlanner planner; - std::vector path; - std::vector::BaseGoal *> goals; - std::vector 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 diff --git a/src/gamedata/GUIModule.cpp b/src/gamedata/GUIModule.cpp index 9bd8bb8..1e1d75a 100644 --- a/src/gamedata/GUIModule.cpp +++ b/src/gamedata/GUIModule.cpp @@ -546,8 +546,10 @@ struct GUIListener : public Ogre::RenderTargetListener { if (list.dynamicNodes.size() > 0) { int j; Ogre::Vector3 queryPos; - std::vector points = list.points; - std::vector distances = list.distances; + std::vector points = + list.getUIData().points; + std::vector distances = + list.getUIData().distances; Ogre::SceneNode *cameraNode = ECS::get().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)); diff --git a/src/gamedata/GameData.cpp b/src/gamedata/GameData.cpp index 9f2ce58..1ebf3b1 100644 --- a/src/gamedata/GameData.cpp +++ b/src/gamedata/GameData.cpp @@ -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 (); ecs.import (); ecs.import (); + ecs.import (); ecs.add(); ecs.system("UpdateDelta") diff --git a/src/gamedata/PhysicsModule.cpp b/src/gamedata/PhysicsModule.cpp index dd8fbb9..f2624ab 100644 --- a/src/gamedata/PhysicsModule.cpp +++ b/src/gamedata/PhysicsModule.cpp @@ -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(v)); gr.velocity = Ogre::Vector3::ZERO; diff --git a/src/gamedata/PlayerActionModule.cpp b/src/gamedata/PlayerActionModule.cpp index efcff8b..b529751 100644 --- a/src/gamedata/PlayerActionModule.cpp +++ b/src/gamedata/PlayerActionModule.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #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( + lua_touserdata( + L, + lua_upvalueindex(1))); + lua_pushstring( + L, handler->getProperties().c_str()); + const std::vector + &nodes = ECS::get() + .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().npcs.at(index); flecs::entity e = npc.e; - for (const auto &anode : e.get().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 (); ecs.component() .on_add([](flecs::entity e, ActionNodeList &alist) { - alist.dirty = true; + alist.nodeMutex = std::make_shared(); + 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("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() .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("ActivateActionNode") .kind(flecs::OnUpdate) .each([this](ActionNodeList &list, const Input &input) { + std::lock_guard 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 town = ECS::get().entity(townid); - int index = list.dynamicNodes[list.selected] + int index = list.dynamicNodes[list.getUIData() + .selected] .props["index"] .get(); 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().enabled) - list.busy = false; + list.setReady(); }); } @@ -367,8 +402,7 @@ struct LuaWordHandler : PlayerActionModule::ActionWordHandler { const TownNPCs::NPCData &npc = town.get().npcs.at(index); flecs::entity e = npc.e; - for (const auto &anode : - e.get().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 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().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().each( - [&](flecs::entity e, const NPCActionNodes &anodes) { - dynamicNodes.insert(dynamicNodes.end(), - anodes.anodes.begin(), - anodes.anodes.end()); - }); - + std::lock_guard lock(*nodeMutex); indexObj = std::make_shared(dynamicNodes); indexObj->index.buildIndex(); dirty = false; } -bool ActionNodeList::query(const Ogre::Vector3 &position, - std::vector &points, - std::vector &distances) +bool ActionNodeList::_query(const Ogre::Vector3 &position, + std::vector &points, + std::vector &distances) { - build(); std::vector tmppoints; std::vector 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 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 &points, + std::vector &distances) +{ + std::lock_guard lock(*nodeMutex); + std::vector tmppoints; + std::vector 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 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 lock(*nodeMutex); + int index = nodes.size(); + nodes.push_back(node); + dirty = true; + return index; +} + +void ActionNodeList::removeNode(int index) +{ + std::lock_guard lock(*nodeMutex); + nodes.erase(nodes.begin() + index); + dirty = true; +} + +const ActionNodeList::UIData &ActionNodeList::getUIData() +{ + std::lock_guard lock(*uidata.mutex); + return uidata; +} + +void ActionNodeList::setUISelected(int selected) +{ + std::lock_guard lock(*uidata.mutex); + + uidata.selected = selected; +} + +void ActionNodeList::setUIPoints(const std::vector &points, + const std::vector &distances) +{ + std::lock_guard lock(*uidata.mutex); + uidata.points = points; + uidata.distances = distances; +} + +void ActionNodeList::UIquery(const Ogre::Vector3 &position) +{ + bool needBuild = false; + + { + std::lock_guard lock(*nodeMutex); + if (dirty || !indexObj) + needBuild = true; + } + if (needBuild) + build(); + { + std::lock_guard 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; +} } diff --git a/src/gamedata/PlayerActionModule.h b/src/gamedata/PlayerActionModule.h index 99e5589..8f2a2e9 100644 --- a/src/gamedata/PlayerActionModule.h +++ b/src/gamedata/PlayerActionModule.h @@ -18,29 +18,45 @@ struct ActionNodeList { float radius; nlohmann::json props; }; - std::vector nodes, dynamicNodes; - std::shared_ptr indexObj; - std::vector points; - std::vector distances; - int selected; + struct UIData { + std::shared_ptr mutex; + int selected; + std::vector points; + std::vector distances; + UIData() + : mutex(std::make_shared()) + , selected(-1) + { + } + }; + +private: bool dirty; bool busy; + std::shared_ptr indexObj; + struct UIData uidata; + bool _query(const Ogre::Vector3 &position, std::vector &points, + std::vector &distances); + +public: + std::shared_ptr nodeMutex; + std::vector nodes, dynamicNodes; + void updateDynamicNodes(); void build(); - bool query(const Ogre::Vector3 &position, std::vector &points, std::vector &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 anodes; + bool query_ai(const Ogre::Vector3 &position, float distance, + std::vector &points, + std::vector &distances); + int addNode(struct ActionNodeList::ActionNode &node); + void removeNode(int index); + const UIData &getUIData(); + void setUISelected(int selected); + void setUIPoints(const std::vector &points, + const std::vector &distances); + void UIquery(const Ogre::Vector3 &position); + void setDirty(); // node was added or removed + void setReady(); + void setBusy(); + bool isBusy(); }; struct PlayerActionModule { struct ActionWordHandler { diff --git a/src/gamedata/TerrainModule.cpp b/src/gamedata/TerrainModule.cpp index 33c4eb9..57e1ed9 100644 --- a/src/gamedata/TerrainModule.cpp +++ b/src/gamedata/TerrainModule.cpp @@ -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, diff --git a/src/gamedata/goap.cpp b/src/gamedata/goap.cpp index 40577cf..876dfc3 100644 --- a/src/gamedata/goap.cpp +++ b/src/gamedata/goap.cpp @@ -1,38 +1,5 @@ #include #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); } diff --git a/src/gamedata/goap.h b/src/gamedata/goap.h index 1e56eb9..6d133bd 100644 --- a/src/gamedata/goap.h +++ b/src/gamedata/goap.h @@ -1,77 +1,35 @@ #ifndef H_GOAP_H_ #define H_GOAP_H_ +#undef NDEBUG #include #include #include #include -namespace ECS +#include +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 _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 struct BaseAction { std::string m_name; + State prereq; + State effects; + int m_cost; public: enum { OK = 0, BUSY, ABORT }; -private: - std::unique_ptr 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 class BasePlanner { struct VisitedState { int priority; @@ -190,20 +126,24 @@ template 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 struct DeclareAction : public BaseAction { +template +struct DeclareAction : public BaseAction { RunnerType runner; template DeclareAction(const std::string &name, Args... args) : runner(args...) - , BaseAction(name, &runner) + , BaseAction(name, &runner) { } }; +} #endif diff --git a/src/gamedata/items/items.h b/src/gamedata/items/items.h index 747bbce..9b21b59 100644 --- a/src/gamedata/items/items.h +++ b/src/gamedata/items/items.h @@ -2,6 +2,8 @@ #define __ITEMS_H__ #include #include +#include "Components.h" +#include "GameData.h" namespace ECS { namespace Items diff --git a/src/gamedata/items/town.cpp b/src/gamedata/items/town.cpp index c8f0d73..e273bc4 100644 --- a/src/gamedata/items/town.cpp +++ b/src/gamedata/items/town.cpp @@ -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().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().c_str(), + sizeof(firstName)); + strncpy(lastName, npc["lastName"].get().c_str(), + sizeof(lastName)); + strncpy(nickName, npc["nickName"].get().c_str(), + sizeof(lastName)); + strncpy(tags, npc["tags"].get().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(); + 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().sceneNode->_getDerivedPosition(); - Ogre::Quaternion npcOrientation = - ECS::get() - .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() + .sceneNode->_getDerivedPosition(); + Ogre::Quaternion npcOrientation = + ECS::get() + .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().is_valid()) + if (ECS::get().has()) { + ECS::get_mut().createAI(e); + ECS::modified(); + } } } } diff --git a/src/world/CMakeLists.txt b/src/world/CMakeLists.txt index f9ac896..1ff299f 100644 --- a/src/world/CMakeLists.txt +++ b/src/world/CMakeLists.txt @@ -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)