2012-03-10 11:43:48 +00:00
|
|
|
#include "inventorystore.hpp"
|
2012-03-13 12:31:11 +00:00
|
|
|
|
|
|
|
#include <iterator>
|
2012-03-13 13:04:19 +00:00
|
|
|
#include <algorithm>
|
|
|
|
|
2018-08-14 19:05:43 +00:00
|
|
|
#include <components/debug/debuglog.hpp>
|
2012-05-18 13:48:55 +00:00
|
|
|
#include <components/esm/loadench.hpp>
|
2014-12-30 00:36:31 +00:00
|
|
|
#include <components/esm/inventorystate.hpp>
|
2015-07-09 17:22:04 +00:00
|
|
|
#include <components/misc/rng.hpp>
|
2012-05-18 13:48:55 +00:00
|
|
|
|
|
|
|
#include "../mwbase/environment.hpp"
|
2012-07-03 10:30:50 +00:00
|
|
|
#include "../mwbase/world.hpp"
|
2013-11-17 22:15:57 +00:00
|
|
|
#include "../mwbase/mechanicsmanager.hpp"
|
2012-05-18 13:48:55 +00:00
|
|
|
|
2012-04-08 10:26:21 +00:00
|
|
|
#include "../mwmechanics/npcstats.hpp"
|
2013-11-16 01:34:43 +00:00
|
|
|
#include "../mwmechanics/spellcasting.hpp"
|
2015-08-21 09:12:39 +00:00
|
|
|
#include "../mwmechanics/actorutil.hpp"
|
2018-12-26 09:45:28 +00:00
|
|
|
#include "../mwmechanics/weapontype.hpp"
|
2012-04-08 10:26:21 +00:00
|
|
|
|
2012-10-01 15:17:04 +00:00
|
|
|
#include "esmstore.hpp"
|
2012-03-13 13:04:19 +00:00
|
|
|
#include "class.hpp"
|
2012-03-13 12:31:11 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2014-01-26 19:15:22 +00:00
|
|
|
|
|
|
|
// 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;
|
2012-03-13 12:31:11 +00:00
|
|
|
}
|
|
|
|
|
2013-11-12 22:23:19 +00:00
|
|
|
void MWWorld::InventoryStore::initSlots (TSlots& slots_)
|
2012-03-13 12:31:11 +00:00
|
|
|
{
|
|
|
|
for (int i=0; i<Slots; ++i)
|
2013-11-12 22:23:19 +00:00
|
|
|
slots_.push_back (end());
|
2012-03-31 15:26:15 +00:00
|
|
|
}
|
|
|
|
|
2015-01-23 15:45:47 +00:00
|
|
|
void MWWorld::InventoryStore::storeEquipmentState(const MWWorld::LiveCellRefBase &ref, int index, ESM::InventoryState &inventory) const
|
2014-02-01 16:07:08 +00:00
|
|
|
{
|
|
|
|
for (int i = 0; i<static_cast<int> (mSlots.size()); ++i)
|
2015-01-23 01:32:43 +00:00
|
|
|
if (mSlots[i].getType()!=-1 && mSlots[i]->getBase()==&ref)
|
2015-01-23 15:45:47 +00:00
|
|
|
{
|
|
|
|
inventory.mEquipmentSlots[index] = i;
|
|
|
|
}
|
2014-02-01 16:07:08 +00:00
|
|
|
|
2015-01-23 15:45:47 +00:00
|
|
|
if (mSelectedEnchantItem.getType()!=-1 && mSelectedEnchantItem->getBase() == &ref)
|
|
|
|
inventory.mSelectedEnchantItem = index;
|
2014-02-01 16:07:08 +00:00
|
|
|
}
|
|
|
|
|
2015-01-23 15:45:47 +00:00
|
|
|
void MWWorld::InventoryStore::readEquipmentState(const MWWorld::ContainerStoreIterator &iter, int index, const ESM::InventoryState &inventory)
|
2014-02-01 16:07:08 +00:00
|
|
|
{
|
2015-01-23 15:45:47 +00:00
|
|
|
if (index == inventory.mSelectedEnchantItem)
|
|
|
|
mSelectedEnchantItem = iter;
|
2015-01-23 14:31:44 +00:00
|
|
|
|
2015-01-23 15:45:47 +00:00
|
|
|
std::map<int, int>::const_iterator found = inventory.mEquipmentSlots.find(index);
|
|
|
|
if (found != inventory.mEquipmentSlots.end())
|
2015-01-23 14:31:44 +00:00
|
|
|
{
|
2015-01-23 15:45:47 +00:00
|
|
|
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
|
|
|
|
int 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)
|
|
|
|
{
|
|
|
|
MWWorld::ContainerStoreIterator newIter = addNewStack(*iter, 1);
|
|
|
|
iter->getRefData().setCount(iter->getRefData().getCount()-1);
|
|
|
|
mSlots[slot] = newIter;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
mSlots[slot] = iter;
|
2015-01-23 14:31:44 +00:00
|
|
|
}
|
2014-02-01 16:07:08 +00:00
|
|
|
}
|
|
|
|
|
2013-11-13 17:51:28 +00:00
|
|
|
MWWorld::InventoryStore::InventoryStore()
|
2018-03-03 10:16:21 +00:00
|
|
|
: ContainerStore()
|
|
|
|
, mInventoryListener(nullptr)
|
2013-11-13 17:51:28 +00:00
|
|
|
, mUpdatesEnabled (true)
|
2013-11-14 13:41:10 +00:00
|
|
|
, mFirstAutoEquip(true)
|
2015-05-01 00:24:27 +00:00
|
|
|
, mSelectedEnchantItem(end())
|
2012-03-31 15:26:15 +00:00
|
|
|
{
|
|
|
|
initSlots (mSlots);
|
2012-03-13 12:31:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
MWWorld::InventoryStore::InventoryStore (const InventoryStore& store)
|
2014-09-26 15:12:48 +00:00
|
|
|
: ContainerStore (store)
|
|
|
|
, mMagicEffects(store.mMagicEffects)
|
2018-03-03 10:16:21 +00:00
|
|
|
, mInventoryListener(store.mInventoryListener)
|
2014-09-26 15:12:48 +00:00
|
|
|
, mUpdatesEnabled(store.mUpdatesEnabled)
|
2015-05-01 00:24:27 +00:00
|
|
|
, mFirstAutoEquip(store.mFirstAutoEquip)
|
2014-09-26 15:12:48 +00:00
|
|
|
, mPermanentMagicEffectMagnitudes(store.mPermanentMagicEffectMagnitudes)
|
2015-05-01 00:24:27 +00:00
|
|
|
, mSelectedEnchantItem(end())
|
2012-03-13 12:31:11 +00:00
|
|
|
{
|
|
|
|
copySlots (store);
|
|
|
|
}
|
|
|
|
|
|
|
|
MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStore& store)
|
|
|
|
{
|
2014-01-26 19:15:22 +00:00
|
|
|
mListener = store.mListener;
|
2018-03-03 10:16:21 +00:00
|
|
|
mInventoryListener = store.mInventoryListener;
|
2012-05-19 15:39:24 +00:00
|
|
|
mMagicEffects = store.mMagicEffects;
|
2013-11-14 13:41:10 +00:00
|
|
|
mFirstAutoEquip = store.mFirstAutoEquip;
|
2013-11-13 17:51:28 +00:00
|
|
|
mPermanentMagicEffectMagnitudes = store.mPermanentMagicEffectMagnitudes;
|
2014-10-05 16:27:26 +00:00
|
|
|
mRechargingItemsUpToDate = false;
|
2012-03-13 12:31:11 +00:00
|
|
|
ContainerStore::operator= (store);
|
|
|
|
mSlots.clear();
|
|
|
|
copySlots (store);
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2019-10-27 22:58:23 +00:00
|
|
|
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip)
|
2013-08-07 12:45:23 +00:00
|
|
|
{
|
2019-10-27 22:58:23 +00:00
|
|
|
const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, count, actorPtr, allowAutoEquip);
|
2013-08-07 12:45:23 +00:00
|
|
|
|
2019-10-12 10:00:36 +00:00
|
|
|
// Auto-equip items if an armor/clothing item is added, but not for the player nor werewolves
|
2019-10-27 22:58:23 +00:00
|
|
|
if (allowAutoEquip && actorPtr != MWMechanics::getPlayer()
|
2017-01-08 19:52:04 +00:00
|
|
|
&& actorPtr.getClass().isNpc() && !actorPtr.getClass().getNpcStats(actorPtr).isWerewolf())
|
2013-08-07 12:45:23 +00:00
|
|
|
{
|
|
|
|
std::string type = itemPtr.getTypeName();
|
2015-02-03 23:21:35 +00:00
|
|
|
if (type == typeid(ESM::Armor).name() || type == typeid(ESM::Clothing).name())
|
2013-08-07 12:45:23 +00:00
|
|
|
autoEquip(actorPtr);
|
|
|
|
}
|
|
|
|
|
2018-03-03 10:16:21 +00:00
|
|
|
if (mListener)
|
2018-12-04 13:47:47 +00:00
|
|
|
mListener->itemAdded(*retVal, count);
|
2018-03-03 10:16:21 +00:00
|
|
|
|
2013-08-07 12:45:23 +00:00
|
|
|
return retVal;
|
|
|
|
}
|
|
|
|
|
2013-08-13 00:06:46 +00:00
|
|
|
void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor)
|
2012-03-13 12:31:11 +00:00
|
|
|
{
|
2013-11-17 12:38:54 +00:00
|
|
|
if (iterator == end())
|
|
|
|
throw std::runtime_error ("can't equip end() iterator, use unequip function instead");
|
|
|
|
|
2012-03-13 12:31:11 +00:00
|
|
|
if (slot<0 || slot>=static_cast<int> (mSlots.size()))
|
|
|
|
throw std::runtime_error ("slot number out of range");
|
|
|
|
|
2012-03-13 13:04:19 +00:00
|
|
|
if (iterator.getContainerStore()!=this)
|
|
|
|
throw std::runtime_error ("attempt to equip an item that is not in the inventory");
|
|
|
|
|
2013-11-12 22:23:19 +00:00
|
|
|
std::pair<std::vector<int>, bool> slots_;
|
2012-03-13 13:04:19 +00:00
|
|
|
|
2014-05-22 18:37:22 +00:00
|
|
|
slots_ = iterator->getClass().getEquipmentSlots (*iterator);
|
2013-11-17 12:38:54 +00:00
|
|
|
|
|
|
|
if (std::find (slots_.first.begin(), slots_.first.end(), slot)==slots_.first.end())
|
|
|
|
throw std::runtime_error ("invalid slot");
|
2012-03-13 13:04:19 +00:00
|
|
|
|
2012-05-12 21:09:03 +00:00
|
|
|
if (mSlots[slot] != end())
|
2013-08-13 00:06:46 +00:00
|
|
|
unequipSlot(slot, actor);
|
2012-03-13 13:04:19 +00:00
|
|
|
|
2012-05-12 21:09:03 +00:00
|
|
|
// unstack item pointed to by iterator if required
|
2013-11-12 22:23:19 +00:00
|
|
|
if (iterator!=end() && !slots_.second && iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped
|
2012-05-12 21:09:03 +00:00
|
|
|
{
|
2014-01-05 19:53:45 +00:00
|
|
|
unstack(*iterator, actor);
|
2012-05-12 21:09:03 +00:00
|
|
|
}
|
2012-04-23 13:27:03 +00:00
|
|
|
|
2012-03-13 12:31:11 +00:00
|
|
|
mSlots[slot] = iterator;
|
2012-03-21 11:48:05 +00:00
|
|
|
|
|
|
|
flagAsModified();
|
2013-10-25 20:16:52 +00:00
|
|
|
|
2015-04-06 03:13:09 +00:00
|
|
|
fireEquipmentChangedEvent(actor);
|
2014-10-04 14:47:29 +00:00
|
|
|
|
2013-11-15 19:29:47 +00:00
|
|
|
updateMagicEffects(actor);
|
2012-03-13 12:31:11 +00:00
|
|
|
}
|
|
|
|
|
2013-08-06 12:55:08 +00:00
|
|
|
void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor)
|
2013-08-06 09:20:51 +00:00
|
|
|
{
|
2014-01-04 19:43:57 +00:00
|
|
|
mUpdatesEnabled = false;
|
2013-08-06 09:20:51 +00:00
|
|
|
for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
2013-10-31 23:54:54 +00:00
|
|
|
unequipSlot(slot, actor);
|
2014-10-04 20:27:23 +00:00
|
|
|
|
2014-01-04 19:43:57 +00:00
|
|
|
mUpdatesEnabled = true;
|
2014-10-11 19:05:12 +00:00
|
|
|
|
2015-04-06 03:13:09 +00:00
|
|
|
fireEquipmentChangedEvent(actor);
|
2014-01-04 19:43:57 +00:00
|
|
|
updateMagicEffects(actor);
|
2013-08-06 09:20:51 +00:00
|
|
|
}
|
|
|
|
|
2012-03-13 12:31:11 +00:00
|
|
|
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot)
|
|
|
|
{
|
2017-02-28 14:43:20 +00:00
|
|
|
return findSlot (slot);
|
2012-03-13 12:31:11 +00:00
|
|
|
}
|
|
|
|
|
2017-02-26 21:24:51 +00:00
|
|
|
MWWorld::ConstContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) const
|
|
|
|
{
|
2017-02-28 14:43:20 +00:00
|
|
|
return findSlot (slot);
|
2012-03-13 12:31:11 +00:00
|
|
|
}
|
2012-03-31 15:26:15 +00:00
|
|
|
|
2017-02-28 14:43:20 +00:00
|
|
|
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];
|
|
|
|
|
|
|
|
if (mSlots[slot]->getRefData().getCount()<1)
|
|
|
|
{
|
|
|
|
// Object has been deleted
|
|
|
|
// This should no longer happen, since the new remove function will unequip first
|
|
|
|
throw std::runtime_error("Invalid slot, make sure you are not calling RefData::setCount for a container object");
|
|
|
|
}
|
|
|
|
|
|
|
|
return mSlots[slot];
|
|
|
|
}
|
|
|
|
|
2018-03-03 10:16:21 +00:00
|
|
|
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)
|
|
|
|
{
|
|
|
|
const ESM::Weapon* esmWeapon = iter->get<ESM::Weapon>()->mBase;
|
|
|
|
|
2018-12-26 09:45:28 +00:00
|
|
|
if (MWMechanics::getWeaponType(esmWeapon->mData.mType)->mWeaponClass == ESM::WeaponType::Ammo)
|
2018-03-03 10:16:21 +00:00
|
|
|
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;
|
2018-12-26 09:45:28 +00:00
|
|
|
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)
|
2018-03-03 10:16:21 +00:00
|
|
|
{
|
|
|
|
if (arrow == end())
|
|
|
|
hasAmmo = false;
|
|
|
|
else
|
|
|
|
slots_[Slot_Ammunition] = arrow;
|
|
|
|
}
|
2018-12-26 09:45:28 +00:00
|
|
|
else if (ammotype == ESM::Weapon::Bolt)
|
2018-03-03 10:16:21 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
2018-12-26 09:45:28 +00:00
|
|
|
if (ammotype == ESM::Weapon::None)
|
2018-03-03 10:16:21 +00:00
|
|
|
slots_[Slot_Ammunition] = end();
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
weaponSkillVisited[maxWeaponSkill] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MWWorld::InventoryStore::autoEquipArmor (const MWWorld::Ptr& actor, TSlots& slots_)
|
2012-03-31 15:26:15 +00:00
|
|
|
{
|
2018-03-03 10:16:21 +00:00
|
|
|
// Only NPCs can wear armor for now.
|
|
|
|
// For creatures we equip only shields.
|
|
|
|
if (!actor.getClass().isNpc())
|
|
|
|
{
|
|
|
|
autoEquipShield(actor, slots_);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-10-16 15:27:17 +00:00
|
|
|
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
|
|
|
const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
|
|
|
|
|
2018-08-29 15:38:12 +00:00
|
|
|
static float fUnarmoredBase1 = store.find("fUnarmoredBase1")->mValue.getFloat();
|
|
|
|
static float fUnarmoredBase2 = store.find("fUnarmoredBase2")->mValue.getFloat();
|
2016-10-16 15:27:17 +00:00
|
|
|
|
2018-03-03 10:16:21 +00:00
|
|
|
int unarmoredSkill = actor.getClass().getSkill(actor, ESM::Skill::Unarmored);
|
2017-02-17 02:11:37 +00:00
|
|
|
float unarmoredRating = (fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill);
|
2016-10-16 15:27:17 +00:00
|
|
|
|
|
|
|
for (ContainerStoreIterator iter (begin(ContainerStore::Type_Clothing | ContainerStore::Type_Armor)); iter!=end(); ++iter)
|
2012-03-31 15:26:15 +00:00
|
|
|
{
|
2012-04-07 18:09:09 +00:00
|
|
|
Ptr test = *iter;
|
2013-12-20 21:38:23 +00:00
|
|
|
|
2016-10-16 15:27:17 +00:00
|
|
|
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;
|
|
|
|
}
|
2012-04-07 18:09:09 +00:00
|
|
|
|
2012-03-31 15:26:15 +00:00
|
|
|
std::pair<std::vector<int>, bool> itemsSlots =
|
2014-01-15 14:50:45 +00:00
|
|
|
iter->getClass().getEquipmentSlots (*iter);
|
2012-03-31 15:26:15 +00:00
|
|
|
|
2018-03-03 10:16:21 +00:00
|
|
|
// checking if current item pointed by iter can be equipped
|
2019-03-07 08:38:55 +00:00
|
|
|
for (int slot : itemsSlots.first)
|
2012-03-31 15:26:15 +00:00
|
|
|
{
|
2017-10-13 12:47:26 +00:00
|
|
|
// if true then it means slot is equipped already
|
2018-03-03 10:16:21 +00:00
|
|
|
// check if slot may require swapping if current item is more valuable
|
2019-03-07 08:38:55 +00:00
|
|
|
if (slots_.at (slot)!=end())
|
2012-03-31 15:26:15 +00:00
|
|
|
{
|
2019-03-07 08:38:55 +00:00
|
|
|
Ptr old = *slots_.at (slot);
|
2012-04-07 18:09:09 +00:00
|
|
|
|
2016-10-16 15:27:17 +00:00
|
|
|
if (iter.getType() == ContainerStore::Type_Armor)
|
2012-04-08 10:26:21 +00:00
|
|
|
{
|
2016-10-16 15:27:17 +00:00
|
|
|
if (old.getTypeName() == typeid(ESM::Armor).name())
|
|
|
|
{
|
2017-02-16 13:41:19 +00:00
|
|
|
if (old.get<ESM::Armor>()->mBase->mData.mType < test.get<ESM::Armor>()->mBase->mData.mType)
|
2016-10-16 15:27:17 +00:00
|
|
|
continue;
|
2017-02-16 13:41:19 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2016-10-16 15:27:17 +00:00
|
|
|
}
|
|
|
|
// suitable armor should replace already equipped clothing
|
2012-04-08 10:26:21 +00:00
|
|
|
}
|
2016-10-16 15:27:17 +00:00
|
|
|
else if (iter.getType() == ContainerStore::Type_Clothing)
|
2012-04-07 18:09:09 +00:00
|
|
|
{
|
2017-10-13 12:47:26 +00:00
|
|
|
// if left ring is equipped
|
2019-03-07 08:38:55 +00:00
|
|
|
if (slot == Slot_LeftRing)
|
2017-10-13 12:39:44 +00:00
|
|
|
{
|
2017-10-13 18:32:52 +00:00
|
|
|
// if there is a place for right ring dont swap it
|
2017-10-13 13:16:07 +00:00
|
|
|
if (slots_.at(Slot_RightRing) == end())
|
2017-10-13 12:39:44 +00:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2017-10-13 13:16:07 +00:00
|
|
|
else // if right ring is equipped too
|
|
|
|
{
|
|
|
|
Ptr rightRing = *slots_.at(Slot_RightRing);
|
|
|
|
|
|
|
|
// we want to swap cheaper ring only if both are equipped
|
2017-10-20 19:05:12 +00:00
|
|
|
if (old.getClass().getValue (old) >= rightRing.getClass().getValue (rightRing))
|
2017-10-13 13:16:07 +00:00
|
|
|
continue;
|
|
|
|
}
|
2017-10-13 12:39:44 +00:00
|
|
|
}
|
|
|
|
|
2016-10-16 15:27:17 +00:00
|
|
|
if (old.getTypeName() == typeid(ESM::Clothing).name())
|
2012-04-08 10:26:21 +00:00
|
|
|
{
|
2016-10-16 15:27:17 +00:00
|
|
|
// check value
|
2017-10-19 12:16:08 +00:00
|
|
|
if (old.getClass().getValue (old) >= test.getClass().getValue (test))
|
2016-10-16 15:27:17 +00:00
|
|
|
// old clothing was more valuable
|
|
|
|
continue;
|
2012-04-08 10:26:21 +00:00
|
|
|
}
|
2016-10-16 15:27:17 +00:00
|
|
|
else
|
|
|
|
// suitable clothing should NOT replace already equipped armor
|
|
|
|
continue;
|
2012-04-07 18:09:09 +00:00
|
|
|
}
|
2012-03-31 15:26:15 +00:00
|
|
|
}
|
2012-04-07 18:09:09 +00:00
|
|
|
|
2012-05-13 09:52:17 +00:00
|
|
|
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)
|
|
|
|
{
|
2014-01-05 19:53:45 +00:00
|
|
|
unstack(*iter, actor);
|
2012-05-13 09:52:17 +00:00
|
|
|
}
|
|
|
|
}
|
2012-04-07 18:09:09 +00:00
|
|
|
|
2017-10-13 12:47:26 +00:00
|
|
|
// if we are here it means item can be equipped or swapped
|
2019-03-07 08:38:55 +00:00
|
|
|
slots_[slot] = iter;
|
2012-04-07 18:09:09 +00:00
|
|
|
break;
|
2012-03-31 15:26:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-05-13 12:58:38 +00:00
|
|
|
|
2018-03-03 10:16:21 +00:00
|
|
|
void MWWorld::InventoryStore::autoEquipShield(const MWWorld::Ptr& actor, TSlots& slots_)
|
2017-01-08 19:52:04 +00:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
std::pair<std::vector<int>, bool> shieldSlots =
|
|
|
|
iter->getClass().getEquipmentSlots(*iter);
|
|
|
|
int slot = shieldSlots.first[0];
|
2020-01-05 20:30:22 +00:00
|
|
|
const ContainerStoreIterator& shield = slots_[slot];
|
2017-01-08 19:52:04 +00:00
|
|
|
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;
|
|
|
|
}
|
2018-03-03 10:16:21 +00:00
|
|
|
slots_[slot] = iter;
|
|
|
|
}
|
|
|
|
}
|
2017-01-08 19:52:04 +00:00
|
|
|
|
2018-03-03 10:16:21 +00:00
|
|
|
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.
|
2019-05-11 16:20:23 +00:00
|
|
|
// 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
|
2018-03-03 10:16:21 +00:00
|
|
|
autoEquipWeapon(actor, slots_);
|
|
|
|
autoEquipArmor(actor, slots_);
|
|
|
|
|
|
|
|
bool changed = false;
|
|
|
|
|
|
|
|
for (std::size_t i=0; i<slots_.size(); ++i)
|
|
|
|
{
|
|
|
|
if (slots_[i] != mSlots[i])
|
|
|
|
{
|
|
|
|
changed = true;
|
|
|
|
break;
|
|
|
|
}
|
2017-01-08 19:52:04 +00:00
|
|
|
}
|
|
|
|
mUpdatesEnabled = true;
|
|
|
|
|
2018-03-03 10:16:21 +00:00
|
|
|
if (changed)
|
2017-01-08 19:52:04 +00:00
|
|
|
{
|
2018-03-03 10:16:21 +00:00
|
|
|
mSlots.swap (slots_);
|
2017-01-08 19:52:04 +00:00
|
|
|
fireEquipmentChangedEvent(actor);
|
|
|
|
updateMagicEffects(actor);
|
2018-03-03 10:16:21 +00:00
|
|
|
flagAsModified();
|
2017-01-08 19:52:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-13 17:51:28 +00:00
|
|
|
const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() const
|
|
|
|
{
|
|
|
|
return mMagicEffects;
|
|
|
|
}
|
|
|
|
|
2013-11-15 19:29:47 +00:00
|
|
|
void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
|
2012-05-18 13:48:55 +00:00
|
|
|
{
|
2013-11-13 17:51:28 +00:00
|
|
|
// To avoid excessive updates during auto-equip
|
|
|
|
if (!mUpdatesEnabled)
|
|
|
|
return;
|
|
|
|
|
2013-11-15 01:08:36 +00:00
|
|
|
// Delay update until the listener is set up
|
2018-03-03 10:16:21 +00:00
|
|
|
if (!mInventoryListener)
|
2013-11-15 01:08:36 +00:00
|
|
|
return;
|
|
|
|
|
2013-11-13 17:51:28 +00:00
|
|
|
mMagicEffects = MWMechanics::MagicEffects();
|
|
|
|
|
2016-02-01 21:30:08 +00:00
|
|
|
if (actor.getClass().getCreatureStats(actor).isDead())
|
|
|
|
return;
|
|
|
|
|
2013-11-13 17:51:28 +00:00
|
|
|
for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
|
2012-05-18 13:48:55 +00:00
|
|
|
{
|
2013-11-13 17:51:28 +00:00
|
|
|
if (*iter==end())
|
|
|
|
continue;
|
2012-05-18 13:48:55 +00:00
|
|
|
|
2014-05-22 18:37:22 +00:00
|
|
|
std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter);
|
2013-11-13 17:51:28 +00:00
|
|
|
|
|
|
|
if (!enchantmentId.empty())
|
|
|
|
{
|
|
|
|
const ESM::Enchantment& enchantment =
|
|
|
|
*MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find (enchantmentId);
|
|
|
|
|
|
|
|
if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect)
|
|
|
|
continue;
|
|
|
|
|
2013-11-15 19:29:47 +00:00
|
|
|
std::vector<EffectParams> params;
|
2013-11-13 17:51:28 +00:00
|
|
|
|
2014-05-25 12:13:07 +00:00
|
|
|
bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().getRefId()) != mPermanentMagicEffectMagnitudes.end());
|
2013-11-15 01:08:36 +00:00
|
|
|
if (!existed)
|
|
|
|
{
|
2013-11-16 01:34:43 +00:00
|
|
|
params.resize(enchantment.mEffects.mList.size());
|
|
|
|
|
2013-11-15 19:29:47 +00:00
|
|
|
int i=0;
|
2019-03-07 08:38:55 +00:00
|
|
|
for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList)
|
2013-11-15 19:29:47 +00:00
|
|
|
{
|
2019-04-08 17:22:10 +00:00
|
|
|
int delta = effect.mMagnMax - effect.mMagnMin;
|
|
|
|
// Roll some dice, one for each effect
|
2019-04-14 09:47:11 +00:00
|
|
|
if (delta)
|
|
|
|
params[i].mRandom = Misc::Rng::rollDice(delta + 1) / static_cast<float>(delta);
|
2019-04-08 17:22:10 +00:00
|
|
|
// Try resisting each effect
|
2019-03-07 08:38:55 +00:00
|
|
|
params[i].mMultiplier = MWMechanics::getEffectMultiplier(effect.mEffectID, actor, actor);
|
2013-11-15 19:29:47 +00:00
|
|
|
++i;
|
|
|
|
}
|
|
|
|
|
2013-11-15 01:08:36 +00:00
|
|
|
// Note that using the RefID as a key here is not entirely correct.
|
|
|
|
// Consider equipping the same item twice (e.g. a ring)
|
|
|
|
// However, permanent enchantments with a random magnitude are kind of an exploit anyway,
|
|
|
|
// so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case.
|
2014-05-25 12:13:07 +00:00
|
|
|
mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()] = params;
|
2013-11-15 01:08:36 +00:00
|
|
|
}
|
2013-11-16 01:34:43 +00:00
|
|
|
else
|
2014-05-25 12:13:07 +00:00
|
|
|
params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()];
|
2013-11-15 01:08:36 +00:00
|
|
|
|
2013-11-13 17:51:28 +00:00
|
|
|
int i=0;
|
2019-03-07 08:38:55 +00:00
|
|
|
for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList)
|
2012-05-18 13:48:55 +00:00
|
|
|
{
|
2013-11-13 17:51:28 +00:00
|
|
|
const ESM::MagicEffect *magicEffect =
|
|
|
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
2019-03-07 08:38:55 +00:00
|
|
|
effect.mEffectID);
|
2012-05-18 13:48:55 +00:00
|
|
|
|
2016-11-14 15:20:17 +00:00
|
|
|
// Fully resisted or can't be applied to target?
|
2019-03-07 08:38:55 +00:00
|
|
|
if (params[i].mMultiplier == 0 || !MWMechanics::checkEffectTarget(effect.mEffectID, actor, actor, actor == MWMechanics::getPlayer()))
|
|
|
|
{
|
|
|
|
i++;
|
2013-11-15 19:29:47 +00:00
|
|
|
continue;
|
2019-03-07 08:38:55 +00:00
|
|
|
}
|
2013-11-15 19:29:47 +00:00
|
|
|
|
2019-03-07 08:38:55 +00:00
|
|
|
float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * params[i].mRandom;
|
2013-11-17 22:15:57 +00:00
|
|
|
magnitude *= params[i].mMultiplier;
|
|
|
|
|
2013-11-15 01:08:36 +00:00
|
|
|
if (!existed)
|
2012-05-18 13:48:55 +00:00
|
|
|
{
|
2013-11-14 13:41:10 +00:00
|
|
|
// 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.
|
2018-03-03 10:16:21 +00:00
|
|
|
mInventoryListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip);
|
2012-05-18 13:48:55 +00:00
|
|
|
}
|
|
|
|
|
2013-11-15 19:29:47 +00:00
|
|
|
if (magnitude)
|
2019-03-07 08:38:55 +00:00
|
|
|
mMagicEffects.add (effect, magnitude);
|
|
|
|
|
|
|
|
i++;
|
2013-11-13 17:51:28 +00:00
|
|
|
}
|
|
|
|
}
|
2012-05-18 13:48:55 +00:00
|
|
|
}
|
|
|
|
|
2013-11-13 17:51:28 +00:00
|
|
|
// Now drop expired effects
|
|
|
|
for (TEffectMagnitudes::iterator it = mPermanentMagicEffectMagnitudes.begin();
|
|
|
|
it != mPermanentMagicEffectMagnitudes.end();)
|
|
|
|
{
|
|
|
|
bool found = false;
|
|
|
|
for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
|
|
|
|
{
|
|
|
|
if (*iter == end())
|
|
|
|
continue;
|
2014-05-25 12:13:07 +00:00
|
|
|
if ((**iter).getCellRef().getRefId() == it->first)
|
2013-11-13 17:51:28 +00:00
|
|
|
{
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!found)
|
|
|
|
mPermanentMagicEffectMagnitudes.erase(it++);
|
|
|
|
else
|
|
|
|
++it;
|
|
|
|
}
|
2013-11-15 01:08:36 +00:00
|
|
|
|
2013-11-17 22:15:57 +00:00
|
|
|
// Magic effects are normally not updated when paused, but we need this to make resistances work immediately after equipping
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor);
|
|
|
|
|
2013-11-15 01:08:36 +00:00
|
|
|
mFirstAutoEquip = false;
|
2012-05-18 13:48:55 +00:00
|
|
|
}
|
|
|
|
|
2017-03-08 00:28:56 +00:00
|
|
|
bool MWWorld::InventoryStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const
|
2012-05-13 12:58:38 +00:00
|
|
|
{
|
2013-11-12 22:12:56 +00:00
|
|
|
bool canStack = MWWorld::ContainerStore::stacks(ptr1, ptr2);
|
2012-05-13 12:58:38 +00:00
|
|
|
if (!canStack)
|
|
|
|
return false;
|
|
|
|
|
2013-11-15 01:08:36 +00:00
|
|
|
// don't stack if either item is currently equipped
|
2012-05-13 12:58:38 +00:00
|
|
|
for (TSlots::const_iterator iter (mSlots.begin());
|
|
|
|
iter!=mSlots.end(); ++iter)
|
|
|
|
{
|
2013-11-12 22:12:56 +00:00
|
|
|
if (*iter != end() && (ptr1 == **iter || ptr2 == **iter))
|
2013-10-23 12:36:55 +00:00
|
|
|
{
|
2014-05-22 18:37:22 +00:00
|
|
|
bool stackWhenEquipped = (*iter)->getClass().getEquipmentSlots(**iter).second;
|
2013-10-23 12:36:55 +00:00
|
|
|
if (!stackWhenEquipped)
|
|
|
|
return false;
|
|
|
|
}
|
2012-05-13 12:58:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2012-05-29 10:35:03 +00:00
|
|
|
|
|
|
|
void MWWorld::InventoryStore::setSelectedEnchantItem(const ContainerStoreIterator& iterator)
|
|
|
|
{
|
|
|
|
mSelectedEnchantItem = iterator;
|
|
|
|
}
|
|
|
|
|
|
|
|
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSelectedEnchantItem()
|
|
|
|
{
|
|
|
|
return mSelectedEnchantItem;
|
|
|
|
}
|
2013-08-12 23:19:33 +00:00
|
|
|
|
2017-10-06 06:54:25 +00:00
|
|
|
int MWWorld::InventoryStore::remove(const std::string& itemId, int count, const Ptr& actor)
|
|
|
|
{
|
|
|
|
return remove(itemId, count, actor, false);
|
|
|
|
}
|
|
|
|
|
2017-09-11 10:49:55 +00:00
|
|
|
int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor)
|
|
|
|
{
|
|
|
|
return remove(item, count, actor, false);
|
|
|
|
}
|
|
|
|
|
2017-10-06 06:54:25 +00:00
|
|
|
int MWWorld::InventoryStore::remove(const std::string& itemId, int count, const Ptr& actor, bool equipReplacement)
|
|
|
|
{
|
|
|
|
int toRemove = count;
|
|
|
|
|
|
|
|
for (ContainerStoreIterator iter(begin()); iter != end() && toRemove > 0; ++iter)
|
|
|
|
if (Misc::StringUtils::ciEqual(iter->getCellRef().getRefId(), itemId))
|
|
|
|
toRemove -= remove(*iter, toRemove, actor, equipReplacement);
|
|
|
|
|
|
|
|
flagAsModified();
|
|
|
|
|
|
|
|
// number of removed items
|
|
|
|
return count - toRemove;
|
|
|
|
}
|
|
|
|
|
2017-04-13 13:18:03 +00:00
|
|
|
int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor, bool equipReplacement)
|
2013-08-12 23:19:33 +00:00
|
|
|
{
|
2014-02-04 03:11:46 +00:00
|
|
|
int retCount = ContainerStore::remove(item, count, actor);
|
2013-10-31 23:54:54 +00:00
|
|
|
|
2015-07-23 00:28:38 +00:00
|
|
|
bool wasEquipped = false;
|
2014-02-04 03:11:46 +00:00
|
|
|
if (!item.getRefData().getCount())
|
|
|
|
{
|
|
|
|
for (int slot=0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
2013-10-31 23:54:54 +00:00
|
|
|
{
|
2014-02-04 03:11:46 +00:00
|
|
|
if (mSlots[slot] == end())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (*mSlots[slot] == item)
|
|
|
|
{
|
|
|
|
unequipSlot(slot, actor);
|
2015-07-23 00:28:38 +00:00
|
|
|
wasEquipped = true;
|
2014-02-04 03:11:46 +00:00
|
|
|
break;
|
|
|
|
}
|
2013-10-31 23:54:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-24 00:14:05 +00:00
|
|
|
// If an armor/clothing item is removed, try to find a replacement,
|
2017-04-13 13:18:03 +00:00
|
|
|
// but not for the player nor werewolves, and not if the RemoveItem script command
|
|
|
|
// was used (equipReplacement is false)
|
|
|
|
if (equipReplacement && wasEquipped && (actor != MWMechanics::getPlayer())
|
2017-01-08 19:52:04 +00:00
|
|
|
&& actor.getClass().isNpc() && !actor.getClass().getNpcStats(actor).isWerewolf())
|
2013-10-24 00:14:05 +00:00
|
|
|
{
|
|
|
|
std::string type = item.getTypeName();
|
2015-02-03 23:21:35 +00:00
|
|
|
if (type == typeid(ESM::Armor).name() || type == typeid(ESM::Clothing).name())
|
2013-10-24 00:14:05 +00:00
|
|
|
autoEquip(actor);
|
|
|
|
}
|
|
|
|
|
2013-11-17 22:15:57 +00:00
|
|
|
if (item.getRefData().getCount() == 0 && mSelectedEnchantItem != end()
|
2014-08-28 14:31:06 +00:00
|
|
|
&& *mSelectedEnchantItem == item)
|
2013-11-17 22:15:57 +00:00
|
|
|
{
|
|
|
|
mSelectedEnchantItem = end();
|
|
|
|
}
|
|
|
|
|
2018-03-03 10:16:21 +00:00
|
|
|
if (mListener)
|
|
|
|
mListener->itemRemoved(item, retCount);
|
|
|
|
|
2013-10-24 00:14:05 +00:00
|
|
|
return retCount;
|
2013-08-12 23:19:33 +00:00
|
|
|
}
|
2013-10-31 23:54:54 +00:00
|
|
|
|
2019-12-14 17:30:46 +00:00
|
|
|
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, const MWWorld::Ptr& actor, bool fireEvent)
|
2013-10-31 23:54:54 +00:00
|
|
|
{
|
2016-01-11 13:24:52 +00:00
|
|
|
if (slot<0 || slot>=static_cast<int> (mSlots.size()))
|
|
|
|
throw std::runtime_error ("slot number out of range");
|
|
|
|
|
2014-02-04 03:11:46 +00:00
|
|
|
ContainerStoreIterator it = mSlots[slot];
|
2013-11-01 00:01:55 +00:00
|
|
|
|
2013-10-31 23:54:54 +00:00
|
|
|
if (it != end())
|
|
|
|
{
|
2013-11-01 00:01:55 +00:00
|
|
|
ContainerStoreIterator retval = it;
|
2013-11-09 01:47:11 +00:00
|
|
|
|
2013-11-14 18:54:00 +00:00
|
|
|
// empty this slot
|
|
|
|
mSlots[slot] = end();
|
|
|
|
|
2014-07-16 13:30:06 +00:00
|
|
|
if (it->getRefData().getCount())
|
2014-02-04 03:11:46 +00:00
|
|
|
{
|
2014-07-16 13:30:06 +00:00
|
|
|
retval = restack(*it);
|
2013-10-31 23:55:24 +00:00
|
|
|
|
2015-08-21 09:12:39 +00:00
|
|
|
if (actor == MWMechanics::getPlayer())
|
2013-10-31 23:55:24 +00:00
|
|
|
{
|
2014-07-16 13:30:06 +00:00
|
|
|
// Unset OnPCEquip Variable on item's script, if it has a script with that variable declared
|
|
|
|
const std::string& script = it->getClass().getScript(*it);
|
|
|
|
if (script != "")
|
|
|
|
(*it).getRefData().getLocals().setVarByInt(script, "onpcequip", 0);
|
2014-08-28 14:31:06 +00:00
|
|
|
}
|
2014-07-16 13:30:06 +00:00
|
|
|
|
2014-08-28 14:31:06 +00:00
|
|
|
if ((mSelectedEnchantItem != end()) && (mSelectedEnchantItem == it))
|
|
|
|
{
|
|
|
|
mSelectedEnchantItem = end();
|
2013-10-31 23:55:24 +00:00
|
|
|
}
|
|
|
|
}
|
2013-10-31 23:54:54 +00:00
|
|
|
|
2019-12-14 17:30:46 +00:00
|
|
|
if (fireEvent)
|
|
|
|
fireEquipmentChangedEvent(actor);
|
|
|
|
|
2013-11-15 19:29:47 +00:00
|
|
|
updateMagicEffects(actor);
|
2013-11-01 00:01:55 +00:00
|
|
|
|
|
|
|
return retval;
|
2013-10-31 23:54:54 +00:00
|
|
|
}
|
2013-11-01 00:01:55 +00:00
|
|
|
|
|
|
|
return it;
|
2013-10-31 23:54:54 +00:00
|
|
|
}
|
2013-10-31 23:21:15 +00:00
|
|
|
|
|
|
|
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWorld::Ptr& item, const MWWorld::Ptr& actor)
|
|
|
|
{
|
|
|
|
for (int slot=0; slot<MWWorld::InventoryStore::Slots; ++slot)
|
|
|
|
{
|
|
|
|
MWWorld::ContainerStoreIterator equipped = getSlot(slot);
|
|
|
|
if (equipped != end() && *equipped == item)
|
|
|
|
return unequipSlot(slot, actor);
|
|
|
|
}
|
|
|
|
|
|
|
|
throw std::runtime_error ("attempt to unequip an item that is not currently equipped");
|
|
|
|
}
|
2013-10-25 20:16:52 +00:00
|
|
|
|
2016-01-19 01:56:35 +00:00
|
|
|
MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(const Ptr& item, const Ptr& actor, 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, actor);
|
|
|
|
|
|
|
|
// 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(iter->getRefData().getCount() + count);
|
|
|
|
item.getRefData().setCount(item.getRefData().getCount() - count);
|
|
|
|
return iter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return unstack(item, actor, item.getRefData().getCount() - count);
|
|
|
|
}
|
|
|
|
|
2016-10-08 01:49:50 +00:00
|
|
|
MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener()
|
2015-06-07 14:50:34 +00:00
|
|
|
{
|
2018-03-03 10:16:21 +00:00
|
|
|
return mInventoryListener;
|
2015-06-07 14:50:34 +00:00
|
|
|
}
|
|
|
|
|
2016-10-08 01:49:50 +00:00
|
|
|
void MWWorld::InventoryStore::setInvListener(InventoryStoreListener *listener, const Ptr& actor)
|
2013-10-25 20:16:52 +00:00
|
|
|
{
|
2018-03-03 10:16:21 +00:00
|
|
|
mInventoryListener = listener;
|
2013-11-15 19:29:47 +00:00
|
|
|
updateMagicEffects(actor);
|
2013-11-15 01:08:36 +00:00
|
|
|
}
|
|
|
|
|
2015-04-06 03:13:09 +00:00
|
|
|
void MWWorld::InventoryStore::fireEquipmentChangedEvent(const Ptr& actor)
|
2013-11-15 01:08:36 +00:00
|
|
|
{
|
|
|
|
if (!mUpdatesEnabled)
|
|
|
|
return;
|
2018-03-03 10:16:21 +00:00
|
|
|
if (mInventoryListener)
|
|
|
|
mInventoryListener->equipmentChanged();
|
2015-04-06 03:13:09 +00:00
|
|
|
|
|
|
|
// if player, update inventory window
|
2015-04-19 00:39:10 +00:00
|
|
|
/*
|
2015-08-21 09:12:39 +00:00
|
|
|
if (actor == MWMechanics::getPlayer())
|
2015-04-06 03:13:09 +00:00
|
|
|
{
|
|
|
|
MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView();
|
|
|
|
}
|
2015-04-19 00:39:10 +00:00
|
|
|
*/
|
2013-10-25 20:16:52 +00:00
|
|
|
}
|
2013-11-15 19:29:47 +00:00
|
|
|
|
|
|
|
void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisitor &visitor)
|
|
|
|
{
|
|
|
|
for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
|
|
|
|
{
|
|
|
|
if (*iter==end())
|
|
|
|
continue;
|
|
|
|
|
2014-05-22 18:37:22 +00:00
|
|
|
std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter);
|
2013-11-15 19:29:47 +00:00
|
|
|
if (enchantmentId.empty())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const ESM::Enchantment& enchantment =
|
|
|
|
*MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find (enchantmentId);
|
|
|
|
|
|
|
|
if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect)
|
|
|
|
continue;
|
|
|
|
|
2014-05-25 12:13:07 +00:00
|
|
|
if (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().getRefId()) == mPermanentMagicEffectMagnitudes.end())
|
2013-11-15 19:29:47 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
int i=0;
|
2019-03-07 08:38:55 +00:00
|
|
|
for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList)
|
2013-11-15 19:29:47 +00:00
|
|
|
{
|
2019-03-07 08:38:55 +00:00
|
|
|
i++;
|
2016-11-14 15:20:17 +00:00
|
|
|
// Don't get spell icon display information for enchantments that weren't actually applied
|
2019-03-07 08:38:55 +00:00
|
|
|
if (mMagicEffects.get(MWMechanics::EffectKey(effect)).getMagnitude() == 0)
|
2016-11-14 15:20:17 +00:00
|
|
|
continue;
|
2019-03-07 08:38:55 +00:00
|
|
|
const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()][i-1];
|
|
|
|
float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * params.mRandom;
|
2013-11-16 01:34:43 +00:00
|
|
|
magnitude *= params.mMultiplier;
|
2015-01-05 17:52:37 +00:00
|
|
|
if (magnitude > 0)
|
2019-03-07 08:38:55 +00:00
|
|
|
visitor.visit(MWMechanics::EffectKey(effect), (**iter).getClass().getName(**iter), (**iter).getCellRef().getRefId(), -1, magnitude);
|
2013-11-15 19:29:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-11-19 15:42:24 +00:00
|
|
|
|
2013-12-08 22:36:37 +00:00
|
|
|
void MWWorld::InventoryStore::purgeEffect(short effectId)
|
|
|
|
{
|
2016-06-29 20:54:11 +00:00
|
|
|
for (TSlots::const_iterator it = mSlots.begin(); it != mSlots.end(); ++it)
|
|
|
|
{
|
|
|
|
if (*it != end())
|
|
|
|
purgeEffect(effectId, (*it)->getCellRef().getRefId());
|
|
|
|
}
|
2013-12-08 22:36:37 +00:00
|
|
|
}
|
2014-02-01 16:07:08 +00:00
|
|
|
|
2015-01-05 17:52:37 +00:00
|
|
|
void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId)
|
|
|
|
{
|
|
|
|
TEffectMagnitudes::iterator effectMagnitudeIt = mPermanentMagicEffectMagnitudes.find(sourceId);
|
|
|
|
if (effectMagnitudeIt == mPermanentMagicEffectMagnitudes.end())
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter)
|
|
|
|
{
|
|
|
|
if (*iter==end())
|
|
|
|
continue;
|
|
|
|
|
2015-12-18 15:58:08 +00:00
|
|
|
if ((*iter)->getCellRef().getRefId() != sourceId)
|
2015-01-05 17:52:37 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter);
|
|
|
|
|
|
|
|
if (!enchantmentId.empty())
|
|
|
|
{
|
|
|
|
const ESM::Enchantment& enchantment =
|
|
|
|
*MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find (enchantmentId);
|
|
|
|
|
|
|
|
if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
std::vector<EffectParams>& params = effectMagnitudeIt->second;
|
|
|
|
|
|
|
|
int i=0;
|
|
|
|
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (enchantment.mEffects.mList.begin());
|
|
|
|
effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i)
|
|
|
|
{
|
|
|
|
if (effectIt->mEffectID != effectId)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom;
|
|
|
|
magnitude *= params[i].mMultiplier;
|
|
|
|
|
|
|
|
if (magnitude)
|
|
|
|
mMagicEffects.add (*effectIt, -magnitude);
|
|
|
|
|
|
|
|
params[i].mMultiplier = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-01 16:07:08 +00:00
|
|
|
void MWWorld::InventoryStore::clear()
|
|
|
|
{
|
|
|
|
mSlots.clear();
|
|
|
|
initSlots (mSlots);
|
|
|
|
ContainerStore::clear();
|
|
|
|
}
|
2014-12-15 12:47:34 +00:00
|
|
|
|
2015-12-18 16:06:58 +00:00
|
|
|
bool MWWorld::InventoryStore::isEquipped(const MWWorld::ConstPtr &item)
|
2014-12-15 12:47:34 +00:00
|
|
|
{
|
|
|
|
for (int i=0; i < MWWorld::InventoryStore::Slots; ++i)
|
|
|
|
{
|
|
|
|
if (getSlot(i) != end() && *getSlot(i) == item)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2014-12-30 00:36:31 +00:00
|
|
|
|
2015-12-17 23:18:06 +00:00
|
|
|
void MWWorld::InventoryStore::writeState(ESM::InventoryState &state) const
|
2014-12-30 00:36:31 +00:00
|
|
|
{
|
|
|
|
MWWorld::ContainerStore::writeState(state);
|
|
|
|
|
|
|
|
for (TEffectMagnitudes::const_iterator it = mPermanentMagicEffectMagnitudes.begin(); it != mPermanentMagicEffectMagnitudes.end(); ++it)
|
|
|
|
{
|
|
|
|
std::vector<std::pair<float, float> > params;
|
|
|
|
for (std::vector<EffectParams>::const_iterator pIt = it->second.begin(); pIt != it->second.end(); ++pIt)
|
|
|
|
{
|
|
|
|
params.push_back(std::make_pair(pIt->mRandom, pIt->mMultiplier));
|
|
|
|
}
|
|
|
|
|
|
|
|
state.mPermanentMagicEffectMagnitudes[it->first] = params;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MWWorld::InventoryStore::readState(const ESM::InventoryState &state)
|
|
|
|
{
|
|
|
|
MWWorld::ContainerStore::readState(state);
|
|
|
|
|
|
|
|
for (ESM::InventoryState::TEffectMagnitudes::const_iterator it = state.mPermanentMagicEffectMagnitudes.begin();
|
|
|
|
it != state.mPermanentMagicEffectMagnitudes.end(); ++it)
|
|
|
|
{
|
|
|
|
std::vector<EffectParams> params;
|
|
|
|
for (std::vector<std::pair<float, float> >::const_iterator pIt = it->second.begin(); pIt != it->second.end(); ++pIt)
|
|
|
|
{
|
|
|
|
EffectParams p;
|
|
|
|
p.mRandom = pIt->first;
|
|
|
|
p.mMultiplier = pIt->second;
|
|
|
|
params.push_back(p);
|
|
|
|
}
|
|
|
|
|
|
|
|
mPermanentMagicEffectMagnitudes[it->first] = params;
|
|
|
|
}
|
|
|
|
}
|