diff --git a/apps/openmw/mwmechanics/spellsuccess.hpp b/apps/openmw/mwmechanics/spellsuccess.hpp index 2cb12ffca..68b89752f 100644 --- a/apps/openmw/mwmechanics/spellsuccess.hpp +++ b/apps/openmw/mwmechanics/spellsuccess.hpp @@ -1,6 +1,8 @@ #ifndef MWMECHANICS_SPELLSUCCESS_H #define MWMECHANICS_SPELLSUCCESS_H +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -27,92 +29,91 @@ namespace MWMechanics return schoolSkillMap[school]; } - inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor) - { - NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor); - - // determine the spell's school - // this is always the school where the player's respective skill is the least advanced - // out of all the magic effects' schools - const std::vector& effects = spell->mEffects.mList; - int school = -1; - int skillLevel = -1; - for (std::vector::const_iterator it = effects.begin(); - it != effects.end(); ++it) - { - const ESM::MagicEffect* effect = - MWBase::Environment::get().getWorld()->getStore().get().find(it->mEffectID); - int _school = effect->mData.mSchool; - int _skillLevel = stats.getSkill (spellSchoolToSkill(_school)).getModified(); - - if (school == -1) - { - school = _school; - skillLevel = _skillLevel; - } - else if (_skillLevel < skillLevel) - { - school = _school; - skillLevel = _skillLevel; - } - } - - return school; - } - - inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) - { - const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - return getSpellSchool(spell, actor); - } - - - // UESP wiki / Morrowind/Spells: - // Chance of success is (Spell's skill * 2 + Willpower / 5 + Luck / 10 - Spell cost - Sound magnitude) * (Current fatigue + Maximum Fatigue * 1.5) / Maximum fatigue * 2 /** * @param spell spell to cast * @param actor calculate spell success chance for this actor (depends on actor's skills) + * @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here * @attention actor has to be an NPC and not a creature! * @return success chance from 0 to 100 (in percent) */ - inline float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor) + inline float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = NULL) { - if (spell->mData.mFlags & ESM::Spell::F_Always // spells with this flag always succeed (usually birthsign spells) - || spell->mData.mType == ESM::Spell::ST_Power) // powers always succeed, but can be cast only once per day - return 100.0; - - if (spell->mEffects.mList.size() == 0) - return 0.0; - NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor); CreatureStats& creatureStats = MWWorld::Class::get(actor).getCreatureStats(actor); - int skillLevel = stats.getSkill (spellSchoolToSkill(getSpellSchool(spell, actor))).getModified(); + if (creatureStats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude) + return 0; - // Sound magic effect (reduces spell casting chance) - int soundMagnitude = creatureStats.getMagicEffects().get (MWMechanics::EffectKey (48)).mMagnitude; + if (spell->mData.mType != ESM::Spell::ST_Spell) + return 100; - int willpower = creatureStats.getAttribute(ESM::Attribute::Willpower).getModified(); - int luck = creatureStats.getAttribute(ESM::Attribute::Luck).getModified(); - int currentFatigue = creatureStats.getFatigue().getCurrent(); - int maxFatigue = creatureStats.getFatigue().getModified(); - int spellCost = spell->mData.mCost; + if (spell->mData.mFlags & ESM::Spell::F_Always) + return 100; - // There we go, all needed variables are there, lets go - float chance = (float(skillLevel * 2) + float(willpower)/5.0 + float(luck)/ 10.0 - spellCost - soundMagnitude) * (float(currentFatigue + maxFatigue * 1.5)) / float(maxFatigue * 2.0); + float y = FLT_MAX; + float lowestSkill = 0; - chance = std::max(0.0f, std::min(100.0f, chance)); // clamp to 0 .. 100 + for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) + { + float x = it->mDuration; + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find( + it->mEffectID); + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage)) + x = std::max(1.f, x); + x *= 0.1 * magicEffect->mData.mBaseCost; + x *= 0.5 * (it->mMagnMin + it->mMagnMax); + x *= it->mArea * 0.05 * magicEffect->mData.mBaseCost; + if (it->mRange == ESM::RT_Target) + x *= 1.5; + static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find( + "fEffectCostMult")->getFloat(); + x *= fEffectCostMult; - return chance; + float s = 2 * stats.getSkill(spellSchoolToSkill(magicEffect->mData.mSchool)).getModified(); + if (s - x < y) + { + y = s - x; + if (effectiveSchool) + *effectiveSchool = magicEffect->mData.mSchool; + lowestSkill = s; + } + } + + + int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude; + int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); + int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); + + float castChance = (lowestSkill - spell->mData.mCost + castBonus + 0.2 * actorWillpower + 0.1 * actorLuck) * stats.getFatigueTerm(); + if (MWBase::Environment::get().getWorld()->getGodModeState() && actor.getRefData().getHandle() == "player") + castChance = 100; + + return std::max(0.f, std::min(100.f, castChance)); } - inline float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor) + inline float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = NULL) { const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - return getSpellSuccessChance(spell, actor); + return getSpellSuccessChance(spell, actor, effectiveSchool); } + + /// @note this only works for ST_Spell + inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) + { + int school = 0; + getSpellSuccessChance(spellId, actor, &school); + return school; + } + + /// @note this only works for ST_Spell + inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor) + { + int school = 0; + getSpellSuccessChance(spell, actor, &school); + return school; + } + } #endif diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e98c965f9..13b8bd79b 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2105,8 +2105,7 @@ namespace MWWorld return; } - // Note: F_Always spells don't contribute to skill progress - if (actor == getPlayer().getPlayer() && !(spell->mData.mFlags & ESM::Spell::F_Always)) + if (actor == getPlayer().getPlayer() && spell->mData.mType == ESM::Spell::ST_Spell) actor.getClass().skillUsageSucceeded(actor, MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(selectedSpell, actor)), 0);