Shields holstering support (feature #5193)

pull/556/head
Andrei Kortunov 5 years ago
parent e8037473ca
commit d3a3b2f1f6

@ -209,6 +209,7 @@
Feature #5132: Unique animations for different weapon types
Feature #5146: Safe Dispose corpse
Feature #5147: Show spell magicka cost in spell buying window
Feature #5193: Weapon sheathing
Task #4686: Upgrade media decoder to a more current FFmpeg API
Task #4695: Optimize Distant Terrain memory consumption
Task #4789: Optimize cell transitions

@ -985,7 +985,11 @@ void CharacterController::handleTextKey(const std::string &groupname, const std:
size_t off = groupname.size()+2;
size_t len = evt.size() - off;
if(evt.compare(off, len, "equip attach") == 0)
if(groupname == "shield" && evt.compare(off, len, "equip attach") == 0)
mAnimation->showCarriedLeft(true);
else if(groupname == "shield" && evt.compare(off, len, "unequip detach") == 0)
mAnimation->showCarriedLeft(false);
else if(evt.compare(off, len, "equip attach") == 0)
mAnimation->showWeapons(true);
else if(evt.compare(off, len, "unequip detach") == 0)
mAnimation->showWeapons(false);
@ -1193,7 +1197,7 @@ bool CharacterController::updateCarriedLeftVisible(const int weaptype) const
// Shields/torches shouldn't be visible during any operation involving two hands
// There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop",
// but they are also present in weapon drawing animation.
return !(getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded);
return mAnimation->updateCarriedLeftVisible(weaptype);
}
bool CharacterController::updateWeaponState(CharacterState& idle)
@ -1275,8 +1279,19 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
{
// Note: we do not disable unequipping animation automatically to avoid body desync
weapgroup = getWeaponAnimation(mWeaponType);
mAnimation->play(weapgroup, priorityWeapon,
MWRender::Animation::BlendMask_All, false,
int unequipMask = MWRender::Animation::BlendMask_All;
bool useShieldAnims = mAnimation->useShieldAnimations();
if (useShieldAnims && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && !(mWeaponType == ESM::Weapon::None && weaptype == ESM::Weapon::Spell))
{
unequipMask = unequipMask |~MWRender::Animation::BlendMask_LeftArm;
mAnimation->play("shield", Priority_Block,
MWRender::Animation::BlendMask_LeftArm, true,
1.0f, "unequip start", "unequip stop", 0.0f, 0);
}
else if (mWeaponType == ESM::Weapon::HandToHand)
mAnimation->showCarriedLeft(false);
mAnimation->play(weapgroup, priorityWeapon, unequipMask, false,
1.0f, "unequip start", "unequip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_UnEquipingWeap;
@ -1301,7 +1316,10 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
if (weaptype != mWeaponType)
{
forcestateupdate = true;
mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype));
bool useShieldAnims = mAnimation->useShieldAnimations();
if (!useShieldAnims)
mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype));
weapgroup = getWeaponAnimation(weaptype);
// Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly,
@ -1316,8 +1334,16 @@ bool CharacterController::updateWeaponState(CharacterState& idle)
if (weaptype != ESM::Weapon::None)
{
mAnimation->showWeapons(false);
mAnimation->play(weapgroup, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
int equipMask = MWRender::Animation::BlendMask_All;
if (useShieldAnims && weaptype != ESM::Weapon::Spell)
{
equipMask = equipMask |~MWRender::Animation::BlendMask_LeftArm;
mAnimation->play("shield", Priority_Block,
MWRender::Animation::BlendMask_LeftArm, true,
1.0f, "equip start", "equip stop", 0.0f, 0);
}
mAnimation->play(weapgroup, priorityWeapon, equipMask, true,
1.0f, "equip start", "equip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_EquipingWeap;

@ -62,6 +62,7 @@ ActorAnimation::~ActorAnimation()
}
mScabbard.reset();
mHolsteredShield.reset();
}
PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor)
@ -83,6 +84,163 @@ PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::st
return PartHolderPtr(new PartHolder(instance));
}
std::string ActorAnimation::getShieldMesh(MWWorld::ConstPtr shield) const
{
std::string mesh = shield.getClass().getModel(shield);
std::string holsteredName = mesh;
holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif");
if(mResourceSystem->getVFS()->exists(holsteredName))
{
osg::ref_ptr<osg::Node> shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName);
SceneUtil::FindByNameVisitor findVisitor ("Bip01 Sheath");
shieldTemplate->accept(findVisitor);
osg::ref_ptr<osg::Node> sheathNode = findVisitor.mFoundNode;
if(!sheathNode)
return std::string();
}
return mesh;
}
bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const
{
static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game");
if (shieldSheathing)
{
const MWWorld::Class &cls = mPtr.getClass();
MWMechanics::CreatureStats &stats = cls.getCreatureStats(mPtr);
if (cls.hasInventoryStore(mPtr) && weaptype != ESM::Weapon::Spell)
{
const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr);
const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if (shield != inv.end() && shield->getTypeName() == typeid(ESM::Armor).name() && !getShieldMesh(*shield).empty())
{
if(stats.getDrawState() != MWMechanics::DrawState_Weapon)
return false;
if (weapon != inv.end())
{
const std::string &type = weapon->getTypeName();
if(type == typeid(ESM::Weapon).name())
{
const MWWorld::LiveCellRef<ESM::Weapon> *ref = weapon->get<ESM::Weapon>();
ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType;
return !(MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded);
}
else if (type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name())
return true;
}
}
}
}
return !(MWMechanics::getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded);
}
void ActorAnimation::updateHolsteredShield(bool showCarriedLeft)
{
static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game");
if (!shieldSheathing)
return;
if (!mPtr.getClass().hasInventoryStore(mPtr))
return;
mHolsteredShield.reset();
if (showCarriedLeft)
return;
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if (shield == inv.end() || shield->getTypeName() != typeid(ESM::Armor).name())
return;
// Can not show holdstered shields with two-handed weapons at all
const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if(weapon == inv.end())
return;
const std::string &type = weapon->getTypeName();
if(type == typeid(ESM::Weapon).name())
{
const MWWorld::LiveCellRef<ESM::Weapon> *ref = weapon->get<ESM::Weapon>();
ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType;
if (MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded)
return;
}
std::string mesh = getShieldMesh(*shield);
if (mesh.empty())
return;
std::string boneName = "Bip01 AttachShield";
osg::Vec4f glowColor = shield->getClass().getEnchantmentColor(*shield);
std::string holsteredName = mesh;
holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif");
bool isEnchanted = !shield->getClass().getEnchantment(*shield).empty();
// If we have no dedicated sheath model, use basic shield model as fallback.
if (!mResourceSystem->getVFS()->exists(holsteredName))
mHolsteredShield = attachMesh(mesh, boneName, isEnchanted, &glowColor);
else
mHolsteredShield = attachMesh(holsteredName, boneName, isEnchanted, &glowColor);
if (!mHolsteredShield)
return;
SceneUtil::FindByNameVisitor findVisitor ("Bip01 Sheath");
mHolsteredShield->getNode()->accept(findVisitor);
osg::Group* shieldNode = findVisitor.mFoundNode;
// If mesh author declared an empty sheath node, use transformation from this node, but use the common shield mesh.
// This approach allows to tweak shield position without need to store the whole shield mesh in the _sh file.
if (shieldNode && !shieldNode->getNumChildren())
{
osg::ref_ptr<osg::Node> fallbackNode = mResourceSystem->getSceneManager()->getInstance(mesh, shieldNode);
if (isEnchanted)
SceneUtil::addEnchantedGlow(shieldNode, mResourceSystem, glowColor);
}
if (mAlpha != 1.f)
mResourceSystem->getSceneManager()->recreateShaders(mHolsteredShield->getNode());
}
bool ActorAnimation::useShieldAnimations() const
{
static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game");
if (!shieldSheathing)
return false;
const MWWorld::Class &cls = mPtr.getClass();
if (!cls.hasInventoryStore(mPtr))
return false;
if (getTextKeyTime("shield: equip attach") < 0 || getTextKeyTime("shield: unequip detach") < 0)
return false;
const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr);
const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if (weapon != inv.end() && shield != inv.end() &&
shield->getTypeName() == typeid(ESM::Armor).name() &&
!getShieldMesh(*shield).empty())
{
const std::string &type = weapon->getTypeName();
if(type == typeid(ESM::Weapon).name())
{
const MWWorld::LiveCellRef<ESM::Weapon> *ref = weapon->get<ESM::Weapon>();
ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType;
return !(MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded);
}
else if (type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name())
return true;
}
return false;
}
osg::Group* ActorAnimation::getBoneByName(const std::string& boneName)
{
if (!mObjectRoot)

@ -38,11 +38,15 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener
virtual void itemAdded(const MWWorld::ConstPtr& item, int count);
virtual void itemRemoved(const MWWorld::ConstPtr& item, int count);
virtual bool isArrowAttached() const { return false; }
virtual bool useShieldAnimations() const;
bool updateCarriedLeftVisible(const int weaptype) const;
protected:
osg::Group* getBoneByName(const std::string& boneName);
virtual void updateHolsteredWeapon(bool showHolsteredWeapons);
virtual void updateHolsteredShield(bool showCarriedLeft);
virtual void updateQuiver();
virtual std::string getShieldMesh(MWWorld::ConstPtr shield) const;
virtual std::string getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon);
virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor);
virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename)
@ -52,6 +56,7 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener
};
PartHolderPtr mScabbard;
PartHolderPtr mHolsteredShield;
private:
void addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight);

