Action nodes work better now

This commit is contained in:
2026-02-01 00:13:33 +03:00
parent 201aa92fe7
commit e6efd89bb0
14 changed files with 548 additions and 126 deletions

View File

@@ -3,6 +3,90 @@ import bpy
ACTION_PROPS = ["name", "action", "action_text", "height", "radius", "tags"]
FURNITURE_PROPS = ["name", "furniture_tags"]
test_tracking = {"lib_name": None}
class TEST_OT_LinkAndPlay(bpy.types.Operator):
bl_idname = "test.link_and_play"
bl_label = "Link and Play Sitting"
filepath: bpy.props.StringProperty()
rig_name: bpy.props.StringProperty()
action_name: bpy.props.StringProperty()
def execute(self, context):
target = context.active_object
path = bpy.path.abspath(self.filepath)
mesh_name = "Body"
with bpy.data.libraries.load(path, link=True) as (data_from, data_to):
# We request all three components
data_to.objects = [n for n in [self.rig_name, mesh_name] if n in data_from.objects]
data_to.actions = [self.action_name] if self.action_name in data_from.actions else []
# 2. Add to scene and create Override
rig_obj = None
for obj in data_to.objects:
bpy.context.collection.objects.link(obj)
obj.matrix_world = target.matrix_world
if obj.type == 'ARMATURE':
rig_obj = obj
if obj.library:
test_tracking["lib_name"] = obj.library.name
if rig_obj:
if not rig_obj.animation_data:
rig_obj.animation_data_create()
if data_to.actions:
rig_obj.animation_data.action = data_to.actions[0]
bpy.ops.screen.animation_play()
return {'FINISHED'}
class TEST_OT_Cleanup(bpy.types.Operator):
bl_idname = "test.cleanup"
bl_label = "Stop & Cleanup"
def execute(self, context):
if context.screen.is_animation_playing:
bpy.ops.screen.animation_cancel(restore_frame=True)
lib_name = test_tracking.get("lib_name")
if lib_name and lib_name in bpy.data.libraries:
lib = bpy.data.libraries[lib_name]
bpy.data.batch_remove(ids=(lib,))
test_tracking["lib_name"] = None
# Deep purge orphans (meshes, armatures, etc)
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
return {'FINISHED'}
class TEST_PT_ObjectPanel(bpy.types.Panel):
"""Only appears in Object Properties when a 'position-' Empty is selected"""
bl_label = "Character Placement Test"
bl_idname = "TEST_PT_object_panel"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
@classmethod
def poll(cls, context):
obj = context.active_object
return obj and obj.type == 'EMPTY' and obj.name.startswith("position-")
def draw(self, context):
layout = self.layout
layout.label(text="Test Controls:", icon='OUTLINER_OB_EMPTY')
# Link button with preset arguments
row = layout.row(align=True)
op = row.operator("test.link_and_play", text="Link & Sit", icon='PLAY')
op.filepath = "//../../edited-normal-male.blend" # SET YOUR FILEPATH
op.rig_name = "male" # SET YOUR RIG NAME
op.action_name = "sitting-chair" # SET YOUR ACTION NAME
# Cleanup button
layout.operator("test.cleanup", text="Stop & Clear", icon='TRASH')
class OBJECT_OT_CreateActionProps(bpy.types.Operator):
"""Initializes custom properties on the selected action- empty"""
bl_idname = "object.create_action_props"
@@ -246,8 +330,6 @@ class OBJECT_PT_FurnitureEmptyPanel(bpy.types.Panel):
op_use = layout.operator("object.create_child_action_node", icon='EMPTY_DATA', text="Add Sit Action Node")
op_use.target_val = "sit"
classes = (TEST_PT_CharacterPanel, TEST_OT_LinkAndPlay, TEST_OT_Cleanup)
def register():
bpy.utils.register_class(OBJECT_OT_CreateActionProps)
bpy.utils.register_class(OBJECT_OT_CreateFurnitureProps)
@@ -256,19 +338,12 @@ def register():
bpy.utils.register_class(OBJECT_OT_DestroyChildByProp)
bpy.utils.register_class(OBJECT_PT_FurnitureEmptyPanel)
bpy.utils.register_class(OBJECT_OT_CreateChildActionNode)
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.test_filepath = bpy.props.StringProperty(name="File Path", subtype='FILE_PATH', default="//char.blend")
bpy.types.Scene.test_obj_name = bpy.props.StringProperty(name="Rig Name", default="Character_Rig")
bpy.types.Scene.test_action_name = bpy.props.StringProperty(name="Action", default="sitting")
bpy.utils.register_class(TEST_OT_LinkAndPlay)
bpy.utils.register_class(TEST_OT_Cleanup)
bpy.utils.register_class(TEST_PT_ObjectPanel)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
del bpy.types.Scene.test_filepath
del bpy.types.Scene.test_obj_name
del bpy.types.Scene.test_action_name
bpy.utils.register_class(OBJECT_OT_CreateActionProps)
bpy.utils.register_class(OBJECT_OT_CreateFurnitureProps)
bpy.utils.unregister_class(OBJECT_PT_ActionEmptyPanel)
@@ -276,6 +351,9 @@ def unregister():
bpy.utils.unregister_class(OBJECT_OT_DestroyChildByProp)
bpy.utils.unregister_class(OBJECT_PT_FurnitureEmptyPanel)
bpy.utils.unregister_class(OBJECT_OT_CreateChildActionNode)
bpy.utils.unregister_class(TEST_OT_LinkAndPlay)
bpy.utils.unregister_class(TEST_OT_Cleanup)
bpy.utils.unregister_class(TEST_PT_ObjectPanel)
if __name__ == "__main__":
register()

View File

@@ -84,6 +84,20 @@ def export_root_objects_to_gltf(output_dir):
action["name"] = child["name"]
else:
action["name"] = desc["name"] + "_" + str(len(desc["actions"]))
if "tags" in child:
atags = child["tags"]
if "," in atags:
atags = [m.strip() for m in atags.split(",")]
action["tags"] = atags
else:
action["tags"] = [atags]
else:
action["tags"] = []
for k, v in child.items():
if k.startswith("goap_prereq_"):
action[k] = v
elif k.startswith("goap_effect_"):
action[k] = v
action["furniture"] = {}
action["furniture"]["name"] = desc["name"]
action["furniture"]["tags"] = desc["tags"]

Binary file not shown.

View File

@@ -757,6 +757,28 @@ setup_action_handler("talk", {
print("tag: " .. tag)
end,
})
setup_action_handler("sit", {
activate = function(this)
print('sit activate')
crash()
end,
event = function(this, event)
print('event', event)
end,
finish = function(this)
end,
})
setup_action_handler("use", {
activate = function(this)
print('sit activate')
crash()
end,
event = function(this, event)
print('event', event)
end,
finish = function(this)
end,
})
--[[
active_dialogues = {}
setup_action_handler("talk", function(town, index, word)

View File

@@ -115,7 +115,10 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
->state("sitting-ground")
->animation("sitting-ground")
->end()
->transition_end("swimming-edge-climb", "idle")
->state("sitting-chair")
->animation("sitting-chair")
->end()
->transition_end("swimming-edge-climb", "idle")
->transition_end("hanging-climb", "idle")
->transition_end("pass-character", "idle")
->end()
@@ -375,7 +378,8 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
.kind(flecs::OnUpdate)
.with<Character>()
.without<CharacterInActuator>()
.each([](flecs::entity e, const CharacterBase &ch,
.without<CharacterControlDisable>()
.each([](flecs::entity e, const CharacterBase &ch,
AnimationControl &anim) {
if (!anim.configured)
return;
@@ -395,6 +399,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
.with<Character>()
.with<Player>()
.without<CharacterInActuator>()
.without<CharacterControlDisable>()
.each([](flecs::entity e, const Input &input,
const CharacterBase &ch, AnimationControl &anim) {
if (!anim.configured)
@@ -474,6 +479,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
.kind(flecs::OnUpdate)
.with<Character>()
.with<Player>()
.without<CharacterControlDisable>()
.each([](flecs::entity e, const Input &input,
const CharacterBase &ch, AnimationControl &anim,
CharacterInActuator &act) {

View File

@@ -27,6 +27,7 @@ CharacterModule::CharacterModule(flecs::world &ecs)
ecs.component<CharacterInActuator>();
ecs.component<Male>();
ecs.component<Female>();
ecs.component<CharacterControlDisable>();
ecs.import <CharacterAnimationModule>();
ecs.import <TerrainModule>();
ecs.import <WaterModule>();
@@ -131,75 +132,14 @@ CharacterModule::CharacterModule(flecs::world &ecs)
else if (current_subm < 0.8f)
ch.is_submerged = false;
});
#if 0
ecs.system<const EngineData, const CharacterBase, CharacterVelocity>(
"HandleGravityBouyanceWater")
.kind(flecs::OnUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.with<InWater>()
.without<CharacterDisablePhysics>()
.without<CharacterUpdatePhysicsState>()
.each([this](flecs::entity e, const EngineData &eng,
const CharacterBase &ch, CharacterVelocity &gr) {
Ogre::Vector3 gravity(0, -9.8f, 0);
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
Ogre::Vector3 v(0, 0, 0);
if (e.has<CharacterGravity>())
v += gravity;
if (e.has<CharacterBuoyancy>()) {
float volume = 2.0f * 0.5f * 0.5f;
float density = 900.0f;
float full_subm = 2.0f;
float mass = 80.0f;
float multiplier = 0.25f;
float current_subm = -Ogre::Math::Clamp(
pos.y + Ogre::Math::Sin(ch.mTimer *
0.13f +
130.0f) *
0.07f,
-full_subm, 0.0f);
Ogre::Vector3 b = -gravity * density * volume *
multiplier * current_subm /
full_subm / mass;
v += b;
}
gr.gvelocity += v * eng.delta;
gr.gvelocity.y =
Ogre::Math::Clamp(gr.gvelocity.y, -2.5f, 1.5f);
gr.gvelocity *= (1.0 - eng.delta);
gr.velocity.y *= (1.0 - eng.delta);
});
#endif
#if 0
ecs.system<const EngineData, const CharacterBase, CharacterVelocity>(
"HandleGravityNoWater")
.kind(flecs::OnUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.without<InWater>()
.with<CharacterGravity>()
.without<CharacterDisablePhysics>()
.each([this](flecs::entity e, const EngineData &eng,
const CharacterBase &ch, CharacterVelocity &gr) {
Ogre::Vector3 gravity(0, -9.8f, 0);
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
gr.gvelocity += gravity * eng.delta;
if (pos.y < -1.2) {
gr.gvelocity.y = 0.0f;
}
gr.gvelocity *= (1.0 - eng.delta);
gr.velocity.y *= (1.0 - eng.delta);
});
#endif
#define TURN_SPEED 500.0f // character turning in degrees per second
ecs.system<const Input, const Camera, CharacterBase>("UpdateBody")
.kind(flecs::OnUpdate)
.with<Character>()
.with<Player>()
.without<CharacterInActuator>()
.each([](flecs::entity e, const Input &input,
.without<CharacterControlDisable>()
.each([](flecs::entity e, const Input &input,
const Camera &camera, CharacterBase &ch) {
ch.mGoalDirection = Ogre::Vector3::ZERO;
float delta = e.world().delta_time();
@@ -604,6 +544,92 @@ void CharacterModule::updateCameraGoal(Camera &camera, Ogre::Real deltaYaw,
}
}
void applyWeightBasedScale(Ogre::Entity *ent,
const Ogre::String &targetBoneName,
const Ogre::Vector3 &scale)
{
Ogre::MeshPtr mesh = ent->getMesh();
Ogre::SkeletonInstance *skel = ent->getSkeleton();
Ogre::Bone *targetBone = skel->getBone(targetBoneName);
// Create a non-uniform scale matrix relative to the bone's local space
Ogre::Matrix4 scaleMatrix = Ogre::Matrix4::IDENTITY;
scaleMatrix.setScale(scale);
for (unsigned short i = 0; i < mesh->getNumSubMeshes(); ++i) {
Ogre::SubMesh *submesh = mesh->getSubMesh(i);
Ogre::VertexData *vertexData = submesh->useSharedVertices ?
mesh->sharedVertexData :
submesh->vertexData;
// 1. Get Position Element
const Ogre::VertexElement *posElem =
vertexData->vertexDeclaration->findElementBySemantic(
Ogre::VES_POSITION);
Ogre::HardwareVertexBufferSharedPtr vbuf =
vertexData->vertexBufferBinding->getBuffer(
posElem->getSource());
// 2. Access Bone Assignments
// This map tells us which bones influence which vertex and by how much
const Ogre::Mesh::VertexBoneAssignmentList &vbal =
submesh->getBoneAssignments();
// Lock buffer for reading and writing
float *pVertices = static_cast<float *>(
vbuf->lock(Ogre::HardwareBuffer::HBL_NORMAL));
for (size_t vIdx = 0; vIdx < vertexData->vertexCount; ++vIdx) {
float totalWeight = 0.0f;
// Find assignments for this specific vertex
for (auto const &[index, assignment] : vbal) {
if (assignment.vertexIndex == vIdx &&
assignment.boneIndex ==
targetBone->getHandle()) {
totalWeight = assignment.weight;
break;
}
}
if (totalWeight > 0.0f) {
float *pPos;
posElem->baseVertexPointerToElement(
reinterpret_cast<unsigned char *>(
pVertices) +
vIdx * vbuf->getVertexSize(),
&pPos);
Ogre::Vector3 vertexPos(pPos[0], pPos[1],
pPos[2]);
// Transform vertex to bone local space, scale it, then move back
// This ensures scaling happens along the bone's axis, not the world axis
Ogre::Vector3 localPos =
targetBone->_getDerivedOrientation()
.Inverse() *
(vertexPos -
targetBone->_getDerivedPosition());
Ogre::Vector3 scaledLocalPos =
scaleMatrix * localPos;
Ogre::Vector3 worldPos =
(targetBone->_getDerivedOrientation() *
scaledLocalPos) +
targetBone->_getDerivedPosition();
// Interpolate based on weight (Lerp) to handle vertices shared with other bones
Ogre::Vector3 finalPos = Ogre::Math::lerp(
vertexPos, worldPos, totalWeight);
pPos[0] = finalPos.x;
pPos[1] = finalPos.y;
pPos[2] = finalPos.z;
}
}
vbuf->unlock();
}
}
void CharacterModule::createCharacter(flecs::entity e,
const Ogre::Vector3 &position,
const Ogre::Quaternion &rotation,

View File

@@ -37,6 +37,7 @@ struct CharacterInActuator {
Ogre::String animationState;
Vector3 prevMotion;
};
struct CharacterControlDisable {};
struct CharacterModule {
CharacterModule(flecs::world &ecs);
void updateCameraGoal(Camera &camera, Ogre::Real deltaYaw,

View File

@@ -543,7 +543,8 @@ struct GUIListener : public Ogre::RenderTargetListener {
} else {
ECS::ActionNodeList &list =
ECS::get_mut<ECS::ActionNodeList>();
if (list.dynamicNodes.size() > 0) {
if (list.dynamicNodes.size() > 0 &&
ECS::get<GUI>().enableActions) {
int j;
Ogre::Vector3 queryPos;
std::vector<size_t> points =
@@ -686,6 +687,7 @@ GUIModule::GUIModule(flecs::world &ecs)
gui.enabled = false;
gui.grab = false;
gui.grabChanged = false;
gui.enableActions = true;
})
.add(flecs::Singleton);
ecs.component<GUIData>()
@@ -695,7 +697,7 @@ GUIModule::GUIModule(flecs::world &ecs)
priv.mGuiOverlay = nullptr;
})
.add(flecs::Singleton);
ecs.set<GUI>({ false, true, false, false, false, "", {}, -1 });
ecs.set<GUI>({ false, true, false, false, false, true, "", {}, -1 });
ecs.set<GUIData>({ nullptr, {}, nullptr });
ui_wait =
ecs.system<const RenderWindow, App, GUIData>("SetupGUI")

View File

@@ -14,6 +14,7 @@ struct GUI {
bool grabChanged;
bool narrationBox;
bool mainMenu;
bool enableActions;
Ogre::String narrationText;
std::vector<Ogre::String> choices;
int narration_answer;

View File

@@ -244,7 +244,7 @@ void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode,
nullptr,
false,
{ 0, 0, 0 } });
ecs.set<GUI>({ true, true, true, false, false, "", {}, -1 });
ecs.set<GUI>({ true, true, true, false, false, true, "", {}, -1 });
ecs.get_mut<GUI>().enabled = true;
ecs.get_mut<GUI>().setWindowGrab(false);
ecs.modified<GUI>();

View File

@@ -12,6 +12,7 @@
#include "GUIModule.h"
#include "GUIModuleCommon.h"
#include "LuaData.h"
#include "PhysicsModule.h"
#include "PlayerActionModule.h"
namespace ECS
@@ -252,27 +253,28 @@ struct LuaNarrationHandler : GUI::NarrationHandler {
};
struct SimpleWordHandler : PlayerActionModule::ActionWordHandler {
void operator()(flecs::entity town, int index,
const Ogre::String &word) override
void operator()(int actor, flecs::entity town, int index,
const Ogre::String &word, int actionNode) override
{
TestNarrativeHandler *handle = OGRE_NEW TestNarrativeHandler();
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"] =
ECS::get<CharacterManagerModule>()
.getPlayer()
.id();
props["recipient"] = e.id();
handle->setProperties(props.dump());
break;
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>();
}
ECS::get_mut<GUI>().addNarrationHandler(handle);
ECS::modified<GUI>();
}
};
@@ -346,11 +348,13 @@ PlayerActionModule::PlayerActionModule(flecs::world &ecs)
.selected]
.action) {
(*it->second)(
town, index,
-1, town, index,
list.dynamicNodes
[list.getUIData()
.selected]
.action);
.action,
list.getUIData()
.selected);
list.setBusy();
}
}
@@ -358,6 +362,19 @@ PlayerActionModule::PlayerActionModule(flecs::world &ecs)
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,
@@ -377,11 +394,197 @@ void PlayerActionModule::removeWordHandler(const Ogre::String &word,
}
}
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;
Ogre::Quaternion newRotation =
anode.rotation * placeLocalRotation[place];
Ogre::Vector3 newPosition =
anode.position + newRotation * 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) {
// Yay!!!
std::cout << "Node data: " << std::endl;
std::cout << anode.props.dump(4) << std::endl;
if (ch.is_valid()) {
PhysicsModule::controlPhysics(ch,
false);
// no control by player or ai
ch.add<CharacterControlDisable>();
if (word == "sit")
ch.set<CharacterInActuator>(
{ "sitting-chair",
{ 0, 0, 0 } });
// else
// ch.set<CharacterInActuator>(
// { "idle", { 0, 0, 0 } });
}
teleport("enter");
delay = 0.0f;
state = 5;
} else if (ECS::get<Input>().act == 0 &&
delay <= 0.2f) {
delay = 0.0f;
state = 10;
}
break;
case 5:
// teleport again to handle possible problems caused by root motion
delay = 0.0f;
teleport("enter");
state++;
break;
case 6:
// 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 7:
// 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 8:
delay = 0.0;
state = 10;
break;
case 10:
delay = 0.0;
state++;
break;
case 11:
// 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 12:
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()(flecs::entity town, int index,
const Ogre::String &word) override
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) {
@@ -397,25 +600,39 @@ struct LuaWordHandler : PlayerActionModule::ActionWordHandler {
} else if (lua_type(L, -1) == LUA_TTABLE) {
luaL_checktype(L, -1, LUA_TTABLE);
lua_pop(L, 1);
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"] =
ECS::get<CharacterManagerModule>()
.getPlayer()
.id();
props["recipient"] = e.id();
handle->setProperties(props.dump());
break;
/* 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>();
}
}
ECS::get_mut<GUI>().addNarrationHandler(handle);
ECS::modified<GUI>();
}
}
};
@@ -460,6 +677,26 @@ int PlayerActionModule::setupLuaActionHandler(lua_State *L)
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);

View File

@@ -60,12 +60,39 @@ public:
};
struct PlayerActionModule {
struct ActionWordHandler {
virtual void operator()(flecs::entity town, int index,
const Ogre::String &word) = 0;
/** actor is -1 for player >=0 for NPCs */
virtual void operator()(int actor, flecs::entity town, int index,
const Ogre::String &word, int actionNode) = 0;
};
struct ActivatedWordHandler {
enum {OK = 0, BUSY, ERROR};
private:
bool active;
public:
ActivatedWordHandler(): active(false) {}
int _update(float delta)
{
if (!active) {
enter();
active = true;
}
int ret = update(delta);
if (ret != BUSY) {
exit(ret);
active = false;
}
return ret;
}
virtual ~ActivatedWordHandler() {}
bool _is_active() {return active;}
private:
virtual void enter() = 0;
virtual void exit(int result) = 0;
virtual int update(float delta) = 0;
};
std::multimap<Ogre::String, ActionWordHandler *>
actionWords;
std::multimap<Ogre::String, ActivatedWordHandler *> activatedWords;
PlayerActionModule(flecs::world &ecs);
void addWordHandler(const Ogre::String &word, ActionWordHandler *handler);
@@ -73,6 +100,8 @@ struct PlayerActionModule {
void addLuaWordHandler(const Ogre::String &word, lua_State *L, int ref);
void removeLuaWordHandler(const Ogre::String &word, lua_State *L, int ref);
int setupLuaActionHandler(lua_State *L);
void addActivatedWordHandler(const Ogre::String &word, ActivatedWordHandler *handler);
void removeActivatedWordHandler(const Ogre::String &word, ActivatedWordHandler *handler);
};
}

View File

@@ -5807,6 +5807,12 @@ struct TownDecorateFurniture : TownTask {
.get<float>();
anode.props =
action;
anode.props
["town"] =
e.id();
anode.props
["index"] =
-1;
anode.position =
worldSensorPosition;
anode.rotation =