Implement Resist & Weakness effects

actorid
scrawl 11 years ago
parent d49b6f19ff
commit b1a29eb27e

@ -69,7 +69,7 @@ add_openmw_dir (mwclass
add_openmw_dir (mwmechanics
mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects
drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow
aiescort aiactivate repair enchanting pathfinding security spellsuccess
aiescort aiactivate repair enchanting pathfinding security spellsuccess spellcasting
)
add_openmw_dir (mwbase

@ -468,9 +468,9 @@ namespace MWClass
{
weapon.getCellRef().mEnchantmentCharge -= castCost;
// Touch
othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ESM::RT_Touch, weapon.getClass().getName(weapon));
othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ptr, ESM::RT_Touch, weapon.getClass().getName(weapon));
// Self
getCreatureStats(ptr).getActiveSpells().addSpell(enchantmentName, ptr, ESM::RT_Self, weapon.getClass().getName(weapon));
getCreatureStats(ptr).getActiveSpells().addSpell(enchantmentName, ptr, ptr, ESM::RT_Self, weapon.getClass().getName(weapon));
// Target
MWBase::Environment::get().getWorld()->launchProjectile(enchantmentName, enchantment->mEffects, ptr, weapon.getClass().getName(weapon));
}
@ -936,7 +936,7 @@ namespace MWClass
/// \todo consider instant effects
return stats.getActiveSpells().addSpell (id, actor);
return stats.getActiveSpells().addSpell (id, actor, actor);
}
void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const

@ -5,7 +5,7 @@
#include "../mwworld/player.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/actionequip.hpp"
#include "../mwmechanics/spellsuccess.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwgui/inventorywindow.hpp"
#include "../mwgui/bookwindow.hpp"
#include "../mwgui/scrollwindow.hpp"

@ -9,7 +9,7 @@
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/actionequip.hpp"
#include "../mwmechanics/spellsuccess.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "spellicons.hpp"
#include "inventorywindow.hpp"

