|
|
|
@ -6,6 +6,7 @@
|
|
|
|
|
#include <components/esm/loadench.hpp>
|
|
|
|
|
#include <components/esm/inventorystate.hpp>
|
|
|
|
|
#include <components/misc/rng.hpp>
|
|
|
|
|
#include <components/settings/settings.hpp>
|
|
|
|
|
|
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
|
|
|
#include "../mwbase/world.hpp"
|
|
|
|
@ -214,36 +215,71 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot)
|
|
|
|
|
return mSlots[slot];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MWWorld::InventoryStore::canActorAutoEquip(const MWWorld::Ptr& actor, const MWWorld::Ptr& item)
|
|
|
|
|
{
|
|
|
|
|
if (!Settings::Manager::getBool("prevent merchant equipping", "Game"))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
// Only autoEquip if we are the original owner of the item.
|
|
|
|
|
// This stops merchants from auto equipping anything you sell to them.
|
|
|
|
|
// ...unless this is a companion, he should always equip items given to him.
|
|
|
|
|
if (!Misc::StringUtils::ciEqual(item.getCellRef().getOwner(), actor.getCellRef().getRefId()) &&
|
|
|
|
|
(actor.getClass().getScript(actor).empty() ||
|
|
|
|
|
!actor.getRefData().getLocals().getIntVar(actor.getClass().getScript(actor), "companion"))
|
|
|
|
|
&& !actor.getClass().getCreatureStats(actor).isDead() // Corpses can be dressed up by the player as desired
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
|
|
|
|
|
{
|
|
|
|
|
if (!actor.getClass().isNpc())
|
|
|
|
|
// autoEquip is no-op for creatures
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
|
|
|
|
const MWWorld::Store<ESM::GameSetting> &store = world->getStore().get<ESM::GameSetting>();
|
|
|
|
|
MWMechanics::NpcStats& stats = actor.getClass().getNpcStats(actor);
|
|
|
|
|
|
|
|
|
|
static float fUnarmoredBase1 = store.find("fUnarmoredBase1")->getFloat();
|
|
|
|
|
static float fUnarmoredBase2 = store.find("fUnarmoredBase2")->getFloat();
|
|
|
|
|
int unarmoredSkill = stats.getSkill(ESM::Skill::Unarmored).getModified();
|
|
|
|
|
|
|
|
|
|
float unarmoredRating = static_cast<int>((fUnarmoredBase1 * unarmoredSkill) * (fUnarmoredBase2 * unarmoredSkill));
|
|
|
|
|
|
|
|
|
|
TSlots slots_;
|
|
|
|
|
initSlots (slots_);
|
|
|
|
|
|
|
|
|
|
// Disable model update during auto-equip
|
|
|
|
|
mUpdatesEnabled = false;
|
|
|
|
|
|
|
|
|
|
for (ContainerStoreIterator iter (begin()); iter!=end(); ++iter)
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
// Don't autoEquip lights. Handled in Actors::updateEquippedLight based on environment light.
|
|
|
|
|
if (test.getTypeName() == typeid(ESM::Light).name())
|
|
|
|
|
{
|
|
|
|
|
if (!canActorAutoEquip(actor, test))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
switch(test.getClass().canBeEquipped (test, actor).first)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
continue;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only autoEquip if we are the original owner of the item.
|
|
|
|
|
// This stops merchants from auto equipping anything you sell to them.
|
|
|
|
|
// ...unless this is a companion, he should always equip items given to him.
|
|
|
|
|
if (!Misc::StringUtils::ciEqual(test.getCellRef().getOwner(), actor.getCellRef().getRefId()) &&
|
|
|
|
|
(actor.getClass().getScript(actor).empty() ||
|
|
|
|
|
!actor.getRefData().getLocals().getIntVar(actor.getClass().getScript(actor), "companion"))
|
|
|
|
|
&& !actor.getClass().getCreatureStats(actor).isDead() // Corpses can be dressed up by the player as desired
|
|
|
|
|
)
|
|
|
|
|
if (iter.getType() == ContainerStore::Type_Armor &&
|
|
|
|
|
test.getClass().getEffectiveArmorRating(test, actor) <= std::max(unarmoredRating, 0.f))
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
int testSkill = test.getClass().getEquipmentSkill (test);
|
|
|
|
|
|
|
|
|
|
std::pair<std::vector<int>, bool> itemsSlots =
|
|
|
|
|
iter->getClass().getEquipmentSlots (*iter);
|
|
|
|
@ -251,51 +287,35 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
|
|
|
|
|
for (std::vector<int>::const_iterator iter2 (itemsSlots.first.begin());
|
|
|
|
|
iter2!=itemsSlots.first.end(); ++iter2)
|
|
|
|
|
{
|
|
|
|
|
if (*iter2 == Slot_CarriedRight) // Items in right hand are situational use, so don't equip them.
|
|
|
|
|
// Equipping weapons is handled by AiCombat. Anything else (lockpicks, probes) can't be used by NPCs anyway (yet)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (iter.getType() == MWWorld::ContainerStore::Type_Weapon)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (slots_.at (*iter2)!=end())
|
|
|
|
|
{
|
|
|
|
|
Ptr old = *slots_.at (*iter2);
|
|
|
|
|
|
|
|
|
|
// check skill
|
|
|
|
|
int oldSkill = old.getClass().getEquipmentSkill (old);
|
|
|
|
|
|
|
|
|
|
bool use = false;
|
|
|
|
|
if (testSkill!=-1 && oldSkill==-1)
|
|
|
|
|
use = true;
|
|
|
|
|
else if (testSkill!=-1 && oldSkill!=-1 && testSkill!=oldSkill)
|
|
|
|
|
if (iter.getType() == ContainerStore::Type_Armor)
|
|
|
|
|
{
|
|
|
|
|
if (actor.getClass().getSkill(actor, oldSkill) > actor.getClass().getSkill (actor, testSkill))
|
|
|
|
|
continue; // rejected, because old item better matched the NPC's skills.
|
|
|
|
|
|
|
|
|
|
if (actor.getClass().getSkill(actor, oldSkill) < actor.getClass().getSkill (actor, testSkill))
|
|
|
|
|
use = true;
|
|
|
|
|
if (old.getTypeName() == typeid(ESM::Armor).name())
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!use)
|
|
|
|
|
else if (iter.getType() == ContainerStore::Type_Clothing)
|
|
|
|
|
{
|
|
|
|
|
// check value
|
|
|
|
|
if (old.getClass().getValue (old)>=
|
|
|
|
|
test.getClass().getValue (test))
|
|
|
|
|
if (old.getTypeName() == typeid(ESM::Clothing).name())
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch(test.getClass().canBeEquipped (test, actor).first)
|
|
|
|
|
{
|
|
|
|
|
case 0:
|
|
|
|
|
continue;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!itemsSlots.second) // if itemsSlots.second is true, item can stay stacked when equipped
|
|
|
|
|
{
|
|
|
|
|
// unstack item pointed to by iterator if required
|
|
|
|
@ -310,6 +330,99 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 };
|
|
|
|
|
|
|
|
|
|
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 = stats.getSkill(static_cast<int>(weaponSkills[j])).getModified();
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (weapon != end() && weapon->getClass().canBeEquipped(*weapon, actor).first)
|
|
|
|
|
{
|
|
|
|
|
std::pair<std::vector<int>, bool> itemsSlots =
|
|
|
|
|
weapon->getClass().getEquipmentSlots (*weapon);
|
|
|
|
|
|
|
|
|
|
for (std::vector<int>::const_iterator slot (itemsSlots.first.begin());
|
|
|
|
|
slot!=itemsSlots.first.end(); ++slot)
|
|
|
|
|
{
|
|
|
|
|
if (!itemsSlots.second)
|
|
|
|
|
{
|
|
|
|
|
if (weapon->getRefData().getCount() > 1)
|
|
|
|
|
{
|
|
|
|
|
unstack(*weapon, actor);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
slots_[*slot] = weapon;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
weaponSkillVisited[maxWeaponSkill] = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool changed = false;
|
|
|
|
|
|
|
|
|
|
for (std::size_t i=0; i<slots_.size(); ++i)
|
|
|
|
|