diff --git a/CHANGELOG.md b/CHANGELOG.md index db5cc57e33..b972226a48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Bug #4816: GetWeaponDrawn returns 1 before weapon is attached Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses Bug #5129: Stuttering animation on Centurion Archer + Bug #5371: Keyframe animation tracks are used for any file that begins with an X Bug #5714: Touch spells cast using ExplodeSpell don't always explode Bug #5849: Paralysis breaks landing Bug #5883: Immobile creatures don't cause water ripples diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index 679dd0b505..4922bb21d1 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -45,7 +45,7 @@ namespace MWClass { if (!model.empty()) { - renderingInterface.getObjects().insertModel(ptr, model, true); + renderingInterface.getObjects().insertModel(ptr, model); ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static); } } diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index f656965ca5..6023e0192a 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -108,7 +108,7 @@ namespace MWClass { if (!model.empty()) { - renderingInterface.getObjects().insertModel(ptr, model, true); + renderingInterface.getObjects().insertModel(ptr, model); } } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 7ede0eef92..cf2f19d681 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -57,7 +57,7 @@ namespace MWClass { if (!model.empty()) { - renderingInterface.getObjects().insertModel(ptr, model, true); + renderingInterface.getObjects().insertModel(ptr, model); ptr.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Static); } } diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index cd833fe609..04d9a758c5 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -40,8 +40,7 @@ namespace MWClass MWWorld::LiveCellRef* ref = ptr.get(); // Insert even if model is empty, so that the light is added - renderingInterface.getObjects().insertModel( - ptr, model, true, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); + renderingInterface.getObjects().insertModel(ptr, model, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index a9de8dedc1..271a7218a1 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -642,15 +643,15 @@ namespace MWPhysics void PhysicsSystem::addActor(const MWWorld::Ptr& ptr, const std::string& mesh) { - osg::ref_ptr shape = mShapeManager->getShape(mesh); + std::string animationMesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mResourceSystem->getVFS()); + osg::ref_ptr shape = mShapeManager->getShape(animationMesh); // Try to get shape from basic model as fallback for creatures if (!ptr.getClass().isNpc() && shape && shape->mCollisionBox.mExtents.length2() == 0) { - const std::string fallbackModel = ptr.getClass().getModel(ptr); - if (fallbackModel != mesh) + if (animationMesh != mesh) { - shape = mShapeManager->getShape(fallbackModel); + shape = mShapeManager->getShape(mesh); } } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 919f8a9e8e..15af30de5d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -609,7 +609,7 @@ namespace MWRender { std::string kfname = Misc::StringUtils::lowerCase(model); - if (kfname.size() > 4 && kfname.compare(kfname.size() - 4, 4, ".nif") == 0) + if (kfname.ends_with(".nif")) kfname.replace(kfname.size() - 4, 4, ".kf"); addSingleAnimSource(kfname, baseModel); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index a724da1f71..c275d6a312 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -17,7 +17,7 @@ namespace MWRender { CreatureAnimation::CreatureAnimation( - const MWWorld::Ptr& ptr, const std::string& model, Resource::ResourceSystem* resourceSystem) + const MWWorld::Ptr& ptr, const std::string& model, Resource::ResourceSystem* resourceSystem, bool animated) : ActorAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), resourceSystem) { MWWorld::LiveCellRef* ref = mPtr.get(); @@ -28,12 +28,14 @@ namespace MWRender if ((ref->mBase->mFlags & ESM::Creature::Bipedal)) addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model); - addAnimSource(model, model); + + if (animated) + addAnimSource(model, model); } } CreatureWeaponAnimation::CreatureWeaponAnimation( - const MWWorld::Ptr& ptr, const std::string& model, Resource::ResourceSystem* resourceSystem) + const MWWorld::Ptr& ptr, const std::string& model, Resource::ResourceSystem* resourceSystem, bool animated) : ActorAnimation(ptr, osg::ref_ptr(ptr.getRefData().getBaseNode()), resourceSystem) , mShowWeapons(false) , mShowCarriedLeft(false) @@ -45,10 +47,10 @@ namespace MWRender setObjectRoot(model, true, false, true); if ((ref->mBase->mFlags & ESM::Creature::Bipedal)) - { addAnimSource(Settings::Manager::getString("xbaseanim", "Models"), model); - } - addAnimSource(model, model); + + if (animated) + addAnimSource(model, model); mPtr.getClass().getInventoryStore(mPtr).setInvListener(this, mPtr); diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index 3342fb3967..05235e5191 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -15,7 +15,8 @@ namespace MWRender class CreatureAnimation : public ActorAnimation { public: - CreatureAnimation(const MWWorld::Ptr& ptr, const std::string& model, Resource::ResourceSystem* resourceSystem); + CreatureAnimation( + const MWWorld::Ptr& ptr, const std::string& model, Resource::ResourceSystem* resourceSystem, bool animated); virtual ~CreatureAnimation() {} }; @@ -28,7 +29,7 @@ namespace MWRender { public: CreatureWeaponAnimation( - const MWWorld::Ptr& ptr, const std::string& model, Resource::ResourceSystem* resourceSystem); + const MWWorld::Ptr& ptr, const std::string& model, Resource::ResourceSystem* resourceSystem, bool animated); virtual ~CreatureWeaponAnimation() {} void equipmentChanged() override { updateParts(); } diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 2d42623bd2..ba4ab45c60 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -68,12 +69,21 @@ namespace MWRender ptr.getRefData().setBaseNode(insert); } - void Objects::insertModel(const MWWorld::Ptr& ptr, const std::string& mesh, bool animated, bool allowLight) + void Objects::insertModel(const MWWorld::Ptr& ptr, const std::string& mesh, bool allowLight) { insertBegin(ptr); ptr.getRefData().getBaseNode()->setNodeMask(Mask_Object); + bool animated = ptr.getClass().useAnim(); + std::string animationMesh = mesh; + if (animated && !mesh.empty()) + { + animationMesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mResourceSystem->getVFS()); + if (animationMesh == mesh) + animated = false; + } - osg::ref_ptr anim(new ObjectAnimation(ptr, mesh, mResourceSystem, animated, allowLight)); + osg::ref_ptr anim( + new ObjectAnimation(ptr, animationMesh, mResourceSystem, animated, allowLight)); mObjects.emplace(ptr.mRef, std::move(anim)); } @@ -83,13 +93,18 @@ namespace MWRender insertBegin(ptr); ptr.getRefData().getBaseNode()->setNodeMask(Mask_Actor); + bool animated = true; + std::string animationMesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mResourceSystem->getVFS()); + if (animationMesh == mesh) + animated = false; + // CreatureAnimation osg::ref_ptr anim; if (weaponsShields) - anim = new CreatureWeaponAnimation(ptr, mesh, mResourceSystem); + anim = new CreatureWeaponAnimation(ptr, animationMesh, mResourceSystem, animated); else - anim = new CreatureAnimation(ptr, mesh, mResourceSystem); + anim = new CreatureAnimation(ptr, animationMesh, mResourceSystem, animated); if (mObjects.emplace(ptr.mRef, anim).second) ptr.getClass().getContainerStore(ptr).setContListener(static_cast(anim.get())); diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 339079cb0a..b4359fdb99 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -72,10 +72,8 @@ namespace MWRender SceneUtil::UnrefQueue& unrefQueue); ~Objects(); - /// @param animated Attempt to load separate keyframes from a .kf file matching the model file? /// @param allowLight If false, no lights will be created, and particles systems will be removed. - void insertModel( - const MWWorld::Ptr& ptr, const std::string& model, bool animated = false, bool allowLight = true); + void insertModel(const MWWorld::Ptr& ptr, const std::string& model, bool allowLight = true); void insertNPC(const MWWorld::Ptr& ptr); void insertCreature(const MWWorld::Ptr& ptr, const std::string& model, bool weaponsShields); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index e5c28a381b..0c8b56ec30 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -90,15 +90,11 @@ namespace rendering.rotateObject(ptr, rotation); } - std::string getModel(const MWWorld::Ptr& ptr, const VFS::Manager* vfs) + std::string getModel(const MWWorld::Ptr& ptr) { if (Misc::ResourceHelpers::isHiddenMarker(ptr.getCellRef().getRefId())) return {}; - bool useAnim = ptr.getClass().useAnim(); - std::string model = ptr.getClass().getModel(ptr); - if (useAnim) - model = Misc::ResourceHelpers::correctActorModelPath(model, vfs); - return model; + return ptr.getClass().getModel(ptr); } void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, const std::vector& pagedRefs, @@ -110,7 +106,7 @@ namespace return; } - std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); + std::string model = getModel(ptr); const auto rotation = makeDirectNodeRotation(ptr); const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); @@ -279,8 +275,7 @@ namespace MWWorld { if (!ptr.getRefData().getBaseNode()) return; - ptr.getClass().insertObjectRendering( - ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering); + ptr.getClass().insertObjectRendering(ptr, getModel(ptr), mRendering); setNodeRotation(ptr, mRendering, makeNodeRotation(ptr, RotationOrder::direct)); reloadTerrain(); } @@ -630,7 +625,7 @@ namespace MWWorld ptr.mRef->mData.mPhysicsPostponed = false; if (ptr.mRef->mData.isEnabled() && ptr.mRef->mData.getCount() > 0) { - std::string model = getModel(ptr, MWBase::Environment::get().getResourceSystem()->getVFS()); + std::string model = getModel(ptr); if (!model.empty()) { const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bd0d665a64..34c39fed82 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2430,10 +2430,8 @@ namespace MWWorld MWBase::Environment::get().getMechanicsManager()->add(getPlayerPtr()); MWBase::Environment::get().getWindowManager()->watchActor(getPlayerPtr()); - std::string model = getPlayerPtr().getClass().getModel(getPlayerPtr()); - model = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS()); mPhysics->remove(getPlayerPtr()); - mPhysics->addActor(getPlayerPtr(), model); + mPhysics->addActor(getPlayerPtr(), getPlayerPtr().getClass().getModel(getPlayerPtr())); applyLoopingParticles(player); diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index d172b4b8c9..c3f1a447bd 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -134,7 +134,11 @@ std::string Misc::ResourceHelpers::correctActorModelPath(const std::string& resP mdlname.insert(mdlname.begin() + p + 1, 'x'); else mdlname.insert(mdlname.begin(), 'x'); - if (!vfs->exists(mdlname)) + std::string kfname = mdlname; + if (kfname.ends_with(".nif")) + kfname.replace(kfname.size() - 4, 4, ".kf"); + + if (!vfs->exists(kfname)) { return resPath; } diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index bc11064654..079171c505 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -28,7 +28,8 @@ namespace Misc std::string correctIconPath(std::string_view resPath, const VFS::Manager* vfs); std::string correctBookartPath(std::string_view resPath, const VFS::Manager* vfs); std::string correctBookartPath(std::string_view resPath, int width, int height, const VFS::Manager* vfs); - /// Use "xfoo.nif" instead of "foo.nif" if available + /// Use "xfoo.nif" instead of "foo.nif" if "xfoo.kf" is available + /// Note that if "xfoo.nif" is actually unavailable, we can't fall back to "foo.nif". :( std::string correctActorModelPath(const std::string& resPath, const VFS::Manager* vfs); std::string correctMeshPath(const std::string& resPath, const VFS::Manager* vfs); diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 56f59451fa..f276df0863 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -208,6 +208,7 @@ namespace NifBullet } // files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see // Animation::addAnimSource). assume all nodes in the file will be animated + // TODO: investigate whether this should and could be optimized. const bool isAnimated = pathFileNameStartsWithX(filename); // If there's no bounding box, we'll have to generate a Bullet collision shape