2017-08-01 09:05:35 +04:00
|
|
|
#include "spellpriority.hpp"
|
2018-01-11 21:08:30 +04:00
|
|
|
#include "weaponpriority.hpp"
|
2017-08-01 09:05:35 +04:00
|
|
|
|
2022-01-22 15:58:41 +01:00
|
|
|
#include <components/esm3/loadench.hpp>
|
|
|
|
#include <components/esm3/loadmgef.hpp>
|
2022-09-05 19:35:15 +02:00
|
|
|
#include <components/esm3/loadrace.hpp>
|
2022-01-22 15:58:41 +01:00
|
|
|
#include <components/esm3/loadspel.hpp>
|
2017-08-01 09:05:35 +04:00
|
|
|
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
|
|
#include "../mwbase/world.hpp"
|
|
|
|
|
|
|
|
#include "../mwworld/class.hpp"
|
|
|
|
#include "../mwworld/esmstore.hpp"
|
|
|
|
#include "../mwworld/inventorystore.hpp"
|
|
|
|
|
2018-10-08 17:06:30 +03:00
|
|
|
#include "creaturestats.hpp"
|
2020-04-04 18:28:53 +03:00
|
|
|
#include "spellresistance.hpp"
|
2020-04-26 20:46:51 +03:00
|
|
|
#include "spellutil.hpp"
|
2020-04-04 17:45:03 +03:00
|
|
|
#include "summoning.hpp"
|
2018-12-26 13:45:28 +04:00
|
|
|
#include "weapontype.hpp"
|
2017-08-01 09:05:35 +04:00
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
int numEffectsToDispel(const MWWorld::Ptr& actor, int effectFilter = -1, bool negative = true)
|
|
|
|
{
|
|
|
|
int toCure = 0;
|
|
|
|
const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells();
|
|
|
|
for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it)
|
|
|
|
{
|
2017-08-14 20:39:04 +04:00
|
|
|
// if the effect filter is not specified, take in account only spells effects. Leave potions, enchanted
|
|
|
|
// items etc.
|
|
|
|
if (effectFilter == -1)
|
|
|
|
{
|
2021-08-27 20:07:50 +02:00
|
|
|
const ESM::Spell* spell
|
2023-04-20 21:07:53 +02:00
|
|
|
= MWBase::Environment::get().getESMStore()->get<ESM::Spell>().search(it->getId());
|
2017-08-14 20:39:04 +04:00
|
|
|
if (!spell || spell->mData.mType != ESM::Spell::ST_Spell)
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-08-27 20:07:50 +02:00
|
|
|
const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it;
|
|
|
|
for (const auto& effect : params.getEffects())
|
2017-08-01 09:05:35 +04:00
|
|
|
{
|
2021-08-27 20:07:50 +02:00
|
|
|
int effectId = effect.mEffectId;
|
2017-08-01 09:05:35 +04:00
|
|
|
if (effectFilter != -1 && effectId != effectFilter)
|
|
|
|
continue;
|
|
|
|
const ESM::MagicEffect* magicEffect
|
2023-04-20 21:07:53 +02:00
|
|
|
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(effectId);
|
2017-08-01 09:05:35 +04:00
|
|
|
|
2021-08-27 20:07:50 +02:00
|
|
|
if (effect.mDuration <= 3) // Don't attempt to dispel if effect runs out shortly anyway
|
2017-08-01 09:05:35 +04:00
|
|
|
continue;
|
|
|
|
|
|
|
|
if (negative && magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
|
|
|
|
++toCure;
|
|
|
|
|
|
|
|
if (!negative && !(magicEffect->mData.mFlags & ESM::MagicEffect::Harmful))
|
|
|
|
++toCure;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return toCure;
|
|
|
|
}
|
2017-08-01 21:42:49 +04:00
|
|
|
|
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type
The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID
Slowly going through all the changes to make, still hundreds of errors
a lot of functions/structures use std::string or stringview to designate an ID. So it takes time
Continues slowly replacing ids. There are technically more and more compilation errors
I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type
Continue moving forward, changes to the stores
slowly moving along
Starting to see the fruit of those changes.
still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type.
More replacements. Things are starting to get easier
I can see more and more often the issue is that the function is awaiting a RefId, but is given a string
there is less need to go down functions and to fix a long list of them.
Still moving forward, and for the first time error count is going down!
Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably
Cells are back to using string for the name, haven't fixed everything yet. Many other changes
Under the bar of 400 compilation errors.
more good progress <100 compile errors!
More progress
Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string
some more progress on other fronts
Mostly game settings clean
one error opened a lot of other errors. Down to 18, but more will prbably appear
only link errors left??
Fixed link errors
OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
|
|
|
float getSpellDuration(const MWWorld::Ptr& actor, const ESM::RefId& spellId)
|
2017-08-01 21:42:49 +04:00
|
|
|
{
|
|
|
|
float duration = 0;
|
|
|
|
const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells();
|
|
|
|
for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it)
|
|
|
|
{
|
2021-08-27 20:07:50 +02:00
|
|
|
if (it->getId() != spellId)
|
2017-08-01 21:42:49 +04:00
|
|
|
continue;
|
|
|
|
|
2021-08-27 20:07:50 +02:00
|
|
|
const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it;
|
|
|
|
for (const auto& effect : params.getEffects())
|
2017-08-01 21:42:49 +04:00
|
|
|
{
|
2021-08-27 20:07:50 +02:00
|
|
|
if (effect.mDuration > duration)
|
|
|
|
duration = effect.mDuration;
|
2017-08-01 21:42:49 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return duration;
|
|
|
|
}
|
2022-01-03 21:05:14 +01:00
|
|
|
|
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type
The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID
Slowly going through all the changes to make, still hundreds of errors
a lot of functions/structures use std::string or stringview to designate an ID. So it takes time
Continues slowly replacing ids. There are technically more and more compilation errors
I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type
Continue moving forward, changes to the stores
slowly moving along
Starting to see the fruit of those changes.
still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type.
More replacements. Things are starting to get easier
I can see more and more often the issue is that the function is awaiting a RefId, but is given a string
there is less need to go down functions and to fix a long list of them.
Still moving forward, and for the first time error count is going down!
Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably
Cells are back to using string for the name, haven't fixed everything yet. Many other changes
Under the bar of 400 compilation errors.
more good progress <100 compile errors!
More progress
Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string
some more progress on other fronts
Mostly game settings clean
one error opened a lot of other errors. Down to 18, but more will prbably appear
only link errors left??
Fixed link errors
OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
|
|
|
bool isSpellActive(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const ESM::RefId& id)
|
2022-01-03 21:05:14 +01:00
|
|
|
{
|
|
|
|
int actorId = caster.getClass().getCreatureStats(caster).getActorId();
|
|
|
|
const auto& active = target.getClass().getCreatureStats(target).getActiveSpells();
|
|
|
|
return std::find_if(active.begin(), active.end(), [&](const auto& spell) {
|
2022-10-18 09:26:55 +02:00
|
|
|
return spell.getCasterActorId() == actorId && spell.getId() == id;
|
2022-01-03 21:05:14 +01:00
|
|
|
}) != active.end();
|
|
|
|
}
|
2017-08-01 09:05:35 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace MWMechanics
|
|
|
|
{
|
|
|
|
int getRangeTypes(const ESM::EffectList& effects)
|
|
|
|
{
|
|
|
|
int types = 0;
|
|
|
|
for (std::vector<ESM::ENAMstruct>::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it)
|
|
|
|
{
|
|
|
|
if (it->mRange == ESM::RT_Self)
|
|
|
|
types |= RangeTypes::Self;
|
|
|
|
else if (it->mRange == ESM::RT_Touch)
|
|
|
|
types |= RangeTypes::Touch;
|
|
|
|
else if (it->mRange == ESM::RT_Target)
|
|
|
|
types |= RangeTypes::Target;
|
|
|
|
}
|
|
|
|
return types;
|
|
|
|
}
|
|
|
|
|
|
|
|
float ratePotion(const MWWorld::Ptr& item, const MWWorld::Ptr& actor)
|
|
|
|
{
|
2021-10-11 11:46:21 +00:00
|
|
|
if (item.getType() != ESM::Potion::sRecordId)
|
2017-08-01 09:05:35 +04:00
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
const ESM::Potion* potion = item.get<ESM::Potion>()->mBase;
|
|
|
|
return rateEffects(potion->mEffects, actor, MWWorld::Ptr());
|
|
|
|
}
|
|
|
|
|
|
|
|
float rateSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy)
|
|
|
|
{
|
|
|
|
float successChance = MWMechanics::getSpellSuccessChance(spell, actor);
|
|
|
|
if (successChance == 0.f)
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
// Don't make use of racial bonus spells, like MW. Can be made optional later
|
|
|
|
if (actor.getClass().isNpc())
|
|
|
|
{
|
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type
The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID
Slowly going through all the changes to make, still hundreds of errors
a lot of functions/structures use std::string or stringview to designate an ID. So it takes time
Continues slowly replacing ids. There are technically more and more compilation errors
I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type
Continue moving forward, changes to the stores
slowly moving along
Starting to see the fruit of those changes.
still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type.
More replacements. Things are starting to get easier
I can see more and more often the issue is that the function is awaiting a RefId, but is given a string
there is less need to go down functions and to fix a long list of them.
Still moving forward, and for the first time error count is going down!
Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably
Cells are back to using string for the name, haven't fixed everything yet. Many other changes
Under the bar of 400 compilation errors.
more good progress <100 compile errors!
More progress
Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string
some more progress on other fronts
Mostly game settings clean
one error opened a lot of other errors. Down to 18, but more will prbably appear
only link errors left??
Fixed link errors
OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
|
|
|
const ESM::RefId& raceid = actor.get<ESM::NPC>()->mBase->mRace;
|
2023-04-20 21:07:53 +02:00
|
|
|
const ESM::Race* race = MWBase::Environment::get().getESMStore()->get<ESM::Race>().find(raceid);
|
2017-08-01 09:05:35 +04:00
|
|
|
if (race->mPowers.exists(spell->mId))
|
|
|
|
return 0.f;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Spells don't stack, so early out if the spell is still active on the target
|
|
|
|
int types = getRangeTypes(spell->mEffects);
|
2022-01-03 21:05:14 +01:00
|
|
|
if ((types & Self) && isSpellActive(actor, actor, spell->mId))
|
2017-08-01 09:05:35 +04:00
|
|
|
return 0.f;
|
2022-01-03 21:05:14 +01:00
|
|
|
if (((types & Touch) || (types & Target)) && isSpellActive(actor, enemy, spell->mId))
|
2017-08-01 09:05:35 +04:00
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
return rateEffects(spell->mEffects, actor, enemy) * (successChance / 100.f);
|
|
|
|
}
|
|
|
|
|
|
|
|
float rateMagicItem(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy)
|
|
|
|
{
|
|
|
|
if (ptr.getClass().getEnchantment(ptr).empty())
|
|
|
|
return 0.f;
|
|
|
|
|
2023-04-20 21:07:53 +02:00
|
|
|
const ESM::Enchantment* enchantment = MWBase::Environment::get().getESMStore()->get<ESM::Enchantment>().find(
|
|
|
|
ptr.getClass().getEnchantment(ptr));
|
2017-08-01 09:05:35 +04:00
|
|
|
|
2017-08-01 21:42:49 +04:00
|
|
|
// Spells don't stack, so early out if the spell is still active on the target
|
|
|
|
int types = getRangeTypes(enchantment->mEffects);
|
|
|
|
if ((types & Self)
|
|
|
|
&& actor.getClass().getCreatureStats(actor).getActiveSpells().isSpellActive(ptr.getCellRef().getRefId()))
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
if (types & (Touch | Target) && getSpellDuration(enemy, ptr.getCellRef().getRefId()) > 3)
|
|
|
|
return 0.f;
|
|
|
|
|
2017-08-01 09:05:35 +04:00
|
|
|
if (enchantment->mData.mType == ESM::Enchantment::CastOnce)
|
|
|
|
{
|
|
|
|
return rateEffects(enchantment->mEffects, actor, enemy);
|
|
|
|
}
|
2017-08-01 21:42:49 +04:00
|
|
|
else if (enchantment->mData.mType == ESM::Enchantment::WhenUsed)
|
2017-08-01 09:05:35 +04:00
|
|
|
{
|
2017-08-01 21:42:49 +04:00
|
|
|
MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
|
|
|
|
|
|
|
|
// Creatures can not wear armor/clothing, so allow creatures to use non-equipped items,
|
|
|
|
if (actor.getClass().isNpc() && !store.isEquipped(ptr))
|
|
|
|
return 0.f;
|
|
|
|
|
2023-06-17 22:25:47 +02:00
|
|
|
int castCost = getEffectiveEnchantmentCastCost(*enchantment, actor);
|
2017-08-01 21:42:49 +04:00
|
|
|
|
|
|
|
if (ptr.getCellRef().getEnchantmentCharge() != -1 && ptr.getCellRef().getEnchantmentCharge() < castCost)
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
float rating = rateEffects(enchantment->mEffects, actor, enemy);
|
|
|
|
|
2023-05-09 20:07:08 -04:00
|
|
|
rating *= 1.25f; // prefer rechargeable magic items over spells
|
2017-08-01 21:42:49 +04:00
|
|
|
return rating;
|
2017-08-01 09:05:35 +04:00
|
|
|
}
|
2017-08-01 21:42:49 +04:00
|
|
|
|
|
|
|
return 0.f;
|
2017-08-01 09:05:35 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
float rateEffect(const ESM::ENAMstruct& effect, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy)
|
|
|
|
{
|
|
|
|
// NOTE: enemy may be empty
|
|
|
|
|
|
|
|
float rating = 1;
|
|
|
|
switch (effect.mEffectID)
|
|
|
|
{
|
|
|
|
case ESM::MagicEffect::Soultrap:
|
|
|
|
case ESM::MagicEffect::AlmsiviIntervention:
|
|
|
|
case ESM::MagicEffect::DivineIntervention:
|
|
|
|
case ESM::MagicEffect::CalmHumanoid:
|
|
|
|
case ESM::MagicEffect::CalmCreature:
|
|
|
|
case ESM::MagicEffect::FrenzyHumanoid:
|
|
|
|
case ESM::MagicEffect::FrenzyCreature:
|
|
|
|
case ESM::MagicEffect::DemoralizeHumanoid:
|
|
|
|
case ESM::MagicEffect::DemoralizeCreature:
|
|
|
|
case ESM::MagicEffect::RallyHumanoid:
|
|
|
|
case ESM::MagicEffect::RallyCreature:
|
|
|
|
case ESM::MagicEffect::Charm:
|
|
|
|
case ESM::MagicEffect::DetectAnimal:
|
|
|
|
case ESM::MagicEffect::DetectEnchantment:
|
|
|
|
case ESM::MagicEffect::DetectKey:
|
|
|
|
case ESM::MagicEffect::Telekinesis:
|
|
|
|
case ESM::MagicEffect::Mark:
|
|
|
|
case ESM::MagicEffect::Recall:
|
|
|
|
case ESM::MagicEffect::Jump:
|
|
|
|
case ESM::MagicEffect::WaterBreathing:
|
|
|
|
case ESM::MagicEffect::SwiftSwim:
|
|
|
|
case ESM::MagicEffect::WaterWalking:
|
|
|
|
case ESM::MagicEffect::SlowFall:
|
|
|
|
case ESM::MagicEffect::Light:
|
|
|
|
case ESM::MagicEffect::Lock:
|
|
|
|
case ESM::MagicEffect::Open:
|
|
|
|
case ESM::MagicEffect::TurnUndead:
|
|
|
|
case ESM::MagicEffect::WeaknessToCommonDisease:
|
|
|
|
case ESM::MagicEffect::WeaknessToBlightDisease:
|
|
|
|
case ESM::MagicEffect::WeaknessToCorprusDisease:
|
|
|
|
case ESM::MagicEffect::CureCommonDisease:
|
|
|
|
case ESM::MagicEffect::CureBlightDisease:
|
|
|
|
case ESM::MagicEffect::CureCorprusDisease:
|
|
|
|
case ESM::MagicEffect::ResistBlightDisease:
|
|
|
|
case ESM::MagicEffect::ResistCommonDisease:
|
|
|
|
case ESM::MagicEffect::ResistCorprusDisease:
|
|
|
|
case ESM::MagicEffect::Invisibility:
|
|
|
|
case ESM::MagicEffect::Chameleon:
|
|
|
|
case ESM::MagicEffect::NightEye:
|
|
|
|
case ESM::MagicEffect::Vampirism:
|
|
|
|
case ESM::MagicEffect::StuntedMagicka:
|
|
|
|
case ESM::MagicEffect::ExtraSpell:
|
|
|
|
case ESM::MagicEffect::RemoveCurse:
|
|
|
|
case ESM::MagicEffect::CommandCreature:
|
|
|
|
case ESM::MagicEffect::CommandHumanoid:
|
|
|
|
return 0.f;
|
|
|
|
|
2018-08-30 23:36:47 +03:00
|
|
|
case ESM::MagicEffect::Blind:
|
|
|
|
{
|
|
|
|
if (enemy.isEmpty())
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy);
|
|
|
|
|
|
|
|
// Enemy can't attack
|
2018-09-01 01:44:29 +03:00
|
|
|
if (stats.isParalyzed() || stats.getKnockedDown())
|
2018-08-30 23:36:47 +03:00
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
// Enemy doesn't attack
|
2022-07-17 19:36:48 +03:00
|
|
|
if (stats.getDrawState() != MWMechanics::DrawState::Weapon)
|
2018-08-30 23:36:47 +03:00
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-08-01 09:05:35 +04:00
|
|
|
case ESM::MagicEffect::Sound:
|
|
|
|
{
|
|
|
|
if (enemy.isEmpty())
|
|
|
|
return 0.f;
|
|
|
|
|
2018-08-30 23:36:47 +03:00
|
|
|
const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy);
|
2017-08-01 09:05:35 +04:00
|
|
|
|
2018-08-30 23:36:47 +03:00
|
|
|
// Enemy can't cast spells
|
2023-05-23 19:06:08 +02:00
|
|
|
if (stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Silence).getMagnitude() > 0)
|
2017-08-01 09:05:35 +04:00
|
|
|
return 0.f;
|
|
|
|
|
2018-09-01 01:44:29 +03:00
|
|
|
if (stats.isParalyzed() || stats.getKnockedDown())
|
2017-08-01 09:05:35 +04:00
|
|
|
return 0.f;
|
|
|
|
|
2018-08-30 23:36:47 +03:00
|
|
|
// Enemy doesn't cast spells
|
2022-07-17 19:36:48 +03:00
|
|
|
if (stats.getDrawState() != MWMechanics::DrawState::Spell)
|
2018-08-30 23:36:47 +03:00
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case ESM::MagicEffect::Silence:
|
|
|
|
{
|
|
|
|
if (enemy.isEmpty())
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy);
|
|
|
|
|
|
|
|
// Enemy can't cast spells
|
2018-09-01 01:44:29 +03:00
|
|
|
if (stats.isParalyzed() || stats.getKnockedDown())
|
2018-08-30 23:36:47 +03:00
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
// Enemy doesn't cast spells
|
2022-07-17 19:36:48 +03:00
|
|
|
if (stats.getDrawState() != MWMechanics::DrawState::Spell)
|
2018-08-30 23:36:47 +03:00
|
|
|
return 0.f;
|
2017-08-01 09:05:35 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case ESM::MagicEffect::RestoreAttribute:
|
|
|
|
return 0.f; // TODO: implement based on attribute damage
|
|
|
|
case ESM::MagicEffect::RestoreSkill:
|
|
|
|
return 0.f; // TODO: implement based on skill damage
|
2022-09-22 21:26:05 +03:00
|
|
|
|
2017-08-01 09:05:35 +04:00
|
|
|
case ESM::MagicEffect::ResistFire:
|
|
|
|
case ESM::MagicEffect::ResistFrost:
|
|
|
|
case ESM::MagicEffect::ResistMagicka:
|
|
|
|
case ESM::MagicEffect::ResistNormalWeapons:
|
|
|
|
case ESM::MagicEffect::ResistParalysis:
|
|
|
|
case ESM::MagicEffect::ResistPoison:
|
|
|
|
case ESM::MagicEffect::ResistShock:
|
|
|
|
case ESM::MagicEffect::SpellAbsorption:
|
|
|
|
case ESM::MagicEffect::Reflect:
|
|
|
|
return 0.f; // probably useless since we don't know in advance what the enemy will cast
|
2022-09-22 21:26:05 +03:00
|
|
|
|
2017-08-01 09:05:35 +04:00
|
|
|
// don't cast these for now as they would make the NPC cast the same effect over and over again, especially
|
|
|
|
// when they have potions
|
|
|
|
case ESM::MagicEffect::FortifyAttribute:
|
|
|
|
case ESM::MagicEffect::FortifyHealth:
|
|
|
|
case ESM::MagicEffect::FortifyMagicka:
|
|
|
|
case ESM::MagicEffect::FortifyFatigue:
|
|
|
|
case ESM::MagicEffect::FortifySkill:
|
|
|
|
case ESM::MagicEffect::FortifyMaximumMagicka:
|
|
|
|
case ESM::MagicEffect::FortifyAttack:
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
case ESM::MagicEffect::Burden:
|
|
|
|
{
|
|
|
|
if (enemy.isEmpty())
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
// Ignore enemy without inventory
|
|
|
|
if (!enemy.getClass().hasInventoryStore(enemy))
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
// burden makes sense only to overburden an enemy
|
|
|
|
float burden = enemy.getClass().getEncumbrance(enemy) - enemy.getClass().getCapacity(enemy);
|
|
|
|
if (burden > 0)
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
if ((effect.mMagnMin + effect.mMagnMax) / 2.f > -burden)
|
|
|
|
rating *= 3;
|
|
|
|
else
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case ESM::MagicEffect::Feather:
|
|
|
|
{
|
|
|
|
// Ignore actors without inventory
|
|
|
|
if (!actor.getClass().hasInventoryStore(actor))
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
// feather makes sense only for overburden actors
|
|
|
|
float burden = actor.getClass().getEncumbrance(actor) - actor.getClass().getCapacity(actor);
|
|
|
|
if (burden <= 0)
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
if ((effect.mMagnMin + effect.mMagnMax) / 2.f >= burden)
|
|
|
|
rating *= 3;
|
|
|
|
else
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case ESM::MagicEffect::Levitate:
|
|
|
|
return 0.f; // AI isn't designed to take advantage of this, and could be perceived as unfair anyway
|
|
|
|
case ESM::MagicEffect::BoundBoots:
|
|
|
|
case ESM::MagicEffect::BoundHelm:
|
|
|
|
if (actor.getClass().isNpc())
|
|
|
|
{
|
|
|
|
// Beast races can't wear helmets or boots
|
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type
The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID
Slowly going through all the changes to make, still hundreds of errors
a lot of functions/structures use std::string or stringview to designate an ID. So it takes time
Continues slowly replacing ids. There are technically more and more compilation errors
I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type
Continue moving forward, changes to the stores
slowly moving along
Starting to see the fruit of those changes.
still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type.
More replacements. Things are starting to get easier
I can see more and more often the issue is that the function is awaiting a RefId, but is given a string
there is less need to go down functions and to fix a long list of them.
Still moving forward, and for the first time error count is going down!
Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably
Cells are back to using string for the name, haven't fixed everything yet. Many other changes
Under the bar of 400 compilation errors.
more good progress <100 compile errors!
More progress
Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string
some more progress on other fronts
Mostly game settings clean
one error opened a lot of other errors. Down to 18, but more will prbably appear
only link errors left??
Fixed link errors
OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
|
|
|
const ESM::RefId& raceid = actor.get<ESM::NPC>()->mBase->mRace;
|
2023-04-20 21:07:53 +02:00
|
|
|
const ESM::Race* race = MWBase::Environment::get().getESMStore()->get<ESM::Race>().find(raceid);
|
2017-08-01 09:05:35 +04:00
|
|
|
if (race->mData.mFlags & ESM::Race::Beast)
|
2021-11-08 17:28:54 +01:00
|
|
|
return 0.f;
|
2022-09-22 21:26:05 +03:00
|
|
|
}
|
|
|
|
else
|
2017-08-01 09:05:35 +04:00
|
|
|
return 0.f;
|
2018-06-28 11:12:48 +04:00
|
|
|
|
|
|
|
break;
|
2021-11-07 22:13:27 +01:00
|
|
|
case ESM::MagicEffect::BoundShield:
|
2021-11-08 17:28:54 +01:00
|
|
|
if (!actor.getClass().hasInventoryStore(actor))
|
|
|
|
return 0.f;
|
|
|
|
else if (!actor.getClass().isNpc())
|
2021-11-07 22:13:27 +01:00
|
|
|
{
|
|
|
|
// If the actor is an NPC they can benefit from the armor rating, otherwise check if we've got a
|
|
|
|
// one-handed weapon to use with the shield
|
2021-11-08 17:28:54 +01:00
|
|
|
const auto& store = actor.getClass().getInventoryStore(actor);
|
|
|
|
auto oneHanded = std::find_if(store.cbegin(MWWorld::ContainerStore::Type_Weapon), store.cend(),
|
|
|
|
[](const MWWorld::ConstPtr& weapon) {
|
|
|
|
if (weapon.getClass().getItemHealth(weapon) <= 0.f)
|
|
|
|
return false;
|
|
|
|
short type = weapon.get<ESM::Weapon>()->mBase->mData.mType;
|
|
|
|
return !(MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded);
|
|
|
|
});
|
|
|
|
if (oneHanded == store.cend())
|
2017-08-01 09:05:35 +04:00
|
|
|
return 0.f;
|
2022-09-22 21:26:05 +03:00
|
|
|
}
|
|
|
|
break;
|
2017-08-01 09:05:35 +04:00
|
|
|
// Creatures can not wear armor
|
|
|
|
case ESM::MagicEffect::BoundCuirass:
|
|
|
|
case ESM::MagicEffect::BoundGloves:
|
|
|
|
if (!actor.getClass().isNpc())
|
2021-11-08 17:28:54 +01:00
|
|
|
return 0.f;
|
2021-11-07 22:13:27 +01:00
|
|
|
break;
|
2017-08-01 09:05:35 +04:00
|
|
|
|
|
|
|
case ESM::MagicEffect::RestoreHealth:
|
|
|
|
case ESM::MagicEffect::RestoreMagicka:
|
|
|
|
case ESM::MagicEffect::RestoreFatigue:
|
|
|
|
if (effect.mRange == ESM::RT_Self)
|
|
|
|
{
|
|
|
|
int priority = 1;
|
2018-08-30 23:36:47 +03:00
|
|
|
if (effect.mEffectID == ESM::MagicEffect::RestoreHealth)
|
2017-08-01 09:05:35 +04:00
|
|
|
priority = 10;
|
2018-08-30 23:36:47 +03:00
|
|
|
const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
|
|
|
const DynamicStat<float>& current
|
|
|
|
= stats.getDynamic(effect.mEffectID - ESM::MagicEffect::RestoreHealth);
|
2020-05-23 23:12:56 +03:00
|
|
|
// NB: this currently assumes the hardcoded magic effect flags are used
|
2018-08-30 23:36:47 +03:00
|
|
|
const float magnitude = (effect.mMagnMin + effect.mMagnMax) / 2.f;
|
2020-05-23 23:12:56 +03:00
|
|
|
const float toHeal = magnitude * std::max(1, effect.mDuration);
|
2017-08-01 09:05:35 +04:00
|
|
|
// Effect doesn't heal more than we need, *or* we are below 1/2 health
|
|
|
|
if (current.getModified() - current.getCurrent() > toHeal
|
|
|
|
|| current.getCurrent() < current.getModified() * 0.5)
|
2022-09-22 21:26:05 +03:00
|
|
|
{
|
2017-08-01 09:05:35 +04:00
|
|
|
return 10000.f * priority
|
|
|
|
- (toHeal
|
|
|
|
- (current.getModified() - current.getCurrent())); // prefer the most fitting potion
|
2022-09-22 21:26:05 +03:00
|
|
|
}
|
|
|
|
else
|
2018-08-31 00:36:30 +03:00
|
|
|
return -10000.f * priority; // Save for later
|
2017-08-01 09:05:35 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ESM::MagicEffect::Dispel:
|
|
|
|
{
|
|
|
|
int numPositive = 0;
|
|
|
|
int numNegative = 0;
|
|
|
|
int diff = 0;
|
|
|
|
|
|
|
|
if (effect.mRange == ESM::RT_Self)
|
2022-09-22 21:26:05 +03:00
|
|
|
{
|
2017-08-01 09:05:35 +04:00
|
|
|
numPositive = numEffectsToDispel(actor, -1, false);
|
|
|
|
numNegative = numEffectsToDispel(actor);
|
|
|
|
|
|
|
|
diff = numNegative - numPositive;
|
2022-09-22 21:26:05 +03:00
|
|
|
}
|
2017-08-01 09:05:35 +04:00
|
|
|
else
|
2022-09-22 21:26:05 +03:00
|
|
|
{
|
2017-08-01 09:05:35 +04:00
|
|
|
if (enemy.isEmpty())
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
numPositive = numEffectsToDispel(enemy, -1, false);
|
|
|
|
numNegative = numEffectsToDispel(enemy);
|
|
|
|
|
|
|
|
diff = numPositive - numNegative;
|
|
|
|
|
|
|
|
// if rating < 0 here, the spell will be considered as negative later
|
|
|
|
rating *= -1;
|
2022-09-22 21:26:05 +03:00
|
|
|
}
|
2017-08-01 09:05:35 +04:00
|
|
|
|
|
|
|
if (diff <= 0)
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
rating *= (diff) / 5.f;
|
2022-09-22 21:26:05 +03:00
|
|
|
|
2017-08-01 09:05:35 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prefer Cure effects over Dispel, because Dispel also removes positive effects
|
|
|
|
case ESM::MagicEffect::CureParalyzation:
|
|
|
|
return 1001.f * numEffectsToDispel(actor, ESM::MagicEffect::Paralyze);
|
|
|
|
|
|
|
|
case ESM::MagicEffect::CurePoison:
|
|
|
|
return 1001.f * numEffectsToDispel(actor, ESM::MagicEffect::Poison);
|
|
|
|
case ESM::MagicEffect::DisintegrateArmor:
|
|
|
|
{
|
|
|
|
if (enemy.isEmpty())
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
// Ignore enemy without inventory
|
|
|
|
if (!enemy.getClass().hasInventoryStore(enemy))
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
MWWorld::InventoryStore& inv = enemy.getClass().getInventoryStore(enemy);
|
|
|
|
|
|
|
|
// According to UESP
|
|
|
|
static const int armorSlots[] = {
|
|
|
|
MWWorld::InventoryStore::Slot_CarriedLeft,
|
|
|
|
MWWorld::InventoryStore::Slot_Cuirass,
|
|
|
|
MWWorld::InventoryStore::Slot_LeftPauldron,
|
|
|
|
MWWorld::InventoryStore::Slot_RightPauldron,
|
|
|
|
MWWorld::InventoryStore::Slot_LeftGauntlet,
|
|
|
|
MWWorld::InventoryStore::Slot_RightGauntlet,
|
|
|
|
MWWorld::InventoryStore::Slot_Helmet,
|
|
|
|
MWWorld::InventoryStore::Slot_Greaves,
|
2022-09-14 00:08:19 +02:00
|
|
|
MWWorld::InventoryStore::Slot_Boots,
|
2017-08-01 09:05:35 +04:00
|
|
|
};
|
|
|
|
|
|
|
|
bool enemyHasArmor = false;
|
|
|
|
|
|
|
|
// Ignore enemy without armor
|
|
|
|
for (unsigned int i = 0; i < sizeof(armorSlots) / sizeof(int); ++i)
|
|
|
|
{
|
|
|
|
MWWorld::ContainerStoreIterator item = inv.getSlot(armorSlots[i]);
|
|
|
|
|
|
|
|
if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor))
|
|
|
|
{
|
|
|
|
enemyHasArmor = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!enemyHasArmor)
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case ESM::MagicEffect::DisintegrateWeapon:
|
|
|
|
{
|
|
|
|
if (enemy.isEmpty())
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
// Ignore enemy without inventory
|
|
|
|
if (!enemy.getClass().hasInventoryStore(enemy))
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
MWWorld::InventoryStore& inv = enemy.getClass().getInventoryStore(enemy);
|
|
|
|
MWWorld::ContainerStoreIterator item = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
|
|
|
|
|
|
|
// Ignore enemy without weapons
|
|
|
|
if (item == inv.end() || (item.getType() != MWWorld::ContainerStore::Type_Weapon))
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-12-29 12:41:37 +01:00
|
|
|
case ESM::MagicEffect::AbsorbAttribute:
|
2017-08-01 09:05:35 +04:00
|
|
|
case ESM::MagicEffect::DamageAttribute:
|
|
|
|
case ESM::MagicEffect::DrainAttribute:
|
|
|
|
if (!enemy.isEmpty()
|
2023-06-19 20:41:54 +02:00
|
|
|
&& enemy.getClass()
|
|
|
|
.getCreatureStats(enemy)
|
|
|
|
.getAttribute(ESM::Attribute::AttributeID(effect.mAttribute))
|
|
|
|
.getModified()
|
|
|
|
<= 0)
|
2017-08-01 09:05:35 +04:00
|
|
|
return 0.f;
|
|
|
|
{
|
|
|
|
if (effect.mAttribute >= 0 && effect.mAttribute < ESM::Attribute::Length)
|
2022-09-22 21:26:05 +03:00
|
|
|
{
|
2017-08-01 09:05:35 +04:00
|
|
|
const float attributePriorities[ESM::Attribute::Length] = {
|
|
|
|
1.0f, // Strength
|
|
|
|
0.5f, // Intelligence
|
|
|
|
0.6f, // Willpower
|
|
|
|
0.7f, // Agility
|
|
|
|
0.5f, // Speed
|
|
|
|
0.8f, // Endurance
|
|
|
|
0.7f, // Personality
|
|
|
|
0.3f // Luck
|
|
|
|
};
|
|
|
|
rating *= attributePriorities[effect.mAttribute];
|
2022-09-22 21:26:05 +03:00
|
|
|
}
|
2017-08-01 09:05:35 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2022-12-29 12:41:37 +01:00
|
|
|
case ESM::MagicEffect::AbsorbSkill:
|
2017-08-01 09:05:35 +04:00
|
|
|
case ESM::MagicEffect::DamageSkill:
|
|
|
|
case ESM::MagicEffect::DrainSkill:
|
|
|
|
if (enemy.isEmpty() || !enemy.getClass().isNpc())
|
|
|
|
return 0.f;
|
2023-06-05 21:21:30 +02:00
|
|
|
if (enemy.getClass().getSkill(enemy, ESM::Skill::indexToRefId(effect.mSkill)) <= 0)
|
2017-08-01 09:05:35 +04:00
|
|
|
return 0.f;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-08-22 09:18:49 +04:00
|
|
|
// Allow only one summoned creature at time
|
|
|
|
if (isSummoningEffect(effect.mEffectID))
|
|
|
|
{
|
|
|
|
MWMechanics::CreatureStats& creatureStats = actor.getClass().getCreatureStats(actor);
|
|
|
|
|
|
|
|
if (!creatureStats.getSummonedCreatureMap().empty())
|
|
|
|
return 0.f;
|
2023-02-18 14:10:47 +01:00
|
|
|
// But rate summons higher than other effects
|
|
|
|
rating = 3.f;
|
2017-08-22 09:18:49 +04:00
|
|
|
}
|
2021-11-07 22:13:27 +01:00
|
|
|
if (effect.mEffectID >= ESM::MagicEffect::BoundDagger && effect.mEffectID <= ESM::MagicEffect::BoundGloves)
|
|
|
|
{
|
2023-02-19 12:05:17 +01:00
|
|
|
// Prefer casting bound items over other spells
|
|
|
|
rating = 2.f;
|
2021-11-07 22:13:27 +01:00
|
|
|
// While rateSpell prevents actors from recasting the same spell, it doesn't prevent them from casting
|
|
|
|
// different spells with the same effect. Multiple instances of the same bound item don't stack so if the
|
2023-02-18 14:10:47 +01:00
|
|
|
// effect is already active, rate it as useless. Likewise, if the actor already has a bound weapon, don't
|
2023-02-19 12:05:17 +01:00
|
|
|
// summon another of a different kind unless what we have is a bow and the actor is out of ammo.
|
|
|
|
// FIXME: This code assumes the summoned item is of the usual type (i.e. a mod hasn't changed Bound Bow to
|
|
|
|
// summon an Axe instead)
|
2023-02-18 14:10:47 +01:00
|
|
|
if (effect.mEffectID <= ESM::MagicEffect::BoundLongbow)
|
|
|
|
{
|
|
|
|
for (int e = ESM::MagicEffect::BoundDagger; e <= ESM::MagicEffect::BoundLongbow; ++e)
|
2023-05-23 19:06:08 +02:00
|
|
|
if (actor.getClass().getCreatureStats(actor).getMagicEffects().getOrDefault(e).getMagnitude() > 0.f
|
2023-02-19 12:05:17 +01:00
|
|
|
&& (e != ESM::MagicEffect::BoundLongbow || effect.mEffectID == e
|
|
|
|
|| rateAmmo(actor, enemy, getWeaponType(ESM::Weapon::MarksmanBow)->mAmmoType) <= 0.f))
|
2023-02-18 14:10:47 +01:00
|
|
|
return 0.f;
|
2023-06-06 17:24:22 +02:00
|
|
|
ESM::RefId skill = ESM::Skill::ShortBlade;
|
2023-02-19 12:05:17 +01:00
|
|
|
if (effect.mEffectID == ESM::MagicEffect::BoundLongsword)
|
|
|
|
skill = ESM::Skill::LongBlade;
|
|
|
|
else if (effect.mEffectID == ESM::MagicEffect::BoundMace)
|
|
|
|
skill = ESM::Skill::BluntWeapon;
|
|
|
|
else if (effect.mEffectID == ESM::MagicEffect::BoundBattleAxe)
|
|
|
|
skill = ESM::Skill::Axe;
|
|
|
|
else if (effect.mEffectID == ESM::MagicEffect::BoundSpear)
|
|
|
|
skill = ESM::Skill::Spear;
|
|
|
|
else if (effect.mEffectID == ESM::MagicEffect::BoundLongbow)
|
|
|
|
{
|
|
|
|
// AI should not summon the bow if there is no suitable ammo.
|
|
|
|
if (rateAmmo(actor, enemy, getWeaponType(ESM::Weapon::MarksmanBow)->mAmmoType) <= 0.f)
|
|
|
|
return 0.f;
|
|
|
|
skill = ESM::Skill::Marksman;
|
|
|
|
}
|
|
|
|
// Prefer summoning items we know how to use
|
|
|
|
rating *= (50.f + actor.getClass().getSkill(actor, skill)) / 100.f;
|
2023-02-18 14:10:47 +01:00
|
|
|
}
|
2023-05-23 19:06:08 +02:00
|
|
|
else if (actor.getClass()
|
|
|
|
.getCreatureStats(actor)
|
|
|
|
.getMagicEffects()
|
|
|
|
.getOrDefault(effect.mEffectID)
|
|
|
|
.getMagnitude()
|
2023-02-18 14:10:47 +01:00
|
|
|
> 0.f)
|
2021-11-07 22:13:27 +01:00
|
|
|
return 0.f;
|
|
|
|
}
|
2017-08-22 09:18:49 +04:00
|
|
|
|
2017-08-01 09:05:35 +04:00
|
|
|
// Underwater casting not possible
|
|
|
|
if (effect.mRange == ESM::RT_Target)
|
|
|
|
{
|
|
|
|
if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(actor), 0.75f))
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
if (enemy.isEmpty())
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
if (MWBase::Environment::get().getWorld()->isUnderwater(MWWorld::ConstPtr(enemy), 0.75f))
|
|
|
|
return 0.f;
|
|
|
|
}
|
|
|
|
|
2018-08-27 13:38:53 +04:00
|
|
|
const ESM::MagicEffect* magicEffect
|
2023-04-20 21:07:53 +02:00
|
|
|
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(effect.mEffectID);
|
2017-08-01 09:05:35 +04:00
|
|
|
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
|
|
|
|
{
|
|
|
|
rating *= -1.f;
|
|
|
|
|
|
|
|
if (enemy.isEmpty())
|
|
|
|
return 0.f;
|
|
|
|
|
|
|
|
// Check resistance for harmful effects
|
|
|
|
CreatureStats& stats = enemy.getClass().getCreatureStats(enemy);
|
|
|
|
|
|
|
|
float resistance = MWMechanics::getEffectResistanceAttribute(effect.mEffectID, &stats.getMagicEffects());
|
|
|
|
|
|
|
|
rating *= (1.f - std::min(resistance, 100.f) / 100.f);
|
|
|
|
}
|
|
|
|
|
|
|
|
// for harmful no-magnitude effects (e.g. silence) check if enemy is already has them
|
|
|
|
// for non-harmful no-magnitude effects (e.g. bound items) check if actor is already has them
|
|
|
|
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
|
|
|
|
{
|
|
|
|
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
|
|
|
|
{
|
|
|
|
CreatureStats& stats = enemy.getClass().getCreatureStats(enemy);
|
|
|
|
|
2023-05-23 19:06:08 +02:00
|
|
|
if (stats.getMagicEffects().getOrDefault(effect.mEffectID).getMagnitude() > 0)
|
2017-08-01 09:05:35 +04:00
|
|
|
return 0.f;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
|
|
|
|
2023-05-23 19:06:08 +02:00
|
|
|
if (stats.getMagicEffects().getOrDefault(effect.mEffectID).getMagnitude() > 0)
|
2017-08-01 09:05:35 +04:00
|
|
|
return 0.f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-27 13:38:53 +04:00
|
|
|
rating *= calcEffectCost(effect, magicEffect);
|
2017-08-01 09:05:35 +04:00
|
|
|
|
|
|
|
// 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.
|
|
|
|
if (effect.mRange != ESM::RT_Self)
|
|
|
|
rating *= -1.f;
|
|
|
|
|
|
|
|
return rating;
|
|
|
|
}
|
|
|
|
|
2022-12-29 12:40:10 +01:00
|
|
|
float rateEffects(
|
|
|
|
const ESM::EffectList& list, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy, bool useSpellMult)
|
2017-08-01 09:05:35 +04:00
|
|
|
{
|
|
|
|
// NOTE: enemy may be empty
|
2018-09-06 17:17:30 +03:00
|
|
|
|
2017-08-01 09:05:35 +04:00
|
|
|
float rating = 0.f;
|
2018-09-06 17:17:30 +03:00
|
|
|
|
|
|
|
const MWWorld::Store<ESM::GameSetting>& gmst
|
2023-04-20 21:07:53 +02:00
|
|
|
= MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
|
2018-09-06 17:17:30 +03:00
|
|
|
static const float fAIMagicSpellMult = gmst.find("fAIMagicSpellMult")->mValue.getFloat();
|
|
|
|
static const float fAIRangeMagicSpellMult = gmst.find("fAIRangeMagicSpellMult")->mValue.getFloat();
|
|
|
|
|
2022-12-29 12:40:10 +01:00
|
|
|
for (const ESM::ENAMstruct& effect : list.mList)
|
2017-08-01 09:05:35 +04:00
|
|
|
{
|
2022-12-29 12:40:10 +01:00
|
|
|
float effectRating = rateEffect(effect, actor, enemy);
|
|
|
|
if (useSpellMult)
|
|
|
|
{
|
|
|
|
if (effect.mRange == ESM::RT_Target)
|
|
|
|
effectRating *= fAIRangeMagicSpellMult;
|
|
|
|
else
|
|
|
|
effectRating *= fAIMagicSpellMult;
|
|
|
|
}
|
|
|
|
rating += effectRating;
|
2017-08-01 09:05:35 +04:00
|
|
|
}
|
|
|
|
return rating;
|
|
|
|
}
|
|
|
|
|
|
|
|
float vanillaRateSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor, const MWWorld::Ptr& enemy)
|
|
|
|
{
|
|
|
|
const MWWorld::Store<ESM::GameSetting>& gmst
|
2023-04-20 21:07:53 +02:00
|
|
|
= MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
|
2017-08-01 09:05:35 +04:00
|
|
|
|
2018-08-29 18:38:12 +03:00
|
|
|
static const float fAIMagicSpellMult = gmst.find("fAIMagicSpellMult")->mValue.getFloat();
|
|
|
|
static const float fAIRangeMagicSpellMult = gmst.find("fAIRangeMagicSpellMult")->mValue.getFloat();
|
2017-08-01 09:05:35 +04:00
|
|
|
|
|
|
|
float mult = fAIMagicSpellMult;
|
|
|
|
|
|
|
|
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = spell->mEffects.mList.begin();
|
|
|
|
effectIt != spell->mEffects.mList.end(); ++effectIt)
|
|
|
|
{
|
|
|
|
if (effectIt->mRange == ESM::RT_Target)
|
|
|
|
{
|
|
|
|
if (!MWBase::Environment::get().getWorld()->isSwimming(enemy))
|
|
|
|
mult = fAIRangeMagicSpellMult;
|
|
|
|
else
|
|
|
|
mult = 0.0f;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return MWMechanics::getSpellSuccessChance(spell, actor) * mult;
|
|
|
|
}
|
|
|
|
}
|