1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-30 12:32:36 +00:00
OpenMW/apps/openmw/mwmechanics/spellcasting.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

648 lines
27 KiB
C++
Raw Normal View History

#include "spellcasting.hpp"
#include <components/esm3/loadench.hpp>
#include <components/esm3/loadmgef.hpp>
2022-09-08 21:08:59 +02:00
#include <components/esm3/loadstat.hpp>
2019-04-21 18:04:27 +03:00
#include <components/misc/constants.hpp>
#include <components/misc/resourcehelpers.hpp>
2015-04-22 17:58:55 +02:00
#include <components/misc/rng.hpp>
#include <components/misc/strings/format.hpp>
#include "../mwbase/environment.hpp"
2014-01-10 21:26:24 +01:00
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/esmstore.hpp"
2015-05-30 01:00:24 +02:00
#include "../mwrender/animation.hpp"
2015-08-21 21:12:39 +12:00
#include "actorutil.hpp"
#include "creaturestats.hpp"
#include "spelleffects.hpp"
#include "spellutil.hpp"
#include "weapontype.hpp"
namespace MWMechanics
{
CastSpell::CastSpell(
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile, const bool manualSpell)
: mCaster(caster)
, mTarget(target)
, mFromProjectile(fromProjectile)
, mManualSpell(manualSpell)
{
}
2022-09-04 14:51:19 +02:00
void CastSpell::explodeSpell(
const ESM::EffectList& effects, const MWWorld::Ptr& ignore, ESM::RangeType rangeType) const
2022-09-04 14:01:36 +02:00
{
const auto world = MWBase::Environment::get().getWorld();
const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct>> toApply;
int index = -1;
for (const ESM::ENAMstruct& effectInfo : effects.mList)
{
++index;
const ESM::MagicEffect* effect = world->getStore().get<ESM::MagicEffect>().find(effectInfo.mEffectID);
if (effectInfo.mRange != rangeType
|| (effectInfo.mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor()))
continue; // Not right range type, or not area effect and hit an actor
if (mFromProjectile && effectInfo.mArea <= 0)
continue; // Don't play explosion for projectiles with 0-area effects
if (!mFromProjectile && effectInfo.mRange == ESM::RT_Touch && !ignore.isEmpty()
&& !ignore.getClass().isActor() && !ignore.getClass().hasToolTip(ignore)
2022-09-04 14:51:19 +02:00
&& (mCaster.isEmpty() || mCaster.getClass().isActor()))
2022-09-04 14:01:36 +02:00
continue; // Don't play explosion for touch spells on non-activatable objects except when spell is from
// a projectile enchantment or ExplodeSpell
// Spawn the explosion orb effect
const ESM::Static* areaStatic;
if (!effect->mArea.empty())
areaStatic = world->getStore().get<ESM::Static>().find(effect->mArea);
else
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
areaStatic = world->getStore().get<ESM::Static>().find(ESM::RefId::stringRefId("VFX_DefaultArea"));
2022-09-04 14:01:36 +02:00
const std::string& texture = effect->mParticle;
if (effectInfo.mArea <= 0)
{
if (effectInfo.mRange == ESM::RT_Target)
world->spawnEffect(
Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel, vfs), texture, mHitPosition, 1.0f);
continue;
}
else
world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel, vfs), texture,
mHitPosition, static_cast<float>(effectInfo.mArea * 2));
// Play explosion sound (make sure to use NoTrack, since we will delete the projectile now)
static const std::string schools[]
= { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" };
{
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
if (!effect->mAreaSound.empty())
sndMgr->playSound3D(mHitPosition, effect->mAreaSound, 1.0f, 1.0f);
else
sndMgr->playSound3D(
mHitPosition, ESM::RefId::stringRefId(schools[effect->mData.mSchool] + " area"), 1.0f, 1.0f);
2022-09-04 14:01:36 +02:00
}
// Get the actors in range of the effect
std::vector<MWWorld::Ptr> objects;
static const int unitsPerFoot = ceil(Constants::UnitsPerFoot);
MWBase::Environment::get().getMechanicsManager()->getObjectsInRange(
mHitPosition, static_cast<float>(effectInfo.mArea * unitsPerFoot), objects);
for (const MWWorld::Ptr& affected : objects)
{
// Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing
// range.
if (affected.getClass().isActor() && !world->isActorCollisionEnabled(affected))
continue;
auto& list = toApply[affected];
while (list.size() < static_cast<std::size_t>(index))
{
// Insert dummy effects to preserve indices
auto& dummy = list.emplace_back(effectInfo);
dummy.mRange = ESM::RT_Self;
assert(dummy.mRange != rangeType);
}
list.push_back(effectInfo);
}
}
// Now apply the appropriate effects to each actor in range
for (auto& applyPair : toApply)
{
// Vanilla-compatible behaviour of never applying the spell to the caster
// (could be changed by mods later)
2022-09-04 14:51:19 +02:00
if (applyPair.first == mCaster)
2022-09-04 14:01:36 +02:00
continue;
if (applyPair.first == ignore)
continue;
ESM::EffectList effectsToApply;
effectsToApply.mList = applyPair.second;
2022-09-04 15:09:31 +02:00
inflict(applyPair.first, effectsToApply, rangeType, true);
2022-09-04 14:01:36 +02:00
}
}
2022-09-04 15:09:31 +02:00
void CastSpell::launchMagicBolt() const
2018-07-15 12:44:25 +04:00
{
osg::Vec3f fallbackDirection(0, 1, 0);
osg::Vec3f offset(0, 0, 0);
if (!mTarget.isEmpty() && mTarget.getClass().isActor())
offset.z() = MWBase::Environment::get().getWorld()->getHalfExtents(mTarget).z();
// Fall back to a "caster to target" direction if we have no other means of determining it
// (e.g. when cast by a non-actor)
if (!mTarget.isEmpty())
2018-07-15 12:44:25 +04:00
fallbackDirection = (mTarget.getRefData().getPosition().asVec3() + offset)
- (mCaster.getRefData().getPosition().asVec3());
MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection, mSlot);
}
2022-09-04 15:09:31 +02:00
void CastSpell::inflict(
const MWWorld::Ptr& target, const ESM::EffectList& effects, ESM::RangeType range, bool exploded) const
{
const bool targetIsActor = !target.isEmpty() && target.getClass().isActor();
if (targetIsActor)
{
// Early-out for characters that have departed.
const auto& stats = target.getClass().getCreatureStats(target);
if (stats.isDead() && stats.isDeathAnimationFinished())
return;
}
// If none of the effects need to apply, we can early-out
bool found = false;
bool containsRecastable = false;
std::vector<const ESM::MagicEffect*> magicEffects;
magicEffects.reserve(effects.mList.size());
const auto& store = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>();
2020-04-25 23:56:05 +03:00
for (const ESM::ENAMstruct& effect : effects.mList)
{
2020-04-25 23:56:05 +03:00
if (effect.mRange == range)
{
found = true;
const ESM::MagicEffect* magicEffect = store.find(effect.mEffectID);
// caster needs to be an actor for linked effects (e.g. Absorb)
if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked
&& (mCaster.isEmpty() || !mCaster.getClass().isActor()))
{
magicEffects.push_back(nullptr);
continue;
}
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable))
containsRecastable = true;
magicEffects.push_back(magicEffect);
}
else
magicEffects.push_back(nullptr);
}
if (!found)
return;
2022-09-04 14:51:19 +02:00
ActiveSpells::ActiveSpellParams params(*this, mCaster);
bool castByPlayer = (!mCaster.isEmpty() && mCaster == getPlayer());
const ActiveSpells* targetSpells = nullptr;
if (targetIsActor)
targetSpells = &target.getClass().getCreatureStats(target).getActiveSpells();
// Re-casting a bound equipment effect has no effect if the spell is still active
if (!containsRecastable && targetSpells && targetSpells->isSpellActive(mId))
{
if (castByPlayer)
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCannotRecast}");
return;
}
for (size_t currentEffectIndex = 0; !target.isEmpty() && currentEffectIndex < effects.mList.size();
++currentEffectIndex)
{
const ESM::ENAMstruct& enam = effects.mList[currentEffectIndex];
if (enam.mRange != range)
continue;
const ESM::MagicEffect* magicEffect = magicEffects[currentEffectIndex];
if (!magicEffect)
continue;
ActiveSpells::ActiveEffect effect;
effect.mEffectId = enam.mEffectID;
effect.mArg = MWMechanics::EffectKey(enam).mArg;
effect.mMagnitude = 0.f;
effect.mMinMagnitude = enam.mMagnMin;
effect.mMaxMagnitude = enam.mMagnMax;
effect.mTimeLeft = 0.f;
effect.mEffectIndex = static_cast<int>(currentEffectIndex);
effect.mFlags = ESM::ActiveEffect::Flag_None;
if (mManualSpell)
effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect;
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
effect.mDuration = hasDuration ? static_cast<float>(enam.mDuration) : 1.f;
bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce;
if (!appliedOnce)
effect.mDuration = std::max(1.f, effect.mDuration);
2020-04-27 12:43:34 +03:00
effect.mTimeLeft = effect.mDuration;
// add to list of active effects, to apply in next frame
params.getEffects().emplace_back(effect);
bool effectAffectsHealth = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful
|| enam.mEffectID == ESM::MagicEffect::RestoreHealth;
2022-09-04 14:51:19 +02:00
if (castByPlayer && target != mCaster && targetIsActor && effectAffectsHealth)
{
// If player is attempting to cast a harmful spell on or is healing a living target, show the target's
// HP bar.
MWBase::Environment::get().getWindowManager()->setEnemy(target);
}
if (!targetIsActor && magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
{
playEffects(target, *magicEffect);
}
}
if (!exploded)
2022-09-04 14:51:19 +02:00
explodeSpell(effects, target, range);
if (!target.isEmpty())
{
if (!params.getEffects().empty())
{
if (targetIsActor)
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(params);
else
{
// Apply effects instantly. We can ignore effect deletion since the entire params object gets
// deleted afterwards anyway and we can ignore reflection since non-actors cannot reflect spells
for (auto& effect : params.getEffects())
2022-09-04 14:51:19 +02:00
applyMagicEffect(target, mCaster, params, effect, 0.f);
}
}
}
}
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 CastSpell::cast(const ESM::RefId& id)
{
2020-04-26 21:37:19 +03:00
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
if (const auto spell = store.get<ESM::Spell>().search(id))
return cast(spell);
2020-04-26 21:37:19 +03:00
if (const auto potion = store.get<ESM::Potion>().search(id))
return cast(potion);
2020-04-26 21:37:19 +03:00
if (const auto ingredient = store.get<ESM::Ingredient>().search(id))
return cast(ingredient);
throw std::runtime_error("ID type cannot be casted");
}
bool CastSpell::cast(const MWWorld::Ptr& item, int slot, bool launchProjectile)
{
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& enchantmentName = item.getClass().getEnchantment(item);
if (enchantmentName.empty())
throw std::runtime_error("can't cast an item without an enchantment");
mSourceName = item.getClass().getName(item);
mId = item.getCellRef().getRefId();
const ESM::Enchantment* enchantment
= MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(enchantmentName);
mSlot = slot;
2017-03-25 22:40:11 +04:00
bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
bool isProjectile = false;
if (item.getType() == ESM::Weapon::sRecordId)
{
int type = item.get<ESM::Weapon>()->mBase->mData.mType;
ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(type)->mWeaponClass;
isProjectile = (weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo);
}
int type = enchantment->mData.mType;
2017-03-25 22:40:11 +04:00
// Check if there's enough charge left
if (!godmode
&& (type == ESM::Enchantment::WhenUsed || (!isProjectile && type == ESM::Enchantment::WhenStrikes)))
{
2017-03-25 22:40:11 +04:00
int castCost = getEffectiveEnchantmentCastCost(static_cast<float>(enchantment->mData.mCost), mCaster);
if (item.getCellRef().getEnchantmentCharge() == -1)
item.getCellRef().setEnchantmentCharge(static_cast<float>(enchantment->mData.mCharge));
if (item.getCellRef().getEnchantmentCharge() < castCost)
{
2015-08-21 21:12:39 +12:00
if (mCaster == getPlayer())
{
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
// Failure sound
int school = 0;
if (!enchantment->mEffects.mList.empty())
{
short effectId = enchantment->mEffects.mList.front().mEffectID;
const ESM::MagicEffect* magicEffect
= MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effectId);
school = magicEffect->mData.mSchool;
}
static const std::string schools[]
= { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" };
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
sndMgr->playSound3D(
mCaster, ESM::RefId::stringRefId("Spell Failure " + schools[school]), 1.0f, 1.0f);
}
return false;
}
// Reduce charge
item.getCellRef().setEnchantmentCharge(item.getCellRef().getEnchantmentCharge() - castCost);
}
if (type == ESM::Enchantment::WhenUsed)
{
2015-08-21 21:12:39 +12:00
if (mCaster == getPlayer())
mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, 1);
}
else if (type == ESM::Enchantment::CastOnce)
2017-03-25 22:40:11 +04:00
{
if (!godmode)
item.getContainerStore()->remove(item, 1);
2017-03-25 22:40:11 +04:00
}
else if (type == ESM::Enchantment::WhenStrikes)
{
2015-08-21 21:12:39 +12:00
if (mCaster == getPlayer())
mCaster.getClass().skillUsageSucceeded(mCaster, ESM::Skill::Enchant, 3);
}
if (isProjectile)
2022-09-04 14:51:19 +02:00
inflict(mTarget, enchantment->mEffects, ESM::RT_Self);
else
2022-09-04 14:51:19 +02:00
inflict(mCaster, enchantment->mEffects, ESM::RT_Self);
if (isProjectile || !mTarget.isEmpty())
2022-09-04 14:51:19 +02:00
inflict(mTarget, enchantment->mEffects, ESM::RT_Touch);
if (launchProjectile)
launchMagicBolt();
else if (isProjectile || !mTarget.isEmpty())
2022-09-04 14:51:19 +02:00
inflict(mTarget, enchantment->mEffects, ESM::RT_Target);
return true;
}
bool CastSpell::cast(const ESM::Potion* potion)
{
mSourceName = potion->mName;
mId = potion->mId;
mType = ESM::ActiveSpells::Type_Consumable;
2022-09-04 14:51:19 +02:00
inflict(mCaster, potion->mEffects, ESM::RT_Self);
return true;
}
bool CastSpell::cast(const ESM::Spell* spell)
{
mSourceName = spell->mName;
mId = spell->mId;
int school = 0;
2017-03-25 22:40:11 +04:00
bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mManualSpell)
{
school = getSpellSchool(spell, mCaster);
CreatureStats& stats = mCaster.getClass().getCreatureStats(mCaster);
2017-03-25 22:40:11 +04:00
if (!godmode)
{
bool fail = false;
// Check success
float successChance = getSpellSuccessChance(spell, mCaster, nullptr, true, false);
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
if (Misc::Rng::roll0to99(prng) >= successChance)
{
if (mCaster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}");
fail = true;
}
2017-03-25 22:40:11 +04:00
if (fail)
{
// Failure sound
static const std::string schools[]
= { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" };
2017-03-25 22:40:11 +04:00
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
sndMgr->playSound3D(
mCaster, ESM::RefId::stringRefId("Spell Failure " + schools[school]), 1.0f, 1.0f);
2017-03-25 22:40:11 +04:00
return false;
}
}
// A power can be used once per 24h
if (spell->mData.mType == ESM::Spell::ST_Power)
stats.getSpells().usePower(spell);
}
2020-04-26 21:37:19 +03:00
if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell))
mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0);
// A non-actor doesn't play its spell cast effects from a character controller, so play them here
if (!mCaster.getClass().isActor())
2020-04-25 23:56:05 +03:00
playSpellCastingEffects(spell->mEffects.mList);
2022-09-04 14:51:19 +02:00
inflict(mCaster, spell->mEffects, ESM::RT_Self);
if (!mTarget.isEmpty())
2022-09-04 14:51:19 +02:00
inflict(mTarget, spell->mEffects, ESM::RT_Touch);
launchMagicBolt();
return true;
}
bool CastSpell::cast(const ESM::Ingredient* ingredient)
{
mId = ingredient->mId;
mType = ESM::ActiveSpells::Type_Consumable;
mSourceName = ingredient->mName;
ESM::ENAMstruct effect;
effect.mEffectID = ingredient->mData.mEffectID[0];
effect.mSkill = ingredient->mData.mSkills[0];
effect.mAttribute = ingredient->mData.mAttributes[0];
effect.mRange = ESM::RT_Self;
effect.mArea = 0;
2020-04-26 21:37:19 +03:00
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
const auto magicEffect = store.get<ESM::MagicEffect>().find(effect.mEffectID);
const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster);
2018-10-08 17:06:30 +03:00
float x = (mCaster.getClass().getSkill(mCaster, ESM::Skill::Alchemy)
+ 0.2f * creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified()
+ 0.1f * creatureStats.getAttribute(ESM::Attribute::Luck).getModified())
* creatureStats.getFatigueTerm();
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
int roll = Misc::Rng::roll0to99(prng);
if (roll > x)
{
// "X has no effect on you"
2020-04-26 21:37:19 +03:00
std::string message = store.get<ESM::GameSetting>().find("sNotifyMessage50")->mValue.getString();
message = Misc::StringUtils::format(message, ingredient->mName);
MWBase::Environment::get().getWindowManager()->messageBox(message);
return false;
}
float magnitude = 0;
float y = roll / std::min(x, 100.f);
y *= 0.25f * x;
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
effect.mDuration = 1;
else
effect.mDuration = static_cast<int>(y);
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
{
2014-04-18 11:43:37 +02:00
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
magnitude = floor((0.05f * y) / (0.1f * magicEffect->mData.mBaseCost));
else
magnitude = floor(y / (0.1f * magicEffect->mData.mBaseCost));
magnitude = std::max(1.f, magnitude);
}
else
magnitude = 1;
effect.mMagnMax = static_cast<int>(magnitude);
effect.mMagnMin = static_cast<int>(magnitude);
ESM::EffectList effects;
effects.mList.push_back(effect);
2022-09-04 14:51:19 +02:00
inflict(mCaster, effects, ESM::RT_Self);
return true;
}
2022-09-04 15:42:40 +02:00
void CastSpell::playSpellCastingEffects(const ESM::Enchantment* enchantment) const
2018-07-15 12:44:25 +04:00
{
2022-09-04 15:42:40 +02:00
playSpellCastingEffects(enchantment->mEffects.mList);
}
void CastSpell::playSpellCastingEffects(const ESM::Spell* spell) const
{
playSpellCastingEffects(spell->mEffects.mList);
}
2018-07-15 12:44:25 +04:00
2022-09-04 15:09:31 +02:00
void CastSpell::playSpellCastingEffects(const std::vector<ESM::ENAMstruct>& effects) const
{
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
std::vector<std::string> addedEffects;
const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
2020-04-25 23:56:05 +03:00
for (const ESM::ENAMstruct& effectData : effects)
2016-08-31 18:25:59 +09:00
{
2020-04-25 23:56:05 +03:00
const auto effect = store.get<ESM::MagicEffect>().find(effectData.mEffectID);
2018-07-15 12:44:25 +04:00
const ESM::Static* castStatic;
if (!effect->mCasting.empty())
castStatic = store.get<ESM::Static>().find(effect->mCasting);
else
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
castStatic = store.get<ESM::Static>().find(ESM::RefId::stringRefId("VFX_DefaultCast"));
2018-07-15 12:44:25 +04:00
// check if the effect was already added
if (std::find(addedEffects.begin(), addedEffects.end(),
Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs))
!= addedEffects.end())
2018-07-15 12:44:25 +04:00
continue;
2020-04-25 23:56:05 +03:00
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster);
2018-07-15 12:44:25 +04:00
if (animation)
{
animation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), effect->mIndex,
2022-08-23 18:25:25 +02:00
false, {}, effect->mParticle);
2018-07-15 12:44:25 +04:00
}
else
{
2019-04-21 18:04:27 +03:00
// If the caster has no animation, add the effect directly to the effectManager
// We must scale and position it manually
float scale = mCaster.getCellRef().getScale();
2020-04-25 23:56:05 +03:00
osg::Vec3f pos(mCaster.getRefData().getPosition().asVec3());
if (!mCaster.getClass().isNpc())
{
osg::Vec3f bounds(MWBase::Environment::get().getWorld()->getHalfExtents(mCaster) * 2.f);
scale *= std::max({ bounds.x(), bounds.y(), bounds.z() / 2.f }) / 64.f;
float offset = 0.f;
if (bounds.z() < 128.f)
offset = bounds.z() - 128.f;
else if (bounds.z() < bounds.x() + bounds.y())
offset = 128.f - bounds.z();
if (MWBase::Environment::get().getWorld()->isFlying(mCaster))
offset /= 20.f;
pos.z() += offset * scale;
}
else
{
// Additionally use the NPC's height
osg::Vec3f npcScaleVec(1.f, 1.f, 1.f);
mCaster.getClass().adjustScale(mCaster, npcScaleVec, true);
scale *= npcScaleVec.z();
}
scale = std::max(scale, 1.f);
MWBase::Environment::get().getWorld()->spawnEffect(
Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), effect->mParticle, pos, scale);
2016-08-31 18:25:59 +09:00
}
if (animation && !mCaster.getClass().isActor())
2016-08-31 18:25:59 +09:00
animation->addSpellCastGlow(effect);
2016-08-31 18:25:59 +09:00
static const std::string schools[]
= { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" };
addedEffects.push_back(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs));
2018-07-15 12:44:25 +04:00
2016-08-31 18:25:59 +09:00
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
if (!effect->mCastSound.empty())
sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f);
else
sndMgr->playSound3D(
mCaster, ESM::RefId::stringRefId(schools[effect->mData.mSchool] + " cast"), 1.0f, 1.0f);
2016-08-31 18:25:59 +09:00
}
}
void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping)
{
if (playNonLooping)
{
static const std::string schools[]
= { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" };
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
if (!magicEffect.mHitSound.empty())
sndMgr->playSound3D(target, magicEffect.mHitSound, 1.0f, 1.0f);
else
sndMgr->playSound3D(
target, ESM::RefId::stringRefId(schools[magicEffect.mData.mSchool] + " hit"), 1.0f, 1.0f);
}
// Add VFX
const ESM::Static* castStatic;
if (!magicEffect.mHit.empty())
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find(magicEffect.mHit);
else
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find(
ESM::RefId::stringRefId("VFX_DefaultHit"));
bool loop = (magicEffect.mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0;
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target);
if (anim && !castStatic->mModel.empty())
{
// Don't play particle VFX unless the effect is new or it should be looping.
if (playNonLooping || loop)
{
const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
anim->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), magicEffect.mIndex,
2022-08-23 18:25:25 +02:00
loop, {}, magicEffect.mParticle);
}
}
}
}