mirror of https://github.com/OpenMW/openmw.git
Merge branch 'effective_magic' into 'master'
Overhaul magic effects Closes #6087, #5863, #5801, #5621, #5596, #5454, #5207, #5198, #4414, #4297, and #1751 See merge request OpenMW/openmw!1116pull/3145/head
commit
1644430bc0
File diff suppressed because it is too large
Load Diff
@ -1,75 +0,0 @@
|
||||
#include "linkedeffects.hpp"
|
||||
|
||||
#include <components/misc/rng.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect,
|
||||
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects)
|
||||
{
|
||||
if (caster.isEmpty() || caster == target || !target.getClass().isActor())
|
||||
return false;
|
||||
|
||||
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
||||
bool isUnreflectable = magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable;
|
||||
if (!isHarmful || isUnreflectable)
|
||||
return false;
|
||||
|
||||
float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude();
|
||||
if (Misc::Rng::roll0to99() >= reflect)
|
||||
return false;
|
||||
|
||||
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Reflect");
|
||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
|
||||
if (animation && !reflectStatic->mModel.empty())
|
||||
animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string());
|
||||
reflectedEffects.mList.emplace_back(effect);
|
||||
return true;
|
||||
}
|
||||
|
||||
void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect,
|
||||
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source)
|
||||
{
|
||||
if (caster.isEmpty() || caster == target)
|
||||
return;
|
||||
|
||||
if (!target.getClass().isActor() || !caster.getClass().isActor())
|
||||
return;
|
||||
|
||||
// Make sure callers don't do something weird
|
||||
if (effect.mEffectID < ESM::MagicEffect::AbsorbAttribute || effect.mEffectID > ESM::MagicEffect::AbsorbSkill)
|
||||
throw std::runtime_error("invalid absorb stat effect");
|
||||
|
||||
if (appliedEffect.mMagnitude == 0)
|
||||
return;
|
||||
|
||||
std::vector<ActiveSpells::ActiveEffect> absorbEffects;
|
||||
ActiveSpells::ActiveEffect absorbEffect = appliedEffect;
|
||||
absorbEffect.mMagnitude *= -1;
|
||||
absorbEffect.mEffectIndex = appliedEffect.mEffectIndex;
|
||||
absorbEffects.emplace_back(absorbEffect);
|
||||
|
||||
// Morrowind negates reflected Absorb spells so the original caster won't be harmed.
|
||||
if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game"))
|
||||
{
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(std::string(), true,
|
||||
absorbEffects, source, caster.getClass().getCreatureStats(caster).getActorId());
|
||||
return;
|
||||
}
|
||||
|
||||
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(std::string(), true,
|
||||
absorbEffects, source, target.getClass().getCreatureStats(target).getActorId());
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
#ifndef MWMECHANICS_LINKEDEFFECTS_H
|
||||
#define MWMECHANICS_LINKEDEFFECTS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct ActiveEffect;
|
||||
struct EffectList;
|
||||
struct ENAMstruct;
|
||||
struct MagicEffect;
|
||||
struct Spell;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
// Try to reflect a spell effect. If it's reflected, it's also put into the passed reflected effects list.
|
||||
bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect,
|
||||
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects);
|
||||
|
||||
// Try to absorb a stat (skill, attribute, etc.) from the target and transfer it to the caster.
|
||||
void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect,
|
||||
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source);
|
||||
}
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,20 @@
|
||||
#ifndef GAME_MWMECHANICS_SPELLEFFECTS_H
|
||||
#define GAME_MWMECHANICS_SPELLEFFECTS_H
|
||||
|
||||
#include "activespells.hpp"
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
// These functions should probably be split up into separate Lua functions for each magic effect when magic is dehardcoded.
|
||||
// That way ESM::MGEF could point to two Lua scripts for each effect. Needs discussion.
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
// Applies a tick of a single effect. Returns true if the effect should be removed immediately
|
||||
bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt);
|
||||
|
||||
// Undoes permanent effects created by ESM::MagicEffect::AppliedOnce
|
||||
void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spell, const ESM::ActiveEffect& effect);
|
||||
}
|
||||
|
||||
#endif
|
@ -1,216 +0,0 @@
|
||||
#include "tickableeffects.hpp"
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/containerstore.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
|
||||
#include "actorutil.hpp"
|
||||
#include "npcstats.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude, bool allowDecreaseBelowZero = false)
|
||||
{
|
||||
DynamicStat<float> stat = creatureStats.getDynamic(index);
|
||||
stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero);
|
||||
creatureStats.setDynamic(index, stat);
|
||||
}
|
||||
|
||||
bool disintegrateSlot (const MWWorld::Ptr& ptr, int slot, float disintegrate)
|
||||
{
|
||||
if (!ptr.getClass().hasInventoryStore(ptr))
|
||||
return false;
|
||||
|
||||
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
|
||||
MWWorld::ContainerStoreIterator item = inv.getSlot(slot);
|
||||
|
||||
if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon))
|
||||
{
|
||||
if (!item->getClass().hasItemHealth(*item))
|
||||
return false;
|
||||
int charge = item->getClass().getItemHealth(*item);
|
||||
if (charge == 0)
|
||||
return false;
|
||||
|
||||
// Store remainder of disintegrate amount (automatically subtracted if > 1)
|
||||
item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate));
|
||||
|
||||
charge = item->getClass().getItemHealth(*item);
|
||||
charge -= std::min(static_cast<int>(disintegrate), charge);
|
||||
item->getCellRef().setCharge(charge);
|
||||
|
||||
if (charge == 0)
|
||||
{
|
||||
// Will unequip the broken item and try to find a replacement
|
||||
if (ptr != getPlayer())
|
||||
inv.autoEquip(ptr);
|
||||
else
|
||||
inv.unequipItem(*item, ptr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude)
|
||||
{
|
||||
if (magnitude == 0.f)
|
||||
return false;
|
||||
|
||||
bool receivedMagicDamage = false;
|
||||
bool godmode = actor == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
||||
|
||||
switch (effectKey.mId)
|
||||
{
|
||||
case ESM::MagicEffect::DamageAttribute:
|
||||
{
|
||||
if (godmode)
|
||||
break;
|
||||
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
|
||||
attr.damage(magnitude);
|
||||
creatureStats.setAttribute(effectKey.mArg, attr);
|
||||
break;
|
||||
}
|
||||
case ESM::MagicEffect::RestoreAttribute:
|
||||
{
|
||||
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
|
||||
attr.restore(magnitude);
|
||||
creatureStats.setAttribute(effectKey.mArg, attr);
|
||||
break;
|
||||
}
|
||||
case ESM::MagicEffect::RestoreHealth:
|
||||
case ESM::MagicEffect::RestoreMagicka:
|
||||
case ESM::MagicEffect::RestoreFatigue:
|
||||
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude);
|
||||
break;
|
||||
case ESM::MagicEffect::DamageHealth:
|
||||
if (godmode)
|
||||
break;
|
||||
receivedMagicDamage = true;
|
||||
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude);
|
||||
break;
|
||||
|
||||
case ESM::MagicEffect::DamageMagicka:
|
||||
case ESM::MagicEffect::DamageFatigue:
|
||||
{
|
||||
if (godmode)
|
||||
break;
|
||||
int index = effectKey.mId-ESM::MagicEffect::DamageHealth;
|
||||
static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game");
|
||||
adjustDynamicStat(creatureStats, index, -magnitude, index == 2 && uncappedDamageFatigue);
|
||||
break;
|
||||
}
|
||||
case ESM::MagicEffect::AbsorbHealth:
|
||||
if (!godmode || magnitude <= 0)
|
||||
{
|
||||
if (magnitude > 0.f)
|
||||
receivedMagicDamage = true;
|
||||
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
|
||||
}
|
||||
break;
|
||||
|
||||
case ESM::MagicEffect::AbsorbMagicka:
|
||||
case ESM::MagicEffect::AbsorbFatigue:
|
||||
if (!godmode || magnitude <= 0)
|
||||
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
|
||||
break;
|
||||
|
||||
case ESM::MagicEffect::DisintegrateArmor:
|
||||
{
|
||||
if (godmode)
|
||||
break;
|
||||
static const std::array<int, 9> priorities
|
||||
{
|
||||
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,
|
||||
MWWorld::InventoryStore::Slot_Boots
|
||||
};
|
||||
for (const int priority : priorities)
|
||||
{
|
||||
if (disintegrateSlot(actor, priority, magnitude))
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ESM::MagicEffect::DisintegrateWeapon:
|
||||
if (!godmode)
|
||||
disintegrateSlot(actor, MWWorld::InventoryStore::Slot_CarriedRight, magnitude);
|
||||
break;
|
||||
|
||||
case ESM::MagicEffect::SunDamage:
|
||||
{
|
||||
// isInCell shouldn't be needed, but updateActor called during game start
|
||||
if (!actor.isInCell() || !actor.getCell()->isExterior() || godmode)
|
||||
break;
|
||||
float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour();
|
||||
float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13)));
|
||||
float damageScale = 1.f - timeDiff / 7.f;
|
||||
// When cloudy, the sun damage effect is halved
|
||||
static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||
"fMagicSunBlockedMult")->mValue.getFloat();
|
||||
|
||||
int weather = MWBase::Environment::get().getWorld()->getCurrentWeather();
|
||||
if (weather > 1)
|
||||
damageScale *= fMagicSunBlockedMult;
|
||||
|
||||
adjustDynamicStat(creatureStats, 0, -magnitude * damageScale);
|
||||
if (magnitude * damageScale > 0.f)
|
||||
receivedMagicDamage = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ESM::MagicEffect::FireDamage:
|
||||
case ESM::MagicEffect::ShockDamage:
|
||||
case ESM::MagicEffect::FrostDamage:
|
||||
case ESM::MagicEffect::Poison:
|
||||
{
|
||||
if (godmode)
|
||||
break;
|
||||
adjustDynamicStat(creatureStats, 0, -magnitude);
|
||||
receivedMagicDamage = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case ESM::MagicEffect::DamageSkill:
|
||||
case ESM::MagicEffect::RestoreSkill:
|
||||
{
|
||||
if (!actor.getClass().isNpc())
|
||||
break;
|
||||
if (godmode && effectKey.mId == ESM::MagicEffect::DamageSkill)
|
||||
break;
|
||||
NpcStats &npcStats = actor.getClass().getNpcStats(actor);
|
||||
SkillValue& skill = npcStats.getSkill(effectKey.mArg);
|
||||
if (effectKey.mId == ESM::MagicEffect::RestoreSkill)
|
||||
skill.restore(magnitude);
|
||||
else
|
||||
skill.damage(magnitude);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (receivedMagicDamage && actor == getPlayer())
|
||||
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
#ifndef MWMECHANICS_TICKABLEEFFECTS_H
|
||||
#define MWMECHANICS_TICKABLEEFFECTS_H
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
class CreatureStats;
|
||||
struct EffectKey;
|
||||
|
||||
/// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed
|
||||
/// Note: this function works in loop, so magic effects should not be removed here to avoid iterator invalidation.
|
||||
/// @return Was the effect a tickable effect with a magnitude?
|
||||
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey& effectKey, float magnitude);
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue