Merge pull request #1615 from akortunov/holstered_weapons

Weapon sheathing
This commit is contained in:
Bret Curtis 2018-11-08 20:44:20 +01:00 committed by GitHub
commit f6243fae83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 673 additions and 168 deletions

View file

@ -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);
}
}

View file

@ -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)

View file

@ -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);

View file

@ -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);

View file

@ -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()

View file

@ -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;

View file

@ -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;
}
}

View file

@ -95,6 +95,7 @@ private:
protected:
virtual void addControllers();
virtual bool isArrowAttached() const;
public:
/**

View file

@ -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

View file

@ -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);

View file

@ -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
/*

View file

@ -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.