diff --git a/Game.cpp b/Game.cpp index 660484b..1a4656c 100644 --- a/Game.cpp +++ b/Game.cpp @@ -17,6 +17,7 @@ #include "TerrainModule.h" #include "GUIModuleCommon.h" #include "AppModule.h" +#include "GUIModule.h" #include "sound.h" class App; class SkyRenderer : public Ogre::SceneManager::Listener { @@ -470,16 +471,10 @@ public: // TODO: implement rough water level calculation float getWaterLevel(const Ogre::Vector3 &position) { - Ogre::Vector3::UNIT_Y; - float etime = - Ogre::ControllerManager::getSingleton().getElapsedTime(); return 0.0f; } void updateWorld(float delta) { -#if 0 - mDynWorld->getBtWorld()->stepSimulation(delta, 3); -#endif if (!ECS::get().has()) goto end; { @@ -495,13 +490,9 @@ public: } end: ECS::update(delta); - -#if 0 - if (ECS::get().enableDbgDraw) - mDbgDraw->update(); -#endif } - class InputListenerChainFlexible : public OgreBites::InputListener { +#if 0 + class InputListenerChainFlexible : public OgreBites::InputListener { protected: std::vector mListenerChain; @@ -604,6 +595,7 @@ end: bool mousePressed(const OgreBites::MouseButtonEvent &evt) override { + std::cout << mListenerChain.size() << std::endl; for (auto listner : mListenerChain) { if (listner->mousePressed(evt)) return true; @@ -628,6 +620,7 @@ end: return false; } }; +#endif flecs::entity input_update; flecs::entity find_wait_gui; void setupInput() @@ -652,108 +645,44 @@ end: "Skybox/Dynamic", "General"); OgreAssert(m, "Sky box material not found."); m->load(); + ECS::get().component().add(flecs::Singleton); + ECS::get().set( + { getRenderWindow(), getDisplayDPI() }); ECS::setupExteriorScene(mScnMgr, /*mDynWorld.get(), */ mCameraNode, mCamera, getRenderWindow()); - ECS::get().set( - { getRenderWindow(), getDisplayDPI() }); - ECS::get() - .observer("UpdateGrab") - .event(flecs::OnSet) - .each([this](ECS::GUI &gui) { - if (gui.grabChanged) - setWindowGrab(gui.grab); - std::cout << "grab: " << gui.grab << "\n"; - std::cout << "GUI enabled: " << gui.enabled - << "\n"; - }); - ECS::get() - .observer("UpdateInputListener") - .event(flecs::OnSet) - .each([this](ECS::App &app) { - if (app.mInput) - removeInputListener(app.mInput); - delete app.mInput; - app.mInput = - OGRE_NEW OgreBites::InputListenerChain( - app.listeners); - addInputListener(app.mInput); - }); -#if 0 - ECS::get() - .observer("SetInputListener2") - .event(flecs::OnSet) - .each([this](ECS::GUI &gui, ECS::App &app) { - if (gui.mGuiInpitListener && - app.listeners.size() == 1) { - app.listeners.clear(); - app.listeners.push_back( - gui.mGuiInpitListener); - app.listeners.push_back(&mKbd); - ECS::modified(); - } - }); -#endif -#if 0 - input_update = - ECS::get() - .system("SetInputListener") - .kind(flecs::OnUpdate) - .each([this](ECS::GUI &gui, ECS::App &app) { - if (app.listeners.size() < 2 && - gui.mGuiInpitListener) { - OgreBites::InputListener *guiListener = - gui.mGuiInpitListener; - if (guiListener) { - app.listeners.clear(); - app.listeners.push_back( - guiListener); - app.listeners.push_back( - &mKbd); - std::cout - << "input update complete\n"; - gui.mGuiInpitListener = - guiListener; - if (app.mInput) - removeInputListener( - app.mInput); - delete app.mInput; - app.mInput = new OgreBites:: - InputListenerChain( - app.listeners); - addInputListener( - app.mInput); - std::cout - << "update listeners: " - << app.listeners - .size() - << "\n"; - if (app.listeners - .size() == - 2) - OgreAssert( - app.listeners.size() == - 2, - ""); - input_update.disable(); - OgreAssert(false, ""); - } else { - app.listeners.clear(); - app.listeners.push_back( - &mKbd); - } - } else - input_update.disable(); - std::cout << "input update " - << app.listeners.size() - << "\n"; - }); -#endif - ECS::get().set( - { initialiseImGui(), - nullptr, - { getImGuiInputListener(), &mKbd } }); - Sound::setup(); + ECS::get() + .observer("UpdateInputListener") + .event(flecs::OnSet) + .each([this](ECS::App &app) { + if (app.mInput) + removeInputListener(app.mInput); + delete app.mInput; + app.mInput = + OGRE_NEW OgreBites::InputListenerChain( + app.listeners); + addInputListener(app.mInput); + }); + ECS::get().set( + { initialiseImGui(), + nullptr, + { getImGuiInputListener(), &mKbd } }); + /* FIXME: this is bad */ + ECS::GUIModule::configure(); + ECS::get() + .observer("UpdateGrab") + .event(flecs::OnSet) + .each([this](ECS::GUI &gui) { + if (gui.grabChanged) + setWindowGrab(gui.grab); + std::cout << "grab: " << gui.grab << "\n"; + std::cout << "GUI enabled: " << gui.enabled + << "\n"; + }); + ECS::get_mut().grab = false; + ECS::get_mut().grabChanged = true; + ECS::modified(); + Sound::setup(); Sound::ding(); } void create_entity_node(const Ogre::String &name, int key) @@ -786,10 +715,6 @@ end: { return mCamera; } - flecs::entity getPlayer() const - { - return ECS::player; - } void enableDbgDraw(bool enable) { ECS::get_mut().enableDbgDraw = enable; diff --git a/lua-scripts/data.lua b/lua-scripts/data.lua index 60acbd5..4a971b5 100644 --- a/lua-scripts/data.lua +++ b/lua-scripts/data.lua @@ -219,30 +219,99 @@ function Quest(name, book) return quest end function StartGameQuest() + local quest = {} -- Parse a book from the Ink file. - local book = narrator.parse_file('stories.initiation') - local quest = Quest('start game', book) - quest.base = {} - quest.base.activate = quest.activate - quest.base.complete = quest.complete - quest.boat = false quest.activate = function(this) print('activate...') - local mc_is_free = function() - this.boat = true - local ent = ecs_get_player_entity() - ecs_character("params-set", ent, "gravity", true) - ecs_character("params-set", ent, "buoyancy", true) - end - this.story:bind('mc_is_free', mc_is_free) - this.base.activate(this) + local book = narrator.parse_file('stories.initiation') + local story = narrator.init_story(book) + this._add_narration({ + book = book, + story = story, + activate = function(this) + local mc_is_free = function() + this.boat = true + local ent = ecs_get_player_entity() + ecs_character("params-set", ent, "gravity", true) + ecs_character("params-set", ent, "buoyancy", true) + end + this.story:bind('mc_is_free', mc_is_free) + this.story:begin() + this:narration_update() + end, + event = function(this, event) + if event == "narration_progress" then + this:narration_update() + end + if event == "narration_answered" then + local answer = this._get_narration_answer() + this.story:choose(answer) + this:narration_update() + end + end, + finish = function(this) + end, + narration_update = function(this) + local ret = "" + local choices = {} + local have_choice = false + local have_paragraph = false + print("narration_update") + if this.story:can_continue() then + print("CAN continue") + have_paragraph = true + local paragraph = this.story:continue(1) + print(dump(paragraph)) + local text = paragraph.text + if paragraph.tags then + -- text = text .. ' #' .. table.concat(paragraph.tags, ' #') + for i, tag in ipairs(paragraph.tags) do + if tag == 'discard' then + text = '' + elseif tag == 'crash' then + print(text) + crash() + end + this:handle_tag(tag) + end + end + ret = text + if this.story:can_choose() then + have_choice = true + local ch = this.story:get_choices() + for i, choice in ipairs(ch) do + table.insert(choices, choice.text) + print(i, dump(choice)) + end + if #choices == 1 and choices[1] == "Ascend" then + this.story:choose(1) + choices = {} + end + if #choices == 1 and choices[1] == "Continue" then + this.story:choose(1) + choices = {} + end + end + else + print("can NOT continue") + end + print(ret) + if (#choices > 0) then + print("choices!!!") + this._narration(ret, choices) + else + this._narration(ret, {}) + end + if not have_choice and not have_paragraph then + this._finish() + end + end, + handle_tag = function(this, tag) + print("tag: " .. tag) + end, + }) end - quest.complete = function(this) - this.base.complete(this) - this.active = false - if not this.boat then - ecs_save_object_debug(boat, 'boat.scene') - end + quest.finish = function(this) end return quest end @@ -616,25 +685,27 @@ setup_handler(function(event, trigger_entity, what_entity) return end if event == "startup" then - main_menu() + -- elseif event == "narration_progress" then print("narration progress!") elseif event == "narration_answered" then local answer = narration_get_answer() print("answered:", answer) + elseif event == "spawn_player" then elseif event == "new_game" then local ent = ecs_get_player_entity() ecs_character("params-set", ent, "gravity", true) ecs_character("params-set", ent, "buoyancy", false) - local quest = StartGameQuest() - quests[quest.name] = quest - for k, v in pairs(quests) do - print(k, v.active) - end - quest:activate() local start_boat = create_boat() table.insert(vehicles, start_boat) table.insert(player_vehicles, start_boat) + local quest = StartGameQuest() + add_quest("main", quest) +-- quests[quest.name] = quest +-- for k, v in pairs(quests) do +-- print(k, v.active) +-- end +-- quest:activate() elseif event == "actuator_created" then print(trigger_entity) local act = create_actuator2(trigger_entity) @@ -829,3 +900,5 @@ setup_action_handler("talk", function(town, index, word) end) ]]-- +main_menu() + diff --git a/src/editor/main.cpp b/src/editor/main.cpp index 75f8a58..dc948ed 100644 --- a/src/editor/main.cpp +++ b/src/editor/main.cpp @@ -661,10 +661,6 @@ public: ECS::get().get_mut().enabled = active; ECS::get().modified(); } - flecs::entity getPlayer() const - { - return ECS::player; - } void enableDbgDraw(bool enable) { ECS::get_mut().enableDbgDraw = enable; diff --git a/src/gamedata/CMakeLists.txt b/src/gamedata/CMakeLists.txt index f0cb781..f921c60 100644 --- a/src/gamedata/CMakeLists.txt +++ b/src/gamedata/CMakeLists.txt @@ -8,7 +8,7 @@ add_subdirectory(items) add_library(GameData STATIC GameData.cpp CharacterModule.cpp WaterModule.cpp SunModule.cpp TerrainModule.cpp 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 + VehicleManagerModule.cpp AppModule.cpp StaticGeometryModule.cpp SmartObject.cpp SlotsModule.cpp QuestModule.cpp PlayerActionModule.cpp CharacterAIModule.cpp goap.cpp) target_link_libraries(GameData PUBLIC lua diff --git a/src/gamedata/CharacterAIModule.cpp b/src/gamedata/CharacterAIModule.cpp index 28f0799..8b37cd4 100644 --- a/src/gamedata/CharacterAIModule.cpp +++ b/src/gamedata/CharacterAIModule.cpp @@ -33,7 +33,6 @@ class ActionNodeActions { const ActionNodeList &alist = ECS::get(); 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(); - // const TownNPCs &npcs = bb.town.get(); - // const TownAI &ai = bb.town.get(); const Ogre::Vector3 &nodePosition = alist.dynamicNodes[node].position; - // flecs::entity e = npcs.npcs.at(bb.index).e; - // bool validActive = e.is_valid() && - // e.has(); const Ogre::Vector3 &npcPosition = bb.getPosition(); float dist = npcPosition.squaredDistance( @@ -359,9 +353,6 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs) std::lock_guard 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(); + town.modified(); ECS::modified< ActionNodeList>(); }); }); }); -#if 1 ecs.system("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() > + 20; + }) + ->addRule( + "hungry", + [](const nlohmann::json &data) + -> bool { + return data["needs_hunger"] + .get() > + 2000; + }) + ->addRule( + "thirsty", + [](const nlohmann::json &data) + -> bool { + return data["needs_thirst"] + .get() > + 1000; + }) + ->addRule( + "toilet", + [](const nlohmann::json &data) + -> bool { + return data["needs_toilet"] + .get() > + 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 lock(*ai.mutex); - std::vector 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 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 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()) @@ -642,7 +644,6 @@ std::unordered_map 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 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 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); @@ -793,34 +774,14 @@ void Blackboard::actionRefAddActions(goap::BaseAction **actions, } } -void Blackboard::updateBits(const std::vector &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 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(); - if (props.find(mbits.checkValue + "_threshold") != props.end()) - threshold = props[mbits.checkValue + "_threshold"] - .get(); - 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() - - currentVal.get()); - } 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(); 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; } } } diff --git a/src/gamedata/CharacterAIModule.h b/src/gamedata/CharacterAIModule.h index d37fd83..f9bc994 100644 --- a/src/gamedata/CharacterAIModule.h +++ b/src/gamedata/CharacterAIModule.h @@ -8,6 +8,8 @@ namespace ECS { +struct TownAI; + struct Blackboard { struct UpdateBit { Ogre::String checkValue; @@ -24,7 +26,7 @@ struct Blackboard { static std::unordered_map mapping; private: - nlohmann::json stats; + // nlohmann::json stats; std::bitset<64> bits, mask; std::vector *> actionRef; int actionRefCount; @@ -45,7 +47,6 @@ public: void apply(const Blackboard &other); Ogre::String dumpActions(); void printActions(); - void fixupBooleanKeys(); void actionRefResize(int count); void actionRefAddAction(goap::BaseAction *action); void actionRefAddActions( @@ -64,10 +65,12 @@ public: { return actionRefCount; } +#if 0 void commit() { populate(stats, mapping); } +#endif void dump_bits() const { std::cout << "bits: " << bits << std::endl; @@ -75,9 +78,9 @@ public: } bool is_valid() const { - return !stats.is_null() && mask != 0; + return /* !stats.is_null() && */ mask != 0; } - void updateBits(const std::vector &updateBits, const nlohmann::json &props); + void updateBits(const TownAI &ai, const nlohmann::json &memory, const nlohmann::json &props); private: static bool is_satisfied_by(const nlohmann::json ¤t, @@ -107,14 +110,62 @@ struct TownAI { goap::BasePlanner > > planner; std::unordered_map blackboards; - typedef goap::BasePlanner >::BaseGoal goal_t; - typedef goap::BasePlanner > planner_t; + typedef goap::BasePlanner< + Blackboard, goap::BaseAction >::BaseGoal goal_t; + typedef goap::BasePlanner > + planner_t; struct Plan { const goal_t *goal; std::vector *> plan; }; std::unordered_map > plans; - std::unordered_map *> > nodeActions; + std::unordered_map *> > + nodeActions; + struct Condition { + std::string key; + std::function condition; + }; + struct MemoryUpdate { + std::string name; + std::function + update; + }; + + struct BitEvolutionBuilder { + std::vector conditions; + BitEvolutionBuilder * + addRule(const std::string &key, + std::function + condition) + { + conditions.push_back({ key, condition }); + return this; + } + std::vector build() + { + return conditions; + } + }; + struct MemoryUpdateBuilder { + std::vector memoryUpdates; + MemoryUpdateBuilder & + addUpdate(const std::string &name, + std::function + update) + { + memoryUpdates.push_back({ name, update }); + return *this; + } + std::vector build() + { + return memoryUpdates; + } + }; + + std::vector conditions; + std::unordered_map memory; + std::vector memoryUpdates; }; struct CharacterAIModule { @@ -122,9 +173,7 @@ struct CharacterAIModule { 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); - void prepareActions(TownAI &ai); + void updateBlackboards(flecs::entity town, ActionNodeList &alist, const TownNPCs &npcs, TownAI &ai); }; } diff --git a/src/gamedata/CharacterAnimationModule.cpp b/src/gamedata/CharacterAnimationModule.cpp index 7d755bf..b65b84b 100644 --- a/src/gamedata/CharacterAnimationModule.cpp +++ b/src/gamedata/CharacterAnimationModule.cpp @@ -259,15 +259,6 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs) } #endif v.velocity = rot * boneMotion / safeDelta; -#if 0 - if (!e.has() && - !e.has()) { - if (eng.startupDelay <= 0.0f) - v.velocity += v.gvelocity; - v.velocity.y = Ogre::Math::Clamp(v.velocity.y, - -10.5f, 10.0f); - } -#endif // if (v.velocity.squaredLength() > 1.4f * 1.4f) // v.velocity = v.velocity.normalisedCopy() * 1.4f; // ch.mBoneMotion = Ogre::Vector3::ZERO; diff --git a/src/gamedata/CharacterModule.cpp b/src/gamedata/CharacterModule.cpp index 76e8c21..9fb4b56 100644 --- a/src/gamedata/CharacterModule.cpp +++ b/src/gamedata/CharacterModule.cpp @@ -307,30 +307,6 @@ CharacterModule::CharacterModule(flecs::world &ecs) // "need contact response"); }); #endif -#if 0 - ecs.system( - "UpdateCharacterPhysics") - .kind(flecs::OnUpdate) - .with() - .with() - .with() - .each([](const EngineData &eng, CharacterBase &ch, - CharacterBody &body) { -#if 0 - if (ch.mBodyNode && !body.mController && - eng.startupDelay < 0.0f) { - body.mController = - new Ogre::Bullet::KinematicMotionSimple( - body.mGhostObject, - ch.mBodyNode); - body.mController->enableManualNarrowPhase(true); - eng.mWorld->getBtWorld()->addAction( - body.mController); - OgreAssert(body.mController, "Need controller"); - } -#endif - }); -#endif #define CAM_HEIGHT 1.6f // height of camera above character's center of mass ecs.system( "UpdateCamera") diff --git a/src/gamedata/Components.h b/src/gamedata/Components.h index d6a7af3..cb3a704 100644 --- a/src/gamedata/Components.h +++ b/src/gamedata/Components.h @@ -78,5 +78,8 @@ struct Body2Entity { struct EditorSceneSwitch { int scene; }; +struct GameState { + bool running; +}; } #endif diff --git a/src/gamedata/GUIModule.cpp b/src/gamedata/GUIModule.cpp index c51e71a..3953d78 100644 --- a/src/gamedata/GUIModule.cpp +++ b/src/gamedata/GUIModule.cpp @@ -26,6 +26,7 @@ #include "CharacterManagerModule.h" #include "items.h" #include "physics.h" +#include "QuestModule.h" #include "GUIModule.h" #include "GUIModuleCommon.h" namespace ECS @@ -60,13 +61,6 @@ struct GUIListener : public Ogre::RenderTargetListener { bigFont = ECS::get().mGuiOverlay->addFont("bigFont", "General"); OgreAssert(bigFont, "Could not load font"); -#if 0 - Ogre::FontPtr _midFont = createFont("midFont", "General", - "Kenney Bold.ttf", 28.0f); -#endif -#if 0 - ImGui::GetIO().Fonts->Build(); -#endif } Ogre::FontPtr createFont(const Ogre::String &name, const Ogre::String &group, @@ -100,8 +94,6 @@ struct GUIListener : public Ogre::RenderTargetListener { ImGui::SetNextWindowSize(ImVec2(window_width, window_height), ImGuiCond_Always); ImGui::Begin("Control"); - // if (ECS::get().get().enabled) - // ECS::get().get().app->setWindowGrab(true); if (ImGui::Button("Quit")) Ogre::Root::getSingleton().queueEndRendering(); if (ImGui::Button("Return")) @@ -251,11 +243,15 @@ struct GUIListener : public Ogre::RenderTargetListener { { int i; Ogre::ImGuiOverlay::NewFrame(); - if (ECS::get().get().startupDelay > 0.0f) { + + if (ECS::get().get().startupDelay > 0.0f && + ECS::get().has()) { ImVec2 size = ImGui::GetMainViewport()->Size; ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); ImGui::SetNextWindowSize(ImVec2(size.x, size.y), ImGuiCond_Always); + ImVec4 solidColor = ImVec4(0.0f, 0.0f, 0.0f, 1.0f); + ImGui::PushStyleColor(ImGuiCol_WindowBg, solidColor); ImGui::Begin( "StartupScreen", nullptr, ImGuiWindowFlags_NoTitleBar | @@ -274,6 +270,7 @@ struct GUIListener : public Ogre::RenderTargetListener { "This game does not autosave. Please use save function to keep your state"); ImGui::PopFont(); ImGui::End(); + ImGui::PopStyleColor(); } else if (ECS::get().narrationHandlers.size() > 0) { ECS::get_mut().grab = false; ECS::get_mut().grabChanged = true; @@ -376,9 +373,14 @@ struct GUIListener : public Ogre::RenderTargetListener { ImVec2 size = ImGui::GetMainViewport()->Size; ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); - ImGui::SetNextWindowSize(ImVec2(size.x, size.y), + ImGui::SetNextWindowSize(ImVec2(size.x + 4, + size.y + 1), ImGuiCond_Always); - ImGui::Begin( + ImVec4 solidColor = + ImVec4(0.0f, 0.0f, 0.0f, 1.0f); + ImGui::PushStyleColor(ImGuiCol_WindowBg, + solidColor); + ImGui::Begin( "MainMenu", nullptr, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDecoration | @@ -388,8 +390,6 @@ struct GUIListener : public Ogre::RenderTargetListener { ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoFocusOnAppearing | 0); - // if (ECS::get().get().enabled) - // ECS::get().get().app->setWindowGrab(true); ImGui::PushFont(bigFont); ImGui::TextWrapped("%s", "Booo!!!!"); bool pressed = false; @@ -412,16 +412,22 @@ struct GUIListener : public Ogre::RenderTargetListener { ImGui::PopFont(); ImGui::Spacing(); ImGui::End(); - if (quit) + ImGui::PopStyleColor(); + if (quit) Ogre::Root::getSingleton() .queueEndRendering(); if (pressed) ECS::get().get().finish(); if (new_game) { - ECS::get().mLua->call_handler( - "new_game"); - } - } else { + ECS::get().set({ true }); + ECS::get().mLua->call_handler( + "new_game_selected"); + } else if (load_game) { + ECS::get().set({ true }); + ECS::get().mLua->call_handler( + "load_game_selected"); + } + } else { buttons_panel(); if (enableEditor) buildings_editor(); @@ -439,7 +445,6 @@ struct GUIListener : public Ogre::RenderTargetListener { ImGui::SetNextWindowSize(ImVec2(window_width, window_height), ImGuiCond_Always); - // ImGui::Begin("Dumb and Stupid", &mKbd.gui_active); ImGui::Begin("Panel..."); std::deque tree_input_queue, tree_output_queue; @@ -645,41 +650,6 @@ struct GUIListener : public Ogre::RenderTargetListener { .action_text .c_str()); } -#if 0 - ImGui::SetNextWindowPos( - ImVec2(screenPos.x, - screenPos.y), - ImGuiCond_Always, - ImVec2(0.5f, 0.5f)); - ImGui::Begin( - ("SensorLabel##" + - Ogre::StringConverter::toString( - p)) - .c_str(), - nullptr, - ImGuiWindowFlags_NoBackground | - ImGuiWindowFlags_NoTitleBar | - ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoInputs); - ImDrawList *drawList = - ImGui::GetWindowDrawList(); - ImVec2 cursor = - ImGui::GetCursorScreenPos(); - drawList->AddCircleFilled( - ImVec2(cursor.x, cursor.y), - 16.0f, - IM_COL32(0, 0, 255, 255)); - drawList->AddCircle( - ImVec2(cursor.x, cursor.y), - 16.0f, - IM_COL32(32, 32, 255, 255)); - - ImGui::TextColored( - ImVec4(1, 0, 0, 1), "%s", - list.nodes[p] - .action_text.c_str()); - ImGui::End(); -#endif } } } @@ -690,13 +660,14 @@ GUIModule::GUIModule(flecs::world &ecs) { ecs.module(); ecs.import (); + ecs.import (); ecs.component() .on_add([](GUI &gui) { gui.enabled = false; gui.grab = false; gui.grabChanged = false; gui.enableActions = true; - }) + }) .add(flecs::Singleton); ecs.component() .on_add([](GUIData &priv) { @@ -705,51 +676,39 @@ GUIModule::GUIModule(flecs::world &ecs) priv.mGuiOverlay = nullptr; }) .add(flecs::Singleton); - ecs.set({ false, true, false, false, false, true, "", {}, -1 }); - ecs.set({ nullptr, {}, nullptr }); - ui_wait = - ecs.system("SetupGUI") - .kind(flecs::OnUpdate) - .each([this](const RenderWindow &window, App &app, - GUIData &gui) { - if (!gui.mGuiOverlay) { - float vpScale = - window.dpi / 96 * - window.window->getWidth() / - 1600.0f; - Ogre::OverlayManager::getSingleton() - .setPixelRatio(vpScale); - std::cout << "GUI configure\n"; - OgreAssert(app.mGuiOverlay, - "No ImGUI overlay"); - gui.mGuiOverlay = app.mGuiOverlay; - gui.mGUIListener = new GUIListener(); - gui.mGuiOverlay->setZOrder(300); - gui.mGuiOverlay->show(); - gui.mGUIListener->panel_width = 300.0f; - gui.mGUIListener->enableEditor = false; - window.window->addListener( - gui.mGUIListener); - int i; +} - const std::vector &groups = - Ogre::ResourceGroupManager:: - getSingleton() - .getResourceGroups(); - for (i = 0; i < groups.size(); i++) { - std::vector names = - *Ogre::ResourceGroupManager::getSingleton() - .findResourceNames( - groups[i], - "*.glb"); - gui.glb_names.insert( - gui.glb_names.end(), - names.begin(), - names.end()); - } - ECS::modified(); - std::cout << "GUI configure finished\n"; - } - }); +void GUIModule::configure() +{ + ECS::get().set({ nullptr, {}, nullptr }); + const RenderWindow &window = ECS::get(); + GUIData &gui = ECS::get_mut(); + const App &app = ECS::get(); + if (gui.mGuiOverlay) + return; + float vpScale = window.dpi / 96 * window.window->getWidth() / 1600.0f; + Ogre::OverlayManager::getSingleton().setPixelRatio(vpScale); + std::cout << "GUI configure\n"; + OgreAssert(app.mGuiOverlay, "No ImGUI overlay"); + gui.mGuiOverlay = app.mGuiOverlay; + gui.mGUIListener = new GUIListener(); + gui.mGuiOverlay->setZOrder(300); + gui.mGuiOverlay->show(); + gui.mGUIListener->panel_width = 300.0f; + gui.mGUIListener->enableEditor = false; + window.window->addListener(gui.mGUIListener); + int i; + + const std::vector &groups = + Ogre::ResourceGroupManager::getSingleton().getResourceGroups(); + for (i = 0; i < groups.size(); i++) { + std::vector names = + *Ogre::ResourceGroupManager::getSingleton() + .findResourceNames(groups[i], "*.glb"); + gui.glb_names.insert(gui.glb_names.end(), names.begin(), + names.end()); + } + ECS::modified(); + std::cout << "GUI configure finished\n"; } } diff --git a/src/gamedata/GUIModule.h b/src/gamedata/GUIModule.h index 890ea7e..d0b5ff1 100644 --- a/src/gamedata/GUIModule.h +++ b/src/gamedata/GUIModule.h @@ -9,6 +9,7 @@ namespace ECS struct GUIModule { flecs::entity ui_wait; GUIModule(flecs::world &ecs); + static void configure(); }; } #endif diff --git a/src/gamedata/GameData.cpp b/src/gamedata/GameData.cpp index d3ef70f..ed26904 100644 --- a/src/gamedata/GameData.cpp +++ b/src/gamedata/GameData.cpp @@ -25,6 +25,7 @@ #include "PlayerActionModule.h" #include "AppModule.h" #include "CharacterAIModule.h" +#include "QuestModule.h" #include "world-build.h" namespace ECS @@ -49,9 +50,9 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode, Ogre::Camera *camera, Ogre::RenderWindow *window) { std::cout << "Setup GameData\n"; - setup_minimal(); - ecs.component().add(flecs::Singleton); - ecs.import (); + setup_minimal(); + ecs.component().add(flecs::Singleton); + ecs.import (); ecs.import (); ecs.import (); ecs.import (); @@ -64,8 +65,11 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode, ecs.import (); ecs.import (); ecs.import (); + ecs.import (); ecs.add(); + ecs.set({ false, false, true, false, false, true, "", {}, -1 }); + ecs.system("UpdateDelta") .kind(flecs::OnUpdate) .each([](EngineData &eng) { @@ -73,6 +77,7 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode, }); ecs.system("UpdateDelay") .kind(flecs::OnUpdate) + .with() .with() .with() .with() @@ -119,49 +124,69 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode, // ecs.set({}); std::cout << "Setup GameData done\n"; - ecs.system("SpawnPlayer").kind(flecs::OnUpdate).interval(0.5f).run([&](flecs::iter &it) { - flecs::entity player = - ECS::get().getPlayer(); - if (!player.is_valid()) { - /* Create player */ - Ogre::Vector3 position; - JPH::BodyID id; - long x, y; - Ogre::TerrainGroup *tg = - ECS::get().mTerrainGroup; - if (tg->isDerivedDataUpdateInProgress()) - return; - tg->convertWorldPositionToTerrainSlot( - Ogre::Vector3(0, 0, 4), &x, &y); - Ogre::Terrain *terrain = tg->getTerrain(x, y); - if (terrain && terrain->isLoaded()) { - if (PhysicsModule::raycastQuery( - Ogre::Vector3(0, 500, 4), - Ogre::Vector3(0, -500, 4), position, - id)) { - if (position.y < -10.0f && - position.y > -50.0f) { - player = - ecs.get_mut< - CharacterManagerModule>() - .createPlayer( - { position.x, - position.y, - position.z }, - Ogre::Quaternion( - Ogre::Radian( - Ogre::Math:: - PI), - Ogre::Vector3:: - UNIT_Y)); - std::cout << position - << std::endl; - // OgreAssert(false, "spawn"); + ecs.system("SpawnPlayer") + .kind(flecs::OnUpdate) + .interval(0.5f) + .each([&](Terrain &mterrain, GameState &game) { + flecs::entity player = + ECS::get().getPlayer(); + if (!player.is_valid()) { + /* Create player */ + Ogre::Vector3 position; + JPH::BodyID id; + long x, y; + Ogre::TerrainGroup *tg = mterrain.mTerrainGroup; + if (tg->isDerivedDataUpdateInProgress()) + return; + tg->convertWorldPositionToTerrainSlot( + Ogre::Vector3(0, 0, 4), &x, &y); + Ogre::Terrain *terrain = tg->getTerrain(x, y); + if (terrain && terrain->isLoaded()) { + if (PhysicsModule::raycastQuery( + Ogre::Vector3(0, 500, 4), + Ogre::Vector3(0, -500, 4), + position, id)) { + if (position.y < -10.0f && + position.y > -50.0f) { + player = + ecs.get_mut< + CharacterManagerModule>() + .createPlayer( + { position.x, + position.y, + position.z }, + Ogre::Quaternion( + Ogre::Radian( + Ogre::Math:: + PI), + Ogre::Vector3:: + UNIT_Y)); + std::cout << position + << std::endl; + } } } - } - } - }); + } + }); + // FIXME: convert this later to scene setup event + static flecs::entity new_game_run = + ecs.system("NewGame") + .kind(flecs::OnUpdate) + .interval(0.5f) + .each([&](GameState &game) { + flecs::entity player = + ECS::get() + .getPlayer(); + if (player.is_valid() && + player.has() && + player.has() && + player.has()) { + ECS::get().mLua->call_handler( + "new_game"); + new_game_run.destruct(); + } + }); + std::cout << "scene setup done" << std::endl; } void setupInteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode, Ogre::Camera *camera, Ogre::RenderWindow *window) @@ -250,8 +275,6 @@ void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode, ecs.modified(); ecs.get_mut().setWindowGrab(true); ecs.modified(); - ecs.get_mut().enabled = true; - ecs.modified(); } void update(float delta) diff --git a/src/gamedata/GameData.h b/src/gamedata/GameData.h index f0d3298..7f924fb 100644 --- a/src/gamedata/GameData.h +++ b/src/gamedata/GameData.h @@ -1,9 +1,9 @@ #ifndef GAMEDATA_H #define GAMEDATA_H +#include #include namespace ECS { -extern flecs::entity player; void setup_minimal(); void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode, Ogre::Camera *camera, Ogre::RenderWindow *window); diff --git a/src/gamedata/LuaData.cpp b/src/gamedata/LuaData.cpp index fee336f..0f8111b 100644 --- a/src/gamedata/LuaData.cpp +++ b/src/gamedata/LuaData.cpp @@ -12,6 +12,7 @@ #include "SlotsModule.h" #include "world-build.h" #include "PlayerActionModule.h" +#include "QuestModule.h" #include "LuaData.h" #include "lua.hpp" extern "C" { @@ -123,7 +124,7 @@ int LuaData::call_handler(const Ogre::String &event) lua_pushstring(L, event.c_str()); lua_pushinteger(L, -1); lua_pushinteger(L, -1); - if (lua_pcall(L, 3, 0, 0)) { + if (lua_pcall(L, 3, 0, 0) != LUA_OK) { Ogre::LogManager::getSingleton().stream() << lua_tostring(L, -1); OgreAssert(false, "Lua error"); @@ -141,7 +142,7 @@ int LuaData::call_handler(const Ogre::String &event, flecs::entity e, lua_pushstring(L, event.c_str()); lua_pushinteger(L, idmap.add_entity(e)); lua_pushinteger(L, idmap.add_entity(o)); - if (lua_pcall(L, 3, 0, 0)) { + if (lua_pcall(L, 3, 0, 0) != LUA_OK) { Ogre::LogManager::getSingleton().stream() << lua_tostring(L, -1); OgreAssert(false, "Lua error"); @@ -315,6 +316,14 @@ LuaData::LuaData() return 0; }); lua_setglobal(L, "setup_action_handler"); + lua_pushcfunction(L, [](lua_State *L) -> int { + luaL_checktype(L, 1, LUA_TSTRING); + luaL_checktype(L, 2, LUA_TTABLE); + ECS::get_mut().addLuaQuest(L); + ECS::modified(); + return 0; + }); + lua_setglobal(L, "add_quest"); lua_pushcfunction(L, [](lua_State *L) -> int { // FIXME return 0; diff --git a/src/gamedata/QuestModule.cpp b/src/gamedata/QuestModule.cpp new file mode 100644 index 0000000..bd8dd34 --- /dev/null +++ b/src/gamedata/QuestModule.cpp @@ -0,0 +1,326 @@ +#include +#include "Components.h" +#include "GameData.h" +#include "GUIModuleCommon.h" +#include "QuestModule.h" + +namespace ECS +{ + +QuestModule::QuestModule(flecs::world &ecs) +{ + ecs.module(); + ecs.observer("EnableQuests") + .event(flecs::OnAdd) + .each([this](GameState &game) { + quest_update = + ECS::get() + .system( + "UpdateQuests") + .each([this](const EngineData &eng) { + for (auto &quest : quests) { + if (!quest->_is_complete()) + quest->_update( + eng.delta); + } + }); + }); + ecs.observer("DisableQuests") + .event(flecs::OnRemove) + .each([this](GameState &game) { + if (quest_update.is_valid()) + quest_update.destruct(); + }); +} + +void QuestModule::addQuest(Quest *quest) +{ + quests.insert(quest); +} + +void QuestModule::removeQuest(Quest *quest) +{ + quests.erase(quests.find(quest)); +} +struct LuaNarrationHandler : GUI::NarrationHandler { + int ref; + lua_State *L; + LuaNarrationHandler(lua_State *L, int ref) + : ref(ref) + , L(L) + { + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + lua_pushlightuserdata(L, this); + lua_pushcclosure( + L, + [](lua_State *L) { + luaL_checktype(L, 1, LUA_TSTRING); + luaL_checktype(L, 2, LUA_TTABLE); + LuaNarrationHandler *handler = + static_cast( + lua_touserdata( + L, + lua_upvalueindex(1))); + Ogre::String event = lua_tostring(L, 1); + std::vector choices; + int choicesLen = (int)lua_rawlen(L, 2); + choices.reserve(choicesLen); + for (int i = 1; i <= choicesLen; ++i) { + lua_rawgeti(L, 2, i); + if (lua_isstring(L, -1)) + choices.push_back( + lua_tostring(L, -1)); + lua_pop(L, 1); + } + handler->_narration(event, choices); + return 0; + }, + 1); + lua_setfield(L, -2, "_narration"); + lua_pushlightuserdata(L, this); + lua_pushcclosure( + L, + [](lua_State *L) { + LuaNarrationHandler *handler = + static_cast( + lua_touserdata( + L, + lua_upvalueindex(1))); + handler->_finish(); + return 0; + }, + 1); + lua_setfield(L, -2, "_finish"); + lua_pushlightuserdata(L, this); + lua_pushcclosure( + L, + [](lua_State *L) { + LuaNarrationHandler *handler = + static_cast( + lua_touserdata( + L, + lua_upvalueindex(1))); + int answer = handler->getNarrationAnswer(); + lua_pushinteger(L, answer); + return 1; + }, + 1); + lua_setfield(L, -2, "_get_narration_answer"); + 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()); + return 1; + }, + 1); + lua_setfield(L, -2, "_get_properties"); + lua_pop(L, 1); + } + void finish() override + { + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + int type = lua_getfield(L, -1, "finish"); + OgreAssert(type == LUA_TFUNCTION, "bad finish()"); + lua_insert(L, -2); + if (lua_pcall(L, 1, 0, 0) != 0) { + std::cerr << lua_tostring(L, -1) << std::endl; + OgreAssert(false, "lua error"); + lua_pop(L, 1); + } + _clear_narration(); + } + void activate() override + { + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + int type = lua_getfield(L, -1, "activate"); + OgreAssert(type == LUA_TFUNCTION, "bad activate()"); + lua_insert(L, -2); + if (lua_pcall(L, 1, 0, 0) != 0) { + std::cerr << lua_tostring(L, -1) << std::endl; + OgreAssert(false, "lua error"); + lua_pop(L, 1); + } + } + void event(const Ogre::String &evt) override + { + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + int type = lua_getfield(L, -1, "event"); + OgreAssert(type == LUA_TFUNCTION, "bad event()"); + lua_insert(L, -2); + lua_pushstring(L, evt.c_str()); + if (lua_pcall(L, 2, 0, 0) != 0) { + std::cerr << lua_tostring(L, -1) << std::endl; + OgreAssert(false, "lua error"); + lua_pop(L, 1); + } + } +}; + +struct LuaQuest : QuestModule::Quest { + lua_State *L; + int ref; + LuaQuest(const std::string &name, lua_State *L, int ref) + : Quest(name) + , L(L) + , ref(ref) + { + OgreAssert(L, "bad Lua state"); + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + lua_pushlightuserdata(L, this); + lua_pushcclosure( + L, + [](lua_State *L) { + LuaQuest *_this = static_cast( + lua_touserdata(L, lua_upvalueindex(1))); + _this->_finish(LuaQuest::OK); + return 0; + }, + 1); + lua_setfield(L, -2, "_finish"); + lua_pushlightuserdata(L, this); + lua_pushcclosure( + L, + [](lua_State *L) { + LuaQuest *_this = static_cast( + lua_touserdata(L, lua_upvalueindex(1))); + luaL_checktype(L, 1, LUA_TTABLE); + lua_pushvalue(L, 1); + int nref = luaL_ref(L, LUA_REGISTRYINDEX); + LuaNarrationHandler *handle = + OGRE_NEW LuaNarrationHandler(L, nref); + ECS::get_mut().addNarrationHandler(handle); + ECS::modified(); + + return 0; + }, + 1); + lua_setfield(L, -2, "_add_narration"); + lua_pop(L, 1); + } + void finish(int rc) override + { + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + int type = lua_getfield(L, -1, "finish"); + OgreAssert(type == LUA_TFUNCTION, "bad finish()"); + lua_insert(L, -2); + lua_pushinteger(L, rc); + if (lua_pcall(L, 2, 0, 0) != 0) { + std::cerr << lua_tostring(L, -1) << std::endl; + OgreAssert(false, "lua error"); + lua_pop(L, 1); + } + } + void activate() override + { + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + int type = lua_getfield(L, -1, "activate"); + OgreAssert(type == LUA_TFUNCTION, "bad activate()"); + lua_insert(L, -2); + if (lua_pcall(L, 1, 0, 0) != 0) { + std::cerr << lua_tostring(L, -1) << std::endl; + OgreAssert(false, "lua error"); + lua_pop(L, 1); + } + } + void event(const Ogre::String &evt) override + { + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + int type = lua_getfield(L, -1, "event"); + OgreAssert(type == LUA_TFUNCTION, "bad event()"); + lua_insert(L, -2); + lua_pushstring(L, evt.c_str()); + if (lua_pcall(L, 2, 0, 0) != 0) { + std::cerr << lua_tostring(L, -1) << std::endl; + OgreAssert(false, "lua error"); + lua_pop(L, 1); + } + } + int update(float delta) override + { + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + int type = lua_getfield(L, -1, "finish"); + OgreAssert(type == LUA_TFUNCTION, "bad finish()"); + lua_insert(L, -2); + if (lua_pcall(L, 1, 1, 0) != LUA_OK) { + std::cerr << lua_tostring(L, -1) << std::endl; + OgreAssert(false, "lua error"); + lua_pop(L, 1); + } else { + int ret = lua_tointeger(L, -1); + lua_pop(L, 1); + return ret; + } + return ERROR; + } +}; + +void QuestModule::addLuaQuest(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TSTRING); + luaL_checktype(L, 2, LUA_TTABLE); + Ogre::String name = lua_tostring(L, 1); + lua_pushvalue(L, 2); + int ref = luaL_ref(L, LUA_REGISTRYINDEX); + struct LuaQuest *quest = OGRE_NEW LuaQuest(name, L, ref); + addQuest(quest); +} + +void QuestModule::Quest::_activate() +{ + activate(); + active = true; +} + +void QuestModule::Quest::_finish(int rc) +{ + finish(rc); + active = false; + if (rc == OK) + complete = true; +} + +QuestModule::Quest::Quest(const std::string &name) + : name(name) + , active(false) + , complete(false) + , updatePeriod(1) + , timeAcc(0) +{ +} + +QuestModule::Quest::~Quest() +{ +} + +int QuestModule::Quest::_update(float delta) +{ + if (!active && !_can_activate()) + return ERROR; + if (!active) + _activate(); + timeAcc += delta; + + if (timeAcc > updatePeriod) + timeAcc = 0; + else + return BUSY; + int ret = update(delta); + if (ret == BUSY) + return ret; + _finish(ret); + return ret; +} + +void QuestModule::Quest::_event(const std::string &evt) +{ + event(evt); +} + +} // namespace ECS diff --git a/src/gamedata/QuestModule.h b/src/gamedata/QuestModule.h new file mode 100644 index 0000000..aab197b --- /dev/null +++ b/src/gamedata/QuestModule.h @@ -0,0 +1,47 @@ +#ifndef ECS_QUESTMODULE_H +#define ECS_QUESTMODULE_H + +#include +#include +#include + +namespace ECS { + +struct QuestModule +{ + struct Quest { + enum {OK = 0, BUSY, ERROR}; + private: + virtual void activate() = 0; + virtual int update(float delta) = 0; + virtual void finish(int ret) = 0; + virtual void event(const std::string &evt) = 0; + virtual bool _can_activate() {return true;}; + protected: + std::string name; + bool active; + bool complete; + float updatePeriod; + float timeAcc; + void _activate(); + void _finish(int ret); + public: + Quest(const std::string &name); + virtual ~Quest(); + int _update(float delta); + void _event(const std::string &evt); + bool _is_active() {return active;} + bool _is_complete() {return complete;} + }; + std::set quests; + flecs::entity quest_update; +public: + QuestModule(flecs::world &ecs); + void addQuest(Quest *quest); + void removeQuest(Quest *quest); + void addLuaQuest(lua_State *L); +}; + +} // namespace ECS + +#endif // ECS_QUESTMODULE_H