diff --git a/Game.cpp b/Game.cpp index 8165225..1a672f0 100644 --- a/Game.cpp +++ b/Game.cpp @@ -236,15 +236,14 @@ public: else if (key == 'f') control |= 64; if (key == 'w' || key == 'a' || key == 's' || key == 'd' || - key == 'e' || key == OgreBites::SDLK_LSHIFT) + key == 'e' || key == OgreBites::SDLK_LSHIFT || key == 'e' || + key == 'f') return true; return false; } bool keyReleased(const OgreBites::KeyboardEvent &evt) override { OgreBites::Keycode key = evt.keysym.sym; - if (isGuiEnabled()) - return false; if (key == 'w') control &= ~1; else if (key == 'a') @@ -255,8 +254,14 @@ public: control &= ~8; else if (key == OgreBites::SDLK_LSHIFT) control &= ~16; - if (key == 'w' || key == 'a' || key == 's' || key == 'd' || - key == OgreBites::SDLK_LSHIFT) + else if (key == 'e') + control &= ~32; + else if (key == 'f') + control &= ~64; + if (isGuiEnabled()) + return false; + if (key == 'w' || key == 'a' || key == 's' || key == 'd' || + key == OgreBites::SDLK_LSHIFT || key == 'e' || key == 'f') return true; return false; } @@ -474,14 +479,19 @@ public: mDynWorld->getBtWorld()->stepSimulation(delta, 3); #endif if (!ECS::get().has()) - return; - /* Update window grab */ - ECS::GUI &gui = ECS::get().get_mut(); - if (gui.grabChanged) { - setWindowGrab(gui.grab); - gui.grabChanged = false; - ECS::get().modified(); - } + goto end; + { + /* Update window grab */ + ECS::GUI &gui = ECS::get().get_mut(); + if (gui.grabChanged) { + setWindowGrab(gui.grab); + gui.grabChanged = false; + ECS::get().modified(); + std::cout << "updateWorld " << gui.grabChanged + << " " << gui.grab << std::endl; + } + } +end: ECS::update(delta); #if 0 diff --git a/assets/blender/buildings/parts/CMakeLists.txt b/assets/blender/buildings/parts/CMakeLists.txt index c0360db..2e151b6 100644 --- a/assets/blender/buildings/parts/CMakeLists.txt +++ b/assets/blender/buildings/parts/CMakeLists.txt @@ -1,6 +1,6 @@ project(building-parts) set(PARTS_FILES pier.blend) -set(FURNITURE_FILES furniture.blend) +set(FURNITURE_FILES furniture.blend furniture-sofa.blend) set(PARTS_OUTPUT_DIRS) foreach(PARTS_FILE ${PARTS_FILES}) get_filename_component(FILE_NAME ${PARTS_FILE} NAME_WE) diff --git a/assets/blender/buildings/parts/export_furniture_parts.py b/assets/blender/buildings/parts/export_furniture_parts.py index c6aa593..6adab66 100644 --- a/assets/blender/buildings/parts/export_furniture_parts.py +++ b/assets/blender/buildings/parts/export_furniture_parts.py @@ -49,9 +49,10 @@ def export_root_objects_to_gltf(output_dir): else: desc["tags"] = [] desc["sensors"] = [] + desc["actions"] = [] desc["positions"] = [] for child in obj.children: - if child.name.startswith("sensor-"): + if child.name.startswith("action-"): if not "action" in child: continue if not "height" in child: @@ -64,29 +65,29 @@ def export_root_objects_to_gltf(output_dir): local_rot_d = ogre_local_matrix.to_quaternion() local_pos = [round(x, 4) for x in local_pos_d] local_rot = [round(x, 4) for x in local_rot_d] - sensor = {} - sensor["position_x"] = local_pos[0] - sensor["position_y"] = local_pos[2] - sensor["position_z"] = local_pos[1] - sensor["rotation_w"] = local_rot[0] - sensor["rotation_x"] = local_rot[1] - sensor["rotation_y"] = local_rot[2] - sensor["rotation_z"] = local_rot[3] - sensor["height"] = child["height"] - sensor["radius"] = child["radius"] - sensor["action"] = child["action"] + action = {} + action["position_x"] = local_pos[0] + action["position_y"] = local_pos[1] + action["position_z"] = local_pos[2] + action["rotation_w"] = local_rot[0] + action["rotation_x"] = local_rot[1] + action["rotation_y"] = local_rot[2] + action["rotation_z"] = local_rot[3] + action["height"] = child["height"] + action["radius"] = child["radius"] + action["action"] = child["action"] if "action_text" in child: - sensor["action_text"] = child["action_text"] + action["action_text"] = child["action_text"] else: - sensor["action_text"] = child["action"].capitalize() + action["action_text"] = child["action"].capitalize() if "name" in child: - sensor["name"] = child["name"] + action["name"] = child["name"] else: - sensor["name"] = desc["name"] + "_" + str(len(desc["sensors"])) - sensor["furniture"] = {} - sensor["furniture"]["name"] = desc["name"] - sensor["furniture"]["tags"] = desc["tags"] - sensor["positions"] = [] + action["name"] = desc["name"] + "_" + str(len(desc["actions"])) + action["furniture"] = {} + action["furniture"]["name"] = desc["name"] + action["furniture"]["tags"] = desc["tags"] + action["positions"] = [] for schild in child.children: if schild.name.startswith("position-"): local_matrix = schild.matrix_local @@ -107,8 +108,8 @@ def export_root_objects_to_gltf(output_dir): position["rotation_x"] = local_rot[1] position["rotation_y"] = local_rot[2] position["rotation_z"] = local_rot[3] - sensor["positions"].append(position) - desc["sensors"].append(sensor) + action["positions"].append(position) + desc["actions"].append(action) if child.name.startswith("position-"): local_matrix = child.matrix_local ogre_local_matrix = basis_change @ local_matrix @ basis_change.inverted() diff --git a/assets/blender/buildings/parts/furniture-sofa.blend b/assets/blender/buildings/parts/furniture-sofa.blend new file mode 100644 index 0000000..804e2a3 --- /dev/null +++ b/assets/blender/buildings/parts/furniture-sofa.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c1a981a35e4cb343aad00e4877cdd0cc5cc16755a139f1595be7a8a9ef1a4c9 +size 762742 diff --git a/assets/blender/buildings/parts/furniture.blend b/assets/blender/buildings/parts/furniture.blend index f76b4e5..2f44115 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:ab498c6ed1003b7339fc2463fae4204a0d71561179cd6db30d4263f781540b72 -size 2143185 +oid sha256:51b6ea0e0716324faa59371451d5210474543d1a5f2d0a98a0c58eb6c18013ce +size 2078756 diff --git a/lua-scripts/data.lua b/lua-scripts/data.lua index 3bd9d8d..3eb02bd 100644 --- a/lua-scripts/data.lua +++ b/lua-scripts/data.lua @@ -655,3 +655,54 @@ setup_handler(function(event, trigger_entity, what_entity) --]] end end) + +--[[ +active_dialogues = {} +setup_action_handler("talk", function(town, index, word) + local ret = "" + local choices = {} + local have_choice = false + local have_paragraph = false + local book = narrator.parse_file('stories.talk') + local story = narrator.init_story(book) + if story == nil then + crash() + end + story:begin() + narrate("Boo!!!!") + if story.can_continue() then + local paragraph = story:continue(1) + print(dump(paragraph)) + if story:can_choose() then + have_choice = true + local ch = story:get_choices() + for i, choice in ipairs(ch) do + table.insert(choices, choice.text) + print(i, dump(choice)) + end + if #choices == 1 and choices[1] == "Ascend" then + story:choose(1) + choices = {} + end + if #choices == 1 and choices[1] == "Continue" then + this.story:choose(1) + choices = {} + end + end + end + if (#choices > 0) then + print("choices!!!") + narrate(ret, choices) + else + narrate(ret) + end + if not have_choice and not have_paragraph then + -- complete + crash() + else + print("can continue") + end + +end) +]]-- + diff --git a/lua-scripts/stories/talk.ink b/lua-scripts/stories/talk.ink new file mode 100644 index 0000000..9e2dce9 --- /dev/null +++ b/lua-scripts/stories/talk.ink @@ -0,0 +1,6 @@ +Dialogue... +Whatever... +* [Ascend] + ~ crash() +- ->END + diff --git a/src/editor/CMakeLists.txt b/src/editor/CMakeLists.txt index 42efd2e..8b6c719 100644 --- a/src/editor/CMakeLists.txt +++ b/src/editor/CMakeLists.txt @@ -9,7 +9,7 @@ find_package(OgreProcedural REQUIRED CONFIG) find_package(pugixml REQUIRED CONFIG) find_package(flecs REQUIRED CONFIG) -set(COPY_DIRECTORIES resources skybox water resources/buildings/parts) +set(COPY_DIRECTORIES characters resources skybox water resources/buildings/parts) set(INSTALL_DEPS ${CMAKE_CURRENT_BINARY_DIR}/resources.cfg) foreach(DIR_NAME ${COPY_DIRECTORIES}) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${DIR_NAME} diff --git a/src/gamedata/CharacterAnimationModule.cpp b/src/gamedata/CharacterAnimationModule.cpp index 028e76f..439fe10 100644 --- a/src/gamedata/CharacterAnimationModule.cpp +++ b/src/gamedata/CharacterAnimationModule.cpp @@ -22,38 +22,28 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs) .kind(flecs::OnUpdate) .each([this](flecs::entity e, const CharacterBase &ch, AnimationControl &anim) { - if (!anim.configured && ch.mSkeleton) { + if (!anim.configured) { int i, j; e.set({}); - ch.mSkeleton->setBlendMode( + ch.mBodyEnt->getSkeleton()->setBlendMode( Ogre::ANIMBLEND_CUMULATIVE); - Ogre::String animNames[] = { - "idle", - "walking", - "running", - "treading_water", - "swimming", - "hanging-idle", - "hanging-climb", - "swimming-hold-edge", - "swimming-edge-climb", - "character-talk", - "pass-character", - "idle-act", - "sitting-chair", - "sitting-ground" - }; - int state_count = sizeof(animNames) / - sizeof(animNames[0]); - anim.mAnimationSystem = - new AnimationSystem(false); - for (i = 0; i < state_count; i++) { + Ogre::AnimationStateSet *animStateSet = + ch.mBodyEnt->getAllAnimationStates(); + const Ogre::AnimationStateMap &animMap = + animStateSet->getAnimationStates(); + anim.mAnimationSystem = + new AnimationSystem(false); + ch.mBodyEnt->getSkeleton() + ->getBone("Root") + ->removeAllChildren(); + for (auto it = animMap.begin(); + it != animMap.end(); it++) { Animation *animation = new Animation( - ch.mSkeleton, - ch.mBodyEnt->getAnimationState( - animNames[i]), - ch.mSkeleton->getAnimation( - animNames[i]), + ch.mBodyEnt->getSkeleton(), + it->second, + ch.mBodyEnt->getSkeleton() + ->getAnimation( + it->first), e); #ifdef VDEBUG std::cout @@ -62,7 +52,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs) #endif animation->setLoop(true); anim.mAnimationSystem->add_animation( - animNames[i], animation); + it->first, animation); } anim.mAnimationSystem ->builder() @@ -157,10 +147,39 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs) CharacterBase &ch, AnimationControl &anim) { float delta = eng.delta; // ch.mBoneMotion = Ogre::Vector3::ZERO; - bool result = anim.mAnimationSystem->addTime(delta); - if (!ch.mRootBone) + if (!anim.mAnimationSystem) return; - // The value we get is interpolated value. When result is true it is new step +#if 0 + ch.mBodyEnt->getSkeleton()->getBone("Root")->reset(); + ch.mBodyEnt->getSkeleton()->getBone("Root")->setPosition( + Ogre::Vector3::ZERO); +#endif + bool result = anim.mAnimationSystem->addTime(delta); + Ogre::Vector3 rootMotion = + anim.mAnimationSystem->getRootMotionDelta(); + ch.mBonePrevMotion = ch.mBoneMotion; + ch.mBoneMotion = rootMotion; + ch.mBodyEnt->_updateAnimation(); + ch.mBodyEnt->getSkeleton()->getBone("Root")->setPosition( + Ogre::Vector3::ZERO); +#if 0 + Ogre::Vector3 deltaMotion; + ch.mBodyEnt->_updateAnimation(); + std::cout << "motion: " << ch.mBoneMotion << " " + << rootMotion << " " << ch.mBonePrevMotion + << std::endl; + rootMotion = ch.mBodyEnt->getSkeleton() + ->getBone("Root") + ->getPosition(); + if (rootMotion.squaredLength() < + ch.mBoneMotion.squaredLength()) + deltaMotion = rootMotion; + else + deltaMotion = rootMotion - ch.mBoneMotion; + ch.mBonePrevMotion = ch.mBoneMotion; + ch.mBoneMotion = deltaMotion; +#endif + // The value we get is interpolated value. When result is true it is new step #if 0 Ogre::Vector3 offset = ch.mRootBone->getPosition(); ch.mBoneMotion = static_cast( @@ -302,54 +321,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs) Ogre::Vector3 colNormal; bool is_on_floor = false; bool penetration = false; -#if 0 - if (eng.startupDelay < 0.0f) { - if (body.mController) { - Ogre::Vector3 rotMotion = - v.velocity * eng.delta; - rotMotion.x = Ogre::Math::Clamp( - rotMotion.x, -0.04f, 0.04f); - rotMotion.y = Ogre::Math::Clamp( - rotMotion.y, -0.025f, 0.1f); - rotMotion.z = Ogre::Math::Clamp( - rotMotion.z, -0.04f, 0.04f); - btVector3 currentPosition = - body.mGhostObject - ->getWorldTransform() - .getOrigin(); - is_on_floor = - body.mController->isOnFloor(); - penetration = body.mController - ->isPenetrating(); - if (is_on_floor) - v.gvelocity = - Ogre::Vector3::ZERO; - - btTransform from( - Ogre::Bullet::convert( - ch.mBodyNode - ->getOrientation()), - Ogre::Bullet::convert( - ch.mBodyNode - ->getPosition())); - ch.mBodyNode->_setDerivedPosition( - ch.mBodyNode - ->_getDerivedPosition() + - rotMotion); - } - } -#endif }); -#if 0 - ecs.system("HandleRootMotionEnd") - .kind(flecs::OnUpdate) - .each([this](flecs::entity e, CharacterVelocity &v, - CharacterBase &ch) { - // zero the velocity; - // v.velocity = Ogre::Vector3::ZERO; - // ch.mBoneMotion = Ogre::Vector3::ZERO; - }); -#endif ecs.system( "HandleNPCAnimations") @@ -360,6 +332,8 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs) const CharacterBase &ch, AnimationControl &anim) { if (!anim.configured) return; + if (!anim.mAnimationSystem) + return; AnimationNodeStateMachine *state_machine = anim.mAnimationSystem ->get( @@ -405,6 +379,8 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs) AnimationControl &anim) { if (!anim.configured) return; + if (!anim.mAnimationSystem) + return; AnimationNodeStateMachine *main_sm = anim.mAnimationSystem ->get( @@ -568,15 +544,6 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs) act.prevMotion.z = input.motion.z; } -#if 0 - if (!controls_idle) { - if (Ogre::Math::Abs(input.motion.z - act.prevMotion.z) > 0.001f) { - if (input.motion.z < 0) - ECS::get_mut().call_handler("actuator_forward", e); - } - ECS::get_mut().call_handler("actuator_controls_update"); - } -#endif act.prevMotion = input.motion; }); ecs.system("UpdateEvents") @@ -616,87 +583,6 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs) << "h=" << ch.mBodyNode->_getDerivedPosition().y << std::endl; }); -#endif -#if 0 - ecs.system( - "UpdateBodyCast") - .kind(flecs::OnUpdate) - .without() - .each([](flecs::entity e, const EngineData &eng, - const CharacterBase &ch, CharacterBody &body) { - struct ResultCallback - : public btCollisionWorld::RayResultCallback { - btCollisionObject *m_me; - btVector3 m_from, m_to, m_hitNormalWorld, - m_hitPointWorld; - ResultCallback(btCollisionObject *me, - const btVector3 &from, - const btVector3 &to) - : m_me(me) - , m_from(from) - , m_to(to) - { - } - btScalar addSingleResult( - btCollisionWorld::LocalRayResult - &rayResult, - bool normalInWorldSpace) override - { - if (rayResult.m_collisionObject == m_me) - return 1.0f; - if (!btPairCachingGhostObject::upcast( - rayResult.m_collisionObject)) - return 1.0f; - if (!(rayResult.m_collisionObject - ->getCollisionFlags() & - btCollisionObject:: - CF_CHARACTER_OBJECT)) - return 1.0f; - m_closestHitFraction = - rayResult.m_hitFraction; - m_collisionObject = - rayResult.m_collisionObject; - if (normalInWorldSpace) - m_hitNormalWorld = - rayResult - .m_hitNormalLocal; - else - m_hitNormalWorld = - m_collisionObject - ->getWorldTransform() - .getBasis() * - rayResult - .m_hitNormalLocal; - m_hitPointWorld.setInterpolate3( - m_from, m_to, - rayResult.m_hitFraction); - return rayResult.m_hitFraction; - } - }; - Ogre::Vector3 offset(0.0f, 0.5f, 0.0f); - float dist = 0.5f; - btVector3 a = Ogre::Bullet::convert( - ch.mBodyNode->getPosition() + offset), - b(Ogre::Bullet::convert( - ch.mBodyNode->getPosition() + - ch.mBodyNode->getOrientation() * - Ogre::Vector3(0, 0, dist) + - offset)); - ResultCallback result(body.mGhostObject, a, b); - // body.mGhostObject->rayTest(a, b, result); - eng.mWorld->getBtWorld()->rayTest(a, b, result); - if (result.hasHit()) { - std::cout << "Hit!!! " << result.m_hitPointWorld - << std::endl; - e.set( - { "idle", { 0, 0, 0 } }); - ECS::get().mLua->call_handler( - "character_enter", e, - ECS::get().entities.at( - const_cast( - result.m_collisionObject))); - } - }); #endif struct AnimationSetCommand : public GameWorld::Command { int operator()(const std::vector &args) diff --git a/src/gamedata/CharacterAnimationModule.h b/src/gamedata/CharacterAnimationModule.h index d5684ff..9711de3 100644 --- a/src/gamedata/CharacterAnimationModule.h +++ b/src/gamedata/CharacterAnimationModule.h @@ -9,22 +9,16 @@ namespace ECS { class RootMotionListener : public Ogre::NodeAnimationTrack::Listener { - Ogre::Vector3 prevTranslation; - mutable Ogre::Vector3 deltaMotion; - flecs::entity e; - public: RootMotionListener(flecs::entity e) : Ogre::NodeAnimationTrack::Listener() - , e(e) - , prevTranslation(Ogre::Vector3::ZERO) - , deltaMotion(Ogre::Vector3::ZERO) { } bool getInterpolatedKeyFrame(const Ogre::AnimationTrack *t, const Ogre::TimeIndex &timeIndex, Ogre::KeyFrame *kf) override { +#if 0 Ogre::TransformKeyFrame *vkf = static_cast(kf); Ogre::KeyFrame *kf1, *kf2; @@ -36,14 +30,12 @@ public: k2 = static_cast(kf2); Ogre::Vector3 translation; Ogre::Quaternion rotation; - if (tm == 0.0f) { + Ogre::Vector3 deltaMotion; + Ogre::Vector3 prevMotion; + if (tm == 0.0f) { rotation = k1->getRotation(); translation = k1->getTranslate(); deltaMotion = translation; - - // vkf->setRotation(k1->getRotation()); - // vkf->setTranslate(k1->getTranslate()); - // vkf->setScale(k1->getScale()); } else { rotation = Ogre::Quaternion::nlerp( tm, k1->getRotation(), k2->getRotation(), true); @@ -55,14 +47,7 @@ public: translation.squaredLength()) deltaMotion = translation; } -#if 0 - std::cout << "time: " << tm - << " Position: " << deltaMotion; - std::cout << " Quaternion: " << rotation; - std::cout << std::endl; -#endif vkf->setTranslate(deltaMotion); - // vkf->setTranslate(translation); vkf->setRotation(rotation); vkf->setScale(Ogre::Vector3(1, 1, 1)); prevTranslation = translation; @@ -70,7 +55,9 @@ public: e.get_mut().mBonePrevMotion = prevTranslation; e.modified(); return true; - } +#endif + return false; + } }; struct AnimationTrigger; struct AnimationTriggerSubscriber { @@ -126,46 +113,66 @@ struct AnimationTrigger { { } }; +struct SetupTracks { + Ogre::NodeAnimationTrack *mHipsTrack; + Ogre::NodeAnimationTrack *mRootTrack; + RootMotionListener *mListener; + Ogre::Vector3 mRootTranslation; + SetupTracks(flecs::entity e, Ogre::Skeleton *skeleton, + Ogre::Animation *animation) + : mListener(OGRE_NEW RootMotionListener(e)) + { + mHipsTrack = nullptr; + mRootTrack = nullptr; + for (const auto &it : animation->_getNodeTrackList()) { + Ogre::NodeAnimationTrack *track = it.second; + Ogre::String trackName = + track->getAssociatedNode()->getName(); + if (trackName == "mixamorig:Hips") { + mHipsTrack = track; + } else if (trackName == "Root") { + mRootTrack = track; + // mRootTracks[i]->removeAllKeyFrames(); + } + } + if (!mRootTrack) { + Ogre::Bone *bone = skeleton->getBone("Root"); + mRootTrack = animation->createNodeTrack( + bone->getHandle(), bone); + Ogre::TransformKeyFrame *kf = + mRootTrack->createNodeKeyFrame(0.0f); + kf->setTranslate(Ogre::Vector3::ZERO); + kf->setRotation(Ogre::Quaternion::IDENTITY); + } + // if (e.has()) // FIXME + // mRootTrack->setListener(mListener); + Ogre::TransformKeyFrame *tkfBeg = + (Ogre::TransformKeyFrame *)mRootTrack->getKeyFrame(0); + Ogre::TransformKeyFrame *tkfEnd = + (Ogre::TransformKeyFrame *)mRootTrack->getKeyFrame( + mRootTrack->getNumKeyFrames() - 1); + mRootTranslation = + tkfEnd->getTranslate() - tkfBeg->getTranslate(); + } +}; struct Animation { Ogre::AnimationState *mAnimationState; Ogre::Animation *mSkelAnimation; - Ogre::NodeAnimationTrack *mHipsTrack; - Ogre::NodeAnimationTrack *mRootTrack; - RootMotionListener *mListener; + SetupTracks *mTracks; float m_weight; float m_accWeight; + flecs::entity e; + Ogre::Skeleton *mSkeleton; Animation(Ogre::Skeleton *skeleton, Ogre::AnimationState *animState, Ogre::Animation *skelAnimation, flecs::entity e) - : mAnimationState(animState) + : mTracks(OGRE_NEW SetupTracks(e, skeleton, skelAnimation)) + , mAnimationState(animState) , mSkelAnimation(skelAnimation) - , mListener(OGRE_NEW RootMotionListener(e)) , m_weight(0) , m_accWeight(0) + , e(e) + , mSkeleton(skeleton) { - int j; - mRootTrack = nullptr; - mHipsTrack = nullptr; - for (const auto &it : mSkelAnimation->_getNodeTrackList()) { - Ogre::NodeAnimationTrack *track = it.second; - Ogre::String trackName = - track->getAssociatedNode()->getName(); - if (trackName == "mixamorig:Hips") { - mHipsTrack = track; - } else if (trackName == "Root") { - mRootTrack = track; - // mRootTracks[i]->removeAllKeyFrames(); - } - } - if (!mRootTrack) { - Ogre::Bone *bone = skeleton->getBone("Root"); - mRootTrack = mSkelAnimation->createNodeTrack( - bone->getHandle(), bone); - Ogre::TransformKeyFrame *kf = - mRootTrack->createNodeKeyFrame(0.0f); - kf->setTranslate(Ogre::Vector3::ZERO); - kf->setRotation(Ogre::Quaternion::IDENTITY); - } - mRootTrack->setListener(mListener); } Ogre::String getName() { @@ -198,22 +205,134 @@ struct Animation { { return m_weight; } - bool addTime(float time) + Ogre::Vector3 rootMotion; + Ogre::Vector3 getRootMotionDelta() + { +#if 0 + Ogre::KeyFrame *kf1, *kf2; + Ogre::TransformKeyFrame *k1, *k2; + unsigned short firstKeyIndex; + Ogre::Real timePos = mAnimationState->getTimePosition(); + Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(timePos); + float tm = mTracks->mRootTrack->getKeyFramesAtTime( + index, &kf1, &kf2, &firstKeyIndex); + k1 = static_cast(kf1); + k2 = static_cast(kf2); + Ogre::Vector3 translation; + Ogre::Quaternion rotation; + if (tm == 0.0f) { + rotation = k1->getRotation(); + translation = k1->getTranslate(); + } else { + rotation = Ogre::Quaternion::nlerp( + tm, k1->getRotation(), + k2->getRotation(), true); + translation = k1->getTranslate() + + (k2->getTranslate() - + k1->getTranslate()) * + tm; + } + return translation * mAnimationState->getWeight(); +#endif + if (mAnimationState->getEnabled()) + return rootMotion * mAnimationState->getWeight(); + else + return Ogre::Vector3(0, 0, 0); + } +#if 0 + void updateRootMotion(Ogre::Real timePos) + { + Ogre::KeyFrame *kf1, *kf2; + Ogre::TransformKeyFrame *k1, *k2; + unsigned short firstKeyIndex; + mSkeleton->getBone("Root")->setManuallyControlled(true); + Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(timePos); + float tm = mTracks->mRootTrack->getKeyFramesAtTime( + index, &kf1, &kf2, &firstKeyIndex); + k1 = static_cast(kf1); + k2 = static_cast(kf2); + Ogre::Vector3 translation; + Ogre::Quaternion rotation; + Ogre::Vector3 deltaMotion = + e.get_mut().mBoneMotion; + Ogre::Vector3 prevTranslation = + e.get_mut().mBonePrevMotion; + if (tm == 0.0f) { + rotation = k1->getRotation() * m_weight; + translation = k1->getTranslate() * m_weight; + deltaMotion = translation; + } else { + rotation = Ogre::Quaternion::nlerp( + tm, k1->getRotation() * m_weight, + k2->getRotation() * m_weight, true); + translation = k1->getTranslate() * m_weight + + (k2->getTranslate() * m_weight - + k1->getTranslate() * m_weight) * + tm; + deltaMotion = translation - prevTranslation; + if (deltaMotion.squaredLength() > + translation.squaredLength()) + deltaMotion = translation; + } + e.get_mut().mBoneMotion = deltaMotion; + e.get_mut().mBonePrevMotion = prevTranslation; + e.modified(); +#if 1 + if (timePos > 0.5f && m_weight > 0.2 && translation.squaredLength() > 0.0f) { + std::cout << timePos << " " << m_weight << " " + << e.get() + .mBodyEnt->getMesh() + ->getName() + << " " << deltaMotion << " " + << prevTranslation << std::endl; + std::cout << translation << " " << rotation << std::endl; +// OgreAssert(false, "updateRootMotion"); + } +#endif + mTracks->mRootTrack->getAssociatedNode()->setPosition( + Ogre::Vector3()); + mSkeleton->getBone("Root")->reset(); + } +#endif + void getKeyframeIndices(Ogre::Real timePos, unsigned short *pindex) + { + Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(timePos); + Ogre::KeyFrame *kf1, *kf2; + mTracks->mRootTrack->getKeyFramesAtTime(index, &kf1, &kf2, pindex); + + } + bool addTime(float time) { bool result = mAnimationState->getEnabled(); if (!result) return result; - Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex( - mAnimationState->getTimePosition()); - Ogre::KeyFrame *kf1, *kf2; - unsigned short prev_index, next_index; - mRootTrack->getKeyFramesAtTime(index, &kf1, &kf2, &prev_index); + unsigned short prev_index, next_index; + Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(mAnimationState->getTimePosition()); + getKeyframeIndices(mAnimationState->getTimePosition(), &prev_index); unsigned int previous_frame = index.getKeyIndex(); + float lastTime = mAnimationState->getTimePosition(); mAnimationState->addTime(time); - index = mSkelAnimation->_getTimeIndex( - mAnimationState->getTimePosition()); - mRootTrack->getKeyFramesAtTime(index, &kf1, &kf2, &next_index); - return prev_index != next_index; + float thisTime = mAnimationState->getTimePosition(); + float length = mAnimationState->getLength(); + bool loop = mAnimationState->getLoop(); + int loops = loop ? (int)std::round((lastTime + time - thisTime) / length) : 0; + Ogre::TransformKeyFrame tkf(0, 0); + mTracks->mRootTrack->getInterpolatedKeyFrame(lastTime, &tkf); + Ogre::Vector3 lastRootPos = tkf.getTranslate(); + mTracks->mRootTrack->getInterpolatedKeyFrame(thisTime, &tkf); + Ogre::Vector3 thisRootPos = tkf.getTranslate(); +#if 0 + if (thisTime > lastTime) + rootMotion = thisRootPos - lastRootPos; + else + rootMotion = mTracks->mRootTranslation + thisRootPos - lastRootPos; +#else + rootMotion = (thisRootPos - lastRootPos) + (loops * mTracks->mRootTranslation); + +#endif + getKeyframeIndices(mAnimationState->getTimePosition(), &next_index); + // updateRootMotion(mAnimationState->getTimePosition()); + return prev_index != next_index; } void reset() { @@ -827,6 +946,14 @@ struct AnimationSystem : AnimationNode { } }; AnimationSystemBuilder m_builder; + Ogre::Vector3 getRootMotionDelta() + { + Ogre::Vector3 motionDelta(0, 0, 0); + int i; + for (i = 0; i < vanimation_list.size(); i++) + motionDelta += vanimation_list[i]->getRootMotionDelta(); + return motionDelta; + } bool addTime(float time) { int i; diff --git a/src/gamedata/CharacterManagerModule.cpp b/src/gamedata/CharacterManagerModule.cpp index 303c12a..9c1a83e 100644 --- a/src/gamedata/CharacterManagerModule.cpp +++ b/src/gamedata/CharacterManagerModule.cpp @@ -4,15 +4,190 @@ #include "Components.h" #include "CharacterModule.h" #include "CharacterAnimationModule.h" +#include "StaticGeometryModule.h" +#include "PhysicsModule.h" +#include "PlayerActionModule.h" +#include "items.h" #include "CharacterManagerModule.h" namespace ECS { +struct TownNPCs { + struct NPCData { + flecs::entity e; + nlohmann::json props; + Ogre::Vector3 position; + Ogre::Quaternion orientation; + Ogre::String model; + }; + + std::map npcs; +}; +struct LivesIn {}; +void createNPCActionNodes(flecs::entity town, flecs::entity e, int index) +{ + NPCActionNodes &anodes = e.get_mut(); + const CharacterBase &ch = e.get(); + Ogre::Vector3 characterPos = ch.mBodyNode->_getDerivedPosition(); + Ogre::Quaternion characterRot = ch.mBodyNode->_getDerivedOrientation(); + if (anodes.anodes.size() > 0) { + int i; + for (i = 0; i < anodes.anodes.size(); i++) { + auto &anode = anodes.anodes[i]; + Ogre::Vector3 offset = Ogre::Vector3::UNIT_Z * 0.3f + + Ogre::Vector3::UNIT_Y; + if (i == 1) + offset = Ogre::Vector3::NEGATIVE_UNIT_Z * 0.3f + + Ogre::Vector3::UNIT_Y; + anode.position = characterPos + characterRot * offset; + anode.rotation = characterRot; + to_json(anode.props["position"], anode.position); + to_json(anode.props["rotation"], anode.rotation); + } + e.modified(); + return; + } + { + ActionNodeList::ActionNode anode; + anode.props["action"] = "talk"; + anode.props["action_text"] = "Talk"; + anode.action = "talk"; + anode.action_text = "Talk"; + Ogre::Vector3 offset = Ogre::Vector3::UNIT_Z * 0.25f + + Ogre::Vector3::UNIT_Y * 1.5f; + anode.position = characterPos + characterRot * offset; + anode.rotation = characterRot; + anode.radius = 0.6f; + anode.height = 1.0f; + to_json(anode.props["position"], anode.position); + to_json(anode.props["rotation"], anode.rotation); + + anode.props["radius"] = anode.radius; + anode.props["height"] = anode.height; + anode.props["town"] = town.id(); + anode.props["index"] = index; + anodes.anodes.push_back(anode); + } + { + ActionNodeList::ActionNode anode; + anode.props["action"] = "action"; + anode.props["action_text"] = "Action"; + anode.action = "action"; + anode.action_text = "Action"; + Ogre::Vector3 offset = Ogre::Vector3::NEGATIVE_UNIT_Z * 0.2f + + Ogre::Vector3::UNIT_Y; + anode.position = characterPos + characterRot * offset; + anode.rotation = characterRot; + anode.radius = 0.3f; + anode.height = 1.0f; + to_json(anode.props["position"], anode.position); + to_json(anode.props["rotation"], anode.rotation); + anode.props["radius"] = anode.radius; + anode.props["height"] = anode.height; + anode.props["town"] = town.id(); + anode.props["index"] = index; + anodes.anodes.push_back(anode); + } + e.modified(); +} CharacterManagerModule::CharacterManagerModule(flecs::world &ecs) { ecs.module(); ecs.import (); ecs.import (); + ecs.import (); + ecs.import (); + ecs.component(); + ecs.component(); + ecs.component(); + ecs.component().on_add( + [](flecs::entity e, NPCActionNodes &anodes) { + anodes.anodes.clear(); + }); + ecs.system("UpdateCharacters") + .immediate() + .kind(flecs::OnUpdate) + .interval(1.0f) + .write() + .write() + .write() + .write() + .write() + .write() + .each([this](flecs::entity town, TerrainItem &item, + TownNPCs &npcs) { + if (!player.is_valid()) + return; + if (!player.has()) + return; + ECS::get().defer_suspend(); + Ogre::Vector3 cameraPos = + player.get() + .mBodyNode->_getDerivedPosition(); + for (auto &npc : npcs.npcs) { + int index = npc.first; + TownNPCs::NPCData &data = npc.second; + Ogre::Vector3 npcPosition = data.position; + Ogre::Quaternion npcOrientation = + data.orientation; + if (cameraPos.squaredDistance(npcPosition) < + 10000.0f) { + if (!data.e.is_valid()) { + data.e = createCharacterData( + data.model, + data.position, + data.orientation); + data.e.add(town); + break; + } + } + if (cameraPos.squaredDistance(npcPosition) > + 22500.0f) { + if (data.e.is_valid()) { + data.e.destruct(); + data.e = flecs::entity(); + break; + } + } + } + ECS::get().defer_resume(); + }); + ecs.system("UpdateCharacters2") + .immediate() + .kind(flecs::OnUpdate) + .write() + .write() + .write() + .write() + .write() + .write() + .each([this](flecs::entity town, TerrainItem &item, + TownNPCs &npcs) { + if (!player.is_valid()) + return; + if (!player.has()) + return; + Ogre::Vector3 cameraPos = + player.get() + .mBodyNode->_getDerivedPosition(); + for (auto &npc : npcs.npcs) { + int index = npc.first; + TownNPCs::NPCData &data = npc.second; + Ogre::Vector3 npcPosition = data.position; + Ogre::Quaternion npcOrientation = + data.orientation; + if (cameraPos.squaredDistance(npcPosition) < + 10000.0f) { + if (data.e.is_valid()) { + if (data.e.has() && + data.e.has(town)) + createNPCActionNodes( + town, data.e, + index); + } + } + } + }); } flecs::entity CharacterManagerModule::createPlayer(const Ogre::Vector3 &position, @@ -43,7 +218,44 @@ CharacterManagerModule::createCharacterData(const Ogre::String model, .entity() .set({ rotation, position }) .set({ model }) - .add(); - return e; + .add() + .add(); + return e; } + +void CharacterManagerModule::registerTownCharacters(flecs::entity town) +{ + Ogre::MeshManager::getSingleton().load("normal-male.glb", "General"); + Ogre::MeshManager::getSingleton().load("normal-female.glb", "General"); + Ogre::String props = StaticGeometryModule::getItemProperties(town); + nlohmann::json j = nlohmann::json::parse(props); + nlohmann::json npcs = nlohmann::json::array(); + if (town.has()) + return; + if (j.find("npcs") != j.end()) + npcs = j["npcs"]; + std::cout << npcs.dump(4) << std::endl; + int index = 0; + std::map npcMap; + for (auto &npc : npcs) { + const char *models[] = { "normal-male.glb", + "normal-female.glb" }; + int sex = npc["sex"].get(); + Ogre::Vector3 npcPosition; + Ogre::Quaternion npcOrientation; + from_json(npc["position"], npcPosition); + from_json(npc["orientation"], npcOrientation); + Ogre::String model = models[sex]; + TownNPCs::NPCData npcData; + npcData.e = flecs::entity(); + npcData.model = model; + npcData.orientation = npcOrientation; + npcData.position = npcPosition; + npcData.props = npc; + npcMap[index] = npcData; + index++; + } + town.set({ npcMap }); +} + } diff --git a/src/gamedata/CharacterManagerModule.h b/src/gamedata/CharacterManagerModule.h index d9137ce..e425d09 100644 --- a/src/gamedata/CharacterManagerModule.h +++ b/src/gamedata/CharacterManagerModule.h @@ -3,6 +3,7 @@ #include namespace ECS { +struct TownCharacterHolder{int index;}; struct CharacterManagerModule { std::set characters; flecs::entity player; @@ -17,6 +18,10 @@ struct CharacterManagerModule { { return player; } + void registerTownCharacters(flecs::entity town); + void setTownCharacter(flecs::entity town, int index, bool enable); + CharacterManagerModule(CharacterManagerModule &&) = delete; + CharacterManagerModule &operator=(CharacterManagerModule&&) = delete; }; } #endif diff --git a/src/gamedata/CharacterModule.cpp b/src/gamedata/CharacterModule.cpp index 5898868..3e47c17 100644 --- a/src/gamedata/CharacterModule.cpp +++ b/src/gamedata/CharacterModule.cpp @@ -282,13 +282,8 @@ CharacterModule::CharacterModule(flecs::world &ecs) ch.mBodyNode->setOrientation(loc.orientation); ch.mBodyNode->setPosition(loc.position); ch.mBodyNode->attachObject(ch.mBodyEnt); - ch.mSkeleton = ch.mBodyEnt->getSkeleton(); OgreAssert(ch.mBodyEnt->getSkeleton()->hasBone("Root"), "No root bone"); - OgreAssert(ch.mSkeleton->hasBone("Root"), - "No root bone"); - ch.mRootBone = ch.mSkeleton->getBone("Root"); - OgreAssert(ch.mRootBone, "No root bone"); ch.mBoneMotion = Ogre::Vector3::ZERO; ch.mBonePrevMotion = Ogre::Vector3::ZERO; }); diff --git a/src/gamedata/CharacterModule.h b/src/gamedata/CharacterModule.h index 7ad75d0..dcafff0 100644 --- a/src/gamedata/CharacterModule.h +++ b/src/gamedata/CharacterModule.h @@ -21,8 +21,6 @@ struct CharacterBase { float mTimer; Ogre::SceneNode *mBodyNode; Ogre::Entity *mBodyEnt; - Ogre::Skeleton *mSkeleton; - Ogre::Node *mRootBone; Ogre::Vector3 mBoneMotion; Ogre::Vector3 mBonePrevMotion; Ogre::Vector3 mGoalDirection; // actual intended direction in world-space diff --git a/src/gamedata/GUIModule.cpp b/src/gamedata/GUIModule.cpp index 45631bc..9bd8bb8 100644 --- a/src/gamedata/GUIModule.cpp +++ b/src/gamedata/GUIModule.cpp @@ -22,7 +22,10 @@ #include "EditorGizmoModule.h" #include "PhysicsModule.h" #include "PlayerActionModule.h" +#include "CharacterModule.h" +#include "CharacterManagerModule.h" #include "items.h" +#include "physics.h" #include "GUIModule.h" #include "GUIModuleCommon.h" namespace ECS @@ -263,60 +266,105 @@ struct GUIListener : public Ogre::RenderTargetListener { "This game does not autosave. Please use save function to keep your state"); ImGui::PopFont(); ImGui::End(); - } else if (ECS::get().get().enabled) { - if (ECS::get().get().narrationBox) { - ImVec2 size = ImGui::GetMainViewport()->Size; - ImGui::SetNextWindowPos(ImVec2(0, - size.y * 0.75f), - ImGuiCond_Always); - ImGui::SetNextWindowSize(ImVec2(size.x, - size.y * 0.25f), - ImGuiCond_Always); - ImGui::Begin("Narration...", NULL, - ImGuiWindowFlags_NoTitleBar); - ImGui::PushFont(midFont); - ImVec2 p = ImGui::GetCursorScreenPos(); - ImGui::TextWrapped( - "%s", ECS::get() - .get() - .narrationText.c_str()); - if (ECS::get().get().choices.size() == 0) { - ImGui::SetCursorScreenPos(p); - if (ImGui::InvisibleButton( - "Background", - ImGui::GetWindowSize())) - ECS::get().mLua->call_handler( - "narration_progress"); - } else { - int i; - for (i = 0; i < ECS::get() - .get() - .choices.size(); - i++) { - if (ImGui::Button( - ECS::get() - .get() - .choices[i] - .c_str())) { - ECS::get() - .get_mut() - .narration_answer = - i + 1; - std::cout << "answer: " - << i + 1 - << std::endl; - ECS::modified(); - ECS::get() - .mLua - ->call_handler( - "narration_answered"); - } - } - } - ImGui::Spacing(); - ImGui::PopFont(); - ImGui::End(); - } else if (ECS::get().get().mainMenu) { + } else if (ECS::get().narrationHandlers.size() > 0) { + ECS::get_mut().grab = false; + ECS::get_mut().grabChanged = true; + ECS::get_mut().enabled = true; + ECS::get_mut().narrationBox = true; + ImVec2 size = ImGui::GetMainViewport()->Size; + ImGui::SetNextWindowPos(ImVec2(0, size.y * 0.75f), + ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(size.x, size.y * 0.25f), + ImGuiCond_Always); + ImGui::Begin("Narration...", NULL, + ImGuiWindowFlags_NoTitleBar); + ImGui::PushFont(midFont); + ImVec2 p = ImGui::GetCursorScreenPos(); + GUI::NarrationHandler *narration = + ECS::get().narrationHandlers.front(); + if (!narration->is_active()) + narration->_event("narration_activate"); + ImGui::TextWrapped( + "%s", narration->getNarrationText().c_str()); + if (narration->getChoices().size() == 0) { + ImGui::SetCursorScreenPos(p); + if (ImGui::InvisibleButton( + "Background", + ImGui::GetWindowSize())) + narration->progress(); + } else { + int i; + for (i = 0; i < narration->getChoices().size(); + i++) { + if (ImGui::Button( + narration->getChoices()[i] + .c_str())) { + narration->setNarrationAnswer( + i + 1); + std::cout << "answer: " << i + 1 + << std::endl; + } + } + } + if (narration->is_complete()) { + ECS::get_mut().enabled = false; + ECS::get_mut().narrationBox = false; + ECS::get_mut().grab = true; + ECS::get_mut().grabChanged = true; + ECS::get_mut().removeNarrationHandler( + narration); + delete narration; + } + ImGui::Spacing(); + ImGui::PopFont(); + ImGui::End(); + ECS::modified(); + } else if (ECS::get().get().narrationBox) { + ImVec2 size = ImGui::GetMainViewport()->Size; + ImGui::SetNextWindowPos(ImVec2(0, size.y * 0.75f), + ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(size.x, size.y * 0.25f), + ImGuiCond_Always); + ImGui::Begin("Narration...", NULL, + ImGuiWindowFlags_NoTitleBar); + ImGui::PushFont(midFont); + ImVec2 p = ImGui::GetCursorScreenPos(); + ImGui::TextWrapped( + "%s", + ECS::get().get().narrationText.c_str()); + if (ECS::get().get().choices.size() == 0) { + ImGui::SetCursorScreenPos(p); + if (ImGui::InvisibleButton( + "Background", + ImGui::GetWindowSize())) + ECS::get().mLua->call_handler( + "narration_progress"); + } else { + int i; + for (i = 0; + i < ECS::get().get().choices.size(); + i++) { + if (ImGui::Button(ECS::get() + .get() + .choices[i] + .c_str())) { + ECS::get() + .get_mut() + .narration_answer = + i + 1; + std::cout << "answer: " << i + 1 + << std::endl; + ECS::modified(); + ECS::get().mLua->call_handler( + "narration_answered"); + } + } + } + ImGui::Spacing(); + ImGui::PopFont(); + ImGui::End(); + } else if (ECS::get().get().enabled) { + if (ECS::get().get().mainMenu) { ImVec2 size = ImGui::GetMainViewport()->Size; ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); @@ -495,27 +543,103 @@ struct GUIListener : public Ogre::RenderTargetListener { } else { ECS::ActionNodeList &list = ECS::get_mut(); - if (list.nodes.size() > 0) { - Ogre::Vector3 queryPos = - ECS::get() - .mCameraNode - ->_getDerivedPosition(); - std::vector points; - list.query(queryPos, points); - for (auto &p : points) { - std::cout << p << std::endl - << list.nodes[p].props.dump() - << std::endl; + if (list.dynamicNodes.size() > 0) { + int j; + Ogre::Vector3 queryPos; + std::vector points = list.points; + std::vector distances = list.distances; + + Ogre::SceneNode *cameraNode = + ECS::get().mCameraNode; + Ogre::Vector3 cameraPos = + cameraNode->_getDerivedPosition(); + float minDistance = 25.0f; + int i; + list.selected = -1; + for (i = 0; i < points.size(); i++) { + size_t p = points[i]; + float distance = distances[i]; + float radius = list.dynamicNodes[p] + .props["radius"] + .get(); + float height = list.dynamicNodes[p] + .props["height"] + .get(); + float actDistance = radius * radius + + height * height; + float textDistance = + actDistance + 2.0f * 2.0f; Ogre::Vector2 screenPos = projectToScreen( - list.nodes[p].position); + list.dynamicNodes[p] + .position); if (screenPos.x < 0) continue; - std::cout << list.nodes[p].position - << " " << screenPos - << std::endl; - ImGui::SetNextWindowPos(ImVec2( - screenPos.x, screenPos.y)); + // FIXME: move this to system to filter points there + Ogre::Vector3 hitPosition; + JPH::BodyID hitBody; + bool hit = + JoltPhysicsWrapper::getSingleton() + .raycastQuery( + list.dynamicNodes[p] + .position, + cameraPos, + hitPosition, + hitBody); + if (hit) + continue; + + if (list.selected == -1) { + if (distance < actDistance) + list.selected = p; + } + ImDrawList *drawList = + ImGui::GetBackgroundDrawList(); + ImVec2 center = ImVec2(screenPos.x, + screenPos.y); + float circleRadius = 8.0f; + ImColor circleColor( + ImVec4(0.3f, 0.3f, 0.3f, 1.0f)); + if (p == list.selected) { + circleRadius = 16.0f; + circleColor = ImColor( + ImVec4(0, 0, 1, 1)); + } + drawList->AddCircleFilled(center, + circleRadius, + circleColor); + drawList->AddCircle( + center, circleRadius, + IM_COL32(64, 64, 255, 255)); + ImVec2 textSize = ImGui::CalcTextSize( + list.dynamicNodes[p] + .action_text.c_str()); + ImVec2 textPos = ImVec2( + center.x - (textSize.x * 0.5f), + center.y + circleRadius + 4.0f); + if (distance < textDistance) { + drawList->AddText( + ImVec2(textPos.x + 1, + textPos.y + 1), + IM_COL32(0, 0, 0, 200), + list.dynamicNodes[p] + .action_text + .c_str()); + drawList->AddText( + ImVec2(textPos.x, + textPos.y), + IM_COL32(255, 32, 64, + 255), + list.dynamicNodes[p] + .action_text + .c_str()); + } +#if 0 + ImGui::SetNextWindowPos( + ImVec2(screenPos.x, + screenPos.y), + ImGuiCond_Always, + ImVec2(0.5f, 0.5f)); ImGui::Begin( ("SensorLabel##" + Ogre::StringConverter::toString( @@ -524,10 +648,27 @@ struct GUIListener : public Ogre::RenderTargetListener { nullptr, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoTitleBar | + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoInputs); - ImGui::TextColored(ImVec4(1, 0, 0, 1), - "SENSOR TRIGGERED"); + ImDrawList *drawList = + ImGui::GetWindowDrawList(); + ImVec2 cursor = + ImGui::GetCursorScreenPos(); + drawList->AddCircleFilled( + ImVec2(cursor.x, cursor.y), + 16.0f, + IM_COL32(0, 0, 255, 255)); + drawList->AddCircle( + ImVec2(cursor.x, cursor.y), + 16.0f, + IM_COL32(32, 32, 255, 255)); + + ImGui::TextColored( + ImVec4(1, 0, 0, 1), "%s", + list.nodes[p] + .action_text.c_str()); ImGui::End(); +#endif } } } diff --git a/src/gamedata/GUIModuleCommon.h b/src/gamedata/GUIModuleCommon.h index 41aaf20..caf5d51 100644 --- a/src/gamedata/GUIModuleCommon.h +++ b/src/gamedata/GUIModuleCommon.h @@ -1,5 +1,9 @@ #ifndef __GUIMODULECOMMON_H__ #define __GUIMODULECOMMON_H__ +#include +#include +#include "Components.h" +#include "GameData.h" namespace ECS { @@ -12,6 +16,83 @@ struct GUI { Ogre::String narrationText; std::vector choices; int narration_answer; + struct NarrationHandler { + private: + Ogre::String mnarrationText; + std::vector mchoices; + int narration_answer; + + private: + bool complete; + bool active; + public: + bool is_complete() + { + return complete; + } + bool is_active() + { + return active; + } + const Ogre::String &getNarrationText() const + { + return mnarrationText; + } + const std::vector &getChoices() const + { + return mchoices; + } + void progress() + { + _event("narration_progress"); + } + void setNarrationAnswer(int answer) + { + narration_answer = answer; + _event("narration_answered"); + } + int getNarrationAnswer() const + { + return narration_answer; + } + + NarrationHandler(): complete(false), active(false) {} + private: + virtual void finish() = 0; + virtual void activate() = 0; + virtual void event(const Ogre::String &event) = 0; + protected: + void _activate() + { + activate(); + active = true; + } + void _finish() + { + finish(); + complete = true; + } + void _narration(const Ogre::String &text, const std::vector &choices) + { + mnarrationText = text; + mchoices = choices; + + } + void _clear_narration() + { + mnarrationText = ""; + mchoices.clear(); + } + public: + void _event(const Ogre::String &ev) + { + if (!active && !complete) + _activate(); + event(ev); + } + virtual ~NarrationHandler() {} + }; + static void setWindowGrab(bool g = true) { ECS::GUI &gui = ECS::get().get_mut(); @@ -30,6 +111,16 @@ struct GUI { ECS::get().modified(); setWindowGrab(true); } + std::vector narrationHandlers; + void addNarrationHandler(struct NarrationHandler *handler) + { + narrationHandlers.push_back(handler); + } + void removeNarrationHandler(struct NarrationHandler *handler) + { + auto it = std::find(narrationHandlers.begin(), narrationHandlers.end(), handler); + narrationHandlers.erase(it); + } }; } diff --git a/src/gamedata/GameData.cpp b/src/gamedata/GameData.cpp index 661d22c..9f2ce58 100644 --- a/src/gamedata/GameData.cpp +++ b/src/gamedata/GameData.cpp @@ -190,6 +190,8 @@ void setupEditor(Ogre::SceneManager *scnMgr, Ogre::SceneNode *cameraNode, ecs.import (); // ecs.import (); ecs.import (); + ecs.import (); + ecs.add(); ecs.system("UpdateDelta") .kind(flecs::OnUpdate) diff --git a/src/gamedata/LuaData.cpp b/src/gamedata/LuaData.cpp index b0fc9ba..baad72f 100644 --- a/src/gamedata/LuaData.cpp +++ b/src/gamedata/LuaData.cpp @@ -11,6 +11,7 @@ #include "EventTriggerModule.h" #include "SlotsModule.h" #include "world-build.h" +#include "PlayerActionModule.h" #include "LuaData.h" #include "luaaa.hpp" extern "C" { @@ -299,7 +300,20 @@ LuaData::LuaData() return 0; }); lua_setglobal(L, "setup_handler"); - lua_pushcfunction(L, [](lua_State *L) -> int { + lua_pushcfunction(L, [](lua_State *L) -> int { + luaL_checktype(L, 1, LUA_TSTRING); + luaL_checktype(L, 2, LUA_TFUNCTION); + ECS::get_mut().setupLuaActionHandler(L); + ECS::modified(); + return 0; + }); + lua_setglobal(L, "setup_action_handler"); + lua_pushcfunction(L, [](lua_State *L) -> int { + // FIXME + return 0; + }); + lua_setglobal(L, "setup_narration_handler"); + lua_pushcfunction(L, [](lua_State *L) -> int { int args = lua_gettop(L); if (args < 1) return 0; @@ -846,6 +860,7 @@ LuaModule::LuaModule(flecs::world &ecs) ecs.module(); ecs.import (); ecs.import (); + ecs.import (); ecs.component(); ecs.component() .on_add([](LuaBase &lua) { diff --git a/src/gamedata/LuaData.h b/src/gamedata/LuaData.h index f482aaa..967fe77 100644 --- a/src/gamedata/LuaData.h +++ b/src/gamedata/LuaData.h @@ -11,7 +11,7 @@ struct LuaData { lua_State *L; std::vector setup_handlers; int setup_handler(); - int call_handler(const Ogre::String &event); + int call_handler(const Ogre::String &event); int call_handler(const Ogre::String &event, flecs::entity e, flecs::entity o); diff --git a/src/gamedata/PhysicsModule.cpp b/src/gamedata/PhysicsModule.cpp index 9408625..2b246ce 100644 --- a/src/gamedata/PhysicsModule.cpp +++ b/src/gamedata/PhysicsModule.cpp @@ -172,14 +172,18 @@ PhysicsModule::PhysicsModule(flecs::world &ecs) .event(flecs::OnRemove) .each([&](flecs::entity e, const JPH::BodyID &id) { JoltPhysicsWrapper::getSingleton().removeBody(id); - JoltPhysicsWrapper::getSingleton().destroyBody(id); + if (e.has() || e.has()) + return; + JoltPhysicsWrapper::getSingleton().destroyBody(id); std::cout << "body destroyed" << std::endl; }); - ecs.observer("SetupCharacterPh") - .event(flecs::OnSet) + ecs.system("SetupCharacterPh") + .kind(flecs::OnUpdate) .with() - .without() - .write() + .without() + .without() + .write() + .write() .each([](flecs::entity e, const EngineData &eng, const CharacterBase &base) { CharacterBody &b = e.ensure(); diff --git a/src/gamedata/PlayerActionModule.cpp b/src/gamedata/PlayerActionModule.cpp index 4a9bdf3..e3ca39e 100644 --- a/src/gamedata/PlayerActionModule.cpp +++ b/src/gamedata/PlayerActionModule.cpp @@ -1,8 +1,16 @@ #include #include #include +#include +#include #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 "PlayerActionModule.h" namespace ECS @@ -51,13 +59,69 @@ struct ActionNodeList::indexObject { } }; +struct TestNarrativeHandler : GUI::NarrationHandler { + int count; + TestNarrativeHandler() + : GUI::NarrationHandler() + , count(0) + { + } + void finish() override + { + _clear_narration(); + } + void activate() override + { + _narration("Dialogue...", {}); + 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 SimpleWordHandler : PlayerActionModule::ActionWordHandler { + void operator()(flecs::entity town, int index, + const Ogre::String &word) override + { + TestNarrativeHandler *handle = OGRE_NEW TestNarrativeHandler(); + ECS::get_mut().addNarrationHandler(handle); + ECS::modified(); + } +}; + PlayerActionModule::PlayerActionModule(flecs::world &ecs) { ecs.module(); + ecs.import (); ecs.component() .on_add([](flecs::entity e, ActionNodeList &alist) { alist.dirty = true; alist.nodes.reserve(1000); + alist.dynamicNodes.reserve(1000); + alist.selected = -1; + alist.busy = false; }) .add(flecs::Singleton); #if 0 @@ -79,25 +143,166 @@ PlayerActionModule::PlayerActionModule(flecs::world &ecs) } }); #endif + ecs.system("updateNodeList") + .kind(flecs::OnUpdate) + .each([](ActionNodeList &list) { + if (list.busy) + return; + if (list.nodes.size() > 0) { + Ogre::SceneNode *cameraNode = + ECS::get().mCameraNode; + Ogre::Vector3 cameraPos = + cameraNode->_getDerivedPosition(); + flecs::entity player = + ECS::get() + .getPlayer(); + if (player.is_valid()) { + Ogre::Vector3 playerPos = + player.get() + .mBodyNode + ->_getDerivedPosition(); + list.query(playerPos, list.points, + list.distances); + } else { + list.query(cameraPos, list.points, + list.distances); + } + } + }); + ecs.system("ActivateActionNode") + .kind(flecs::OnUpdate) + .each([this](ActionNodeList &list, const Input &input) { + if (input.control & 32) + std::cout << "act pressed" << std::endl; + if (list.busy) + return; + if (input.act_pressed && list.selected >= 0) { + std::cout << list.dynamicNodes[list.selected] + .props.dump(4) + << std::endl; + flecs::entity_t townid = + list.dynamicNodes[list.selected] + .props["town"] + .get(); + flecs::entity town = ECS::get().entity(townid); + int index = list.dynamicNodes[list.selected] + .props["index"] + .get(); + for (auto it = actionWords.begin(); + it != actionWords.end(); it++) { + if (it->first == + list.dynamicNodes[list.selected] + .action) { + (*it->second)( + town, index, + list.dynamicNodes + [list.selected] + .action); + list.busy = true; + } + } + } + if (!ECS::get().enabled) + list.busy = false; + }); + SimpleWordHandler *handler = OGRE_NEW SimpleWordHandler; + addWordHandler("talk", handler); +} + +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++; + } +} + +struct LuaWordHandler : PlayerActionModule::ActionWordHandler { + lua_State *L; + int ref; + void operator()(flecs::entity town, int index, + const Ogre::String &word) override + { + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + 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"); + } + } +}; +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(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); + 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); + return 0; } void ActionNodeList::build() { - indexObj = std::make_shared(nodes); + dynamicNodes.clear(); + dynamicNodes.insert(dynamicNodes.end(), nodes.begin(), nodes.end()); + ECS::get().query_builder().each( + [&](flecs::entity e, const NPCActionNodes &anodes) { + dynamicNodes.insert(dynamicNodes.end(), + anodes.anodes.begin(), + anodes.anodes.end()); + }); + + indexObj = std::make_shared(dynamicNodes); indexObj->index.buildIndex(); dirty = false; - std::cout << "index built" << std::endl; } bool ActionNodeList::query(const Ogre::Vector3 &position, - std::vector &points) + std::vector &points, + std::vector &distances) { - if (dirty) - build(); + build(); std::vector tmppoints; std::vector tmpdistances; points.clear(); points.reserve(4); + distances.clear(); + distances.reserve(4); tmppoints.resize(4); tmpdistances.resize(4); nanoflann::KNNResultSet resultSet(4); @@ -106,9 +311,10 @@ bool ActionNodeList::query(const Ogre::Vector3 &position, nanoflann::SearchParameters()); int i; for (i = 0; i < resultSet.size(); i++) - if (tmpdistances[i] < 25.0f) + if (tmpdistances[i] < 25.0f) { points.push_back(tmppoints[i]); + distances.push_back(tmpdistances[i]); + } return ret; } - } diff --git a/src/gamedata/PlayerActionModule.h b/src/gamedata/PlayerActionModule.h index 8a611d0..99e5589 100644 --- a/src/gamedata/PlayerActionModule.h +++ b/src/gamedata/PlayerActionModule.h @@ -2,6 +2,7 @@ #define PLAYERACTIONMODULE_H #include #include +#include #include namespace ECS { @@ -13,13 +14,19 @@ struct ActionNodeList { Ogre::String action_text; Ogre::Vector3 position; Ogre::Quaternion rotation; + float height; + float radius; nlohmann::json props; }; - std::vector nodes; + std::vector nodes, dynamicNodes; std::shared_ptr indexObj; + std::vector points; + std::vector distances; + int selected; bool dirty; + bool busy; void build(); - bool query(const Ogre::Vector3 &position, std::vector &points); + bool query(const Ogre::Vector3 &position, std::vector &points, std::vector &distances); int addNode(struct ActionNodeList::ActionNode &node) { int index = nodes.size(); @@ -32,10 +39,24 @@ struct ActionNodeList { nodes.erase(nodes.begin() + index); } }; +struct NPCActionNodes { + std::vector anodes; +}; +struct PlayerActionModule { + struct ActionWordHandler { + virtual void operator()(flecs::entity town, int index, + const Ogre::String &word) = 0; + }; + + std::multimap + actionWords; -struct PlayerActionModule -{ PlayerActionModule(flecs::world &ecs); + void addWordHandler(const Ogre::String &word, ActionWordHandler *handler); + void removeWordHandler(const Ogre::String &word, ActionWordHandler *handler); + 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); }; } diff --git a/src/gamedata/items/town.cpp b/src/gamedata/items/town.cpp index 5251a97..b01a1ef 100644 --- a/src/gamedata/items/town.cpp +++ b/src/gamedata/items/town.cpp @@ -18,6 +18,7 @@ #include "PhysicsModule.h" #include "LuaData.h" #include "PlayerActionModule.h" +#include "CharacterManagerModule.h" #include "items.h" #include "town.h" @@ -2088,6 +2089,65 @@ void runAllScriptsForTown(flecs::entity e) j["districts"] = districts; StaticGeometryModule::setItemProperties(e, j.dump()); } +bool editNPCs(nlohmann::json &npcs) +{ + bool changed = false; + ImGui::Text("NPC"); + int id = 0; + for (auto &npc : npcs) { + ImGui::Text("%s", npc["lastName"].get().c_str()); + if (ImGui::SmallButton( + ("Spawn##" + Ogre::StringConverter::toString(id)) + .c_str())) { + int sex = npc["sex"].get(); + const char *models[] = { "normal-male.glb", + "normal-female.glb" }; + Ogre::Vector3 npcPosition; + Ogre::Quaternion npcOrientation; + from_json(npc["position"], npcPosition); + from_json(npc["orientation"], npcOrientation); + + // FIXME: create TownCharacterManager and register NPCs through there + ECS::get_mut() + .createCharacterData(models[sex], npcPosition, + npcOrientation); + } + if (ImGui::SmallButton( + ("Delete##" + Ogre::StringConverter::toString(id)) + .c_str())) { + npcs.erase(id); + changed = true; + break; + } + id++; + } + ImGui::Text("New NPC"); + static char lastName[64] = { 0 }; + ImGui::InputText("Last name", lastName, sizeof(lastName)); + static char firstName[64] = { 0 }; + ImGui::InputText("First name", firstName, sizeof(firstName)); + static char tags[256] = { 0 }; + ImGui::InputText("Tags", tags, sizeof(firstName)); + static int selection = 0; + const char *items[] = { "Male", "Female" }; + ImGui::Combo("Sex", &selection, items, 2); + if (ImGui::SmallButton("Add NPC")) { + nlohmann::json npc; + npc["lastName"] = Ogre::String(lastName); + Ogre::Vector3 npcPosition = + ECS::get().sceneNode->_getDerivedPosition(); + Ogre::Quaternion npcOrientation = + ECS::get() + .sceneNode->_getDerivedOrientation(); + to_json(npc["position"], npcPosition); + to_json(npc["orientation"], npcOrientation); + npc["sex"] = selection; + npcs.push_back(npc); + changed = true; + } + ImGui::Separator(); + return changed; +} void createTownPopup(const std::pair item) { Ogre::String prop = StaticGeometryModule::getItemProperties(item.first); @@ -2124,6 +2184,11 @@ void createTownPopup(const std::pair item) changed = changed || editColorRects(colorRects); ImGui::Separator(); } + nlohmann::json npcs = nlohmann::json::array(); + if (j.find("npcs") != j.end()) + npcs = j["npcs"]; + + changed = changed || editNPCs(npcs); nlohmann::json districts = nlohmann::json::array(); for (auto &district : j["districts"]) districts.push_back(district); @@ -2168,6 +2233,7 @@ void createTownPopup(const std::pair item) ImGui::Separator(); ImGui::Text("%s", j.dump(4).c_str()); if (changed) { + j["npcs"] = npcs; j["districts"] = districts; j["colorRects"] = colorRects; j["lotTemplates"] = lotTemplates; @@ -5458,6 +5524,7 @@ struct TownDecorateFurniture : TownTask { .child_of(e) .set( { node }); +#if 0 if (furniture.find( "sensors") != furniture.end()) { @@ -5599,6 +5666,93 @@ struct TownDecorateFurniture : TownTask { #endif } } +#endif + if (furniture.find( + "actions") != + furniture.end()) { + for (const auto & + action : + furniture["actions"]) { + std::cout + << "SENSOR: " + << action.dump() + << std::endl; + std::cout + << furniture + .dump() + << std::endl; + Ogre::Vector3 + actionPosition; + actionPosition + .x = + action["position_x"] + .get(); + actionPosition + .y = + action["position_y"] + .get(); + actionPosition + .z = + action["position_z"] + .get(); + Ogre::Quaternion worldSensorRotation = + worldCenterOrientation * + Ogre::Quaternion( + Ogre::Degree( + 90.0f * + (float)rotation), + Ogre::Vector3:: + UNIT_Y); + Ogre::Vector3 worldSensorPosition = + worldCenterPosition + + offsetX + + offsetZ + + offsetY + + offset + + worldSensorRotation * + actionPosition; + float height = + action["height"] + .get(); + float radius = + action["radius"] + .get(); + if (ECS::get() + .has()) { + ActionNodeList::ActionNode + anode; + anode.action = + action["action"] + .get(); + anode.action_text = + action["action_text"] + .get(); + anode.radius = + action["radius"] + .get(); + anode.height = + action["height"] + .get(); + anode.props = + action; + anode.position = + worldSensorPosition; + anode.rotation = + worldSensorRotation; + ECS::get_mut< + ActionNodeList>() + .addNode( + anode); + std::cout + << "action: " + << action.dump( + 4) + << std::endl; + ECS::modified< + ActionNodeList>(); + } + } + } } } } @@ -5620,6 +5774,11 @@ struct TownDecorateFurniture : TownTask { } }; +void registerTownNPCs(flecs::entity e) +{ + ECS::get_mut().registerTownCharacters(e); + ECS::modified(); +} void createTown(flecs::entity e, Ogre::SceneNode *sceneNode, Ogre::StaticGeometry *geo) { @@ -5693,6 +5852,7 @@ void createTown(flecs::entity e, Ogre::SceneNode *sceneNode, geo->build(); }); }); + registerTownNPCs(e); } } }