New game works almost as intended, quest system

This commit is contained in:
2026-02-05 18:21:25 +03:00
parent 4fb7e94fed
commit 0405214388
16 changed files with 826 additions and 517 deletions

155
Game.cpp
View File

@@ -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<ECS::GUI>())
goto end;
{
@@ -495,13 +490,9 @@ public:
}
end:
ECS::update(delta);
#if 0
if (ECS::get<ECS::EngineData>().enableDbgDraw)
mDbgDraw->update();
#endif
}
class InputListenerChainFlexible : public OgreBites::InputListener {
#if 0
class InputListenerChainFlexible : public OgreBites::InputListener {
protected:
std::vector<OgreBites::InputListener *> 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<ECS::RenderWindow>().add(flecs::Singleton);
ECS::get().set<ECS::RenderWindow>(
{ getRenderWindow(), getDisplayDPI() });
ECS::setupExteriorScene(mScnMgr,
/*mDynWorld.get(), */ mCameraNode,
mCamera, getRenderWindow());
ECS::get().set<ECS::RenderWindow>(
{ getRenderWindow(), getDisplayDPI() });
ECS::get()
.observer<ECS::GUI>("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<ECS::App>("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<ECS::GUI, ECS::App>("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<ECS::App>();
}
});
#endif
#if 0
input_update =
ECS::get()
.system<ECS::GUI, ECS::App>("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<ECS::App>(
{ initialiseImGui(),
nullptr,
{ getImGuiInputListener(), &mKbd } });
Sound::setup();
ECS::get()
.observer<ECS::App>("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<ECS::App>(
{ initialiseImGui(),
nullptr,
{ getImGuiInputListener(), &mKbd } });
/* FIXME: this is bad */
ECS::GUIModule::configure();
ECS::get()
.observer<ECS::GUI>("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<ECS::GUI>().grab = false;
ECS::get_mut<ECS::GUI>().grabChanged = true;
ECS::modified<ECS::GUI>();
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<ECS::EngineData>().enableDbgDraw = enable;

View File

@@ -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()

View File

@@ -661,10 +661,6 @@ public:
ECS::get().get_mut<ECS::GUI>().enabled = active;
ECS::get().modified<ECS::GUI>();
}
flecs::entity getPlayer() const
{
return ECS::player;
}
void enableDbgDraw(bool enable)
{
ECS::get_mut<ECS::EngineData>().enableDbgDraw = enable;

View File

@@ -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

View File

@@ -33,7 +33,6 @@ class ActionNodeActions {
const ActionNodeList &alist =
ECS::get<ActionNodeList>();
state.apply(effects);
// node position can change in case of character
const Ogre::Vector3 &nodePosition =
alist.dynamicNodes[node].position;
state.setPosition(nodePosition);
@@ -49,13 +48,8 @@ class ActionNodeActions {
{
const ActionNodeList &alist =
ECS::get<ActionNodeList>();
// const TownNPCs &npcs = bb.town.get<TownNPCs>();
// const TownAI &ai = bb.town.get<TownAI>();
const Ogre::Vector3 &nodePosition =
alist.dynamicNodes[node].position;
// flecs::entity e = npcs.npcs.at(bb.index).e;
// bool validActive = e.is_valid() &&
// e.has<CharacterBase>();
const Ogre::Vector3 &npcPosition =
bb.getPosition();
float dist = npcPosition.squaredDistance(
@@ -359,9 +353,6 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
std::lock_guard<std::mutex> lock(
ecs_mutex);
alist.build();
updateBlackboardsBits(
town, alist, npcs, ai);
updateBlackboards(town, alist,
npcs, ai);
}
@@ -370,12 +361,12 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
->addMainThreadTask([this, town,
&alist]() {
town.modified<TownAI>();
town.modified<TownNPCs>();
ECS::modified<
ActionNodeList>();
});
});
});
#if 1
ecs.system<TownAI, TownNPCs>("PlanAI")
.kind(flecs::OnUpdate)
.interval(0.5f)
@@ -394,7 +385,6 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
});
});
});
#endif
}
void CharacterAIModule::createAI(flecs::entity town)
@@ -466,7 +456,6 @@ void CharacterAIModule::buildPlans(flecs::entity town, const TownNPCs &npcs,
<< std::endl;
ai.plans[plan_tasks.front().blackboard.index].push_back(
plan_tasks.front().plan);
// OgreAssert(false, "plan...");
}
plan_tasks.pop_front();
} else
@@ -504,20 +493,24 @@ void CharacterAIModule::createBlackboards(flecs::entity town,
strength += 10;
dexterity += 10;
}
// FIXME: use separate "memory" for stats
// Do not keep actual stats in blackboard
nlohmann::json memory;
memory["strength"] = strength;
memory["dexterity"] = dexterity;
memory["health"] = health;
memory["stamina"] = stamina;
memory["needs_hunger"] = 0;
memory["needs_thirst"] = 0;
memory["needs_toilet"] = 0;
nlohmann::json bb;
bb["strength"] = strength;
bb["dexterity"] = dexterity;
bb["health"] = health;
bb["stamina"] = stamina;
bb["needs_hunger"] = 0;
bb["needs_thirst"] = 0;
bb["needs_toilet"] = 0;
bb["have_water"] = 0;
bb["have_food"] = 0;
bb["have_medicine"] = 0;
bb["at_object"] = 0;
ai.memory[it->first] = memory;
ai.blackboards[it->first] = Blackboard(bb);
ai.blackboards[it->first].index = it->first;
ai.blackboards.at(it->first).town = town;
@@ -525,93 +518,103 @@ void CharacterAIModule::createBlackboards(flecs::entity town,
ai.blackboards.at(it->first).actionRefResize(0);
ai.blackboards.at(it->first).actionRefAddActions(
ai.actions);
ai.conditions =
TownAI::BitEvolutionBuilder()
.addRule(
"healthy",
[](const nlohmann::json &data)
-> bool {
return data["health"]
.get<int>() >
20;
})
->addRule(
"hungry",
[](const nlohmann::json &data)
-> bool {
return data["needs_hunger"]
.get<int>() >
2000;
})
->addRule(
"thirsty",
[](const nlohmann::json &data)
-> bool {
return data["needs_thirst"]
.get<int>() >
1000;
})
->addRule(
"toilet",
[](const nlohmann::json &data)
-> bool {
return data["needs_toilet"]
.get<int>() >
1500;
})
->build();
ai.memoryUpdates =
TownAI::MemoryUpdateBuilder()
.addUpdate(
"health",
[](int index,
const std::string &name,
int value) -> int {
if (value < 50)
return value +
1;
else
return value;
})
.addUpdate("needs_hunger",
[](int index,
const std::string &name,
int value) -> int {
return value + 1;
})
.addUpdate("needs_thirst",
[](int index,
const std::string &name,
int value) -> int {
return value + 1;
})
.addUpdate("needs_toilet",
[](int index,
const std::string &name,
int value) -> int {
return value + 1;
})
.build();
}
}
}
void CharacterAIModule::updateBlackboardsBits(flecs::entity town,
ActionNodeList &alist,
const TownNPCs &npcs, TownAI &ai)
{
OgreAssert(town.is_valid(), "Bad town entity");
std::lock_guard<std::mutex> lock(*ai.mutex);
std::vector<struct Blackboard::UpdateBit> updateBits = {
{ "health", 0, 0, 100, 20, "healthy" },
{ "needs_hunger", 1, 0, 10000, 2000, "hungry" },
{ "needs_thirst", 1, 0, 10000, 1000, "thirsty" },
{ "needs_toilet", 1, 0, 10000, 1500, "toilet" }
};
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) {
if (ai.blackboards.find(it->first) == ai.blackboards.end())
continue;
auto &bb = ai.blackboards.at(it->first);
bb.commit();
}
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) {
if (ai.blackboards.find(it->first) == ai.blackboards.end())
continue;
ai.blackboards.at(it->first).index = it->first;
ai.blackboards.at(it->first).town = town;
auto &bb = ai.blackboards.at(it->first);
bb.updateBits(updateBits, it->second.props);
}
}
void CharacterAIModule::updateBlackboards(flecs::entity town,
const ActionNodeList &alist,
ActionNodeList &alist,
const TownNPCs &npcs, TownAI &ai)
{
std::lock_guard<std::mutex> lock(*ai.mutex);
OgreAssert(town.is_valid(), "Bad town entity");
alist.build();
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) {
if (ai.blackboards.find(it->first) == ai.blackboards.end())
continue;
ai.blackboards.at(it->first).index = it->first;
ai.blackboards.at(it->first).town = town;
for (auto e : ai.memoryUpdates) {
std::string key = e.name;
auto &memory = ai.memory.at(it->first);
if (memory.find(key) == memory.end())
memory[key] = 0;
int value = memory[key].get<int>();
int new_value = e.update(it->first, key, value);
if (value != new_value)
memory[key] = new_value;
}
auto &bb = ai.blackboards.at(it->first);
bb.updateBits(ai, ai.memory.at(it->first), it->second.props);
bb.query_ai();
bb.fixupBooleanKeys();
}
prepareActions(ai);
}
void CharacterAIModule::prepareActions(TownAI &ai)
{
// baking actions
for (auto &action : ai.actions) {
action->effects.commit();
action->prereq.commit();
}
for (auto &naction : ai.nodeActions)
for (auto &action : naction.second) {
action->effects.commit();
action->prereq.commit();
}
#if 0
for (const auto &action : ai.actions) {
std::cout << action->get_name() << std::endl;
std::cout << "effects: " << std::endl;
action->effects.dump_bits();
std::cout << "prereq: " << std::endl;
action->prereq.dump_bits();
}
for (auto &naction : ai.nodeActions)
for (const auto &action : naction.second) {
std::cout << action->get_name() << std::endl;
std::cout << action->get_name() << std::endl;
std::cout << "effects: " << std::endl;
action->effects.dump_bits();
std::cout << "prereq: " << std::endl;
action->prereq.dump_bits();
}
std::cout << "end dump" << std::endl;
std::cout << "dump" << std::endl;
for (auto &m : Blackboard::mapping)
std::cout << m.first << ": " << m.second << std::endl;
std::cout << "end dump" << std::endl;
// OgreAssert(false, "baking actions");
#endif
}
void Blackboard::_actionRefResize(int count)
@@ -628,8 +631,7 @@ void Blackboard::_actionRefResize(int count)
}
Blackboard::Blackboard()
: stats(nlohmann::json::object())
, object(-1)
: object(-1)
, index(-1)
, actionRefCount(0)
, mutex(std::make_shared<std::mutex>())
@@ -642,7 +644,6 @@ std::unordered_map<std::string, size_t> Blackboard::mapping;
Blackboard::Blackboard(const nlohmann::json &stats)
: Blackboard()
{
this->stats = stats;
populate(stats, mapping);
}
@@ -679,11 +680,6 @@ void Blackboard::populate(const nlohmann::json &stats,
bool Blackboard::operator==(const Blackboard &other) const
{
OgreAssert(mask != 0 && other.mask != 0, "blackboard not prepared");
#if 0
if (mask == 0 || other.mask == 0)
return is_satisfied_by(this->stats, other.stats);
else
#endif
return (bits & other.mask) == (other.bits & other.mask);
}
@@ -695,7 +691,7 @@ bool Blackboard::operator!=(const Blackboard &other) const
void Blackboard::apply(const Blackboard &other)
{
std::lock_guard<std::mutex> lock(*mutex);
stats.update(other.stats);
// stats.update(other.stats);
// 1. Clear bits in 'values' that are about to be overwritten (where effect mask is 1)
// 2. OR the result with the effect values (filtered by the effect mask)
bits = (bits & ~other.mask) | (other.bits & other.mask);
@@ -713,8 +709,10 @@ Ogre::String Blackboard::dumpActions()
for (count = 0; count < actionRefCount; count++) {
auto &action = actionRef[count];
ret += "name: " + action->get_name() + "\n";
ret += "\tprereq:\n" + action->prereq.stats.dump(4) + "\n";
ret += "\teffects:\n" + action->effects.stats.dump(4) + "\n";
ret += "\tprereq:\n";
action->prereq.dump_bits();
ret += "\teffects:\n";
action->effects.dump_bits();
}
return ret;
}
@@ -724,23 +722,6 @@ void Blackboard::printActions()
std::cout << dumpActions() << std::endl;
}
void Blackboard::fixupBooleanKeys()
{
std::lock_guard<std::mutex> lock(*mutex);
int count;
for (count = 0; count < actionRefCount; count++) {
auto &action = actionRef[count];
const nlohmann::json &prereq = action->prereq.stats;
const nlohmann::json &effects = action->effects.stats;
for (auto it = prereq.begin(); it != prereq.end(); it++)
if (stats.find(it.key()) == stats.end())
stats[it.key()] = 0;
for (auto it = effects.begin(); it != effects.end(); it++)
if (stats.find(it.key()) == stats.end())
stats[it.key()] = 0;
}
}
void Blackboard::actionRefResize(int count)
{
std::lock_guard<std::mutex> lock(*mutex);
@@ -793,34 +774,14 @@ void Blackboard::actionRefAddActions(goap::BaseAction<Blackboard> **actions,
}
}
void Blackboard::updateBits(const std::vector<UpdateBit> &updateBits,
void Blackboard::updateBits(const TownAI &ai, const nlohmann::json &memory,
const nlohmann::json &props)
{
for (const auto &mbits : updateBits) {
// OgreAssert(mapping.find(mbits.checkValue) != mapping.end(),
// "No key " + mbits.checkValue);
OgreAssert(mapping.find(mbits.writeValue) != mapping.end(),
"No key " + mbits.writeValue);
int value = stats[mbits.checkValue].get<int>();
int maxValue = mbits.maxValue;
int minValue = mbits.minValue;
int threshold = mbits.threshold;
if (props.find(mbits.checkValue + "_max") != props.end())
maxValue = props[mbits.checkValue + "_max"].get<int>();
if (props.find(mbits.checkValue + "_threshold") != props.end())
threshold = props[mbits.checkValue + "_threshold"]
.get<int>();
value += mbits.recover;
if (value > maxValue)
value = maxValue;
if (value >= threshold)
stats[mbits.writeValue] = 1;
else
stats[mbits.writeValue] = 0;
if (value < mbits.minValue)
value = mbits.minValue;
stats[mbits.checkValue] = value;
for (const auto &mcond : ai.conditions) {
if (mapping.find(mcond.key) == mapping.end())
populate(nlohmann::json::object({ { mcond.key, 0 } }),
mapping);
bits.set(mapping[mcond.key], mcond.condition(memory));
}
}
@@ -869,34 +830,6 @@ int Blackboard::distance_to(const Blackboard &goal) const
int distance = 0;
OgreAssert(mask != 0 && goal.mask != 0, "blackboard not prepared");
return ((bits ^ goal.bits) & goal.mask).count();
#if 0
OgreAssert(goal.stats.is_object(),
"Not an object:\n" + goal.stats.dump(4));
for (auto it = goal.stats.begin(); it != goal.stats.end(); ++it) {
const std::string &key = it.key();
const auto &goalVal = it.value();
// If current state doesn't have the key, treat it as a maximum difference
if (stats.find(key) == stats.end()) {
distance += 100; // Example: High cost for missing state
continue;
}
const auto &currentVal = stats[key];
if (goalVal.is_number() && currentVal.is_number()) {
// Add numerical difference
distance += std::abs(goalVal.get<float>() -
currentVal.get<float>());
} else {
// Check non-numeric equality
if (goalVal != currentVal) {
distance += 1; // Penalty for mismatch
}
}
}
return distance;
#endif
}
void Blackboard::setPosition(const Ogre::Vector3 &position)
@@ -922,7 +855,6 @@ void Blackboard::query_ai()
ActionNodeList &alist = ECS::get_mut<ActionNodeList>();
alist.query_ai(position, distance, points, distances);
_actionRefResize(ai.actions.size());
int actionRefIndex = ai.actions.size();
int nodeActionCount = 0;
for (size_t point : points) {
Ogre::Vector3 &p = alist.dynamicNodes[point].position;
@@ -931,10 +863,10 @@ void Blackboard::query_ai()
if (object >= 0 && (size_t)object == point &&
distance > radius * radius) {
object = -1;
stats["at_object"] = 0;
bits.set(mapping["at_object"], false);
} else if (object >= 0 && (size_t)object == point &&
distance <= radius * radius)
stats["at_object"] = 1;
bits.set(mapping["at_object"], true);
/* some nodes do not have usable actions */
if (ai.nodeActions[point].size() > 0) {
OgreAssert(ai.nodeActions[point].size() > 0,
@@ -951,7 +883,6 @@ void Blackboard::query_ai()
j["global_position_y"] = p.y;
j["global_position_z"] = p.z;
nodes.push_back(j);
stats["nodes"] = nodes;
}
}
}

View File

@@ -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<std::string, size_t> mapping;
private:
nlohmann::json stats;
// nlohmann::json stats;
std::bitset<64> bits, mask;
std::vector<goap::BaseAction<Blackboard> *> 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<Blackboard> *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<UpdateBit> &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 &current,
@@ -107,14 +110,62 @@ struct TownAI {
goap::BasePlanner<Blackboard, goap::BaseAction<Blackboard> > >
planner;
std::unordered_map<int, Blackboard> blackboards;
typedef goap::BasePlanner<Blackboard, goap::BaseAction<Blackboard> >::BaseGoal goal_t;
typedef goap::BasePlanner<Blackboard, goap::BaseAction<Blackboard> > planner_t;
typedef goap::BasePlanner<
Blackboard, goap::BaseAction<Blackboard> >::BaseGoal goal_t;
typedef goap::BasePlanner<Blackboard, goap::BaseAction<Blackboard> >
planner_t;
struct Plan {
const goal_t *goal;
std::vector<goap::BaseAction<Blackboard> *> plan;
};
std::unordered_map<int, std::vector<struct Plan> > plans;
std::unordered_map<int, std::vector<goap::BaseAction<Blackboard> *> > nodeActions;
std::unordered_map<int, std::vector<goap::BaseAction<Blackboard> *> >
nodeActions;
struct Condition {
std::string key;
std::function<bool(const nlohmann::json &data)> condition;
};
struct MemoryUpdate {
std::string name;
std::function<int(int index, const std::string &name, int value)>
update;
};
struct BitEvolutionBuilder {
std::vector<Condition> conditions;
BitEvolutionBuilder *
addRule(const std::string &key,
std::function<bool(const nlohmann::json &data)>
condition)
{
conditions.push_back({ key, condition });
return this;
}
std::vector<Condition> build()
{
return conditions;
}
};
struct MemoryUpdateBuilder {
std::vector<MemoryUpdate> memoryUpdates;
MemoryUpdateBuilder &
addUpdate(const std::string &name,
std::function<int(int index, const std::string &name,
int value)>
update)
{
memoryUpdates.push_back({ name, update });
return *this;
}
std::vector<MemoryUpdate> build()
{
return memoryUpdates;
}
};
std::vector<Condition> conditions;
std::unordered_map<int, nlohmann::json> memory;
std::vector<MemoryUpdate> 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);
};
}

