mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-06 00:55:50 +00:00
763 lines
24 KiB
C++
763 lines
24 KiB
C++
#include "inventorystore.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
|
|
#include <components/esm3/inventorystate.hpp>
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
|
|
#include "../mwmechanics/actorutil.hpp"
|
|
#include "../mwmechanics/npcstats.hpp"
|
|
#include "../mwmechanics/weapontype.hpp"
|
|
|
|
#include "class.hpp"
|
|
#include "esmstore.hpp"
|
|
|
|
void MWWorld::InventoryStore::copySlots(const InventoryStore& store)
|
|
{
|
|
// some const-trickery, required because of a flaw in the handling of MW-references and the
|
|
// resulting workarounds
|
|
for (std::vector<ContainerStoreIterator>::const_iterator iter(const_cast<InventoryStore&>(store).mSlots.begin());
|
|
iter != const_cast<InventoryStore&>(store).mSlots.end(); ++iter)
|
|
{
|
|
std::size_t distance = std::distance(const_cast<InventoryStore&>(store).begin(), *iter);
|
|
|
|
ContainerStoreIterator slot = begin();
|
|
|
|
std::advance(slot, distance);
|
|
|
|
mSlots.push_back(slot);
|
|
}
|
|
|
|
// some const-trickery, required because of a flaw in the handling of MW-references and the
|
|
// resulting workarounds
|
|
std::size_t distance = std::distance(
|
|
const_cast<InventoryStore&>(store).begin(), const_cast<InventoryStore&>(store).mSelectedEnchantItem);
|
|
ContainerStoreIterator slot = begin();
|
|
std::advance(slot, distance);
|
|
mSelectedEnchantItem = slot;
|
|
}
|
|
|
|
void MWWorld::InventoryStore::initSlots(TSlots& slots_)
|
|
{
|
|
for (int i = 0; i < Slots; ++i)
|
|
slots_.push_back(end());
|
|
}
|
|
|
|
void MWWorld::InventoryStore::storeEquipmentState(
|
|
const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const
|
|
{
|
|
for (int32_t i = 0; i < MWWorld::InventoryStore::Slots; ++i)
|
|
{
|
|
if (mSlots[i].getType() != -1 && mSlots[i]->getBase() == &ref)
|
|
inventory.mEquipmentSlots[static_cast<uint32_t>(index)] = i;
|
|
}
|
|
|
|
if (mSelectedEnchantItem.getType() != -1 && mSelectedEnchantItem->getBase() == &ref)
|
|
inventory.mSelectedEnchantItem = index;
|
|
}
|
|
|
|
void MWWorld::InventoryStore::readEquipmentState(
|
|
const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory)
|
|
{
|
|
if (index == inventory.mSelectedEnchantItem)
|
|
mSelectedEnchantItem = iter;
|
|
|
|
auto found = inventory.mEquipmentSlots.find(index);
|
|
if (found != inventory.mEquipmentSlots.end())
|
|
{
|
|
if (found->second < 0 || found->second >= MWWorld::InventoryStore::Slots)
|
|
throw std::runtime_error("Invalid slot index in inventory state");
|
|
|
|
// make sure the item can actually be equipped in this slot
|
|
int32_t slot = found->second;
|
|
std::pair<std::vector<int>, bool> allowedSlots = iter->getClass().getEquipmentSlots(*iter);
|
|
if (!allowedSlots.first.size())
|
|
return;
|
|
if (std::find(allowedSlots.first.begin(), allowedSlots.first.end(), slot) == allowedSlots.first.end())
|
|
slot = allowedSlots.first.front();
|
|
|
|
// unstack if required
|
|
if (!allowedSlots.second && iter->getRefData().getCount() > 1)
|
|
{
|
|
int count = iter->getRefData().getCount(false);
|
|
MWWorld::ContainerStoreIterator newIter = addNewStack(*iter, count > 0 ? 1 : -1);
|
|
iter->getRefData().setCount(subtractItems(count, 1));
|
|
mSlots[slot] = newIter;
|
|
}
|
|
else
|
|
mSlots[slot] = iter;
|
|
}
|
|
}
|
|
|
|
MWWorld::InventoryStore::InventoryStore()
|
|
: ContainerStore()
|
|
, mInventoryListener(nullptr)
|
|
, mUpdatesEnabled(true)
|
|
, mFirstAutoEquip(true)
|
|
, mSelectedEnchantItem(end())
|
|
{
|
|
initSlots(mSlots);
|
|
}
|
|
|
|
MWWorld::InventoryStore::InventoryStore(const InventoryStore& store)
|
|
: ContainerStore(store)
|
|
, mInventoryListener(store.mInventoryListener)
|
|
, mUpdatesEnabled(store.mUpdatesEnabled)
|
|
, mFirstAutoEquip(store.mFirstAutoEquip)
|
|
, mSelectedEnchantItem(end())
|
|
{
|
|
copySlots(store);
|
|
}
|
|
|
|
MWWorld::InventoryStore& MWWorld::InventoryStore::operator=(const InventoryStore& store)
|
|
{
|
|
if (this == &store)
|
|
return *this;
|
|
|
|
mListener = store.mListener;
|
|
mInventoryListener = store.mInventoryListener;
|
|
mFirstAutoEquip = store.mFirstAutoEquip;
|
|
mRechargingItemsUpToDate = false;
|
|
ContainerStore::operator=(store);
|
|
mSlots.clear();
|
|
copySlots(store);
|
|
return *this;
|
|
}
|
|
|
|
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(
|
|
const Ptr& itemPtr, int count, bool allowAutoEquip, bool resolve)
|
|
{
|
|
const MWWorld::ContainerStoreIterator& retVal
|
|
= MWWorld::ContainerStore::add(itemPtr, count, allowAutoEquip, resolve);
|
|
|
|
// Auto-equip items if an armor/clothing item is added, but not for the player nor werewolves
|
|
const Ptr& actor = getPtr();
|
|
if (allowAutoEquip && actor != MWMechanics::getPlayer() && actor.getClass().isNpc()
|
|
&& !actor.getClass().getNpcStats(actor).isWerewolf())
|
|
{
|
|
auto type = itemPtr.getType();
|
|
if (type == ESM::Armor::sRecordId || type == ESM::Clothing::sRecordId)
|
|
autoEquip();
|
|
}
|
|
|
|
if (mListener)
|
|
mListener->itemAdded(*retVal, count);
|
|
|
|
return retVal;
|
|
}
|
|
|
|
void MWWorld::InventoryStore::equip(int slot, const ContainerStoreIterator& iterator)
|
|
{
|
|
if (iterator == end())
|
|
throw std::runtime_error("can't equip end() iterator, use unequip function instead");
|
|
|
|
if (slot < 0 || slot >= static_cast<int>(mSlots.size()))
|
|
throw std::runtime_error("slot number out of range");
|
|
|
|
if (iterator.getContainerStore() != this)
|
|
throw std::runtime_error("attempt to equip an item that is not in the inventory");
|
|
|
|
std::pair<std::vector<int>, bool> slots_;
|
|
|
|
slots_ = iterator->getClass().getEquipmentSlots(*iterator);
|
|
|
|
if (std::find(slots_.first.begin(), slots_.first.end(), slot) == slots_.first.end())
|
|
throw std::runtime_error("invalid slot");
|
|
|
|
if (mSlots[slot] != end())
|
|
unequipSlot(slot);
|
|
|
|
// unstack item pointed to by iterator if required
|
|
if (iterator != end() && !slots_.second
|
|
&& iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped
|
|
{
|
|
unstack(*iterator);
|
|
}
|
|
|
|
mSlots[slot] = iterator;
|
|
|
|
flagAsModified();
|
|
|
|
fireEquipmentChangedEvent();
|
|
}
|
|
|
|
void MWWorld::InventoryStore::unequipAll()
|
|
{
|
|
mUpdatesEnabled = false;
|
|
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
|
unequipSlot(slot);
|
|
|
|
mUpdatesEnabled = true;
|
|
|
|
fireEquipmentChangedEvent();
|
|
}
|
|
|
|
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot(int slot)
|
|
{
|
|
return findSlot(slot);
|
|
}
|
|
|
|
MWWorld::ConstContainerStoreIterator MWWorld::InventoryStore::getSlot(int slot) const
|
|
{
|
|
return findSlot(slot);
|
|
}
|
|
|
|
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::findSlot(int slot) const
|
|
{
|
|
if (slot < 0 || slot >= static_cast<int>(mSlots.size()))
|
|
throw std::runtime_error("slot number out of range");
|
|
|
|
if (mSlots[slot] == end())
|
|
return mSlots[slot];
|
|
|
|
// NOTE: mSlots[slot]->getRefData().getCount() can be zero if the item is marked
|
|
// for removal by a Lua script, but the removal action is not yet processed.
|
|
// The item will be automatically unequiped in the current frame.
|
|
|
|
return mSlots[slot];
|
|
}
|
|
|
|
void MWWorld::InventoryStore::autoEquipWeapon(TSlots& slots_)
|
|
{
|
|
const Ptr& actor = getPtr();
|
|
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::RefId 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)
|
|
{
|
|
float max = 0;
|
|
int maxWeaponSkill = -1;
|
|
|
|
for (int j = 0; j < static_cast<int>(weaponSkillsLength); ++j)
|
|
{
|
|
float skillValue = actor.getClass().getSkill(actor, 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)
|
|
{
|
|
const ESM::Weapon* esmWeapon = iter->get<ESM::Weapon>()->mBase;
|
|
|
|
if (MWMechanics::getWeaponType(esmWeapon->mData.mType)->mWeaponClass == ESM::WeaponType::Ammo)
|
|
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)
|
|
{
|
|
// Do not equip ranged weapons, if there is no suitable ammo
|
|
bool hasAmmo = true;
|
|
const MWWorld::LiveCellRef<ESM::Weapon>* ref = weapon->get<ESM::Weapon>();
|
|
int type = ref->mBase->mData.mType;
|
|
int ammotype = MWMechanics::getWeaponType(type)->mAmmoType;
|
|
if (ammotype == ESM::Weapon::Arrow)
|
|
{
|
|
if (arrow == end())
|
|
hasAmmo = false;
|
|
else
|
|
slots_[Slot_Ammunition] = arrow;
|
|
}
|
|
else if (ammotype == ESM::Weapon::Bolt)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
int slot = itemsSlots.first.front();
|
|
slots_[slot] = weapon;
|
|
|
|
if (ammotype == ESM::Weapon::None)
|
|
slots_[Slot_Ammunition] = end();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
weaponSkillVisited[maxWeaponSkill] = true;
|
|
}
|
|
}
|
|
|
|
void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_)
|
|
{
|
|
// Only NPCs can wear armor for now.
|
|
// For creatures we equip only shields.
|
|
const Ptr& actor = getPtr();
|
|
if (!actor.getClass().isNpc())
|
|
{
|
|
autoEquipShield(slots_);
|
|
return;
|
|
}
|
|
|
|
const MWWorld::Store<ESM::GameSetting>& store = MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
|
|
|
|
static float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat();
|
|
static float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat();
|
|
|
|
float unarmoredSkill = actor.getClass().getSkill(actor, ESM::Skill::Unarmored);
|
|
float unarmoredRating = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill);
|
|
|
|
for (ContainerStoreIterator iter(begin(ContainerStore::Type_Clothing | ContainerStore::Type_Armor)); iter != end();
|
|
++iter)
|
|
{
|
|
Ptr test = *iter;
|
|
|
|
switch (test.getClass().canBeEquipped(test, actor).first)
|
|
{
|
|
case 0:
|
|
continue;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (iter.getType() == ContainerStore::Type_Armor
|
|
&& test.getClass().getEffectiveArmorRating(test, actor) <= std::max(unarmoredRating, 0.f))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
std::pair<std::vector<int>, bool> itemsSlots = iter->getClass().getEquipmentSlots(*iter);
|
|
|
|
// checking if current item pointed by iter can be equipped
|
|
for (int slot : itemsSlots.first)
|
|
{
|
|
// if true then it means slot is equipped already
|
|
// check if slot may require swapping if current item is more valuable
|
|
if (slots_.at(slot) != end())
|
|
{
|
|
Ptr old = *slots_.at(slot);
|
|
|
|
if (iter.getType() == ContainerStore::Type_Armor)
|
|
{
|
|
if (old.getType() == ESM::Armor::sRecordId)
|
|
{
|
|
if (old.get<ESM::Armor>()->mBase->mData.mType < test.get<ESM::Armor>()->mBase->mData.mType)
|
|
continue;
|
|
|
|
if (old.get<ESM::Armor>()->mBase->mData.mType == test.get<ESM::Armor>()->mBase->mData.mType)
|
|
{
|
|
if (old.getClass().getEffectiveArmorRating(old, actor)
|
|
>= test.getClass().getEffectiveArmorRating(test, actor))
|
|
// old armor had better armor rating
|
|
continue;
|
|
}
|
|
}
|
|
// suitable armor should replace already equipped clothing
|
|
}
|
|
else if (iter.getType() == ContainerStore::Type_Clothing)
|
|
{
|
|
// if left ring is equipped
|
|
if (slot == Slot_LeftRing)
|
|
{
|
|
// if there is a place for right ring dont swap it
|
|
if (slots_.at(Slot_RightRing) == end())
|
|
{
|
|
continue;
|
|
}
|
|
else // if right ring is equipped too
|
|
{
|
|
Ptr rightRing = *slots_.at(Slot_RightRing);
|
|
|
|
// we want to swap cheaper ring only if both are equipped
|
|
if (old.getClass().getValue(old) >= rightRing.getClass().getValue(rightRing))
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (old.getType() == ESM::Clothing::sRecordId)
|
|
{
|
|
// check value
|
|
if (old.getClass().getValue(old) >= test.getClass().getValue(test))
|
|
// old clothing was more valuable
|
|
continue;
|
|
}
|
|
else
|
|
// suitable clothing should NOT replace already equipped armor
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!itemsSlots.second) // if itemsSlots.second is true, item can stay stacked when equipped
|
|
{
|
|
// unstack item pointed to by iterator if required
|
|
if (iter->getRefData().getCount() > 1)
|
|
{
|
|
unstack(*iter);
|
|
}
|
|
}
|
|
|
|
// if we are here it means item can be equipped or swapped
|
|
slots_[slot] = iter;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MWWorld::InventoryStore::autoEquipShield(TSlots& slots_)
|
|
{
|
|
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, getPtr()).first != 1)
|
|
continue;
|
|
std::pair<std::vector<int>, bool> shieldSlots = iter->getClass().getEquipmentSlots(*iter);
|
|
int slot = shieldSlots.first[0];
|
|
const ContainerStoreIterator& shield = slots_[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;
|
|
}
|
|
slots_[slot] = iter;
|
|
}
|
|
}
|
|
|
|
void MWWorld::InventoryStore::autoEquip()
|
|
{
|
|
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 ignore equipment armor rating and only equip shields
|
|
// Use custom logic for them - select shield based on its health instead of armor rating
|
|
autoEquipWeapon(slots_);
|
|
autoEquipArmor(slots_);
|
|
|
|
bool changed = false;
|
|
|
|
for (std::size_t i = 0; i < slots_.size(); ++i)
|
|
{
|
|
if (slots_[i] != mSlots[i])
|
|
{
|
|
changed = true;
|
|
break;
|
|
}
|
|
}
|
|
mUpdatesEnabled = true;
|
|
|
|
if (changed)
|
|
{
|
|
mSlots.swap(slots_);
|
|
fireEquipmentChangedEvent();
|
|
flagAsModified();
|
|
}
|
|
}
|
|
|
|
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getPreferredShield()
|
|
{
|
|
TSlots slots;
|
|
initSlots(slots);
|
|
autoEquipArmor(slots);
|
|
return slots[Slot_CarriedLeft];
|
|
}
|
|
|
|
bool MWWorld::InventoryStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const
|
|
{
|
|
bool canStack = MWWorld::ContainerStore::stacks(ptr1, ptr2);
|
|
if (!canStack)
|
|
return false;
|
|
|
|
// don't stack if either item is currently equipped
|
|
for (TSlots::const_iterator iter(mSlots.begin()); iter != mSlots.end(); ++iter)
|
|
{
|
|
if (*iter != end() && (ptr1 == **iter || ptr2 == **iter))
|
|
{
|
|
bool stackWhenEquipped = (*iter)->getClass().getEquipmentSlots(**iter).second;
|
|
if (!stackWhenEquipped)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void MWWorld::InventoryStore::setSelectedEnchantItem(const ContainerStoreIterator& iterator)
|
|
{
|
|
mSelectedEnchantItem = iterator;
|
|
}
|
|
|
|
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem()
|
|
{
|
|
return mSelectedEnchantItem;
|
|
}
|
|
|
|
int MWWorld::InventoryStore::remove(const Ptr& item, int count, bool equipReplacement, bool resolve)
|
|
{
|
|
int retCount = ContainerStore::remove(item, count, equipReplacement, resolve);
|
|
|
|
bool wasEquipped = false;
|
|
if (!item.getRefData().getCount())
|
|
{
|
|
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
|
{
|
|
if (mSlots[slot] == end())
|
|
continue;
|
|
|
|
if (*mSlots[slot] == item)
|
|
{
|
|
unequipSlot(slot);
|
|
wasEquipped = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If an armor/clothing item is removed, try to find a replacement,
|
|
// but not for the player nor werewolves, and not if the RemoveItem script command
|
|
// was used (equipReplacement is false)
|
|
const Ptr& actor = getPtr();
|
|
if (equipReplacement && wasEquipped && (actor != MWMechanics::getPlayer()) && actor.getClass().isNpc()
|
|
&& !actor.getClass().getNpcStats(actor).isWerewolf())
|
|
{
|
|
auto type = item.getType();
|
|
if (type == ESM::Armor::sRecordId || type == ESM::Clothing::sRecordId)
|
|
autoEquip();
|
|
}
|
|
|
|
if (item.getRefData().getCount() == 0 && mSelectedEnchantItem != end() && *mSelectedEnchantItem == item)
|
|
{
|
|
mSelectedEnchantItem = end();
|
|
}
|
|
|
|
if (mListener)
|
|
mListener->itemRemoved(item, retCount);
|
|
|
|
return retCount;
|
|
}
|
|
|
|
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, bool applyUpdates)
|
|
{
|
|
if (slot < 0 || slot >= static_cast<int>(mSlots.size()))
|
|
throw std::runtime_error("slot number out of range");
|
|
|
|
ContainerStoreIterator it = mSlots[slot];
|
|
|
|
if (it != end())
|
|
{
|
|
ContainerStoreIterator retval = it;
|
|
|
|
// empty this slot
|
|
mSlots[slot] = end();
|
|
|
|
if (it->getRefData().getCount())
|
|
{
|
|
retval = restack(*it);
|
|
|
|
if (getPtr() == MWMechanics::getPlayer())
|
|
{
|
|
// Unset OnPCEquip Variable on item's script, if it has a script with that variable declared
|
|
const ESM::RefId& script = it->getClass().getScript(*it);
|
|
if (!script.empty())
|
|
(*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0);
|
|
}
|
|
|
|
if ((mSelectedEnchantItem != end()) && (mSelectedEnchantItem == it))
|
|
{
|
|
mSelectedEnchantItem = end();
|
|
}
|
|
}
|
|
|
|
if (applyUpdates)
|
|
{
|
|
fireEquipmentChangedEvent();
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
return it;
|
|
}
|
|
|
|
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWorld::Ptr& item)
|
|
{
|
|
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
|
{
|
|
MWWorld::ContainerStoreIterator equipped = getSlot(slot);
|
|
if (equipped != end() && *equipped == item)
|
|
return unequipSlot(slot);
|
|
}
|
|
|
|
throw std::runtime_error("attempt to unequip an item that is not currently equipped");
|
|
}
|
|
|
|
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(const Ptr& item, int count)
|
|
{
|
|
if (!isEquipped(item))
|
|
throw std::runtime_error("attempt to unequip an item that is not currently equipped");
|
|
if (count <= 0)
|
|
throw std::runtime_error("attempt to unequip nothing (count <= 0)");
|
|
if (count > item.getRefData().getCount())
|
|
throw std::runtime_error("attempt to unequip more items than equipped");
|
|
|
|
if (count == item.getRefData().getCount())
|
|
return unequipItem(item);
|
|
|
|
// Move items to an existing stack if possible, otherwise split count items out into a new stack.
|
|
// Moving counts manually here, since ContainerStore's restack can't target unequipped stacks.
|
|
for (MWWorld::ContainerStoreIterator iter(begin()); iter != end(); ++iter)
|
|
{
|
|
if (stacks(*iter, item) && !isEquipped(*iter))
|
|
{
|
|
iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), count));
|
|
item.getRefData().setCount(subtractItems(item.getRefData().getCount(false), count));
|
|
return iter;
|
|
}
|
|
}
|
|
|
|
return unstack(item, item.getRefData().getCount() - count);
|
|
}
|
|
|
|
MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener() const
|
|
{
|
|
return mInventoryListener;
|
|
}
|
|
|
|
void MWWorld::InventoryStore::setInvListener(InventoryStoreListener* listener)
|
|
{
|
|
mInventoryListener = listener;
|
|
}
|
|
|
|
void MWWorld::InventoryStore::fireEquipmentChangedEvent()
|
|
{
|
|
if (!mUpdatesEnabled)
|
|
return;
|
|
if (mInventoryListener)
|
|
mInventoryListener->equipmentChanged();
|
|
|
|
// if player, update inventory window
|
|
/*
|
|
if (mActor == MWMechanics::getPlayer())
|
|
{
|
|
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView();
|
|
}
|
|
*/
|
|
}
|
|
|
|
void MWWorld::InventoryStore::clear()
|
|
{
|
|
mSlots.clear();
|
|
initSlots(mSlots);
|
|
ContainerStore::clear();
|
|
}
|
|
|
|
bool MWWorld::InventoryStore::isEquipped(const MWWorld::ConstPtr& item)
|
|
{
|
|
for (int i = 0; i < MWWorld::InventoryStore::Slots; ++i)
|
|
{
|
|
if (getSlot(i) != end() && *getSlot(i) == item)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MWWorld::InventoryStore::isFirstEquip()
|
|
{
|
|
bool first = mFirstAutoEquip;
|
|
mFirstAutoEquip = false;
|
|
return first;
|
|
}
|