diff --git a/assets/blender/buildings/parts/edit-firniture-props.py b/assets/blender/buildings/parts/edit-firniture-props.py index ae00e2b..e3b41ad 100644 --- a/assets/blender/buildings/parts/edit-firniture-props.py +++ b/assets/blender/buildings/parts/edit-firniture-props.py @@ -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() diff --git a/assets/blender/buildings/parts/export_furniture_parts.py b/assets/blender/buildings/parts/export_furniture_parts.py index 6adab66..70520a2 100644 --- a/assets/blender/buildings/parts/export_furniture_parts.py +++ b/assets/blender/buildings/parts/export_furniture_parts.py @@ -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"] diff --git a/assets/blender/buildings/parts/furniture.blend b/assets/blender/buildings/parts/furniture.blend index 2f44115..ad819e7 100644 --- a/assets/blender/buildings/parts/furniture.blend +++ b/assets/blender/buildings/parts/furniture.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51b6ea0e0716324faa59371451d5210474543d1a5f2d0a98a0c58eb6c18013ce -size 2078756 +oid sha256:36d5375c2163be7df868529629bc4fefd69e298c6ec113c5108274ced98af218 +size 2098522 diff --git a/assets/blender/edited-normal-male.blend b/assets/blender/edited-normal-male.blend index 836279e..943fdfc 100644 --- a/assets/blender/edited-normal-male.blend +++ b/assets/blender/edited-normal-male.blend @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:760d0a0cd224eb993b0c2c5fef75174a45541bd88f1031557e77451d76fda2e9 -size 13158099 +oid sha256:79830f989bb019636a3edbd4c635687d5c99652ac89cf5d51f82cd34f2532434 +size 13213685 diff --git a/lua-scripts/data.lua b/lua-scripts/data.lua index 2b6d718..60acbd5 100644 --- a/lua-scripts/data.lua +++ b/lua-scripts/data.lua @@ -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) diff --git a/src/gamedata/CharacterAnimationModule.cpp b/src/gamedata/CharacterAnimationModule.cpp index 439fe10..7d755bf 100644 --- a/src/gamedata/CharacterAnimationModule.cpp +++ b/src/gamedata/CharacterAnimationModule.cpp @@ -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() .without() - .each([](flecs::entity e, const CharacterBase &ch, + .without() + .each([](flecs::entity e, const CharacterBase &ch, AnimationControl &anim) { if (!anim.configured) return; @@ -395,6 +399,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs) .with() .with() .without() + .without() .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() .with() + .without() .each([](flecs::entity e, const Input &input, const CharacterBase &ch, AnimationControl &anim, CharacterInActuator &act) { diff --git a/src/gamedata/CharacterModule.cpp b/src/gamedata/CharacterModule.cpp index 3b70aa3..76e8c21 100644 --- a/src/gamedata/CharacterModule.cpp +++ b/src/gamedata/CharacterModule.cpp @@ -27,6 +27,7 @@ CharacterModule::CharacterModule(flecs::world &ecs) ecs.component(); ecs.component(); ecs.component(); + ecs.component(); ecs.import (); ecs.import (); ecs.import (); @@ -131,75 +132,14 @@ CharacterModule::CharacterModule(flecs::world &ecs) else if (current_subm < 0.8f) ch.is_submerged = false; }); -#if 0 - ecs.system( - "HandleGravityBouyanceWater") - .kind(flecs::OnUpdate) - .with() - .with() - .with() - .without() - .without() - .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()) - v += gravity; - if (e.has()) { - 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( - "HandleGravityNoWater") - .kind(flecs::OnUpdate) - .with() - .with() - .without() - .with() - .without() - .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("UpdateBody") .kind(flecs::OnUpdate) .with() .with() .without() - .each([](flecs::entity e, const Input &input, + .without() + .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( + 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( + 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, diff --git a/src/gamedata/CharacterModule.h b/src/gamedata/CharacterModule.h index 3ddc2ae..ca9b0aa 100644 --- a/src/gamedata/CharacterModule.h +++ b/src/gamedata/CharacterModule.h @@ -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, diff --git a/src/gamedata/GUIModule.cpp b/src/gamedata/GUIModule.cpp index 1e1d75a..4a83917 100644 --- a/src/gamedata/GUIModule.cpp +++ b/src/gamedata/GUIModule.cpp @@ -543,7 +543,8 @@ struct GUIListener : public Ogre::RenderTargetListener { } else { ECS::ActionNodeList &list = ECS::get_mut(); - if (list.dynamicNodes.size() > 0) { + if (list.dynamicNodes.size() > 0 && + ECS::get().enableActions) { int j; Ogre::Vector3 queryPos; std::vector 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() @@ -695,7 +697,7 @@ GUIModule::GUIModule(flecs::world &ecs) priv.mGuiOverlay = nullptr; }) .add(flecs::Singleton); - ecs.set({ false, true, false, false, false, "", {}, -1 }); + ecs.set({ false, true, false, false, false, true, "", {}, -1 }); ecs.set({ nullptr, {}, nullptr }); ui_wait = ecs.system("SetupGUI") diff --git a/src/gamedata/GUIModuleCommon.h b/src/gamedata/GUIModuleCommon.h index a89e0d9..b428664 100644 --- a/src/gamedata/GUIModuleCommon.h +++ b/src/gamedata/GUIModuleCommon.h @@ -14,6 +14,7 @@ struct GUI { bool grabChanged; bool narrationBox; bool mainMenu; + bool enableActions; Ogre::String narrationText; std::vector choices; int narration_answer; diff --git a/src/gamedata/GameData.cpp b/src/gamedata/GameData.cpp index 1ebf3b1..d3ef70f 100644 --- a/src/gamedata/GameData.cpp +++ b/src/gamedata/GameData.cpp @@ -244,7 +244,7 @@ void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode, nullptr, false, { 0, 0, 0 } }); - ecs.set({ true, true, true, false, false, "", {}, -1 }); + ecs.set({ true, true, true, false, false, true, "", {}, -1 }); ecs.get_mut().enabled = true; ecs.get_mut().setWindowGrab(false); ecs.modified(); diff --git a/src/gamedata/PlayerActionModule.cpp b/src/gamedata/PlayerActionModule.cpp index b529751..0bce026 100644 --- a/src/gamedata/PlayerActionModule.cpp +++ b/src/gamedata/PlayerActionModule.cpp @@ -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().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() - .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().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().addNarrationHandler(handle); + ECS::modified(); } - ECS::get_mut().addNarrationHandler(handle); - ECS::modified(); } }; @@ -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().enabled) list.setReady(); }); + ecs.system("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 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 placeLocalOffset; + std::unordered_map 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().dynamicNodes[actionNode]; + if (actor == -1) + ch = ECS::get().getPlayer(); + else if (actor >= 0) { + const TownNPCs &npcs = town.get(); + 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(); + localPosition.y = position["position_y"].get(); + localPosition.z = position["position_z"].get(); + localRotation.w = position["rotation_w"].get(); + localRotation.x = position["rotation_x"].get(); + localRotation.y = position["rotation_y"].get(); + localRotation.z = position["rotation_z"].get(); + placeLocalOffset[position["name"].get()] = + localPosition; + placeLocalRotation[position["name"].get()] = + 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()) { + ch.get() + .mBodyNode->_setDerivedOrientation(newRotation); + ch.get().mBodyNode->_setDerivedPosition( + newPosition); + } + if (actor >= 0) { + town.get_mut().npcs[actor].position = + newPosition; + town.get_mut().npcs[actor].orientation = + newRotation; + town.modified(); + } + } + int update(float delta) + { + switch (state) { + case 0: + if (ECS::get().act) + delay += delta; + // activate anly after delay + if (ECS::get().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(); + if (word == "sit") + ch.set( + { "sitting-chair", + { 0, 0, 0 } }); + // else + // ch.set( + // { "idle", { 0, 0, 0 } }); + } + teleport("enter"); + delay = 0.0f; + state = 5; + } else if (ECS::get().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().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().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().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().enableActions = false; + ECS::modified(); + std::cout << "enter" << std::endl; + } + void exit(int result) + { + ch.remove(); + ch.remove(); + PhysicsModule::controlPhysics(ch, true); + // OgreAssert(false, "exit"); + delay = 0.0f; + state = 0; + ECS::get_mut().enableActions = true; + ECS::modified(); + 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().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() - .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().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().addNarrationHandler(handle); + ECS::modified(); + } else { + if (activeActors.find(actor) == + activeActors.end()) { + TestActivatedWordHandler *handler = + OGRE_NEW TestActivatedWordHandler( + actor, town, index, + word, actionNode); + ECS::get_mut() + .addActivatedWordHandler( + word, handler); + ECS::modified(); } } - ECS::get_mut().addNarrationHandler(handle); - ECS::modified(); } } }; @@ -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 lock(*nodeMutex); diff --git a/src/gamedata/PlayerActionModule.h b/src/gamedata/PlayerActionModule.h index 8f2a2e9..9c9aea3 100644 --- a/src/gamedata/PlayerActionModule.h +++ b/src/gamedata/PlayerActionModule.h @@ -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 actionWords; + std::multimap 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); }; } diff --git a/src/gamedata/items/town.cpp b/src/gamedata/items/town.cpp index e273bc4..91c4fa4 100644 --- a/src/gamedata/items/town.cpp +++ b/src/gamedata/items/town.cpp @@ -5807,6 +5807,12 @@ struct TownDecorateFurniture : TownTask { .get(); anode.props = action; + anode.props + ["town"] = + e.id(); + anode.props + ["index"] = + -1; anode.position = worldSensorPosition; anode.rotation =