@ -16,11 +16,14 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwrender/animation.hpp"
#include "../mwworld/class.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "creaturestats.hpp"
#include "npcstats.hpp"
@ -74,7 +77,7 @@ namespace MWMechanics
for (std::vector<ESM::ENAMstruct>::const_iterator effectIter (effects.first.mList.begin());
effectIter!=effects.first.mList.end(); ++effectIter, ++i)
{
float magnitude = iter->second.mRandom[i];
float random = iter->second.mRandom[i];
if (effectIter->mRange != iter->second.mRange)
continue;
@ -83,7 +86,7 @@ namespace MWMechanics
int duration = effectIter->mDuration;
if (effects.second.first)
duration *= magnitude;
duration *= random;
MWWorld::TimeStamp end = start;
end += static_cast<double> (duration)*
@ -102,19 +105,21 @@ namespace MWMechanics
if (effectIter->mDuration==0)
{
param.mMagnitude =
static_cast<int> (magnitude / (0.1 * magicEffect->mData.mBaseCost));
static_cast<int> (random / (0.1 * magicEffect->mData.mBaseCost));
}
else
{
param.mMagnitude =
static_cast<int> (0.05*magnitude / (0.1 * magicEffect->mData.mBaseCost));
static_cast<int> (0.05*random / (0.1 * magicEffect->mData.mBaseCost));
}
}
else
param.mMagnitude = static_cast<int> (
(effectIter->mMagnMax-effectIter->mMagnMin)*magnitude + effectIter->mMagnMin);
mEffects.add (*effectIter, param);
(effectIter->mMagnMax-effectIter->mMagnMin)*random + effectIter->mMagnMin);
param.mMagnitude *= iter->second.mMultiplier[i];
if (param.mMagnitude)
mEffects.add (*effectIter, param);
}
}
}
@ -185,7 +190,7 @@ namespace MWMechanics
: mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp())
{}
bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range, const std::string& name, int effectIndex)
bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, ESM::RangeType range, const std::string& name, int effectIndex)
{
const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor);
@ -197,6 +202,8 @@ namespace MWMechanics
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin());
iter!=effects.first.mList.end(); ++iter)
{
if (iter->mRange != range)
continue;
if (iter->mDuration)
{
found = true;
@ -204,41 +211,37 @@ namespace MWMechanics
}
}
// If none of the effects need to apply, no need to add the spell
if (!found)
return false;
TContainer::iterator iter = mSpells.find (id);
float random = static_cast<float> (std::rand()) / RAND_MAX;
if (effects.second.first)
{
// ingredient -> special treatment required.
const NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor);
float x =
(npcStats.getSkill (ESM::Skill::Alchemy).getModified() +
0.2 * creatureStats.getAttribute (1).getModified()
+ 0.1 * creatureStats.getAttribute (7).getModified())
* creatureStats.getFatigueTerm();
random *= 100;
random = random / std::min (x, 100.0f);
random *= 0.25 * x;
}
ActiveSpellParams params;
for (unsigned int i=0; i<effects.first.mList.size(); ++i)
params.mRandom.push_back(static_cast<float> (std::rand()) / RAND_MAX);
{
float random = static_cast<float> (std::rand()) / RAND_MAX;
if (effects.second.first)
{
// ingredient -> special treatment required.
const NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor);
float x =
(npcStats.getSkill (ESM::Skill::Alchemy).getModified() +
0.2 * creatureStats.getAttribute (1).getModified()
+ 0.1 * creatureStats.getAttribute (7).getModified())
* creatureStats.getFatigueTerm();
random *= 100;
random = random / std::min (x, 100.0f);
random *= 0.25 * x;
}
params.mRandom.push_back(random);
}
params.mRange = range;
params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp();
params.mName = name;
if (iter==mSpells.end() || stacks)
mSpells.insert (std::make_pair (id, params));
else
iter->second = params;
params.mMultiplier.resize(effects.first.mList.size(), 1);
/*
for (int i=0; i<effects.first.mList.size(); ++i)
@ -258,19 +261,35 @@ namespace MWMechanics
}
*/
// Play sounds & particles
bool first=true;
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin());
iter!=effects.first.mList.end(); ++iter)
int i = 0;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.first.mList.begin());
effectIt!=effects.first.mList.end(); ++effectIt, ++i)
{
if (iter->mRange != range)
if (effectIt->mRange != range)
continue;
// Try resisting effect in case its harmful
const ESM::Spell *spell =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id);
params.mMultiplier[i] = MWMechanics::getEffectMultiplier(effectIt->mEffectID, actor, caster, spell);
if (params.mMultiplier[i] == 0)
{
if (actor.getRefData().getHandle() == "player")
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
else
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
}
// If fully resisted, don't play sounds or particles
if (params.mMultiplier[i] == 0)
continue;
// TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World.
const ESM::MagicEffect *magicEffect =
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
iter->mEffectID);
effectIt->mEffectID);
// Only the sound of the first effect plays
if (first)
@ -296,6 +315,11 @@ namespace MWMechanics
first = false;
}
if (iter==mSpells.end() || stacks)
mSpells.insert (std::make_pair (id, params));
else
iter->second = params;
mSpellsChanged = true;
return true;
@ -415,7 +439,9 @@ namespace MWMechanics
magnitude = static_cast<int> (0.05*it->second.mRandom[i] / (0.1 * magicEffect->mData.mBaseCost));
}
visitor.visit(*effectIt, name, magnitude, remainingTime);
magnitude *= it->second.mMultiplier[i];
if (magnitude)
visitor.visit(*effectIt, name, magnitude, remainingTime);
}
}
}

@ -37,7 +37,7 @@ namespace MWMechanics
// Effect magnitude multiplier. Use 0 to completely disable the effect
// (if it was resisted, reflected or absorbed). Use (0,1) for partially resisted.
std::vector<bool> mMultiplier;
std::vector<float> mMultiplier;
// Display name, we need this for enchantments, which don't have a name - so you need to supply the
// name of the item with the enchantment to addSpell
@ -85,11 +85,12 @@ namespace MWMechanics
ActiveSpells();
bool addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range = ESM::RT_Self, const std::string& name = "", int effectIndex = -1);
bool addSpell (const std::string& id, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, ESM::RangeType range = ESM::RT_Self, const std::string& name = "", int effectIndex = -1);
///< Overwrites an existing spell with the same ID. If the spell does not have any
/// non-instant effects, it is ignored.
/// @param id
/// @param actor
/// @param actor actor to add the spell to
/// @param caster actor who casted the spell
/// @param range Only effects with range type \a range will be applied
/// @param name Display name for enchantments, since they don't have a name in their record
/// @param effectIndex Only apply one specific effect - useful for reflecting spells, since each effect is reflected individually

