Research:Magic

From OpenMW Wiki
Jump to: navigation, search

Spell Casting

Chance of successful spell cast

Actions affected On cast attempt, on AI spell evaluation, on UI update or in spellmaking
Description Uses common term fatigueTerm.
Implementation status Implemented
Analysis status Verified
if actor is silenced: return 0
if <flag> and current magicka < magicka cost: return 0      # see comments
if spell.castingType not (spell or power): return 100
 
if castingType is power:
    check power timeout for this spell (24 hours from previous use)
    if power is available: return 100 else return 0
 
if castingType is spell:
    if spell is flagged always succeeds: return 100
 
    y = FLOAT_MAX
    for each effect in spell:
        x = effect.duration
        if not effect.magicEffect.flags & UNCAPPED_DAMAGE: x = max(1, x)
        x *= 0.1 * effect.magicEffect.baseMagickaCost
        x *= 0.5 * (effect.magnitudeMin + effect.magnitudeMax)
        x += effect.radius * 0.05 * effect.magicEffect.baseMagickaCost
        if effect.rangeType & CAST_TARGET: x *= 1.5
        x *= fEffectCostMult
 
        s = 2 * actor.skill[effect.magicEffect.school.associatedSkillId]
        if (s - x) < y:
            y = s - x
            effectiveSchool = effect.magicEffect.school
            lowestSkill = s
 
    # castBonus is the stat modified by the Sound magic effect;
    # it behaves in the same way as other hidden stats, that is positive for a bonus, and negative for a penalty
    castChance = (lowestSkill - spellCost + actor.castBonus + 0.2 * actorWillpower + 0.1 * actorLuck) * fatigueTerm
    if godmode is on: castChance = 100
    return castChance, effectiveSchool


Comments

The cast chance calculation is used in multiple places, some where magicka use may not be important, so there is a <flag>, which is normally true for casting/AI use and the inventory spell list, and false for all other UI uses of spell chance. The function returns the weakest school used out of all the effects, which is the skill that will gain progress when the spell is cast. Note that spells that are marked "always succeed" do not seem to contribute to skill progress.

Cast cost

Actions affected On cast attempt
Description Magicka and fatigue costs of casting. Uses common term normalizedEncumbrance.
Implementation status Implemented, bug corrected
Analysis status Verified, but fatigue cost contains a bug

Casting cost is an independent variable in the spell data. For spellmaker generated spells, refer to Spell merchant.

magicka loss = spell.magickaCost

Failed casts still incur all costs.


Casting incurs a fatigue hit.

fatigue loss = magickaCost * (fFatigueSpellBase * normalizedEncumbrance * fFatigueSpellMult)

which is how Morrowind behaves but is clearly incorrect; a fixed formula is

fatigue loss = magickaCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult)

The GMST fFatigueSpellCostMult is unused.

Magic resistance

Spell application type "Magic"

Actions affected On magic effect hit, applies separately for each effect
Description Uses resistance effect attributes.
Implementation status Implemented
Analysis status Surrounding mechanics still need evaluating
i = look up corresponding resistance effect attribute for effect
if effect is not linked to a resistance effect attribute: effect cannot be resisted
 
resistance = get defending actor effect attribute i
castChance = attackers chance of casting spell (uncapped)
 
x = (willpower + 0.1 * luck) * fatigueTerm
if castChance > 0: x *= 50 / castChance
roll float [0..100]
if effect.flags & NO_MAGNITUDE: roll -= resistance
 
if x <= roll:
    x = 0
else:
    if effect.flags & NO_MAGNITUDE:
        x = 100
    else:
        x = roll / min(x, 100)
 
x = min(x + resistance, 100)
 
if x > 100: effect is fully resisted, show message, remove effect
else: effect is applied with resistance x%
Comments

Note that this punishes casting difficult (low cast chance) spells versus high willpower enemies. The castChance scaling will go almost guarantee resists if castChance < 25%.

Has an interesting situation, where spells with magnitude _do_ take resistance from willpower but at a very low rate (0.01x due to lack of rescaling) than would be expected, making willpower resistance almost useless for that class of spells.

The way resistance affects the final attribute, is that magnitude is stored untouched, resistance is stored with the active effect, and resistance is applied every time the spell has an application tick. The outward effect is that spell effect tooltips list the original magnitude, without factoring resistance.