@ -153,6 +153,8 @@ public:
void setTextKeyListener(TextKeyListener* listener);
virtual bool updateCarriedLeftVisible(const int weaptype) const { return false; };
protected:
class AnimationTime : public SceneUtil::ControllerSource
{
@ -453,6 +455,7 @@ public:
/// @note The matching is case-insensitive.
const osg::Node* getNode(const std::string& name) const;
virtual bool useShieldAnimations() const { return false; }
virtual void showWeapons(bool showWeapon) {}
virtual void showCarriedLeft(bool show) {}
virtual void setWeaponGroup(const std::string& group, bool relativeDuration) {}

@ -90,6 +90,7 @@ void CreatureWeaponAnimation::updateParts()
updateHolsteredWeapon(!mShowWeapons);
updateQuiver();
updateHolsteredShield(mShowCarriedLeft);
if (mShowWeapons)
updatePart(mWeapon, MWWorld::InventoryStore::Slot_CarriedRight);

@ -24,6 +24,8 @@
#include <components/nifosg/nifloader.hpp> // TextKeyMapHolder
#include <components/vfs/manager.hpp>
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/class.hpp"
@ -511,6 +513,55 @@ void NpcAnimation::updateNpcBase()
mWeaponAnimationTime->updateStartTime();
}
std::string NpcAnimation::getShieldMesh(MWWorld::ConstPtr shield) const
{
std::string mesh = shield.getClass().getModel(shield);
const ESM::Armor *armor = shield.get<ESM::Armor>()->mBase;
std::vector<ESM::PartReference> bodyparts = armor->mParts.mParts;
if (!bodyparts.empty())
{
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
const MWWorld::Store<ESM::BodyPart> &partStore = store.get<ESM::BodyPart>();
// For NPCs try to get shield model from bodyparts first, with ground model as fallback
for (auto & part : bodyparts)
{
if (part.mPart != ESM::PRT_Shield)
continue;
std::string bodypartName;
if (!mNpc->isMale() && !part.mFemale.empty())
bodypartName = part.mFemale;
else if (!part.mMale.empty())
bodypartName = part.mMale;
if (!bodypartName.empty())
{
const ESM::BodyPart *bodypart = 0;
bodypart = partStore.search(bodypartName);
if (bodypart->mData.mType != ESM::BodyPart::MT_Armor)
return "";
else if (!bodypart->mModel.empty())
mesh = "meshes\\" + bodypart->mModel;
}
}
}
std::string holsteredName = mesh;
holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif");
if(mResourceSystem->getVFS()->exists(holsteredName))
{
osg::ref_ptr<osg::Node> shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName);
SceneUtil::FindByNameVisitor findVisitor ("Bip01 Sheath");
shieldTemplate->accept(findVisitor);
osg::ref_ptr<osg::Node> sheathNode = findVisitor.mFoundNode;
if(!sheathNode)
return std::string();
}
return mesh;
}
void NpcAnimation::updateParts()
{
if (!mObjectRoot.get())
@ -954,6 +1005,8 @@ void NpcAnimation::showCarriedLeft(bool show)
}
else
removeIndividualPart(ESM::PRT_Shield);
updateHolsteredShield(mShowCarriedLeft);
}
void NpcAnimation::attachArrow()
@ -1051,6 +1104,14 @@ void NpcAnimation::setWeaponGroup(const std::string &group, bool relativeDuratio
void NpcAnimation::equipmentChanged()
{
static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game");
if (shieldSheathing)
{
int weaptype;
MWMechanics::getActiveWeapon(mPtr, &weaptype);
showCarriedLeft(updateCarriedLeftVisible(weaptype));
}
updateParts();
}

@ -98,6 +98,7 @@ private:
protected:
virtual void addControllers();
virtual bool isArrowAttached() const;
virtual std::string getShieldMesh(MWWorld::ConstPtr shield) const;
public:
/**

@ -184,6 +184,22 @@ Otherwise they wait for the enemies or the player to do an attack first.
Please note this setting has not been extensively tested and could have side effects with certain quests.
This setting can be toggled in Advanced tab of the launcher.
shield sheathing
----------------
:Type: boolean
:Range: True/False
:Default: False
If this setting is true, OpenMW will utilize shield sheathing-compatible assets to display holstered shields.
To make use of this, you need to have an xbase_anim_sh.nif file with weapon bones that will be injected into the skeleton.
Also you can use additional _sh meshes for more precise shield placement.
Warning: this feature may conflict with mods that use pseudo-shields to emulate item in actor's hand (e.g. books, baskets, pick axes).
To avoid conflicts, you can use _sh mesh without "Bip01 Sheath" node for such "shields" meshes, or declare its bodypart as Clothing type, not as Armor.
Also you can use an _sh node with empty "Bip01 Sheath" node.
In this case the engine will use basic shield model, but will use transformations from the "Bip01 Sheath" node.
weapon sheathing
----------------

@ -255,6 +255,9 @@ strength influences hand to hand = 0
# Render holstered weapons (with quivers and scabbards), requires modded assets
weapon sheathing = false
# Render holstered shield when it is not in actor's hands, requires modded assets
shield sheathing = false
# Allow non-standard ammunition solely to bypass normal weapon resistance or weakness
only appropriate ammunition bypasses resistance = false

Loading…
Cancel
Save