mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-26 18:35:20 +00:00
5c4e1252e9
Fixes #5483 This only applies to "base game" spells. When adding an AutoCalc spell with TES:CS its cost is computed and stored inside game files. This stored cost was being used by OpenMW and the actual cost was never recomputed at runtime whereas Morrowind.exe discards the stored cost. While this worked fine in vanilla, mods can update AutoCalc spell effects without ever updating the stored cost. The formula was mostly there already but there was a few differences, namely a 1 second offset in duration.
212 lines
7.3 KiB
C++
212 lines
7.3 KiB
C++
#include "spellmodel.hpp"
|
|
|
|
#include <components/debug/debuglog.hpp>
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
#include "../mwbase/windowmanager.hpp"
|
|
|
|
#include "../mwmechanics/creaturestats.hpp"
|
|
#include "../mwmechanics/spellutil.hpp"
|
|
|
|
#include "../mwworld/esmstore.hpp"
|
|
#include "../mwworld/inventorystore.hpp"
|
|
#include "../mwworld/class.hpp"
|
|
|
|
namespace
|
|
{
|
|
|
|
bool sortSpells(const MWGui::Spell& left, const MWGui::Spell& right)
|
|
{
|
|
if (left.mType != right.mType)
|
|
return left.mType < right.mType;
|
|
|
|
std::string leftName = Misc::StringUtils::lowerCase(left.mName);
|
|
std::string rightName = Misc::StringUtils::lowerCase(right.mName);
|
|
|
|
return leftName.compare(rightName) < 0;
|
|
}
|
|
|
|
}
|
|
|
|
namespace MWGui
|
|
{
|
|
|
|
SpellModel::SpellModel(const MWWorld::Ptr &actor, const std::string& filter)
|
|
: mActor(actor), mFilter(filter)
|
|
{
|
|
}
|
|
|
|
SpellModel::SpellModel(const MWWorld::Ptr &actor)
|
|
: mActor(actor)
|
|
{
|
|
}
|
|
|
|
bool SpellModel::matchingEffectExists(std::string filter, const ESM::EffectList &effects)
|
|
{
|
|
auto wm = MWBase::Environment::get().getWindowManager();
|
|
const MWWorld::ESMStore &store =
|
|
MWBase::Environment::get().getWorld()->getStore();
|
|
|
|
for (const auto& effect : effects.mList)
|
|
{
|
|
short effectId = effect.mEffectID;
|
|
|
|
if (effectId != -1)
|
|
{
|
|
const ESM::MagicEffect *magicEffect =
|
|
store.get<ESM::MagicEffect>().search(effectId);
|
|
std::string effectIDStr = ESM::MagicEffect::effectIdToString(effectId);
|
|
std::string fullEffectName = wm->getGameSettingString(effectIDStr, "");
|
|
|
|
if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill && effect.mSkill != -1)
|
|
{
|
|
fullEffectName += " " + wm->getGameSettingString(ESM::Skill::sSkillNameIds[effect.mSkill], "");
|
|
}
|
|
|
|
if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute && effect.mAttribute != -1)
|
|
{
|
|
fullEffectName += " " + wm->getGameSettingString(ESM::Attribute::sGmstAttributeIds[effect.mAttribute], "");
|
|
}
|
|
|
|
std::string convert = Misc::StringUtils::lowerCaseUtf8(fullEffectName);
|
|
if (convert.find(filter) != std::string::npos)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SpellModel::update()
|
|
{
|
|
mSpells.clear();
|
|
|
|
MWMechanics::CreatureStats& stats = mActor.getClass().getCreatureStats(mActor);
|
|
const MWMechanics::Spells& spells = stats.getSpells();
|
|
|
|
const MWWorld::ESMStore &esmStore =
|
|
MWBase::Environment::get().getWorld()->getStore();
|
|
|
|
std::string filter = Misc::StringUtils::lowerCaseUtf8(mFilter);
|
|
|
|
for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it)
|
|
{
|
|
const ESM::Spell* spell = it->first;
|
|
if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell)
|
|
continue;
|
|
|
|
std::string name = Misc::StringUtils::lowerCaseUtf8(spell->mName);
|
|
|
|
if (name.find(filter) == std::string::npos
|
|
&& !matchingEffectExists(filter, spell->mEffects))
|
|
continue;
|
|
|
|
Spell newSpell;
|
|
newSpell.mName = spell->mName;
|
|
if (spell->mData.mType == ESM::Spell::ST_Spell)
|
|
{
|
|
newSpell.mType = Spell::Type_Spell;
|
|
std::string cost = std::to_string(MWMechanics::calcSpellCost(*spell));
|
|
std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor)));
|
|
newSpell.mCostColumn = cost + "/" + chance;
|
|
}
|
|
else
|
|
newSpell.mType = Spell::Type_Power;
|
|
newSpell.mId = spell->mId;
|
|
|
|
newSpell.mSelected = (MWBase::Environment::get().getWindowManager()->getSelectedSpell() == spell->mId);
|
|
newSpell.mActive = true;
|
|
newSpell.mCount = 1;
|
|
mSpells.push_back(newSpell);
|
|
}
|
|
|
|
MWWorld::InventoryStore& invStore = mActor.getClass().getInventoryStore(mActor);
|
|
for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it)
|
|
{
|
|
MWWorld::Ptr item = *it;
|
|
const std::string enchantId = item.getClass().getEnchantment(item);
|
|
if (enchantId.empty())
|
|
continue;
|
|
const ESM::Enchantment* enchant = esmStore.get<ESM::Enchantment>().search(enchantId);
|
|
if (!enchant)
|
|
{
|
|
Log(Debug::Warning) << "Warning: Can't find enchantment '" << enchantId << "' on item " << item.getCellRef().getRefId();
|
|
continue;
|
|
}
|
|
|
|
if (enchant->mData.mType != ESM::Enchantment::WhenUsed && enchant->mData.mType != ESM::Enchantment::CastOnce)
|
|
continue;
|
|
|
|
std::string name = Misc::StringUtils::lowerCaseUtf8(item.getClass().getName(item));
|
|
|
|
if (name.find(filter) == std::string::npos
|
|
&& !matchingEffectExists(filter, enchant->mEffects))
|
|
continue;
|
|
|
|
Spell newSpell;
|
|
newSpell.mItem = item;
|
|
newSpell.mId = item.getCellRef().getRefId();
|
|
newSpell.mName = item.getClass().getName(item);
|
|
newSpell.mCount = item.getRefData().getCount();
|
|
newSpell.mType = Spell::Type_EnchantedItem;
|
|
newSpell.mSelected = invStore.getSelectedEnchantItem() == it;
|
|
|
|
// FIXME: move to mwmechanics
|
|
if (enchant->mData.mType == ESM::Enchantment::CastOnce)
|
|
{
|
|
newSpell.mCostColumn = "100/100";
|
|
newSpell.mActive = false;
|
|
}
|
|
else
|
|
{
|
|
if (!item.getClass().getEquipmentSlots(item).first.empty()
|
|
&& item.getClass().canBeEquipped(item, mActor).first == 0)
|
|
continue;
|
|
|
|
int castCost = MWMechanics::getEffectiveEnchantmentCastCost(static_cast<float>(enchant->mData.mCost), mActor);
|
|
|
|
std::string cost = std::to_string(castCost);
|
|
int currentCharge = int(item.getCellRef().getEnchantmentCharge());
|
|
if (currentCharge == -1)
|
|
currentCharge = enchant->mData.mCharge;
|
|
std::string charge = std::to_string(currentCharge);
|
|
newSpell.mCostColumn = cost + "/" + charge;
|
|
|
|
newSpell.mActive = invStore.isEquipped(item);
|
|
}
|
|
mSpells.push_back(newSpell);
|
|
}
|
|
|
|
std::stable_sort(mSpells.begin(), mSpells.end(), sortSpells);
|
|
}
|
|
|
|
size_t SpellModel::getItemCount() const
|
|
{
|
|
return mSpells.size();
|
|
}
|
|
|
|
SpellModel::ModelIndex SpellModel::getSelectedIndex() const
|
|
{
|
|
ModelIndex selected = -1;
|
|
for (SpellModel::ModelIndex i = 0; i<int(getItemCount()); ++i)
|
|
{
|
|
if (getItem(i).mSelected) {
|
|
selected = i;
|
|
break;
|
|
}
|
|
}
|
|
return selected;
|
|
}
|
|
|
|
Spell SpellModel::getItem(ModelIndex index) const
|
|
{
|
|
if (index < 0 || index >= int(mSpells.size()))
|
|
throw std::runtime_error("invalid spell index supplied");
|
|
return mSpells[index];
|
|
}
|
|
|
|
}
|