View File

@@ -259,15 +259,6 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
}
#endif
v.velocity = rot * boneMotion / safeDelta;
#if 0
if (!e.has<CharacterDisablePhysics>() &&
!e.has<CharacterInActuator>()) {
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;

View File

@@ -307,30 +307,6 @@ CharacterModule::CharacterModule(flecs::world &ecs)
// "need contact response");
});
#endif
#if 0
ecs.system<const EngineData, CharacterBase, CharacterBody>(
"UpdateCharacterPhysics")
.kind(flecs::OnUpdate)
.with<Character>()
.with<TerrainReady>()
.with<WaterReady>()
.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<const EngineData, Camera, const CharacterBase>(
"UpdateCamera")

View File

@@ -78,5 +78,8 @@ struct Body2Entity {
struct EditorSceneSwitch {
int scene;
};
struct GameState {
bool running;
};
}
#endif

View File

@@ -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<GUIData>().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<GUI>().enabled)
// ECS::get().get<App>().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<EngineData>().startupDelay > 0.0f) {
if (ECS::get().get<EngineData>().startupDelay > 0.0f &&
ECS::get().has<GameState>()) {
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<GUI>().narrationHandlers.size() > 0) {
ECS::get_mut<GUI>().grab = false;
ECS::get_mut<GUI>().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<GUI>().enabled)
// ECS::get().get<App>().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<GUI>().finish();
if (new_game) {
ECS::get<LuaBase>().mLua->call_handler(
"new_game");
}
} else {
ECS::get().set<GameState>({ true });
ECS::get<LuaBase>().mLua->call_handler(
"new_game_selected");
} else if (load_game) {
ECS::get().set<GameState>({ true });
ECS::get<LuaBase>().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<Ogre::SceneNode *> 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<GUIModule>();
ecs.import <AppModule>();
ecs.import <QuestModule>();
ecs.component<GUI>()
.on_add([](GUI &gui) {
gui.enabled = false;
gui.grab = false;
gui.grabChanged = false;
gui.enableActions = true;
})
})
.add(flecs::Singleton);
ecs.component<GUIData>()
.on_add([](GUIData &priv) {
@@ -705,51 +676,39 @@ GUIModule::GUIModule(flecs::world &ecs)
priv.mGuiOverlay = nullptr;
})
.add(flecs::Singleton);
ecs.set<GUI>({ false, true, false, false, false, true, "", {}, -1 });
ecs.set<GUIData>({ nullptr, {}, nullptr });
ui_wait =
ecs.system<const RenderWindow, App, GUIData>("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<Ogre::String> &groups =
Ogre::ResourceGroupManager::
getSingleton()
.getResourceGroups();
for (i = 0; i < groups.size(); i++) {
std::vector<Ogre::String> names =
*Ogre::ResourceGroupManager::getSingleton()
.findResourceNames(
groups[i],
"*.glb");
gui.glb_names.insert(
gui.glb_names.end(),
names.begin(),
names.end());
}
ECS::modified<ECS::GUI>();
std::cout << "GUI configure finished\n";
}
});
void GUIModule::configure()
{
ECS::get().set<GUIData>({ nullptr, {}, nullptr });
const RenderWindow &window = ECS::get<RenderWindow>();
GUIData &gui = ECS::get_mut<GUIData>();
const App &app = ECS::get<App>();
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<Ogre::String> &groups =
Ogre::ResourceGroupManager::getSingleton().getResourceGroups();
for (i = 0; i < groups.size(); i++) {
std::vector<Ogre::String> names =
*Ogre::ResourceGroupManager::getSingleton()
.findResourceNames(groups[i], "*.glb");
gui.glb_names.insert(gui.glb_names.end(), names.begin(),
names.end());
}
ECS::modified<GUIData>();
std::cout << "GUI configure finished\n";
}
}

