Files
ogre-prototype/src/gamedata/PlayerActionModule.cpp
2026-02-01 23:25:38 +03:00

887 lines
28 KiB
C++

#include <nanoflann.hpp>
#include <vector>
#include <iostream>
#include <flecs.h>
#include <nlohmann/json.hpp>
#include <lua.hpp>
#include "Components.h"
#include "GameData.h"
#include "CharacterManagerModule.h"
#include "CharacterModule.h"
#include "items.h"
#include "GUIModule.h"
#include "GUIModuleCommon.h"
#include "LuaData.h"
#include "PhysicsModule.h"
#include "PlayerActionModule.h"
namespace ECS
{
struct OgreVector3Adaptor {
const std::vector<ActionNodeList::ActionNode> &nodes;
OgreVector3Adaptor(const std::vector<ActionNodeList::ActionNode> &nodes)
: nodes(nodes)
{
}
// Required by nanoflann: Number of data points
inline size_t kdtree_get_point_count() const
{
return nodes.size();
}
// Required by nanoflann: Returns the distance between the vector and a point
// Using squared distance is standard for performance
inline float kdtree_get_pt(const size_t idx, const size_t dim) const
{
return nodes[idx].position[dim];
}
// Optional: bounding box optimization (return false if not implemented)
template <class BBOX> bool kdtree_get_bbox(BBOX & /*bb*/) const
{
return false;
}
};
typedef nanoflann::KDTreeSingleIndexAdaptor<
nanoflann::L2_Simple_Adaptor<float, OgreVector3Adaptor>,
OgreVector3Adaptor, 3 /* dimensionality */
>
OgreKDTree;
struct ActionNodeList::indexObject {
OgreVector3Adaptor adaptor;
OgreKDTree index;
indexObject(const std::vector<ActionNodeList::ActionNode> &nodes)
: adaptor(nodes)
, index(3, adaptor,
nanoflann::KDTreeSingleIndexAdaptorParams(10))
{
}
};
struct TestNarrativeHandler : GUI::NarrationHandler {
int count;
TestNarrativeHandler()
: GUI::NarrationHandler()
, count(0)
{
}
void finish() override
{
_clear_narration();
}
void activate() override
{
_narration("Greetings...", {});
std::cout << getPropsJSON().dump(4) << std::endl;
count = 0;
}
void event(const Ogre::String &evt) override
{
if (evt == "narration_progress" ||
evt == "narration_answered") {
count++;
if (count == 1) {
_narration(
"Question..." +
Ogre::StringConverter::toString(
count),
{ "Answer1", "Answer2" });
} else {
_narration(
"Whatever..." +
Ogre::StringConverter::toString(
count),
{});
}
if (count > 5)
_finish();
}
if (evt == "narration_answered")
std::cout << "answer: " << getNarrationAnswer()
<< std::endl;
}
};
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_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());
const std::vector<ActionNodeList::ActionNode>
&nodes = ECS::get<ActionNodeList>()
.dynamicNodes;
lua_newtable(L);
int i;
for (i = 0; i < nodes.size(); i++) {
lua_pushinteger(L, i + 1);
lua_pushstring(
L,
nodes[i].props.dump().c_str());
lua_settable(L, -3);
}
return 1;
},
1);
lua_setfield(L, -2, "_get_goals");
lua_pop(L, 1);
}
void finish() override
{
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 SimpleWordHandler : PlayerActionModule::ActionWordHandler {
void operator()(int actor, flecs::entity town, int index,
const Ogre::String &word, int actionNode) override
{
if (index >= 0) {
TestNarrativeHandler *handle =
OGRE_NEW TestNarrativeHandler();
/* this is for NPCs only, right? */
const TownNPCs::NPCData &npc =
town.get<TownNPCs>().npcs.at(index);
flecs::entity e = npc.e;
for (const auto &anode : npc.actionNodes) {
if (anode.action == word) {
nlohmann::json props = anode.props;
props["initiator"] = actor;
props["recipient"] = index;
handle->setProperties(props.dump());
break;
}
}
ECS::get_mut<GUI>().addNarrationHandler(handle);
ECS::modified<GUI>();
}
}
};
PlayerActionModule::PlayerActionModule(flecs::world &ecs)
{
ecs.module<PlayerActionModule>();
ecs.import <CharacterManagerModule>();
ecs.component<ActionNodeList>()
.on_add([](flecs::entity e, ActionNodeList &alist) {
alist.nodeMutex = std::make_shared<std::mutex>();
alist.setDirty();
alist.nodes.reserve(1000);
alist.dynamicNodes.reserve(1000);
alist.setUISelected(-1);
alist.setReady();
})
.add(flecs::Singleton);
ecs.system<ActionNodeList>("updateNodeList")
.kind(flecs::OnUpdate)
.each([](ActionNodeList &list) {
if (list.isBusy())
return;
if (list.nodes.size() > 0) {
Ogre::SceneNode *cameraNode =
ECS::get<Camera>().mCameraNode;
Ogre::Vector3 cameraPos =
cameraNode->_getDerivedPosition();
flecs::entity player =
ECS::get<CharacterManagerModule>()
.getPlayer();
if (player.is_valid()) {
Ogre::Vector3 playerPos =
player.get<CharacterBase>()
.mBodyNode
->_getDerivedPosition();
list.UIquery(playerPos);
} else {
list.UIquery(cameraPos);
}
}
});
ecs.system<ActionNodeList, const Input>("ActivateActionNode")
.kind(flecs::OnUpdate)
.each([this](ActionNodeList &list, const Input &input) {
std::lock_guard<std::mutex> lock(*list.nodeMutex);
if (input.control & 32)
std::cout << "act pressed" << std::endl;
if (list.isBusy())
return;
if (input.act_pressed &&
list.getUIData().selected >= 0) {
std::cout
<< list.dynamicNodes[list.getUIData()
.selected]
.props.dump(4)
<< std::endl;
flecs::entity_t townid =
list.dynamicNodes[list.getUIData()
.selected]
.props["town"]
.get<flecs::entity_t>();
flecs::entity town = ECS::get().entity(townid);
int index = list.dynamicNodes[list.getUIData()
.selected]
.props["index"]
.get<int>();
for (auto it = actionWords.begin();
it != actionWords.end(); it++) {
if (it->first ==
list.dynamicNodes[list.getUIData()
.selected]
.action) {
(*it->second)(
-1, town, index,
list.dynamicNodes
[list.getUIData()
.selected]
.action,
list.getUIData()
.selected);
list.setBusy();
}
}
}
if (!ECS::get<GUI>().enabled)
list.setReady();
});
ecs.system<const EngineData>("UpdateActivatedWords")
.kind(flecs::OnUpdate)
.each([this](const EngineData &eng) {
for (auto it = activatedWords.begin();
it != activatedWords.end();) {
int ret = it->second->_update(eng.delta);
if (ret != ActivatedWordHandler::BUSY) {
delete it->second;
it = activatedWords.erase(it);
} else
it++;
}
});
}
void PlayerActionModule::addWordHandler(const Ogre::String &word,
ActionWordHandler *handler)
{
actionWords.insert({ word, handler });
}
void PlayerActionModule::removeWordHandler(const Ogre::String &word,
ActionWordHandler *handler)
{
for (auto it = actionWords.begin(); it != actionWords.end();) {
if (it->first == word && it->second == handler)
it = actionWords.erase(it);
else
it++;
}
}
static std::set<int> activeActors;
struct TestActivatedWordHandler : PlayerActionModule::ActivatedWordHandler {
int actor;
flecs::entity town;
int index;
Ogre::String word;
int actionNode;
ActionNodeList::ActionNode anode;
flecs::entity ch;
int state;
float delay;
std::unordered_map<Ogre::String, Ogre::Vector3> placeLocalOffset;
std::unordered_map<Ogre::String, Ogre::Quaternion> placeLocalRotation;
TestActivatedWordHandler(int actor, flecs::entity town, int index,
const Ogre::String &word, int actionNode)
: PlayerActionModule::ActivatedWordHandler()
, actor(actor)
, town(town)
, index(index)
, word(word)
, actionNode(actionNode)
, state(0)
, delay(0)
{
activeActors.insert(actor);
// dynamic nodes can disappear on us so to avoid that use a copy
anode = ECS::get<ActionNodeList>().dynamicNodes[actionNode];
if (actor == -1)
ch = ECS::get<CharacterManagerModule>().getPlayer();
else if (actor >= 0) {
const TownNPCs &npcs = town.get<TownNPCs>();
ch = npcs.npcs.at(actor).e;
}
if (anode.props.find("positions") == anode.props.end())
goto out;
for (const auto &position : anode.props["positions"]) {
if (position.find("name") == position.end())
continue;
Ogre::Vector3 localPosition;
Ogre::Quaternion localRotation;
localPosition.x = position["position_x"].get<float>();
localPosition.y = position["position_y"].get<float>();
localPosition.z = position["position_z"].get<float>();
localRotation.w = position["rotation_w"].get<float>();
localRotation.x = position["rotation_x"].get<float>();
localRotation.y = position["rotation_y"].get<float>();
localRotation.z = position["rotation_z"].get<float>();
placeLocalOffset[position["name"].get<Ogre::String>()] =
localPosition;
placeLocalRotation[position["name"].get<Ogre::String>()] =
localRotation;
}
out:;
}
void teleport(const Ogre::String &place)
{
if (placeLocalOffset.find(place) == placeLocalOffset.end())
return;
std::cout << "local offset: " << placeLocalOffset[place]
<< std::endl;
std::cout << "parent offset: " << anode.position << std::endl;
Ogre::Quaternion newRotation =
anode.rotation * placeLocalRotation[place];
Ogre::Vector3 newPosition =
anode.position +
anode.rotation * placeLocalOffset[place];
if (ch.is_valid() && ch.has<CharacterBase>()) {
ch.get<CharacterBase>()
.mBodyNode->_setDerivedOrientation(newRotation);
ch.get<CharacterBase>().mBodyNode->_setDerivedPosition(
newPosition);
}
if (actor >= 0) {
town.get_mut<TownNPCs>().npcs[actor].position =
newPosition;
town.get_mut<TownNPCs>().npcs[actor].orientation =
newRotation;
town.modified<TownNPCs>();
}
}
int update(float delta)
{
switch (state) {
case 0:
if (ECS::get<Input>().act)
delay += delta;
// activate anly after delay
if (ECS::get<Input>().act == 0 && delay > 0.2f) {
delay = 0.0f;
state = 10;
// Yay!!!
std::cout << "Node data: " << std::endl;
std::cout << anode.props.dump(4) << std::endl;
} else if (ECS::get<Input>().act == 0 &&
delay <= 0.2f) {
delay = 0.0f;
state = 100;
}
break;
case 10:
if (ch.is_valid()) {
PhysicsModule::controlPhysics(ch, false);
// no control by player or ai
ch.add<CharacterControlDisable>();
// else
// ch.set<CharacterInActuator>(
// { "idle", { 0, 0, 0 } });
}
delay = 0.0f;
state++;
break;
case 11:
teleport("enter");
delay = 0.0f;
state++;
break;
case 12:
if (ch.is_valid()) {
if (word == "sit")
ch.set<CharacterInActuator>(
{ "sitting-chair",
{ 0, 0, 0 } });
}
teleport("enter");
delay = 0.0f;
state++;
break;
case 13:
teleport("enter");
delay = 0.0f;
state = 50;
break;
case 50:
// teleport again to handle possible problems caused by root motion
delay = 0.0f;
teleport("enter");
state++;
break;
case 51:
// do not move anywhere until we depress key and wait a bit
if (ECS::get<Input>().act == 0)
delay += delta;
if (delay > 1.0f) {
delay = 0.0f;
state++;
}
break;
case 52:
// if the key is pressed for a second move to next state
if (ECS::get<Input>().act)
delay += delta;
if (delay > 1.0f) {
delay = 0.0f;
state++;
}
break;
case 53:
delay = 0.0;
state = 100;
break;
case 100:
delay = 0.0;
state++;
break;
case 101:
// wait until key is depressed for a second
if (ECS::get<Input>().act == 0)
delay += delta;
if (delay > 1.0f) {
delay = 0.0;
state++;
}
break;
case 102:
delay = 0.0f;
state = 0;
return OK;
break;
}
std::cout << this << " delay: " << delay << " state: " << state
<< std::endl;
return BUSY;
}
void enter()
{
delay = 0.0f;
state = 0;
ECS::get_mut<GUI>().enableActions = false;
ECS::modified<GUI>();
std::cout << "enter" << std::endl;
}
void exit(int result)
{
ch.remove<CharacterInActuator>();
ch.remove<CharacterControlDisable>();
PhysicsModule::controlPhysics(ch, true);
// OgreAssert(false, "exit");
delay = 0.0f;
state = 0;
ECS::get_mut<GUI>().enableActions = true;
ECS::modified<GUI>();
std::cout << "exit" << std::endl;
}
virtual ~TestActivatedWordHandler()
{
activeActors.erase(actor);
}
};
struct LuaWordHandler : PlayerActionModule::ActionWordHandler {
lua_State *L;
int ref;
void operator()(int actor, flecs::entity town, int index,
const Ogre::String &word, int actionNode) override
{
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
if (lua_type(L, -1) == LUA_TFUNCTION) {
luaL_checktype(L, -1, LUA_TFUNCTION);
lua_pushinteger(L, town.id());
lua_pushinteger(L, index);
lua_pushstring(L, word.c_str());
if (lua_pcall(L, 3, 0, 0)) {
Ogre::LogManager::getSingleton().stream()
<< lua_tostring(L, -1);
OgreAssert(false, "Lua error");
}
} else if (lua_type(L, -1) == LUA_TTABLE) {
luaL_checktype(L, -1, LUA_TTABLE);
lua_pop(L, 1);
/* trying to talk to NPC activates narration mode */
if (index >= 0 && word == "talk") {
LuaNarrationHandler *handle =
OGRE_NEW LuaNarrationHandler(L, ref);
const TownNPCs::NPCData &npc =
town.get<TownNPCs>().npcs.at(index);
flecs::entity e = npc.e;
for (const auto &anode : npc.actionNodes) {
if (anode.action == word) {
nlohmann::json props =
anode.props;
props["initiator"] = actor;
props["recipient"] = index;
handle->setProperties(
props.dump());
break;
}
}
ECS::get_mut<GUI>().addNarrationHandler(handle);
ECS::modified<GUI>();
} else {
if (activeActors.find(actor) ==
activeActors.end()) {
TestActivatedWordHandler *handler =
OGRE_NEW TestActivatedWordHandler(
actor, town, index,
word, actionNode);
ECS::get_mut<PlayerActionModule>()
.addActivatedWordHandler(
word, handler);
ECS::modified<PlayerActionModule>();
}
}
}
}
};
void PlayerActionModule::addLuaWordHandler(const Ogre::String &word,
lua_State *L, int ref)
{
struct LuaWordHandler *handler = OGRE_NEW LuaWordHandler;
handler->L = L;
handler->ref = ref;
addWordHandler(word, handler);
}
void PlayerActionModule::removeLuaWordHandler(const Ogre::String &word,
lua_State *L, int ref)
{
for (auto it = actionWords.begin(); it != actionWords.end();) {
LuaWordHandler *handler =
static_cast<LuaWordHandler *>(it->second);
if (it->first == word && handler->L == L && handler->ref == ref)
it = actionWords.erase(it);
else
it++;
}
}
int PlayerActionModule::setupLuaActionHandler(lua_State *L)
{
luaL_checktype(L, 1, LUA_TSTRING);
if (lua_type(L, 2) == LUA_TFUNCTION) {
luaL_checktype(L, 2, LUA_TFUNCTION);
Ogre::String word = lua_tostring(L, 1);
lua_pushvalue(L, 2);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
addLuaWordHandler(word, L, ref);
} else if (lua_type(L, 2) == LUA_TTABLE) {
luaL_checktype(L, 2, LUA_TTABLE);
Ogre::String word = lua_tostring(L, 1);
lua_pushvalue(L, 2);
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
addLuaWordHandler(word, L, ref);
}
return 0;
}
void PlayerActionModule::addActivatedWordHandler(const Ogre::String &word,
ActivatedWordHandler *handler)
{
for (auto it = activatedWords.begin(); it != activatedWords.end();
it++) {
}
activatedWords.insert({ word, handler });
}
void PlayerActionModule::removeActivatedWordHandler(
const Ogre::String &word, ActivatedWordHandler *handler)
{
for (auto it = activatedWords.begin(); it != activatedWords.end();) {
if (it->first == word && it->second == handler)
it = activatedWords.erase(it);
else
it++;
}
}
void ActionNodeList::updateDynamicNodes()
{
std::lock_guard<std::mutex> lock(*nodeMutex);
if (dynamicNodes.size() > nodes.size())
dynamicNodes.resize(nodes.size());
else {
dynamicNodes.clear();
dynamicNodes.insert(dynamicNodes.end(), nodes.begin(),
nodes.end());
}
ECS::get().query_builder<const TownNPCs>().each(
[this](flecs::entity town, const TownNPCs &npcs) {
for (auto it = npcs.npcs.begin(); it != npcs.npcs.end();
it++) {
dynamicNodes.insert(
dynamicNodes.end(),
it->second.actionNodes.begin(),
it->second.actionNodes.end());
}
});
dirty = true;
}
void ActionNodeList::build()
{
std::lock_guard<std::mutex> lock(*nodeMutex);
indexObj = std::make_shared<ActionNodeList::indexObject>(dynamicNodes);
indexObj->index.buildIndex();
dirty = false;
}
bool ActionNodeList::_query(const Ogre::Vector3 &position,
std::vector<size_t> &points,
std::vector<float> &distances)
{
std::vector<size_t> tmppoints;
std::vector<float> tmpdistances;
points.clear();
points.reserve(4);
distances.clear();
distances.reserve(4);
tmppoints.resize(4);
tmpdistances.resize(4);
if (!indexObj) {
dirty = true;
return false;
}
nanoflann::KNNResultSet<float> resultSet(4);
resultSet.init(tmppoints.data(), tmpdistances.data());
bool ret = indexObj->index.findNeighbors(resultSet, &position.x,
nanoflann::SearchParameters());
int i;
for (i = 0; i < resultSet.size(); i++)
if (tmpdistances[i] < 25.0f) {
points.push_back(tmppoints[i]);
distances.push_back(tmpdistances[i]);
}
return ret;
}
bool ActionNodeList::query_ai(const Ogre::Vector3 &position, float distance,
std::vector<size_t> &points,
std::vector<float> &distances)
{
std::lock_guard<std::mutex> lock(*nodeMutex);
std::vector<size_t> tmppoints;
std::vector<float> tmpdistances;
points.clear();
points.reserve(100);
distances.clear();
distances.reserve(100);
tmppoints.resize(100);
tmpdistances.resize(100);
if (!indexObj) {
dirty = true;
return false;
}
nanoflann::KNNResultSet<float> resultSet(100);
resultSet.init(tmppoints.data(), tmpdistances.data());
bool ret = indexObj->index.findNeighbors(resultSet, &position.x,
nanoflann::SearchParameters());
int i;
for (i = 0; i < resultSet.size(); i++)
if (tmpdistances[i] < distance) {
points.push_back(tmppoints[i]);
distances.push_back(tmpdistances[i]);
}
return ret;
}
int ActionNodeList::addNode(ActionNode &node)
{
std::lock_guard<std::mutex> lock(*nodeMutex);
int index = nodes.size();
nodes.push_back(node);
dirty = true;
return index;
}
void ActionNodeList::removeNode(int index)
{
std::lock_guard<std::mutex> lock(*nodeMutex);
nodes.erase(nodes.begin() + index);
dirty = true;
}
const ActionNodeList::UIData &ActionNodeList::getUIData()
{
std::lock_guard<std::mutex> lock(*uidata.mutex);
return uidata;
}
void ActionNodeList::setUISelected(int selected)
{
std::lock_guard<std::mutex> lock(*uidata.mutex);
uidata.selected = selected;
}
void ActionNodeList::setUIPoints(const std::vector<size_t> &points,
const std::vector<float> &distances)
{
std::lock_guard<std::mutex> lock(*uidata.mutex);
uidata.points = points;
uidata.distances = distances;
}
void ActionNodeList::UIquery(const Ogre::Vector3 &position)
{
bool needBuild = false;
{
std::lock_guard<std::mutex> lock(*nodeMutex);
if (dirty || !indexObj)
needBuild = true;
}
if (needBuild)
build();
{
std::lock_guard<std::mutex> lock(*uidata.mutex);
_query(position, uidata.points, uidata.distances);
}
}
void ActionNodeList::setDirty()
{
dirty = true;
}
void ActionNodeList::setReady()
{
busy = false;
}
void ActionNodeList::setBusy()
{
busy = true;
}
bool ActionNodeList::isBusy()
{
return busy;
}
}