Compare commits

..

3 Commits

Author SHA1 Message Date
0405214388 New game works almost as intended, quest system 2026-02-05 18:21:25 +03:00
4fb7e94fed Fixed up GOAP support 2026-02-04 04:45:32 +03:00
4d47125ea9 Fix addon 2026-02-03 08:03:26 +03:00
21 changed files with 989 additions and 579 deletions

View File

@@ -258,7 +258,7 @@ add_custom_target(stage_files ALL DEPENDS ${CMAKE_BINARY_DIR}/resources.cfg ${MA
add_custom_target(remove_scenes COMMAND rm -f ${VRM_SOURCE} ${VRM_IMPORTED_BLENDS} ${CHARACTER_GLBS})
target_compile_definitions(Game PRIVATE FLECS_CPP_NO_AUTO_REGISTRATION)
target_compile_definitions(Game PRIVATE FLECS_CPP_NO_AUTO_REGISTRATION JPH_PROFILE_ENABLED)
install(TARGETS Game DESTINATION bin)
install(TARGETS Editor DESTINATION bin)

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;

Binary file not shown.

View File

@@ -6,7 +6,7 @@ script_list = [item for item in file_list if item.endswith('.zip')]
for file in file_list:
path_to_file = os.path.join(path_to_script_dir, file)
bpy.ops.preferences.addon_install(overwrite=True, target='DEFAULT', filepath=path_to_file, filter_folder=True, filter_python=False, filter_glob="*.py;*.zip")
enableTheseAddons = ["VRM_Addon_for_Blender-release", "blender2ogre"]
enableTheseAddons = ["VRM_Addon_for_Blender-release", "io_ogre"]
for string in enableTheseAddons:
name = enableTheseAddons
bpy.ops.preferences.addon_enable(module = string)

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(
@@ -164,16 +158,16 @@ public:
}
Blackboard actionPrereq({ jactionPrereq });
Blackboard actionEffect({ jactionEffect });
if (!prereq.stats.is_null())
if (!prereq.is_valid())
actionPrereq.apply(prereq);
m_actions.push_back(OGRE_NEW RunActionNode(
node, action, actionPrereq, actionEffect, cost));
if (effectName == "") {
std::cout << props.dump(4) << std::endl;
std::cout << "Prereq" << std::endl;
std::cout << actionPrereq.stats.dump(4) << std::endl;
actionPrereq.dump_bits();
std::cout << "Effect" << std::endl;
std::cout << actionEffect.stats.dump(4) << std::endl;
actionEffect.dump_bits();
OgreAssert(false, "action");
}
}
@@ -241,7 +235,7 @@ CharacterAIModule::CharacterAIModule(flecs::world &ecs)
2000 },
#endif
{ "EatMedicine",
{ { { "have_medicine", 1 }, { "healty", 0 } } },
{ { { "have_medicine", 1 }, { "healthy", 0 } } },
{ { { "healthy", 1 } } },
100 },
{ "UseToilet",
@@ -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)
@@ -402,91 +392,89 @@ void CharacterAIModule::createAI(flecs::entity town)
town.add<TownAI>();
}
struct PlanTask {
Blackboard blackboard;
TownAI::goal_t goal;
TownAI::Plan plan;
TownAI::planner_t *planner;
bool operator()()
{
auto buildPlan = [this](Blackboard &blackboard,
const TownAI::goal_t &goal,
TownAI::Plan &plan) -> bool {
if (goal.is_reached(blackboard))
return false;
plan.goal = &goal;
std::vector<goap::BaseAction<Blackboard> *> path;
int actionCount = blackboard.getActionsCount();
auto actionsData = blackboard.getActionsData();
path.resize(actionCount * actionCount);
int path_length = planner->plan(
blackboard, goal, actionsData, actionCount,
path.data(), path.size());
if (path_length > 0) {
plan.goal = &goal;
plan.plan.insert(plan.plan.end(), path.begin(),
path.begin() + path_length);
return true;
}
return false;
};
return buildPlan(blackboard, goal, plan);
}
PlanTask(Blackboard &blackboard, const TownAI::goal_t &goal,
TownAI::planner_t *planner)
: blackboard(blackboard)
, goal(goal)
, planner(planner)
{
}
};
static std::deque<PlanTask> plan_tasks;
void CharacterAIModule::buildPlans(flecs::entity town, const TownNPCs &npcs,
TownAI &ai)
{
OgreAssert(town.is_valid(), "Bad town entity");
std::lock_guard<std::mutex> lock(*ai.mutex);
auto planner = ai.planner;
auto buildPlan = [planner](Blackboard &blackboard,
const TownAI::goal_t &goal,
TownAI::Plan &plan) -> bool {
if (goal.is_reached(blackboard))
return false;
plan.goal = &goal;
std::vector<goap::BaseAction<Blackboard> *> path;
int actionCount = blackboard.getActionsCount();
auto actionsData = blackboard.getActionsData();
path.resize(actionCount * actionCount);
int path_length = planner->plan(blackboard, goal, actionsData,
actionCount, path.data(),
path.size());
if (path_length > 0) {
plan.goal = &goal;
plan.plan.insert(plan.plan.end(), path.begin(),
path.begin() + path_length);
return true;
}
return false;
};
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) {
if (ai.blackboards.find(it->first) == ai.blackboards.end())
continue;
auto &bb = ai.blackboards.at(it->first);
/* if there are plans, skip until these get discarded */
if (ai.plans.find(it->first) != ai.plans.end() &&
ai.plans.at(it->first).size() > 0)
continue;
const auto &npc = npcs.npcs.at(it->first);
int index = it->first;
ai.plans[index] = {};
for (const auto &goal : ai.goals) {
struct TownAI::Plan plan;
bool created = buildPlan(bb, goal, plan);
#if 0
std::cout << "blackboard: "
<< bb.stats.dump(4)
<< std::endl;
std::cout << "goal: "
<< goal.goal.stats.dump(4)
<< std::endl;
#endif
#if 0
std::cout << "Actions: " << std::endl;
for (auto &action : actions) {
std::cout << "name: "
<< action->get_name()
<< std::endl;
std::cout
<< "\tprereq:\n"
<< action->prereq.stats
.dump(4)
<< std::endl;
std::cout
<< "\teffects:\n"
<< action->effects.stats
.dump(4)
<< std::endl;
}
#endif
#if 1
if (created) {
std::cout << bb.index << " ";
std::cout << "Goal: " << goal.get_name();
std::cout << std::endl;
std::cout << "Path: ";
for (auto &action : plan.plan) {
OgreAssert(action, "No action");
std::cout << action->get_name() + " ";
}
std::cout << " size: " << plan.plan.size()
<< std::endl;
ai.plans[it->first].push_back(plan);
OgreAssert(false, "plan");
if (plan_tasks.size() > 0) {
bool created = (plan_tasks.front())();
if (created) {
std::cout << plan_tasks.front().blackboard.index << " ";
std::cout << "Goal: "
<< plan_tasks.front().goal.get_name();
plan_tasks.front().goal.goal.dump_bits();
std::cout << std::endl;
std::cout << "Path: ";
for (auto &action : plan_tasks.front().plan.plan) {
OgreAssert(action, "No action");
std::cout << action->get_name() + " ";
}
#endif
std::cout << " size: "
<< plan_tasks.front().plan.plan.size()
<< std::endl;
ai.plans[plan_tasks.front().blackboard.index].push_back(
plan_tasks.front().plan);
}
plan_tasks.pop_front();
} else
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) {
if (ai.blackboards.find(it->first) ==
ai.blackboards.end())
continue;
auto &bb = ai.blackboards.at(it->first);
/* if there are plans, skip until these get discarded */
if (ai.plans.find(it->first) != ai.plans.end() &&
ai.plans.at(it->first).size() > 0)
continue;
const auto &npc = npcs.npcs.at(it->first);
int index = it->first;
ai.plans[index] = {};
for (const auto &goal : ai.goals)
plan_tasks.emplace_back(bb, goal,
ai.planner.get());
}
}
}
void CharacterAIModule::createBlackboards(flecs::entity town,
@@ -505,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;
@@ -526,103 +518,102 @@ void CharacterAIModule::createBlackboards(flecs::entity town,
ai.blackboards.at(it->first).actionRefResize(0);
ai.blackboards.at(it->first).actionRefAddActions(
ai.actions);
}
}
}
void CharacterAIModule::updateBlackboardsBits(flecs::entity town,
ActionNodeList &alist,
const TownNPCs &npcs, TownAI &ai)
{
OgreAssert(town.is_valid(), "Bad town entity");
std::lock_guard<std::mutex> lock(*ai.mutex);
struct UpdateBit {
Ogre::String checkValue;
int recover;
int minValue;
int maxValue;
int threshold;
Ogre::String writeValue;
};
struct UpdateBit updateBits[] = {
{ "health", 0, 0, 100, 20, "healthy" },
{ "needs_hunger", 1, 0, 10000, 2000, "hungry" },
{ "needs_thirst", 1, 0, 10000, 1000, "thirsty" },
{ "needs_toilet", 1, 0, 10000, 1500, "toilet" }
};
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end(); it++) {
if (ai.blackboards.find(it->first) == ai.blackboards.end())
continue;
auto &stats = ai.blackboards.at(it->first).stats;
auto &object = ai.blackboards.at(it->first).object;
ai.blackboards.at(it->first).index = it->first;
ai.blackboards.at(it->first).town = town;
for (const auto &bits : updateBits) {
int value = stats[bits.checkValue].get<int>();
int maxValue = bits.maxValue;
int minValue = bits.minValue;
int threshold = bits.threshold;
if (it->second.props.find(bits.checkValue + "_max") !=
it->second.props.end())
maxValue =
it->second
.props[bits.checkValue + "_max"]
.get<int>();
if (it->second.props.find(bits.checkValue +
"_threshold") !=
it->second.props.end())
threshold = it->second
.props[bits.checkValue +
"_threshold"]
.get<int>();
value += bits.recover;
if (value > maxValue)
value = maxValue;
if (value >= threshold)
stats[bits.writeValue] = 1;
else
stats[bits.writeValue] = 0;
if (value < bits.minValue)
value = bits.minValue;
stats[bits.checkValue] = value;
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::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;
auto &stats = ai.blackboards.at(it->first).stats;
auto &object = ai.blackboards.at(it->first).object;
ai.blackboards.at(it->first).index = it->first;
ai.blackboards.at(it->first).town = town;
for (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();
#if 0
OgreAssert(nodeActionCount > 0 ||
points.size() == 0,
"no node actions and no points");
if (nodeActionCount == 0) {
std::cout << "nodes:"
<< alist.nodes.size() << " "
<< alist.dynamicNodes.size()
<< std::endl;
std::cout << "points: " << points.size()
<< std::endl;
std::cout << "position: " << position
<< std::endl;
}
OgreAssert(nodeActionCount > 0,
"no node actions");
#endif
bb.fixupBooleanKeys();
}
}
@@ -640,23 +631,56 @@ 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>())
, bits(0)
, mask(0)
{
}
std::unordered_map<std::string, size_t> Blackboard::mapping;
Blackboard::Blackboard(const nlohmann::json &stats)
: Blackboard()
{
this->stats = stats;
populate(stats, mapping);
}
void Blackboard::populate(const nlohmann::json &stats,
std::unordered_map<std::string, size_t> &mapping)
{
if (stats.empty())
return;
for (auto &[key, value] : stats.items()) {
if (value.is_number_integer()) {
int val = value.get<int>();
if (val == 0 || val == 1) {
// Update mapping if key is new
if (mapping.find(key) == mapping.end()) {
size_t next_bit = mapping.size();
if (next_bit < 64) {
mapping[key] = next_bit;
} else {
OgreAssert(false,
"Out of bits");
continue;
}
}
size_t bit_idx = mapping[key];
bits.set(bit_idx, val == 1);
mask.set(bit_idx);
}
}
}
}
bool Blackboard::operator==(const Blackboard &other) const
{
return is_satisfied_by(this->stats, other.stats);
OgreAssert(mask != 0 && other.mask != 0, "blackboard not prepared");
return (bits & other.mask) == (other.bits & other.mask);
}
bool Blackboard::operator!=(const Blackboard &other) const
@@ -667,7 +691,13 @@ 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);
// 3. Update our mask to include any new state definitions introduced by the effect
mask |= other.mask;
}
Ogre::String Blackboard::dumpActions()
@@ -679,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;
}
@@ -690,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);
@@ -759,6 +774,17 @@ void Blackboard::actionRefAddActions(goap::BaseAction<Blackboard> **actions,
}
}
void Blackboard::updateBits(const TownAI &ai, const nlohmann::json &memory,
const nlohmann::json &props)
{
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));
}
}
struct ComparePair {
const nlohmann::json &current;
const nlohmann::json &target;
@@ -802,33 +828,8 @@ bool Blackboard::is_satisfied_by(const nlohmann::json &current,
int Blackboard::distance_to(const Blackboard &goal) const
{
int distance = 0;
OgreAssert(goal.stats.is_object(),
"Not an object:\n" + goal.stats.dump(4));
for (auto it = goal.stats.begin(); it != goal.stats.end(); ++it) {
const std::string &key = it.key();
const auto &goalVal = it.value();
// If current state doesn't have the key, treat it as a maximum difference
if (stats.find(key) == stats.end()) {
distance += 100; // Example: High cost for missing state
continue;
}
const auto &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;
OgreAssert(mask != 0 && goal.mask != 0, "blackboard not prepared");
return ((bits ^ goal.bits) & goal.mask).count();
}
void Blackboard::setPosition(const Ogre::Vector3 &position)
@@ -854,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;
@@ -863,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,
@@ -883,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,14 +8,26 @@
namespace ECS
{
struct TownAI;
struct Blackboard {
nlohmann::json stats;
struct UpdateBit {
Ogre::String checkValue;
int recover;
int minValue;
int maxValue;
int threshold;
Ogre::String writeValue;
};
int object;
int index;
flecs::entity town;
std::shared_ptr<std::mutex> mutex;
static std::unordered_map<std::string, size_t> mapping;
private:
// nlohmann::json stats;
std::bitset<64> bits, mask;
std::vector<goap::BaseAction<Blackboard> *> actionRef;
int actionRefCount;
int actionRefPtr;
@@ -25,6 +37,7 @@ private:
void _actionRefResize(int count);
void _actionRefAddActions(
const std::vector<goap::BaseAction<Blackboard> *> &actions);
void populate(const nlohmann::json &stats, std::unordered_map<std::string, size_t>& mapping);
public:
Blackboard();
@@ -34,21 +47,40 @@ 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(
const std::vector<goap::BaseAction<Blackboard> *> &actions);
void actionRefAddActions(goap::BaseAction<Blackboard> **actions,
int count);
const goap::BaseAction<Blackboard> *const *getActionsData() const
{
return actionRef.data();
}
goap::BaseAction<Blackboard> **getActionsData()
{
return actionRef.data();
}
int getActionsCount()
int getActionsCount() const
{
return actionRefCount;
}
#if 0
void commit()
{
populate(stats, mapping);
}
#endif
void dump_bits() const
{
std::cout << "bits: " << bits << std::endl;
std::cout << "mask: " << mask << std::endl;
}
bool is_valid() const
{
return /* !stats.is_null() && */ mask != 0;
}
void updateBits(const TownAI &ai, const nlohmann::json &memory, const nlohmann::json &props);
private:
static bool is_satisfied_by(const nlohmann::json &current,
@@ -78,13 +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> >::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 {
@@ -92,8 +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 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

View File

@@ -16,3 +16,5 @@ find_package(flecs REQUIRED CONFIG)
add_library(physics STATIC physics.cpp)
target_link_libraries(physics PUBLIC OgreMain Jolt::Jolt)
target_include_directories(physics PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(physics PRIVATE JPH_PROFILE_ENABLED)