mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-06 10:15:33 +00:00
Merge pull request #2047 from akortunov/holstered_shield
Shields holstering support
This commit is contained in:
commit
262d87846c
10 changed files with 282 additions and 7 deletions
|
@ -210,6 +210,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…
Reference in a new issue