mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-29 21:45:32 +00:00
Weapon sheathing support, including quivers and scabbards (feature #4673)
This commit is contained in:
parent
c114e1278e
commit
92e45507d8
16 changed files with 673 additions and 168 deletions
|
@ -3,9 +3,9 @@
|
|||
|
||||
Feature #2229: Improve pathfinding AI
|
||||
Feature #3442: Default values for fallbacks from ini file
|
||||
Feature #4673: Weapon sheathing
|
||||
Task #4686: Upgrade media decoder to a more current FFmpeg API
|
||||
|
||||
|
||||
0.45.0
|
||||
------
|
||||
|
||||
|
|
|
@ -166,7 +166,7 @@ namespace MWClass
|
|||
getContainerStore(ptr).fill(ref->mBase->mInventory, ptr.getCellRef().getRefId());
|
||||
|
||||
if (hasInventory)
|
||||
getInventoryStore(ptr).autoEquipShield(ptr);
|
||||
getInventoryStore(ptr).autoEquip(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include "actoranimation.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <osg/Node>
|
||||
|
@ -9,11 +8,22 @@
|
|||
#include <components/esm/loadligh.hpp>
|
||||
#include <components/esm/loadcell.hpp>
|
||||
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
|
||||
#include <components/sceneutil/attach.hpp>
|
||||
#include <components/sceneutil/lightmanager.hpp>
|
||||
#include <components/sceneutil/lightutil.hpp>
|
||||
#include <components/sceneutil/visitor.hpp>
|
||||
|
||||
#include <components/fallback/fallback.hpp>
|
||||
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
@ -43,6 +53,8 @@ ActorAnimation::ActorAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr<osg::Group>
|
|||
|
||||
// Make sure we cleaned object from effects, just in cast if we re-use node
|
||||
removeEffects();
|
||||
|
||||
mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game");
|
||||
}
|
||||
|
||||
ActorAnimation::~ActorAnimation()
|
||||
|
@ -51,6 +63,302 @@ ActorAnimation::~ActorAnimation()
|
|||
{
|
||||
mInsert->removeChild(iter->second);
|
||||
}
|
||||
|
||||
mScabbard.reset();
|
||||
}
|
||||
|
||||
PartHolderPtr ActorAnimation::getWeaponPart(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor)
|
||||
{
|
||||
osg::Group* parent = getBoneByName(bonename);
|
||||
if (!parent)
|
||||
return nullptr;
|
||||
|
||||
osg::ref_ptr<osg::Node> instance = mResourceSystem->getSceneManager()->getInstance(model, parent);
|
||||
|
||||
const NodeMap& nodeMap = getNodeMap();
|
||||
NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename));
|
||||
if (found == nodeMap.end())
|
||||
return PartHolderPtr();
|
||||
|
||||
if (enchantedGlow)
|
||||
addGlow(instance, *glowColor);
|
||||
|
||||
return PartHolderPtr(new PartHolder(instance));
|
||||
}
|
||||
|
||||
osg::Group* ActorAnimation::getBoneByName(std::string boneName)
|
||||
{
|
||||
if (!mObjectRoot)
|
||||
return nullptr;
|
||||
|
||||
SceneUtil::FindByNameVisitor findVisitor (boneName);
|
||||
mObjectRoot->accept(findVisitor);
|
||||
|
||||
return findVisitor.mFoundNode;
|
||||
}
|
||||
|
||||
std::string ActorAnimation::getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon)
|
||||
{
|
||||
std::string boneName;
|
||||
if(weapon.isEmpty())
|
||||
return boneName;
|
||||
|
||||
const std::string &type = weapon.getClass().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 getHolsteredWeaponBoneName(weaponType);
|
||||
}
|
||||
|
||||
return boneName;
|
||||
}
|
||||
|
||||
std::string ActorAnimation::getHolsteredWeaponBoneName(const unsigned int weaponType)
|
||||
{
|
||||
std::string boneName;
|
||||
|
||||
switch(weaponType)
|
||||
{
|
||||
case ESM::Weapon::ShortBladeOneHand:
|
||||
boneName = "Bip01 ShortBladeOneHand";
|
||||
break;
|
||||
case ESM::Weapon::LongBladeOneHand:
|
||||
boneName = "Bip01 LongBladeOneHand";
|
||||
break;
|
||||
case ESM::Weapon::BluntOneHand:
|
||||
boneName = "Bip01 BluntOneHand";
|
||||
break;
|
||||
case ESM::Weapon::AxeOneHand:
|
||||
boneName = "Bip01 LongBladeOneHand";
|
||||
break;
|
||||
case ESM::Weapon::LongBladeTwoHand:
|
||||
boneName = "Bip01 LongBladeTwoClose";
|
||||
break;
|
||||
case ESM::Weapon::BluntTwoClose:
|
||||
boneName = "Bip01 BluntTwoClose";
|
||||
break;
|
||||
case ESM::Weapon::AxeTwoHand:
|
||||
boneName = "Bip01 AxeTwoClose";
|
||||
break;
|
||||
case ESM::Weapon::BluntTwoWide:
|
||||
boneName = "Bip01 BluntTwoWide";
|
||||
break;
|
||||
case ESM::Weapon::SpearTwoWide:
|
||||
boneName = "Bip01 SpearTwoWide";
|
||||
break;
|
||||
case ESM::Weapon::MarksmanBow:
|
||||
boneName = "Bip01 MarksmanBow";
|
||||
break;
|
||||
case ESM::Weapon::MarksmanCrossbow:
|
||||
boneName = "Bip01 MarksmanCrossbow";
|
||||
break;
|
||||
case ESM::Weapon::MarksmanThrown:
|
||||
boneName = "Bip01 MarksmanThrown";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return boneName;
|
||||
}
|
||||
|
||||
void ActorAnimation::injectWeaponBones()
|
||||
{
|
||||
if (!mResourceSystem->getVFS()->exists("meshes\\xbase_anim_sh.nif"))
|
||||
{
|
||||
mWeaponSheathing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Node> sheathSkeleton = mResourceSystem->getSceneManager()->getInstance("meshes\\xbase_anim_sh.nif");
|
||||
|
||||
for (unsigned int type=0; type<=ESM::Weapon::MarksmanThrown; ++type)
|
||||
{
|
||||
const std::string holsteredBoneName = getHolsteredWeaponBoneName(type);
|
||||
|
||||
SceneUtil::FindByNameVisitor findVisitor (holsteredBoneName);
|
||||
sheathSkeleton->accept(findVisitor);
|
||||
osg::ref_ptr<osg::Node> sheathNode = findVisitor.mFoundNode;
|
||||
|
||||
if (sheathNode && sheathNode.get()->getNumParents())
|
||||
{
|
||||
osg::Group* sheathParent = getBoneByName(sheathNode.get()->getParent(0)->getName());
|
||||
|
||||
if (sheathParent)
|
||||
{
|
||||
sheathNode.get()->getParent(0)->removeChild(sheathNode);
|
||||
sheathParent->addChild(sheathNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// To make sure we do not run morph controllers for weapons, i.e. bows
|
||||
class EmptyCallback : public osg::NodeCallback
|
||||
{
|
||||
public:
|
||||
|
||||
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
|
||||
{
|
||||
if (!mWeaponSheathing)
|
||||
return;
|
||||
|
||||
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
||||
return;
|
||||
|
||||
mScabbard.reset();
|
||||
|
||||
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
if (weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name())
|
||||
return;
|
||||
|
||||
// Since throwing weapons stack themselves, do not show such weapon itself
|
||||
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
||||
showHolsteredWeapons = false;
|
||||
|
||||
std::string mesh = weapon->getClass().getModel(*weapon);
|
||||
std::string scabbardName = mesh;
|
||||
|
||||
std::string boneName = getHolsteredWeaponBoneName(*weapon);
|
||||
if (mesh.empty() || boneName.empty())
|
||||
return;
|
||||
|
||||
// If the scabbard is not found, use a weapon mesh as fallback
|
||||
scabbardName = scabbardName.replace(scabbardName.size()-4, 4, "_sh.nif");
|
||||
bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty();
|
||||
if(!mResourceSystem->getVFS()->exists(scabbardName))
|
||||
{
|
||||
if (showHolsteredWeapons)
|
||||
{
|
||||
osg::Vec4f glowColor = getEnchantmentColor(*weapon);
|
||||
mScabbard = getWeaponPart(mesh, boneName, isEnchanted, &glowColor);
|
||||
if (mScabbard)
|
||||
mScabbard->getNode()->setUpdateCallback(new EmptyCallback);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
mScabbard = getWeaponPart(scabbardName, boneName);
|
||||
|
||||
osg::Group* weaponNode = getBoneByName("Bip01 Weapon");
|
||||
if (!weaponNode)
|
||||
return;
|
||||
|
||||
// When we draw weapon, hide the Weapon node from sheath model.
|
||||
// Otherwise add the enchanted glow to it.
|
||||
if (!showHolsteredWeapons)
|
||||
{
|
||||
weaponNode->setNodeMask(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If mesh author declared empty weapon node, use transformation from this node, but use the common weapon mesh.
|
||||
// This approach allows to tweak weapon position without need to store the whole weapon mesh in the _sh file.
|
||||
if (!weaponNode->getNumChildren())
|
||||
{
|
||||
osg::ref_ptr<osg::Node> fallbackNode = mResourceSystem->getSceneManager()->getInstance(mesh, weaponNode);
|
||||
fallbackNode->setUpdateCallback(new EmptyCallback);
|
||||
}
|
||||
|
||||
if (isEnchanted)
|
||||
{
|
||||
osg::Vec4f glowColor = getEnchantmentColor(*weapon);
|
||||
addGlow(weaponNode, glowColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActorAnimation::updateQuiver()
|
||||
{
|
||||
if (!mWeaponSheathing)
|
||||
return;
|
||||
|
||||
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
||||
return;
|
||||
|
||||
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name())
|
||||
return;
|
||||
|
||||
std::string mesh = weapon->getClass().getModel(*weapon);
|
||||
std::string boneName = getHolsteredWeaponBoneName(*weapon);
|
||||
if (mesh.empty() || boneName.empty())
|
||||
return;
|
||||
|
||||
osg::Group* ammoNode = getBoneByName("Bip01 Ammo");
|
||||
if (!ammoNode)
|
||||
return;
|
||||
|
||||
// Special case for throwing weapons - they do not use ammo, but they stack themselves
|
||||
bool suitableAmmo = false;
|
||||
MWWorld::ConstContainerStoreIterator ammo = weapon;
|
||||
unsigned int ammoCount = 0;
|
||||
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
||||
{
|
||||
ammoCount = ammo->getRefData().getCount();
|
||||
osg::Group* throwingWeaponNode = getBoneByName("Weapon Bone");
|
||||
if (throwingWeaponNode && throwingWeaponNode->getNumChildren())
|
||||
ammoCount--;
|
||||
|
||||
suitableAmmo = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||
if (ammo == inv.end())
|
||||
return;
|
||||
|
||||
ammoCount = ammo->getRefData().getCount();
|
||||
bool arrowAttached = isArrowAttached();
|
||||
if (arrowAttached)
|
||||
ammoCount--;
|
||||
|
||||
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)
|
||||
suitableAmmo = ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Bolt;
|
||||
else if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanBow)
|
||||
suitableAmmo = ammo->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::Arrow;
|
||||
}
|
||||
|
||||
if (ammoNode && suitableAmmo)
|
||||
{
|
||||
// We should not show more ammo than equipped and more than quiver mesh has
|
||||
ammoCount = std::min(ammoCount, ammoNode->getNumChildren());
|
||||
|
||||
// Remove existing ammo nodes
|
||||
for (unsigned int i=0; i<ammoNode->getNumChildren(); ++i)
|
||||
{
|
||||
osg::ref_ptr<osg::Group> arrowNode = ammoNode->getChild(i)->asGroup();
|
||||
if (!arrowNode->getNumChildren())
|
||||
continue;
|
||||
|
||||
osg::ref_ptr<osg::Node> arrowChildNode = arrowNode->getChild(0);
|
||||
arrowNode->removeChild(arrowChildNode);
|
||||
}
|
||||
|
||||
// Add new ones
|
||||
osg::Vec4f glowColor = getEnchantmentColor(*ammo);
|
||||
std::string model = ammo->getClass().getModel(*ammo);
|
||||
for (unsigned int i=0; i<ammoCount; ++i)
|
||||
{
|
||||
osg::ref_ptr<osg::Group> arrowNode = ammoNode->getChild(i)->asGroup();
|
||||
osg::ref_ptr<osg::Node> arrow = mResourceSystem->getSceneManager()->getInstance(model, arrowNode);
|
||||
if (!ammo->getClass().getEnchantment(*ammo).empty())
|
||||
addGlow(arrow, glowColor);
|
||||
}
|
||||
}
|
||||
|
||||
// recreate shaders for invisible actors, otherwise new nodes will be visible
|
||||
if (mAlpha != 1.f)
|
||||
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
|
||||
}
|
||||
|
||||
void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/)
|
||||
|
@ -63,6 +371,24 @@ void ActorAnimation::itemAdded(const MWWorld::ConstPtr& item, int /*count*/)
|
|||
addHiddenItemLight(item, light);
|
||||
}
|
||||
}
|
||||
|
||||
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
||||
return;
|
||||
|
||||
// If the count of equipped ammo or throwing weapon was changed, we should update quiver
|
||||
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name())
|
||||
return;
|
||||
|
||||
MWWorld::ConstContainerStoreIterator ammo = inv.end();
|
||||
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
||||
ammo = weapon;
|
||||
else
|
||||
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||
|
||||
if(ammo != inv.end() && item.getCellRef().getRefId() == ammo->getCellRef().getRefId())
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/)
|
||||
|
@ -78,6 +404,24 @@ void ActorAnimation::itemRemoved(const MWWorld::ConstPtr& item, int /*count*/)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mPtr.getClass().hasInventoryStore(mPtr))
|
||||
return;
|
||||
|
||||
// If the count of equipped ammo or throwing weapon was changed, we should update quiver
|
||||
const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
|
||||
MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
if(weapon == inv.end() || weapon->getTypeName() != typeid(ESM::Weapon).name())
|
||||
return;
|
||||
|
||||
MWWorld::ConstContainerStoreIterator ammo = inv.end();
|
||||
if (weapon->get<ESM::Weapon>()->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
||||
ammo = weapon;
|
||||
else
|
||||
ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
|
||||
|
||||
if(ammo != inv.end() && item.getCellRef().getRefId() == ammo->getCellRef().getRefId())
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
void ActorAnimation::addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight)
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <osg/ref_ptr>
|
||||
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
|
||||
#include "animation.hpp"
|
||||
|
||||
|
@ -36,6 +37,24 @@ 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; }
|
||||
|
||||
protected:
|
||||
bool mWeaponSheathing;
|
||||
osg::Group* getBoneByName(std::string boneName);
|
||||
virtual void updateHolsteredWeapon(bool showHolsteredWeapons);
|
||||
virtual void injectWeaponBones();
|
||||
virtual void updateQuiver();
|
||||
virtual std::string getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon);
|
||||
virtual std::string getHolsteredWeaponBoneName(const unsigned int weaponType);
|
||||
virtual PartHolderPtr getWeaponPart(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor);
|
||||
virtual PartHolderPtr getWeaponPart(const std::string& model, const std::string& bonename)
|
||||
{
|
||||
osg::Vec4f stubColor = osg::Vec4f(0,0,0,0);
|
||||
return getWeaponPart(model, bonename, false, &stubColor);
|
||||
};
|
||||
|
||||
PartHolderPtr mScabbard;
|
||||
|
||||
private:
|
||||
void addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight);
|
||||
|
|
|
@ -1748,8 +1748,6 @@ namespace MWRender
|
|||
}
|
||||
else
|
||||
{
|
||||
osg::StateSet* stateset (new osg::StateSet);
|
||||
|
||||
osg::BlendFunc* blendfunc (new osg::BlendFunc);
|
||||
stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
|
||||
|
||||
|
|
|
@ -49,7 +49,12 @@ CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const
|
|||
setObjectRoot(model, true, false, true);
|
||||
|
||||
if((ref->mBase->mFlags&ESM::Creature::Bipedal))
|
||||
{
|
||||
if (mWeaponSheathing)
|
||||
injectWeaponBones();
|
||||
|
||||
addAnimSource("meshes\\xbase_anim.nif", model);
|
||||
}
|
||||
addAnimSource(model, model);
|
||||
|
||||
mPtr.getClass().getInventoryStore(mPtr).setInvListener(this, mPtr);
|
||||
|
@ -84,6 +89,9 @@ void CreatureWeaponAnimation::updateParts()
|
|||
mWeapon.reset();
|
||||
mShield.reset();
|
||||
|
||||
updateHolsteredWeapon(!mShowWeapons);
|
||||
updateQuiver();
|
||||
|
||||
if (mShowWeapons)
|
||||
updatePart(mWeapon, MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
if (mShowCarriedLeft)
|
||||
|
@ -157,14 +165,21 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot)
|
|||
}
|
||||
}
|
||||
|
||||
bool CreatureWeaponAnimation::isArrowAttached() const
|
||||
{
|
||||
return mAmmunition != nullptr;
|
||||
}
|
||||
|
||||
void CreatureWeaponAnimation::attachArrow()
|
||||
{
|
||||
WeaponAnimation::attachArrow(mPtr);
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
void CreatureWeaponAnimation::releaseArrow(float attackStrength)
|
||||
{
|
||||
WeaponAnimation::releaseArrow(mPtr, attackStrength);
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
osg::Group *CreatureWeaponAnimation::getArrowBone()
|
||||
|
|
|
@ -54,6 +54,8 @@ namespace MWRender
|
|||
/// to indicate the facing orientation of the character.
|
||||
virtual void setPitchFactor(float factor) { mPitchFactor = factor; }
|
||||
|
||||
protected:
|
||||
virtual bool isArrowAttached() const;
|
||||
|
||||
private:
|
||||
PartHolderPtr mWeapon;
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
#include <components/sceneutil/skeleton.hpp>
|
||||
#include <components/sceneutil/lightmanager.hpp>
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include <components/nifosg/nifloader.hpp> // TextKeyMapHolder
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
@ -308,6 +310,12 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
|
|||
if(mViewMode == viewMode)
|
||||
return;
|
||||
|
||||
// Disable weapon sheathing in the 1st-person mode
|
||||
if (viewMode == VM_FirstPerson)
|
||||
mWeaponSheathing = false;
|
||||
else
|
||||
mWeaponSheathing = Settings::Manager::getBool("weapon sheathing", "Game");
|
||||
|
||||
mViewMode = viewMode;
|
||||
rebuild();
|
||||
|
||||
|
@ -389,6 +397,7 @@ void NpcAnimation::setRenderBin()
|
|||
|
||||
void NpcAnimation::rebuild()
|
||||
{
|
||||
mScabbard.reset();
|
||||
updateNpcBase();
|
||||
|
||||
MWBase::Environment::get().getMechanicsManager()->forceStateUpdate(mPtr);
|
||||
|
@ -460,6 +469,11 @@ void NpcAnimation::updateNpcBase()
|
|||
|
||||
setObjectRoot(smodel, true, true, false);
|
||||
|
||||
if (mWeaponSheathing)
|
||||
injectWeaponBones();
|
||||
|
||||
updateParts();
|
||||
|
||||
if(!is1stPerson)
|
||||
{
|
||||
const std::string base = "meshes\\xbase_anim.nif";
|
||||
|
@ -488,8 +502,6 @@ void NpcAnimation::updateNpcBase()
|
|||
mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView));
|
||||
}
|
||||
|
||||
updateParts();
|
||||
|
||||
mWeaponAnimationTime->updateStartTime();
|
||||
}
|
||||
|
||||
|
@ -899,7 +911,8 @@ void NpcAnimation::showWeapons(bool showWeapon)
|
|||
attachArrow();
|
||||
}
|
||||
}
|
||||
if (mAlpha != 1.f)
|
||||
// Note: we will need to recreate shaders later if we use weapon sheathing anyway, so there is no point to update them here
|
||||
if (mAlpha != 1.f && !mWeaponSheathing)
|
||||
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
|
||||
}
|
||||
else
|
||||
|
@ -909,6 +922,9 @@ void NpcAnimation::showWeapons(bool showWeapon)
|
|||
if (mPtr == MWMechanics::getPlayer())
|
||||
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
|
||||
}
|
||||
|
||||
updateHolsteredWeapon(!mShowWeapons);
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
void NpcAnimation::showCarriedLeft(bool show)
|
||||
|
@ -936,11 +952,13 @@ void NpcAnimation::showCarriedLeft(bool show)
|
|||
void NpcAnimation::attachArrow()
|
||||
{
|
||||
WeaponAnimation::attachArrow(mPtr);
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
void NpcAnimation::releaseArrow(float attackStrength)
|
||||
{
|
||||
WeaponAnimation::releaseArrow(mPtr, attackStrength);
|
||||
updateQuiver();
|
||||
}
|
||||
|
||||
osg::Group* NpcAnimation::getArrowBone()
|
||||
|
@ -1185,4 +1203,9 @@ void NpcAnimation::setAccurateAiming(bool enabled)
|
|||
mAccurateAiming = enabled;
|
||||
}
|
||||
|
||||
bool NpcAnimation::isArrowAttached() const
|
||||
{
|
||||
return mAmmunition != nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -95,6 +95,7 @@ private:
|
|||
|
||||
protected:
|
||||
virtual void addControllers();
|
||||
virtual bool isArrowAttached() const;
|
||||
|
||||
public:
|
||||
/**
|
||||
|
|
|
@ -330,7 +330,8 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr
|
|||
item.getRefData().getLocals().setVarByInt(script, "onpcadd", 1);
|
||||
}
|
||||
|
||||
if (mListener)
|
||||
// we should not fire event for InventoryStore yet - it has some custom logic
|
||||
if (mListener && !actorPtr.getClass().hasInventoryStore(actorPtr))
|
||||
mListener->itemAdded(item, count);
|
||||
|
||||
return it;
|
||||
|
@ -439,7 +440,8 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, const Ptr& actor
|
|||
|
||||
flagAsModified();
|
||||
|
||||
if (mListener)
|
||||
// we should not fire event for InventoryStore yet - it has some custom logic
|
||||
if (mListener && !actor.getClass().hasInventoryStore(actor))
|
||||
mListener->itemRemoved(item, count - toRemove);
|
||||
|
||||
// number of removed items
|
||||
|
|
|
@ -68,6 +68,9 @@ namespace MWWorld
|
|||
|
||||
static const std::string sGoldId;
|
||||
|
||||
protected:
|
||||
ContainerStoreListener* mListener;
|
||||
|
||||
private:
|
||||
|
||||
MWWorld::CellRefList<ESM::Potion> potions;
|
||||
|
@ -87,8 +90,6 @@ namespace MWWorld
|
|||
///< Stores result of levelled item spawns. <(refId, spawningGroup), count>
|
||||
/// This is used to restock levelled items(s) if the old item was sold.
|
||||
|
||||
ContainerStoreListener* mListener;
|
||||
|
||||
mutable float mCachedWeight;
|
||||
mutable bool mWeightUpToDate;
|
||||
ContainerStoreIterator addImp (const Ptr& ptr, int count);
|
||||
|
|
|
@ -99,7 +99,8 @@ void MWWorld::InventoryStore::readEquipmentState(const MWWorld::ContainerStoreIt
|
|||
}
|
||||
|
||||
MWWorld::InventoryStore::InventoryStore()
|
||||
: mListener(nullptr)
|
||||
: ContainerStore()
|
||||
, mInventoryListener(nullptr)
|
||||
, mUpdatesEnabled (true)
|
||||
, mFirstAutoEquip(true)
|
||||
, mSelectedEnchantItem(end())
|
||||
|
@ -111,7 +112,7 @@ MWWorld::InventoryStore::InventoryStore()
|
|||
MWWorld::InventoryStore::InventoryStore (const InventoryStore& store)
|
||||
: ContainerStore (store)
|
||||
, mMagicEffects(store.mMagicEffects)
|
||||
, mListener(store.mListener)
|
||||
, mInventoryListener(store.mInventoryListener)
|
||||
, mUpdatesEnabled(store.mUpdatesEnabled)
|
||||
, mFirstAutoEquip(store.mFirstAutoEquip)
|
||||
, mPermanentMagicEffectMagnitudes(store.mPermanentMagicEffectMagnitudes)
|
||||
|
@ -124,6 +125,7 @@ MWWorld::InventoryStore::InventoryStore (const InventoryStore& store)
|
|||
MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStore& store)
|
||||
{
|
||||
mListener = store.mListener;
|
||||
mInventoryListener = store.mInventoryListener;
|
||||
mMagicEffects = store.mMagicEffects;
|
||||
mFirstAutoEquip = store.mFirstAutoEquip;
|
||||
mPermanentMagicEffectMagnitudes = store.mPermanentMagicEffectMagnitudes;
|
||||
|
@ -147,6 +149,9 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr,
|
|||
autoEquip(actorPtr);
|
||||
}
|
||||
|
||||
if (mListener)
|
||||
mListener->itemAdded(itemPtr, count);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
@ -246,25 +251,195 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::findSlot (int slot) con
|
|||
return mSlots[slot];
|
||||
}
|
||||
|
||||
void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
|
||||
void MWWorld::InventoryStore::autoEquipWeapon (const MWWorld::Ptr& actor, TSlots& slots_)
|
||||
{
|
||||
if (!actor.getClass().isNpc())
|
||||
{
|
||||
// In original game creatures do not autoequip weapon, but we need it for weapon sheathing.
|
||||
// The only case when the difference is noticable - when this creature sells weapon.
|
||||
// So just disable weapon autoequipping for creatures which sells weapon.
|
||||
int services = actor.getClass().getServices(actor);
|
||||
bool sellsWeapon = services & (ESM::NPC::Weapon|ESM::NPC::MagicItems);
|
||||
if (sellsWeapon)
|
||||
return;
|
||||
}
|
||||
|
||||
static const ESM::Skill::SkillEnum weaponSkills[] =
|
||||
{
|
||||
ESM::Skill::LongBlade,
|
||||
ESM::Skill::Axe,
|
||||
ESM::Skill::Spear,
|
||||
ESM::Skill::ShortBlade,
|
||||
ESM::Skill::Marksman,
|
||||
ESM::Skill::BluntWeapon
|
||||
};
|
||||
const size_t weaponSkillsLength = sizeof(weaponSkills) / sizeof(weaponSkills[0]);
|
||||
|
||||
bool weaponSkillVisited[weaponSkillsLength] = { false };
|
||||
|
||||
// give arrows/bolt with max damage by default
|
||||
int arrowMax = 0;
|
||||
int boltMax = 0;
|
||||
ContainerStoreIterator arrow(end());
|
||||
ContainerStoreIterator bolt(end());
|
||||
|
||||
// rate ammo
|
||||
for (ContainerStoreIterator iter(begin(ContainerStore::Type_Weapon)); iter!=end(); ++iter)
|
||||
{
|
||||
const ESM::Weapon* esmWeapon = iter->get<ESM::Weapon>()->mBase;
|
||||
|
||||
if (esmWeapon->mData.mType == ESM::Weapon::Arrow)
|
||||
{
|
||||
if (esmWeapon->mData.mChop[1] >= arrowMax)
|
||||
{
|
||||
arrowMax = esmWeapon->mData.mChop[1];
|
||||
arrow = iter;
|
||||
}
|
||||
}
|
||||
else if (esmWeapon->mData.mType == ESM::Weapon::Bolt)
|
||||
{
|
||||
if (esmWeapon->mData.mChop[1] >= boltMax)
|
||||
{
|
||||
boltMax = esmWeapon->mData.mChop[1];
|
||||
bolt = iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rate weapon
|
||||
for (int i = 0; i < static_cast<int>(weaponSkillsLength); ++i)
|
||||
{
|
||||
int max = 0;
|
||||
int maxWeaponSkill = -1;
|
||||
|
||||
for (int j = 0; j < static_cast<int>(weaponSkillsLength); ++j)
|
||||
{
|
||||
int skillValue = actor.getClass().getSkill(actor, static_cast<int>(weaponSkills[j]));
|
||||
if (skillValue > max && !weaponSkillVisited[j])
|
||||
{
|
||||
max = skillValue;
|
||||
maxWeaponSkill = j;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxWeaponSkill == -1)
|
||||
break;
|
||||
|
||||
max = 0;
|
||||
ContainerStoreIterator weapon(end());
|
||||
|
||||
for (ContainerStoreIterator iter(begin(ContainerStore::Type_Weapon)); iter!=end(); ++iter)
|
||||
{
|
||||
if (!canActorAutoEquip(actor, *iter))
|
||||
continue;
|
||||
|
||||
const ESM::Weapon* esmWeapon = iter->get<ESM::Weapon>()->mBase;
|
||||
|
||||
if (esmWeapon->mData.mType == ESM::Weapon::Arrow || esmWeapon->mData.mType == ESM::Weapon::Bolt)
|
||||
continue;
|
||||
|
||||
if (iter->getClass().getEquipmentSkill(*iter) == weaponSkills[maxWeaponSkill])
|
||||
{
|
||||
if (esmWeapon->mData.mChop[1] >= max)
|
||||
{
|
||||
max = esmWeapon->mData.mChop[1];
|
||||
weapon = iter;
|
||||
}
|
||||
|
||||
if (esmWeapon->mData.mSlash[1] >= max)
|
||||
{
|
||||
max = esmWeapon->mData.mSlash[1];
|
||||
weapon = iter;
|
||||
}
|
||||
|
||||
if (esmWeapon->mData.mThrust[1] >= max)
|
||||
{
|
||||
max = esmWeapon->mData.mThrust[1];
|
||||
weapon = iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isBow = false;
|
||||
bool isCrossbow = false;
|
||||
if (weapon != end())
|
||||
{
|
||||
const MWWorld::LiveCellRef<ESM::Weapon> *ref = weapon->get<ESM::Weapon>();
|
||||
ESM::Weapon::Type type = (ESM::Weapon::Type)ref->mBase->mData.mType;
|
||||
|
||||
if (type == ESM::Weapon::MarksmanBow)
|
||||
isBow = true;
|
||||
else if (type == ESM::Weapon::MarksmanCrossbow)
|
||||
isCrossbow = true;
|
||||
}
|
||||
|
||||
if (weapon != end() && weapon->getClass().canBeEquipped(*weapon, actor).first)
|
||||
{
|
||||
// Do not equip ranged weapons, if there is no suitable ammo
|
||||
bool hasAmmo = true;
|
||||
if (isBow == true)
|
||||
{
|
||||
if (arrow == end())
|
||||
hasAmmo = false;
|
||||
else
|
||||
slots_[Slot_Ammunition] = arrow;
|
||||
}
|
||||
if (isCrossbow == true)
|
||||
{
|
||||
if (bolt == end())
|
||||
hasAmmo = false;
|
||||
else
|
||||
slots_[Slot_Ammunition] = bolt;
|
||||
}
|
||||
|
||||
if (hasAmmo)
|
||||
{
|
||||
std::pair<std::vector<int>, bool> itemsSlots = weapon->getClass().getEquipmentSlots (*weapon);
|
||||
|
||||
if (!itemsSlots.first.empty())
|
||||
{
|
||||
if (!itemsSlots.second)
|
||||
{
|
||||
if (weapon->getRefData().getCount() > 1)
|
||||
{
|
||||
unstack(*weapon, actor);
|
||||
}
|
||||
}
|
||||
|
||||
int slot = itemsSlots.first.front();
|
||||
slots_[slot] = weapon;
|
||||
|
||||
if (!isBow && !isCrossbow)
|
||||
slots_[Slot_Ammunition] = end();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
weaponSkillVisited[maxWeaponSkill] = true;
|
||||
}
|
||||
}
|
||||
|
||||
void MWWorld::InventoryStore::autoEquipArmor (const MWWorld::Ptr& actor, TSlots& slots_)
|
||||
{
|
||||
// Only NPCs can wear armor for now.
|
||||
// For creatures we equip only shields.
|
||||
if (!actor.getClass().isNpc())
|
||||
{
|
||||
autoEquipShield(actor, slots_);
|
||||
return;
|
||||
}
|
||||
|
||||
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
|
||||
|
||||
static float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat();
|
||||
static float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat();
|
||||
int unarmoredSkill = actor.getClass().getSkill(actor, ESM::Skill::Unarmored);
|
||||
|
||||
int unarmoredSkill = actor.getClass().getSkill(actor, ESM::Skill::Unarmored);
|
||||
float unarmoredRating = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill);
|
||||
|
||||
TSlots slots_;
|
||||
initSlots (slots_);
|
||||
|
||||
// Disable model update during auto-equip
|
||||
mUpdatesEnabled = false;
|
||||
|
||||
// Autoequip clothing, armor and weapons.
|
||||
// Equipping lights is handled in Actors::updateEquippedLight based on environment light.
|
||||
for (ContainerStoreIterator iter (begin(ContainerStore::Type_Clothing | ContainerStore::Type_Armor)); iter!=end(); ++iter)
|
||||
{
|
||||
Ptr test = *iter;
|
||||
|
@ -289,12 +464,12 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
|
|||
std::pair<std::vector<int>, bool> itemsSlots =
|
||||
iter->getClass().getEquipmentSlots (*iter);
|
||||
|
||||
// checking if current item poited by iter can be equipped
|
||||
// checking if current item pointed by iter can be equipped
|
||||
for (std::vector<int>::const_iterator iter2 (itemsSlots.first.begin());
|
||||
iter2!=itemsSlots.first.end(); ++iter2)
|
||||
{
|
||||
// if true then it means slot is equipped already
|
||||
// check if slot may require swapping if current item is more valueable
|
||||
// check if slot may require swapping if current item is more valuable
|
||||
if (slots_.at (*iter2)!=end())
|
||||
{
|
||||
Ptr old = *slots_.at (*iter2);
|
||||
|
@ -362,98 +537,48 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
|
|||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const ESM::Skill::SkillEnum weaponSkills[] =
|
||||
void MWWorld::InventoryStore::autoEquipShield(const MWWorld::Ptr& actor, TSlots& slots_)
|
||||
{
|
||||
for (ContainerStoreIterator iter(begin(ContainerStore::Type_Armor)); iter != end(); ++iter)
|
||||
{
|
||||
ESM::Skill::LongBlade,
|
||||
ESM::Skill::Axe,
|
||||
ESM::Skill::Spear,
|
||||
ESM::Skill::ShortBlade,
|
||||
ESM::Skill::Marksman,
|
||||
ESM::Skill::BluntWeapon
|
||||
};
|
||||
const size_t weaponSkillsLength = sizeof(weaponSkills) / sizeof(weaponSkills[0]);
|
||||
|
||||
bool weaponSkillVisited[weaponSkillsLength] = { false };
|
||||
|
||||
for (int i = 0; i < static_cast<int>(weaponSkillsLength); ++i)
|
||||
{
|
||||
int max = 0;
|
||||
int maxWeaponSkill = -1;
|
||||
|
||||
for (int j = 0; j < static_cast<int>(weaponSkillsLength); ++j)
|
||||
if (iter->get<ESM::Armor>()->mBase->mData.mType != ESM::Armor::Shield)
|
||||
continue;
|
||||
if (iter->getClass().canBeEquipped(*iter, actor).first != 1)
|
||||
continue;
|
||||
if (iter->getClass().getItemHealth(*iter) <= 0)
|
||||
continue;
|
||||
std::pair<std::vector<int>, bool> shieldSlots =
|
||||
iter->getClass().getEquipmentSlots(*iter);
|
||||
if (shieldSlots.first.empty())
|
||||
continue;
|
||||
int slot = shieldSlots.first[0];
|
||||
const ContainerStoreIterator& shield = mSlots[slot];
|
||||
if (shield != end()
|
||||
&& shield.getType() == Type_Armor && shield->get<ESM::Armor>()->mBase->mData.mType == ESM::Armor::Shield)
|
||||
{
|
||||
int skillValue = actor.getClass().getSkill(actor, static_cast<int>(weaponSkills[j]));
|
||||
|
||||
if (skillValue > max && !weaponSkillVisited[j])
|
||||
{
|
||||
max = skillValue;
|
||||
maxWeaponSkill = j;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxWeaponSkill == -1)
|
||||
break;
|
||||
|
||||
max = 0;
|
||||
ContainerStoreIterator weapon(end());
|
||||
|
||||
for (ContainerStoreIterator iter(begin(ContainerStore::Type_Weapon)); iter!=end(); ++iter)
|
||||
{
|
||||
if (!canActorAutoEquip(actor, *iter))
|
||||
if (shield->getClass().getItemHealth(*shield) >= iter->getClass().getItemHealth(*iter))
|
||||
continue;
|
||||
|
||||
const ESM::Weapon* esmWeapon = iter->get<ESM::Weapon>()->mBase;
|
||||
|
||||
if (esmWeapon->mData.mType == ESM::Weapon::Arrow || esmWeapon->mData.mType == ESM::Weapon::Bolt)
|
||||
continue;
|
||||
|
||||
if (iter->getClass().getEquipmentSkill(*iter) == weaponSkills[maxWeaponSkill])
|
||||
{
|
||||
if (esmWeapon->mData.mChop[1] >= max)
|
||||
{
|
||||
max = esmWeapon->mData.mChop[1];
|
||||
weapon = iter;
|
||||
}
|
||||
|
||||
if (esmWeapon->mData.mSlash[1] >= max)
|
||||
{
|
||||
max = esmWeapon->mData.mSlash[1];
|
||||
weapon = iter;
|
||||
}
|
||||
|
||||
if (esmWeapon->mData.mThrust[1] >= max)
|
||||
{
|
||||
max = esmWeapon->mData.mThrust[1];
|
||||
weapon = iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (weapon != end() && weapon->getClass().canBeEquipped(*weapon, actor).first)
|
||||
{
|
||||
std::pair<std::vector<int>, bool> itemsSlots =
|
||||
weapon->getClass().getEquipmentSlots (*weapon);
|
||||
|
||||
if (!itemsSlots.first.empty())
|
||||
{
|
||||
if (!itemsSlots.second)
|
||||
{
|
||||
if (weapon->getRefData().getCount() > 1)
|
||||
{
|
||||
unstack(*weapon, actor);
|
||||
}
|
||||
}
|
||||
|
||||
int slot = itemsSlots.first.front();
|
||||
slots_[slot] = weapon;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
weaponSkillVisited[maxWeaponSkill] = true;
|
||||
slots_[slot] = iter;
|
||||
}
|
||||
}
|
||||
|
||||
void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
|
||||
{
|
||||
TSlots slots_;
|
||||
initSlots (slots_);
|
||||
|
||||
// Disable model update during auto-equip
|
||||
mUpdatesEnabled = false;
|
||||
|
||||
// Autoequip clothing, armor and weapons.
|
||||
// Equipping lights is handled in Actors::updateEquippedLight based on environment light.
|
||||
// Note: creatures do not use the armor mitigation and can equip only shields
|
||||
// Use a custom logic for them - select shield based on its health instead of armor rating (since it useless for creatures)
|
||||
autoEquipWeapon(actor, slots_);
|
||||
autoEquipArmor(actor, slots_);
|
||||
|
||||
bool changed = false;
|
||||
|
||||
|
@ -476,50 +601,6 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
|
|||
}
|
||||
}
|
||||
|
||||
void MWWorld::InventoryStore::autoEquipShield(const MWWorld::Ptr& actor)
|
||||
{
|
||||
bool updated = false;
|
||||
|
||||
mUpdatesEnabled = false;
|
||||
for (ContainerStoreIterator iter(begin(ContainerStore::Type_Armor)); iter != end(); ++iter)
|
||||
{
|
||||
if (iter->get<ESM::Armor>()->mBase->mData.mType != ESM::Armor::Shield)
|
||||
continue;
|
||||
|
||||
if (iter->getClass().canBeEquipped(*iter, actor).first != 1)
|
||||
continue;
|
||||
|
||||
if (iter->getClass().getItemHealth(*iter) <= 0)
|
||||
continue;
|
||||
|
||||
std::pair<std::vector<int>, bool> shieldSlots =
|
||||
iter->getClass().getEquipmentSlots(*iter);
|
||||
|
||||
if (shieldSlots.first.empty())
|
||||
continue;
|
||||
|
||||
int slot = shieldSlots.first[0];
|
||||
const ContainerStoreIterator& shield = mSlots[slot];
|
||||
|
||||
if (shield != end()
|
||||
&& shield.getType() == Type_Armor && shield->get<ESM::Armor>()->mBase->mData.mType == ESM::Armor::Shield)
|
||||
{
|
||||
if (shield->getClass().getItemHealth(*shield) >= iter->getClass().getItemHealth(*iter))
|
||||
continue;
|
||||
}
|
||||
|
||||
equip(slot, iter, actor);
|
||||
updated = true;
|
||||
}
|
||||
mUpdatesEnabled = true;
|
||||
|
||||
if (updated)
|
||||
{
|
||||
fireEquipmentChangedEvent(actor);
|
||||
updateMagicEffects(actor);
|
||||
}
|
||||
}
|
||||
|
||||
const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() const
|
||||
{
|
||||
return mMagicEffects;
|
||||
|
@ -532,7 +613,7 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
|
|||
return;
|
||||
|
||||
// Delay update until the listener is set up
|
||||
if (!mListener)
|
||||
if (!mInventoryListener)
|
||||
return;
|
||||
|
||||
mMagicEffects = MWMechanics::MagicEffects();
|
||||
|
@ -603,7 +684,7 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
|
|||
// During first auto equip, we don't play any sounds.
|
||||
// Basically we don't want sounds when the actor is first loaded,
|
||||
// the items should appear as if they'd always been equipped.
|
||||
mListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip);
|
||||
mInventoryListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip);
|
||||
}
|
||||
|
||||
if (magnitude)
|
||||
|
@ -737,6 +818,9 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor
|
|||
mSelectedEnchantItem = end();
|
||||
}
|
||||
|
||||
if (mListener)
|
||||
mListener->itemRemoved(item, retCount);
|
||||
|
||||
return retCount;
|
||||
}
|
||||
|
||||
|
@ -822,12 +906,12 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(con
|
|||
|
||||
MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener()
|
||||
{
|
||||
return mListener;
|
||||
return mInventoryListener;
|
||||
}
|
||||
|
||||
void MWWorld::InventoryStore::setInvListener(InventoryStoreListener *listener, const Ptr& actor)
|
||||
{
|
||||
mListener = listener;
|
||||
mInventoryListener = listener;
|
||||
updateMagicEffects(actor);
|
||||
}
|
||||
|
||||
|
@ -835,8 +919,8 @@ void MWWorld::InventoryStore::fireEquipmentChangedEvent(const Ptr& actor)
|
|||
{
|
||||
if (!mUpdatesEnabled)
|
||||
return;
|
||||
if (mListener)
|
||||
mListener->equipmentChanged();
|
||||
if (mInventoryListener)
|
||||
mInventoryListener->equipmentChanged();
|
||||
|
||||
// if player, update inventory window
|
||||
/*
|
||||
|
|
|
@ -69,7 +69,7 @@ namespace MWWorld
|
|||
|
||||
MWMechanics::MagicEffects mMagicEffects;
|
||||
|
||||
InventoryStoreListener* mListener;
|
||||
InventoryStoreListener* mInventoryListener;
|
||||
|
||||
// Enables updates of magic effects and actor model whenever items are equipped or unequipped.
|
||||
// This is disabled during autoequip to avoid excessive updates
|
||||
|
@ -94,6 +94,10 @@ namespace MWWorld
|
|||
|
||||
TSlots mSlots;
|
||||
|
||||
void autoEquipWeapon(const MWWorld::Ptr& actor, TSlots& slots_);
|
||||
void autoEquipArmor(const MWWorld::Ptr& actor, TSlots& slots_);
|
||||
void autoEquipShield(const MWWorld::Ptr& actor, TSlots& slots_);
|
||||
|
||||
// selected magic item (for using enchantments of type "Cast once" or "Cast when used")
|
||||
ContainerStoreIterator mSelectedEnchantItem;
|
||||
|
||||
|
@ -164,9 +168,6 @@ namespace MWWorld
|
|||
void autoEquip (const MWWorld::Ptr& actor);
|
||||
///< Auto equip items according to stats and item value.
|
||||
|
||||
void autoEquipShield(const MWWorld::Ptr& actor);
|
||||
///< Auto-equip the shield with most health.
|
||||
|
||||
const MWMechanics::MagicEffects& getMagicEffects() const;
|
||||
///< Return magic effects from worn items.
|
||||
|
||||
|
|
|
@ -396,7 +396,8 @@ namespace Resource
|
|||
{
|
||||
const char* reserved[] = {"Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm",
|
||||
"Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle",
|
||||
"Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "ArrowBone", "Camera"};
|
||||
"Left Clavicle", "Weapon Bone", "Tail", "Bip01", "Root Bone", "BoneOffset", "AttachLight", "Arrow", "Camera"};
|
||||
|
||||
reservedNames = std::vector<std::string>(reserved, reserved + sizeof(reserved)/sizeof(reserved[0]));
|
||||
|
||||
for (unsigned int i=0; i<sizeof(reserved)/sizeof(reserved[0]); ++i)
|
||||
|
|
|
@ -171,9 +171,20 @@ followers attack on sight
|
|||
Make player followers and escorters start combat with enemies who have started combat with them or the player.
|
||||
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.
|
||||
|
||||
weapon sheathing
|
||||
----------------
|
||||
|
||||
:Type: boolean
|
||||
:Range: True/False
|
||||
:Default: False
|
||||
|
||||
If this setting is true, OpenMW will utilize weapon sheathing-compatible assets to display holstered weapons.
|
||||
|
||||
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.
|
||||
Additional _sh suffix models are not essential for weapon sheathing to work but will act as quivers or scabbards for the weapons they correspond to.
|
||||
|
||||
use additional anim sources
|
||||
---------------------------
|
||||
|
||||
|
|
|
@ -239,6 +239,9 @@ barter disposition change is permanent = false
|
|||
# 2 means werewolves are ignored)
|
||||
strength influences hand to hand = 0
|
||||
|
||||
# Render holstered weapons (with quivers and scabbards), requires modded assets
|
||||
weapon sheathing = false
|
||||
|
||||
[General]
|
||||
|
||||
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).
|
||||
|
|
Loading…
Reference in a new issue