@ -114,6 +114,72 @@ namespace MWMechanics
return school;
}
/// @return >=100 for fully resisted. can also return negative value for damage amplification.
inline float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL)
{
const ESM::MagicEffect *magicEffect =
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
effectId);
const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
float resisted = 0;
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
{
short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId);
short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId);
float resistance = 0;
if (resistanceEffect != -1)
resistance += stats.getMagicEffects().get(resistanceEffect).mMagnitude;
if (weaknessEffect != -1)
resistance -= stats.getMagicEffects().get(weaknessEffect).mMagnitude;
float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
float luck = stats.getAttribute(ESM::Attribute::Luck).getModified();
float x = (willpower + 0.1 * luck) * stats.getFatigueTerm();
// This makes spells that are easy to cast harder to resist and vice versa
if (spell != NULL)
{
float castChance = getSpellSuccessChance(spell, caster);
if (castChance > 0)
x *= 50 / castChance;
}
float roll = static_cast<float>(std::rand()) / RAND_MAX * 100;
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
roll -= resistance;
if (x <= roll)
x = 0;
else
{
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
x = 100;
else
x = roll / std::min(x, 100.f);
}
x = std::min(x + resistance, 100.f);
resisted = x;
}
return resisted;
}
inline float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL)
{
float resistance = getEffectResistance(effectId, actor, caster, spell);
if (resistance >= 0)
return 1 - resistance / 100.f;
else
return -(resistance-100) / 100.f;
}
}
#endif

@ -16,7 +16,7 @@ namespace MWWorld
// TODO: Apply RT_Self effects on the door / container that triggered the trap. Not terribly useful, but you could
// make it lock itself when activated for example.
actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(mSpellId, actor, ESM::RT_Touch);
actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(mSpellId, actor, actor, ESM::RT_Touch);
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(mSpellId);

@ -11,6 +11,8 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "esmstore.hpp"
#include "class.hpp"
@ -292,47 +294,37 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect)
continue;
// Roll some dice, one for each effect
std::vector<EffectParams> params;
params.resize(enchantment.mEffects.mList.size());
for (unsigned int i=0; i<params.size();++i)
params[i].mRandom = static_cast<float> (std::rand()) / RAND_MAX;
bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) != mPermanentMagicEffectMagnitudes.end());
if (!existed)
{
// Roll some dice, one for each effect
params.resize(enchantment.mEffects.mList.size());
for (unsigned int i=0; i<params.size();++i)
params[i].mRandom = static_cast<float> (std::rand()) / RAND_MAX;
// Try resisting each effect
int i=0;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (enchantment.mEffects.mList.begin());
effectIt!=enchantment.mEffects.mList.end(); ++effectIt)
{
const ESM::MagicEffect *magicEffect =
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
effectIt->mEffectID);
//const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
float resisted = 0;
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
{
}
params[i].mMultiplier = (100.f - resisted) / 100.f;
params[i].mMultiplier = MWMechanics::getEffectMultiplier(effectIt->mEffectID, actor, actor);
++i;
}
// Note that using the RefID as a key here is not entirely correct.
// Consider equipping the same item twice (e.g. a ring)
// However, permanent enchantments with a random magnitude are kind of an exploit anyway,
// so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case.
mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = params;
}
else
params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID];
int i=0;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (enchantment.mEffects.mList.begin());
effectIt!=enchantment.mEffects.mList.end(); ++effectIt)
effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i)
{
const ESM::MagicEffect *magicEffect =
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
@ -355,7 +347,6 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
magnitude *= params[i].mMultiplier;
if (magnitude)
mMagicEffects.add (*effectIt, magnitude);
++i;
}
}
}
@ -554,8 +545,9 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (enchantment.mEffects.mList.begin());
effectIt!=enchantment.mEffects.mList.end(); ++effectIt)
{
float random = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i].mRandom;
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random;
const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i];
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom;
magnitude *= params.mMultiplier;
visitor.visit(*effectIt, (**iter).getClass().getName(**iter), magnitude);
++i;

@ -26,7 +26,7 @@
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/spellsuccess.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwrender/sky.hpp"
@ -2158,7 +2158,7 @@ namespace MWWorld
// Now apply the spell!
// Apply Self portion
actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(selectedSpell, actor, ESM::RT_Self, sourceName);
actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(selectedSpell, actor, actor, ESM::RT_Self, sourceName);
// Apply Touch portion
// TODO: Distance is probably incorrect, and should it be hardcoded?
@ -2166,7 +2166,7 @@ namespace MWWorld
if (!contact.first.isEmpty())
{
if (contact.first.getClass().isActor())
contact.first.getClass().getCreatureStats(contact.first).getActiveSpells().addSpell(selectedSpell, contact.first, ESM::RT_Touch, sourceName);
contact.first.getClass().getCreatureStats(contact.first).getActiveSpells().addSpell(selectedSpell, contact.first, actor, ESM::RT_Touch, sourceName);
else
{
// We hit a non-actor, e.g. a door. Only instant effects are relevant.

Loading…
Cancel
Save