diff --git a/Assets/Scripts/Game/Formulas/FormulaHelper.cs b/Assets/Scripts/Game/Formulas/FormulaHelper.cs
index 8e0fbf86a6..4af462d71e 100644
--- a/Assets/Scripts/Game/Formulas/FormulaHelper.cs
+++ b/Assets/Scripts/Game/Formulas/FormulaHelper.cs
@@ -1739,6 +1739,127 @@ public static void FatigueDamage(EnemyEntity attacker, DaggerfallEntity target,
#endregion
+
+ #region Spell Absorption
+
+ ///
+ /// Tests incoming effect for spell absorption. If absorption succeeds the entity will
+ /// block effect and recover spell points equal to the casting cost of blocked effect.
+ /// If target does not have enough spell points free to absorb effect cost then effect will NOT be absorbed.
+ /// For example if player has 0 of 50 spell points available, they can absorb an incoming effect costing up to 50 spell points.
+ /// An effect costing 51 spell points cannot be absorbed. It's "all or nothing".
+ /// Notes:
+ /// - There are two variants of spell absorption in Daggerfall.
+ /// - Career-based: This is the "none / in light / in darkness / always" assigned to entity career kit.
+ /// - Effect-based: Generated by having an active Spell Absorption effect from a spell or item.
+ /// - In classic effect-based absorption from spells/items will override career-based absorption. Not sure if bug.
+ /// - Career-based absorption will always succeed chance check.
+ /// - Spell-based will roll for check on each absorb attempt.
+ ///
+ /// Incoming effect.
+ /// Source bundle target type for spell cost calculation.
+ /// Caster entity for spell cost calculation.
+ /// Target entity for spell cost calculation.
+ /// The absorb incumbent effect, if any, on target
+ /// The amount of spell points absorbed. 0 if absorb failed
+ public static int TryAbsorption(IEntityEffect effect, TargetTypes targetType, DaggerfallEntity casterEntity, DaggerfallEntity targetEntity, SpellAbsorption absorbEffectOnTarget)
+ {
+ Func del;
+ if (TryGetOverride("TryAbsorption", out del))
+ {
+ return del(effect, targetType, casterEntity, targetEntity, absorbEffectOnTarget);
+ }
+
+ var absorbSpellPointsOut = 0;
+
+ // Effect cannot be null
+ if (effect == null)
+ return 0;
+
+ // Currently only absorbing Destruction magic - not sure on status of absorbing other magic schools
+ // This is to prevent something as benign as a self-heal from player being blocked and absorbed
+ // With current design, absorption is checked for ALL incoming effects to entity so require some sanity checks
+ if (effect.Properties.MagicSkill != DFCareer.MagicSkills.Destruction)
+ return 0;
+
+ // Get casting cost for this effect
+ // Costs are calculated as if target cast the spell, not the actual caster
+ // Note that if player self-absorbs a spell this will be equal anyway
+ int effectCastingCost = GetEffectCastingCost(effect, targetType, targetEntity);
+
+ // The entity must have enough spell points free to absorb incoming effect
+ int availableSpellPoints = targetEntity.MaxMagicka - targetEntity.CurrentMagicka;
+ if (effectCastingCost > availableSpellPoints)
+ return 0;
+ else
+ absorbSpellPointsOut = effectCastingCost;
+
+ // Handle effect-based absorption
+ if (absorbEffectOnTarget != null && TryEffectBasedAbsorption(effect, absorbEffectOnTarget, targetEntity))
+ return absorbSpellPointsOut;
+
+ // Handle career-based absorption
+ if (targetEntity.Career.SpellAbsorption != DFCareer.SpellAbsorptionFlags.None && TryCareerBasedAbsorption(effect, targetEntity))
+ return absorbSpellPointsOut;
+
+ // Handle persistant absorption (e.g. special advantage general/day/night or from weapon effects)
+ if (targetEntity.IsAbsorbingSpells)
+ return absorbSpellPointsOut;
+
+ return 0;
+ }
+
+ static int GetEffectCastingCost(IEntityEffect effect, TargetTypes targetType, DaggerfallEntity casterEntity)
+ {
+ (int _, int spellPointCost) = FormulaHelper.CalculateEffectCosts(effect, effect.Settings, casterEntity);
+ spellPointCost = FormulaHelper.ApplyTargetCostMultiplier(spellPointCost, targetType);
+
+ // Spells always cost at least 5 spell points
+ // Otherwise it's possible for absorbs to make spell point pool go down as spell costs 5 but caster absorbs 0
+ if (spellPointCost < 5)
+ spellPointCost = 5;
+
+ //Debug.LogFormat("Calculated {0} spell point cost for effect {1}", spellPointCost, effect.Key);
+
+ return spellPointCost;
+ }
+
+ static bool TryEffectBasedAbsorption(IEntityEffect effect, SpellAbsorption absorbEffect, DaggerfallEntity entity)
+ {
+ int chance = absorbEffect.Settings.ChanceBase + absorbEffect.Settings.ChancePlus * (int)Mathf.Floor(entity.Level / absorbEffect.Settings.ChancePerLevel);
+
+ return Dice100.SuccessRoll(chance);
+ }
+
+ static bool TryCareerBasedAbsorption(IEntityEffect effect, DaggerfallEntity entity)
+ {
+ // Always resists
+ DFCareer.SpellAbsorptionFlags spellAbsorption = entity.Career.SpellAbsorption;
+ if (spellAbsorption == DFCareer.SpellAbsorptionFlags.Always)
+ return true;
+
+ // Resist in darkness (inside building or dungeon or outside at night)
+ // Use player for inside/outside context - everything is where the player is
+ if (spellAbsorption == DFCareer.SpellAbsorptionFlags.InDarkness)
+ {
+ if (GameManager.Instance.PlayerEnterExit.IsPlayerInside)
+ return true;
+ else if (DaggerfallUnity.Instance.WorldTime.Now.IsNight)
+ return true;
+ }
+
+ // Resist in light (outside during the day)
+ if (spellAbsorption == DFCareer.SpellAbsorptionFlags.InLight)
+ {
+ if (!GameManager.Instance.PlayerEnterExit.IsPlayerInside && DaggerfallUnity.Instance.WorldTime.Now.IsDay)
+ return true;
+ }
+
+ return false;
+ }
+
+ #endregion
+
#region Enemies
// Generates health for enemy classes based on level and class
diff --git a/Assets/Scripts/Game/MagicAndEffects/EntityEffectManager.cs b/Assets/Scripts/Game/MagicAndEffects/EntityEffectManager.cs
index 0d6d2c94b3..858e10cfa3 100644
--- a/Assets/Scripts/Game/MagicAndEffects/EntityEffectManager.cs
+++ b/Assets/Scripts/Game/MagicAndEffects/EntityEffectManager.cs
@@ -501,12 +501,14 @@ public void AssignBundle(EntityEffectBundle sourceBundle, AssignBundleFlags flag
// Set parent bundle
effect.ParentBundle = instancedBundle;
- // Spell Absorption, Reflection, Resistance - must have a caster entity set
- if (sourceBundle.CasterEntityBehaviour)
+ // Spell Absorption, Reflection, Resistance - must have a caster entity set and only work for spells
+ if (sourceBundle.CasterEntityBehaviour && sourceBundle.Settings.BundleType == BundleTypes.Spell)
{
// Spell Absorption
- int absorbSpellPoints;
- if (sourceBundle.Settings.BundleType == BundleTypes.Spell && TryAbsorption(effect, sourceBundle.Settings.TargetType, sourceBundle.CasterEntityBehaviour.Entity, out absorbSpellPoints))
+
+ SpellAbsorption absorbEffect = FindIncumbentEffect() as SpellAbsorption;
+ int absorbSpellPoints = FormulaHelper.TryAbsorption(effect, sourceBundle.Settings.TargetType, sourceBundle.CasterEntityBehaviour.Entity, entityBehaviour.Entity, absorbEffect);
+ if (absorbSpellPoints > 0)
{
// Spell passed all checks and was absorbed - tally cost output to target
totalAbsorbed += absorbSpellPoints;
@@ -518,11 +520,11 @@ public void AssignBundle(EntityEffectBundle sourceBundle, AssignBundleFlags flag
}
// Spell Reflection
- if (!bypassSavingThrows && sourceBundle.Settings.BundleType == BundleTypes.Spell && TryReflection(sourceBundle))
+ if (!bypassSavingThrows && TryReflection(sourceBundle))
continue;
// Spell Resistance
- if (!bypassSavingThrows && sourceBundle.Settings.BundleType == BundleTypes.Spell && TryResistance(sourceBundle))
+ if (!bypassSavingThrows && TryResistance(sourceBundle))
continue;
}
@@ -1136,68 +1138,7 @@ void RerollEffectCallback(IEntityEffect effectTemplate, DaggerfallUnityItem item
#endregion
- #region Spell Absorption
-
- ///
- /// Tests incoming effect for spell absorption. If absorption succeeds the entity will
- /// block effect and recover spell points equal to the casting cost of blocked effect.
- /// If target does not have enough spell points free to absorb effect cost then effect will NOT be absorbed.
- /// For example if player has 0 of 50 spell points available, they can absorb an incoming effect costing up to 50 spell points.
- /// An effect costing 51 spell points cannot be absorbed. It's "all or nothing".
- /// Notes:
- /// - There are two variants of spell absorption in Daggerfall.
- /// - Career-based: This is the "none / in light / in darkness / always" assigned to entity career kit.
- /// - Effect-based: Generated by having an active Spell Absorption effect from a spell or item.
- /// - In classic effect-based absorption from spells/items will override career-based absorption. Not sure if bug.
- /// - Career-based absorption will always succeed chance check.
- /// - Spell-based will roll for check on each absorb attempt.
- ///
- /// Incoming effect.
- /// Source bundle target type for spell cost calculation.
- /// Source caster entity behaviour for spell cost calculation.
- /// Number of spell points absorbed. Only valid when returning true.
- /// True if absorbed.
- bool TryAbsorption(IEntityEffect effect, TargetTypes targetType, DaggerfallEntity casterEntity, out int absorbSpellPointsOut)
- {
- absorbSpellPointsOut = 0;
-
- // Effect cannot be null
- if (effect == null)
- return false;
-
- // Currently only absorbing Destruction magic - not sure on status of absorbing other magic schools
- // This is to prevent something as benign as a self-heal from player being blocked and absorbed
- // With current design, absorption is checked for ALL incoming effects to entity so require some sanity checks
- if (effect.Properties.MagicSkill != DFCareer.MagicSkills.Destruction)
- return false;
-
- // Get casting cost for this effect
- // Costs are calculated as if target cast the spell, not the actual caster
- // Note that if player self-absorbs a spell this will be equal anyway
- int effectCastingCost = GetEffectCastingCost(effect, targetType, entityBehaviour.Entity);
-
- // The entity must have enough spell points free to absorb incoming effect
- int availableSpellPoints = entityBehaviour.Entity.MaxMagicka - entityBehaviour.Entity.CurrentMagicka;
- if (effectCastingCost > availableSpellPoints)
- return false;
- else
- absorbSpellPointsOut = effectCastingCost;
-
- // Handle effect-based absorption
- SpellAbsorption absorbEffect = FindIncumbentEffect() as SpellAbsorption;
- if (absorbEffect != null && TryEffectBasedAbsorption(effect, absorbEffect, entityBehaviour.Entity))
- return true;
-
- // Handle career-based absorption
- if (entityBehaviour.Entity.Career.SpellAbsorption != DFCareer.SpellAbsorptionFlags.None && TryCareerBasedAbsorption(effect, entityBehaviour.Entity))
- return true;
-
- // Handle persistant absorption (e.g. special advantage general/day/night or from weapon effects)
- if (entityBehaviour.Entity.IsAbsorbingSpells)
- return true;
-
- return false;
- }
+ #region Reflection & Resistance
///
/// Tests incoming spell bundle for reflection.
@@ -1267,56 +1208,7 @@ bool TryResistance(EntityEffectBundle sourceBundle)
}
return false;
- }
-
- int GetEffectCastingCost(IEntityEffect effect, TargetTypes targetType, DaggerfallEntity casterEntity)
- {
- (int _, int spellPointCost) = FormulaHelper.CalculateEffectCosts(effect, effect.Settings, casterEntity);
- spellPointCost = FormulaHelper.ApplyTargetCostMultiplier(spellPointCost, targetType);
-
- // Spells always cost at least 5 spell points
- // Otherwise it's possible for absorbs to make spell point pool go down as spell costs 5 but caster absorbs 0
- if (spellPointCost < 5)
- spellPointCost = 5;
-
- //Debug.LogFormat("Calculated {0} spell point cost for effect {1}", spellPointCost, effect.Key);
-
- return spellPointCost;
- }
-
- bool TryEffectBasedAbsorption(IEntityEffect effect, SpellAbsorption absorbEffect, DaggerfallEntity entity)
- {
- int chance = absorbEffect.Settings.ChanceBase + absorbEffect.Settings.ChancePlus * (int)Mathf.Floor(entity.Level / absorbEffect.Settings.ChancePerLevel);
-
- return Dice100.SuccessRoll(chance);
- }
-
- bool TryCareerBasedAbsorption(IEntityEffect effect, DaggerfallEntity entity)
- {
- // Always resists
- DFCareer.SpellAbsorptionFlags spellAbsorption = entity.Career.SpellAbsorption;
- if (spellAbsorption == DFCareer.SpellAbsorptionFlags.Always)
- return true;
-
- // Resist in darkness (inside building or dungeon or outside at night)
- // Use player for inside/outside context - everything is where the player is
- if (spellAbsorption == DFCareer.SpellAbsorptionFlags.InDarkness)
- {
- if (GameManager.Instance.PlayerEnterExit.IsPlayerInside)
- return true;
- else if (DaggerfallUnity.Instance.WorldTime.Now.IsNight)
- return true;
- }
-
- // Resist in light (outside during the day)
- if (spellAbsorption == DFCareer.SpellAbsorptionFlags.InLight)
- {
- if (!GameManager.Instance.PlayerEnterExit.IsPlayerInside && DaggerfallUnity.Instance.WorldTime.Now.IsDay)
- return true;
- }
-
- return false;
- }
+ }
#endregion