mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-29 03:26:38 +00:00 
			
		
		
		
	Merge pull request #1329 from akortunov/priorityfix
Combat AI: make default spell priority calculation formula close to vanilla
This commit is contained in:
		
						commit
						eac2e52841
					
				
					 5 changed files with 83 additions and 39 deletions
				
			
		|  | @ -448,22 +448,11 @@ namespace MWGui | ||||||
| 
 | 
 | ||||||
|         for (std::vector<ESM::ENAMstruct>::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) |         for (std::vector<ESM::ENAMstruct>::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) | ||||||
|         { |         { | ||||||
|             float x = 0.5f * (it->mMagnMin + it->mMagnMax); |             const ESM::ENAMstruct& effect = *it; | ||||||
| 
 | 
 | ||||||
|             const ESM::MagicEffect* effect = |             y += std::max(1.f, MWMechanics::calcEffectCost(effect)); | ||||||
|                 store.get<ESM::MagicEffect>().find(it->mEffectID); |  | ||||||
| 
 | 
 | ||||||
|             x *= 0.1f * effect->mData.mBaseCost; |             if (effect.mRange == ESM::RT_Target) | ||||||
|             x *= 1 + it->mDuration; |  | ||||||
|             x += 0.05f * std::max(1, it->mArea) * effect->mData.mBaseCost; |  | ||||||
| 
 |  | ||||||
|             float fEffectCostMult = |  | ||||||
|                 store.get<ESM::GameSetting>().find("fEffectCostMult")->getFloat(); |  | ||||||
| 
 |  | ||||||
|             y += x * fEffectCostMult; |  | ||||||
|             y = std::max(1.f,y); |  | ||||||
| 
 |  | ||||||
|             if (it->mRange == ESM::RT_Target) |  | ||||||
|                 y *= 1.5; |                 y *= 1.5; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -483,8 +472,10 @@ namespace MWGui | ||||||
| 
 | 
 | ||||||
|         mPriceLabel->setCaption(MyGUI::utility::toString(int(price))); |         mPriceLabel->setCaption(MyGUI::utility::toString(int(price))); | ||||||
| 
 | 
 | ||||||
|         float chance = MWMechanics::getSpellSuccessChance(&mSpell, MWMechanics::getPlayer()); |         float chance = MWMechanics::calcSpellBaseSuccessChance(&mSpell, MWMechanics::getPlayer(), NULL); | ||||||
|         mSuccessChance->setCaption(MyGUI::utility::toString(int(chance))); | 
 | ||||||
|  |         int intChance = std::min(100, int(chance)); | ||||||
|  |         mSuccessChance->setCaption(MyGUI::utility::toString(intChance)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // ------------------------------------------------------------------------------------------------
 |     // ------------------------------------------------------------------------------------------------
 | ||||||
|  |  | ||||||
|  | @ -577,8 +577,6 @@ namespace MWMechanics | ||||||
|                 return 0.f; |                 return 0.f; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         rating *= magicEffect->mData.mBaseCost; |  | ||||||
| 
 |  | ||||||
|         if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) |         if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) | ||||||
|         { |         { | ||||||
|             rating *= -1.f; |             rating *= -1.f; | ||||||
|  | @ -613,13 +611,8 @@ namespace MWMechanics | ||||||
|                     return 0.f; |                     return 0.f; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             rating *= (effect.mMagnMin + effect.mMagnMax)/2.f; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) |         rating *= calcEffectCost(effect); | ||||||
|             rating *= effect.mDuration; |  | ||||||
| 
 | 
 | ||||||
|         // Currently treating all "on target" or "on touch" effects to target the enemy actor.
 |         // Currently treating all "on target" or "on touch" effects to target the enemy actor.
 | ||||||
|         // Combat AI is egoistic, so doesn't consider applying positive effects to friendly actors.
 |         // Combat AI is egoistic, so doesn't consider applying positive effects to friendly actors.
 | ||||||
|  | @ -636,6 +629,9 @@ namespace MWMechanics | ||||||
|         for (std::vector<ESM::ENAMstruct>::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it) |         for (std::vector<ESM::ENAMstruct>::const_iterator it = list.mList.begin(); it != list.mList.end(); ++it) | ||||||
|         { |         { | ||||||
|             rating += rateEffect(*it, actor, enemy); |             rating += rateEffect(*it, actor, enemy); | ||||||
|  | 
 | ||||||
|  |             if (it->mRange == ESM::RT_Target) | ||||||
|  |                 rating *= 1.5f; | ||||||
|         } |         } | ||||||
|         return rating; |         return rating; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| #include "autocalcspell.hpp" | #include "autocalcspell.hpp" | ||||||
|  | #include "spellcasting.hpp" | ||||||
| 
 | 
 | ||||||
| #include <climits> | #include <climits> | ||||||
| #include <limits> | #include <limits> | ||||||
|  | @ -255,27 +256,39 @@ namespace MWMechanics | ||||||
| 
 | 
 | ||||||
|     void calcWeakestSchool (const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm) |     void calcWeakestSchool (const ESM::Spell* spell, const int* actorSkills, int& effectiveSchool, float& skillTerm) | ||||||
|     { |     { | ||||||
|  |         // Morrowind for some reason uses a formula slightly different from magicka cost calculation
 | ||||||
|         float minChance = std::numeric_limits<float>::max(); |         float minChance = std::numeric_limits<float>::max(); | ||||||
| 
 | 
 | ||||||
|         const ESM::EffectList& effects = spell->mEffects; |         const ESM::EffectList& effects = spell->mEffects; | ||||||
|         for (std::vector<ESM::ENAMstruct>::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it) |         for (std::vector<ESM::ENAMstruct>::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it) | ||||||
|         { |         { | ||||||
|             const ESM::ENAMstruct& effect = *it; |             const ESM::ENAMstruct& effect = *it; | ||||||
|             float x = static_cast<float>(effect.mDuration); |  | ||||||
| 
 |  | ||||||
|             const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effect.mEffectID); |             const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effect.mEffectID); | ||||||
|             if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage)) |  | ||||||
|                 x = std::max(1.f, x); |  | ||||||
| 
 | 
 | ||||||
|             x *= 0.1f * magicEffect->mData.mBaseCost; |             int minMagn = 1; | ||||||
|             x *= 0.5f * (effect.mMagnMin + effect.mMagnMax); |             int maxMagn = 1; | ||||||
|             x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost; |             if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) | ||||||
|  |             { | ||||||
|  |                 minMagn = effect.mMagnMin; | ||||||
|  |                 maxMagn = effect.mMagnMax; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             int duration = 0; | ||||||
|  |             if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) | ||||||
|  |                 duration = effect.mDuration; | ||||||
|  | 
 | ||||||
|  |             static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore() | ||||||
|  |                 .get<ESM::GameSetting>().find("fEffectCostMult")->getFloat(); | ||||||
|  | 
 | ||||||
|  |             float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); | ||||||
|  |             x *= 0.1 * magicEffect->mData.mBaseCost; | ||||||
|  |             x *= 1 + duration; | ||||||
|  |             x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost; | ||||||
|  |             x *= fEffectCostMult; | ||||||
|  | 
 | ||||||