Spell application types "Disease" or "Blight"

Actions affected On spell hit, applies once for entire spell
Description
Implementation status Implemented
Analysis status Surrounding mechanics still need evaluating
x = actor effect attribute "Resist Disease" or "Resist Blight"
roll 100, if roll <= x then fully resist the disease

Item Charge

Using enchanted items

Actions affected On enchanted item use
Description Charge consumed on use depends on skill.
Implementation status Implemented
Analysis status Verified; but a poorly scaling mechanic
x = 0.01 * (110 - pcEnchant)
charge used = int(x * enchant base charge cost)

This makes items last 10 times longer at 100 enchant compared to 10 enchant, making it scale a little too well. MCP uses x = 0.025 * (400 - pcEnchant).


Natural recharge

Actions affected On game tick
Description Enchanted items may gain charge over time.
Implementation status Implemented
Analysis status Verified

Every item in the player's inventory charges by tickTime * fMagicItemRechargePerSecond. Items anywhere else do not charge.

Soulgem charging

Refer to Player Craft Skills.


Magic effects

Spells are combinations of one or more effects, which have associated code specific to the effect, and properties which cover cast ranges, resistances and other generalized behaviours that may be shared by a small set of effects.


Effect attributes

All actors have "hidden" attributes that effects apply to, to allow effects to stack, have resistance applied and counteract each other. These attributes are saved with actors, appearing in the actor data in the same order. All effect attributes are applied and stored as integers. Resists and weaknesses are unified under the Resist- attributes, with resistances being positive and weaknesses being negative. Non-magnitude spells have an effective attribute of 1 when active.

Some attributes provide resistance values vs magic effects when calculating magic mitigation. These resistances only apply in certain cases, check magic resistance.

Index Name Provides resistance against magic effects
0 Fortify Attack
1 Sanctuary Disintegrate *
2 Resist Magicka Drain *, Damage *, Absorb *, Weakness to *, Burden, Charm, Silence, Blind, Sound, Calm, Frenzy, Demoralize, Rally, Turn Undead
3 Resist Fire Fire
4 Resist Frost Frost
5 Resist Shock Shock
6 Resist Common Disease Vampirism
7 Resist Blight Disease
8 Resist Corprus Corprus
9 Resist Poison Poison
10 Resist Paralysis Paralysis
11 Chameleon
12 Resist Normal Weapons
13 Water Breathing
14 Water Walking
15 Swift Swim
16 Jump
17 Levitate
18 Shield
19 Sound
20 Silence
21 Blind
22 Paralyze
23 Invisibility
24 Fight
25 Flee
26 Hello
27 Alarm

Effect flags

These are fixed properties of magic effects. Obtained from the code, these are not affected by mods.

Bit Enum name Status Description
0x1 TARGET_SKILL implemented Affects a specific skill, which is specified elsewhere in the effect structure.
0x2 TARGET_ATTR implemented Affects a specific attribute, which is specified elsewhere in the effect structure.
0x4 NO_DURATION implemented Has no duration. Only runs effect once on cast.
0x8 NO_MAGNITUDE implemented Has no magnitude.
0x10 HARMFUL implemented Counts as a harmful effect. Interpreted as useful effects for attack by the AI, and is treated as a bad effect in alchemy.
0x20 CONTINUOUS_VFX implemented The effect's hit particle VFX repeats for the full duration of the spell, rather than occurring once on hit.
0x40 CAST_SELF implemented Allows range - cast on self.
0x80 CAST_TOUCH implemented Allows range - cast on touch.
0x100 CAST_TARGET implemented Allows range - cast on target.
0x200 - 0x800 not started Unverified. Appears unused.
0x1000 APPLIED_ONCE not started An effect that is applied once it lands, instead of continuously. Allows an effect to reduce an attribute below zero; removes the normal minimum effect duration of 1 second.
0x2000 STEALTH not started Unverified. Used only by chameleon effect.
0x4000 NON_RECASTABLE not started Does not land if parent spell is already affecting target. Shows "you cannot re-cast" message for self target.
0x8000 ILLEGAL_DAEDRA not started Unverified. Appears to be flagged on summon daedra effects. Possibly for guards to detect summoning in towns.
0x10000 UNREFLECTABLE implemented Cannot be reflected, the effect always lands normally.
0x20000 CASTER_LINKED implemented Must quench if caster is dead, or not an NPC/creature. Not allowed in containter/door trap spells.


Flags for all effects:

int effectFlags[] = {
    0x11c8, 0x11c0, 0x11c8, 0x11e0, 0x11e0, 0x11e0, 0x11e0, 0x11d0,
    0x11c0, 0x11c0, 0x11e0, 0x11c0, 0x11184, 0x11184, 0x1f0, 0x1f0,
    0x1f0, 0x11d2, 0x11f0, 0x11d0, 0x11d0, 0x11d1, 0x1d2, 0x1f0,
    0x1d0, 0x1d0, 0x1d1, 0x1f0, 0x11d0, 0x11d0, 0x11d0, 0x11d0,
    0x11d0, 0x11d0, 0x11d0, 0x11d0, 0x11d0, 0x1d0, 0x1d0, 0x11c8,
    0x31c0, 0x11c0, 0x11c0, 0x11c0, 0x1180, 0x11d8, 0x11d8, 0x11d0,
    0x11d0, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180,
    0x11180, 0x11c4, 0x111b8, 0x1040, 0x104c, 0x104c, 0x104c, 0x104c,
    0x1040, 0x1040, 0x1040, 0x11c0, 0x11c0, 0x1cc, 0x1cc, 0x1cc,
    0x1cc, 0x1cc, 0x1c2, 0x1c0, 0x1c0, 0x1c0, 0x1c1, 0x11c2,
    0x11c0, 0x11c0, 0x11c0, 0x11c1, 0x11c0, 0x21192, 0x20190, 0x20190,
    0x20190, 0x21191, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0,
    0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x1c0, 0x11190, 0x9048, 0x9048,
    0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048,
    0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x11c0, 0x1180, 0x1180,
    0x5048, 0x5048, 0x5048, 0x5048, 0x5048, 0x5048, 0x1188, 0x5048,
    0x5048, 0x5048, 0x5048, 0x5048, 0x1048, 0x104c, 0x1048, 0x40,
    0x11c8, 0x1048, 0x1048, 0x1048, 0x1048, 0x1048, 0x1048
};

Specific magic effects

While all magic effects have programmed behaviour, there are specific ones that may have complicated side effects. Any particular information for a single effect is documented here.


Corprus

Actions affected On application, and every 24 hours
Description Spell effect "Corprus"
Implementation status
Analysis status Under research

The corprus effect is closely linked to the corprus spell. Every 24 hours after the contraction of corprus, all other effects on the same spell as corprus, that also have effect flag 0x1000 (APPLIED_ONCE) are re-applied in a special case. This increases the magnitude of the effect, while correctly recording the amount applied so it can be removed later. Without other effects on the spell, corprus does nothing.

On removal, the drain spells are removed normally, while another special case interdicts any removal of positive attribute effects. Instead, a fortification is converted into a restore effect of the same magnitude.


Sun Damage

Actions affected Continuously applied, triggering the hit fader to pulse
Description Spell effect "Sun Damage"
Implementation status
Analysis status Verified

TitleCaps variables indicate values taken from the morrowind.ini section related to that object.

if current cell is interior only: return 0
 
if gamehour <= Weather.SunriseTime or gamehour >= Weather.SunsetTime + Weather.SunsetDuration:
    sunRisen = 0
elif gamehour <= Weather.SunriseTime + Weather.SunriseDuration:
    sunRisen = (gamehour - Weather.SunriseTime) / Weather.SunriseDuration
elif gamehour > Weather.SunsetTime:
    sunRisen = 1 - ((gamehour - Weather.SunsetTime) / Weather.SunsetDuration)
else:
    sunRisen = 1
 
if weather is changing:
    # transition is 0 at the start of a weather change and 1.0 at the end
    # note that CloudsMaximumPercent is not actually a percentage
    if transition < nextWeather.CloudsMaximumPercent:
        t = transition / nextWeather.CloudsMaximumPercent
        sunVisibility = (1-t) * currentWeather.GlareView + t * nextWeather.GlareView
    else:
        sunVisibility = currentWeather.GlareView
 
damageScale = max(sunVisibility * sunRisen, fMagicSunBlockedMult * sunRisen)
damageScale = max(0, min(damage, 1))
 
return damageScale * spelleffect.magnitude