View File

@@ -9,6 +9,7 @@ namespace ECS
struct GUIModule {
flecs::entity ui_wait;
GUIModule(flecs::world &ecs);
static void configure();
};
}
#endif

View File

@@ -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<RenderWindow>().add(flecs::Singleton);
ecs.import <CharacterModule>();
setup_minimal();
ecs.component<GameState>().add(flecs::Singleton);
ecs.import <CharacterModule>();
ecs.import <BoatModule>();
ecs.import <PhysicsModule>();
ecs.import <WaterModule>();
@@ -64,8 +65,11 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
ecs.import <CharacterAnimationModule>();
ecs.import <PlayerActionModule>();
ecs.import <CharacterAIModule>();
ecs.import <QuestModule>();
ecs.add<ActionNodeList>();
ecs.set<GUI>({ false, false, true, false, false, true, "", {}, -1 });
ecs.system<EngineData>("UpdateDelta")
.kind(flecs::OnUpdate)
.each([](EngineData &eng) {
@@ -73,6 +77,7 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
});
ecs.system<EngineData>("UpdateDelay")
.kind(flecs::OnUpdate)
.with<GameState>()
.with<TerrainReady>()
.with<WaterReady>()
.with<GroundCheckReady>()
@@ -119,49 +124,69 @@ void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
// ecs.set<Body2Entity>({});
std::cout << "Setup GameData done\n";
ecs.system("SpawnPlayer").kind(flecs::OnUpdate).interval(0.5f).run([&](flecs::iter &it) {
flecs::entity player =
ECS::get<CharacterManagerModule>().getPlayer();
if (!player.is_valid()) {
/* Create player */
Ogre::Vector3 position;
JPH::BodyID id;
long x, y;
Ogre::TerrainGroup *tg =
ECS::get<ECS::Terrain>().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<Terrain, GameState>("SpawnPlayer")
.kind(flecs::OnUpdate)
.interval(0.5f)
.each([&](Terrain &mterrain, GameState &game) {
flecs::entity player =
ECS::get<CharacterManagerModule>().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<GameState>("NewGame")
.kind(flecs::OnUpdate)
.interval(0.5f)
.each([&](GameState &game) {
flecs::entity player =
ECS::get<CharacterManagerModule>()
.getPlayer();
if (player.is_valid() &&
player.has<CharacterBase>() &&
player.has<Character>() &&
player.has<Player>()) {
ECS::get<LuaBase>().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<GUI>();
ecs.get_mut<GUI>().setWindowGrab(true);
ecs.modified<GUI>();
ecs.get_mut<GUI>().enabled = true;
ecs.modified<GUI>();
}
void update(float delta)

View File

@@ -1,9 +1,9 @@
#ifndef GAMEDATA_H
#define GAMEDATA_H
#include <Ogre.h>
#include <flecs.h>
namespace ECS
{
extern flecs::entity player;
void setup_minimal();
void setupExteriorScene(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
Ogre::Camera *camera, Ogre::RenderWindow *window);

View File

@@ -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<QuestModule>().addLuaQuest(L);
ECS::modified<QuestModule>();
return 0;
});
lua_setglobal(L, "add_quest");
lua_pushcfunction(L, [](lua_State *L) -> int {
// FIXME
return 0;

View File

@@ -0,0 +1,326 @@
#include <iostream>
#include "Components.h"
#include "GameData.h"
#include "GUIModuleCommon.h"
#include "QuestModule.h"
namespace ECS
{
QuestModule::QuestModule(flecs::world &ecs)
{
ecs.module<QuestModule>();
ecs.observer<GameState>("EnableQuests")
.event(flecs::OnAdd)
.each([this](GameState &game) {
quest_update =
ECS::get()
.system<const EngineData>(
"UpdateQuests")
.each([this](const EngineData &eng) {
for (auto &quest : quests) {
if (!quest->_is_complete())
quest->_update(
eng.delta);
}
});
});
ecs.observer<GameState>("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<LuaNarrationHandler *>(
lua_touserdata(
L,
lua_upvalueindex(1)));
Ogre::String event = lua_tostring(L, 1);
std::vector<Ogre::String> 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<LuaNarrationHandler *>(
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<LuaNarrationHandler *>(
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<LuaNarrationHandler *>(
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<LuaQuest *>(
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<LuaQuest *>(
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<GUI>().addNarrationHandler(handle);
ECS::modified<GUI>();
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

View File

@@ -0,0 +1,47 @@
#ifndef ECS_QUESTMODULE_H
#define ECS_QUESTMODULE_H
#include <flecs.h>
#include <Ogre.h>
#include <lua.hpp>
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<Quest *> 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