|             if (effect.mRange == ESM::RT_Target) |             if (effect.mRange == ESM::RT_Target) | ||||||
|                 x *= 1.5f; |                 x *= 1.5f; | ||||||
| 
 | 
 | ||||||
|             static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fEffectCostMult")->getFloat(); |  | ||||||
|             x *= fEffectCostMult; |  | ||||||
| 
 |  | ||||||
|             float s = 2.f * actorSkills[mapSchoolToSkill(magicEffect->mData.mSchool)]; |             float s = 2.f * actorSkills[mapSchoolToSkill(magicEffect->mData.mSchool)]; | ||||||
|             if (s - x < minChance) |             if (s - x < minChance) | ||||||
|             { |             { | ||||||
|  |  | ||||||
|  | @ -44,8 +44,36 @@ namespace MWMechanics | ||||||
|         return schoolSkillMap[school]; |         return schoolSkillMap[school]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap) |     float calcEffectCost(const ESM::ENAMstruct& effect) | ||||||
|     { |     { | ||||||
|  |         const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effect.mEffectID); | ||||||
|  | 
 | ||||||
|  |         int minMagn = 1; | ||||||
|  |         int maxMagn = 1; | ||||||
|  |         if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) | ||||||
|  |         { | ||||||
|  |             minMagn = effect.mMagnMin; | ||||||
|  |             maxMagn = effect.mMagnMax; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         int duration = 0; | ||||||
|  |         if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) | ||||||
|  |             duration = effect.mDuration; | ||||||
|  | 
 | ||||||
|  |         static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore() | ||||||
|  |             .get<ESM::GameSetting>().find("fEffectCostMult")->getFloat(); | ||||||
|  | 
 | ||||||
|  |         float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); | ||||||
|  |         x *= 0.1 * magicEffect->mData.mBaseCost; | ||||||
|  |         x *= 1 + duration; | ||||||
|  |         x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost; | ||||||
|  | 
 | ||||||
