Action nodes work better now
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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.
Binary file not shown.
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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 =
|
||||
|
||||
Reference in New Issue
Block a user