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

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