|  |         return x * fEffectCostMult; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool) | ||||||
|  |     { | ||||||
|  |         // Morrowind for some reason uses a formula slightly different from magicka cost calculation
 | ||||||
|         float y = std::numeric_limits<float>::max(); |         float y = std::numeric_limits<float>::max(); | ||||||
|         float lowestSkill = 0; |         float lowestSkill = 0; | ||||||
| 
 | 
 | ||||||
|  | @ -54,8 +82,10 @@ namespace MWMechanics | ||||||
|             float x = static_cast<float>(it->mDuration); |             float x = static_cast<float>(it->mDuration); | ||||||
|             const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find( |             const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find( | ||||||
|                         it->mEffectID); |                         it->mEffectID); | ||||||
|  | 
 | ||||||
|             if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage)) |             if (!(magicEffect->mData.mFlags & ESM::MagicEffect::UncappedDamage)) | ||||||
|                 x = std::max(1.f, x); |                 x = std::max(1.f, x); | ||||||
|  | 
 | ||||||
|             x *= 0.1f * magicEffect->mData.mBaseCost; |             x *= 0.1f * magicEffect->mData.mBaseCost; | ||||||
|             x *= 0.5f * (it->mMagnMin + it->mMagnMax); |             x *= 0.5f * (it->mMagnMin + it->mMagnMax); | ||||||
|             x *= it->mArea * 0.05f * magicEffect->mData.mBaseCost; |             x *= it->mArea * 0.05f * magicEffect->mData.mBaseCost; | ||||||
|  | @ -75,6 +105,18 @@ namespace MWMechanics | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         CreatureStats& stats = actor.getClass().getCreatureStats(actor); | ||||||
|  | 
 | ||||||
|  |         int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); | ||||||
|  |         int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); | ||||||
|  | 
 | ||||||
|  |         float castChance = (lowestSkill - spell->mData.mCost + 0.2f * actorWillpower + 0.1f * actorLuck); | ||||||
|  | 
 | ||||||
|  |         return castChance; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap) | ||||||
|  |     { | ||||||
|         bool godmode = actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); |         bool godmode = actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); | ||||||
| 
 | 
 | ||||||
|         CreatureStats& stats = actor.getClass().getCreatureStats(actor); |         CreatureStats& stats = actor.getClass().getCreatureStats(actor); | ||||||
|  | @ -98,10 +140,8 @@ namespace MWMechanics | ||||||
| 
 | 
 | ||||||
|         float castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude(); |         float castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude(); | ||||||
| 
 | 
 | ||||||
|         int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); |         float castChance = calcSpellBaseSuccessChance(spell, actor, effectiveSchool) + castBonus; | ||||||
|         int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); |         castChance *= stats.getFatigueTerm(); | ||||||
| 
 |  | ||||||
|         float castChance = (lowestSkill - spell->mData.mCost + castBonus + 0.2f * actorWillpower + 0.1f * actorLuck) * stats.getFatigueTerm(); |  | ||||||
| 
 | 
 | ||||||
|         if (!cap) |         if (!cap) | ||||||
|             return std::max(0.f, castChance); |             return std::max(0.f, castChance); | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| #ifndef MWMECHANICS_SPELLSUCCESS_H | #ifndef MWMECHANICS_SPELLSUCCESS_H | ||||||
| #define MWMECHANICS_SPELLSUCCESS_H | #define MWMECHANICS_SPELLSUCCESS_H | ||||||
| 
 | 
 | ||||||
|  | #include <components/esm/effectlist.hpp> | ||||||
| #include <components/esm/loadskil.hpp> | #include <components/esm/loadskil.hpp> | ||||||
| 
 | 
 | ||||||
| #include "../mwworld/ptr.hpp" | #include "../mwworld/ptr.hpp" | ||||||
|  | @ -21,6 +22,8 @@ namespace MWMechanics | ||||||
| 
 | 
 | ||||||
|     ESM::Skill::SkillEnum spellSchoolToSkill(int school); |     ESM::Skill::SkillEnum spellSchoolToSkill(int school); | ||||||
| 
 | 
 | ||||||
|  |     float calcEffectCost(const ESM::ENAMstruct& effect); | ||||||
|  | 
 | ||||||
|     bool isSummoningEffect(int effectId); |     bool isSummoningEffect(int effectId); | ||||||
| 
 | 
 | ||||||
|     /**
 |     /**
 | ||||||
|  | @ -62,6 +65,7 @@ namespace MWMechanics | ||||||
|     bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer); |     bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer); | ||||||
| 
 | 
 | ||||||
|     int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor); |     int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor); | ||||||
|  |     float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool); | ||||||
| 
 | 
 | ||||||
|     /// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed
 |     /// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed
 | ||||||
|     /// @return Was the effect a tickable effect with a magnitude?
 |     /// @return Was the effect a tickable effect with a magnitude?
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue