private float CalculateSpellCriticalChance(Unit victim, SpellInfo spellInfo, SpellSchoolMask schoolMask) { if (spellInfo.HasAttribute(SpellAttributes.CantCrit) || spellInfo.DamageClass == SpellDamageClass.None) { return(0.0f); } if (spellInfo.HasAttribute(SpellAttributes.AlwaysCrit)) { return(100.0f); } float critChance = unit.CritPercentage; if (victim == null) { return(Mathf.Max(critChance, 0.0f)); } switch (spellInfo.DamageClass) { case SpellDamageClass.Magic: if (!spellInfo.IsPositive) { critChance += victim.Auras.TotalAuraModifier(AuraEffectType.ModAttackerSpellCritChance); } goto default; case SpellDamageClass.Melee: if (!spellInfo.IsPositive) { critChance += victim.Auras.TotalAuraModifier(AuraEffectType.ModAttackerMeleeCritChance); } goto default; case SpellDamageClass.Ranged: if (!spellInfo.IsPositive) { critChance += victim.Auras.TotalAuraModifier(AuraEffectType.ModAttackerRangedCritChance); } goto default; default: critChance += victim.Auras.TotalAuraModifierForCaster(AuraEffectType.ModAttackerSpellCritChanceForCaster, unit.Id); critChance += victim.Auras.TotalAuraModifier(AuraEffectType.ModAttackerSpellAndWeaponCritChance); break; } return(Mathf.Max(critChance, 0.0f)); }
internal bool IsImmuneToSpell(SpellInfo spellInfo, Unit caster) { if (spellInfo.HasAttribute(SpellAttributes.UnaffectedByInvulnerability)) { return(false); } if (IsImmuneToSpellSchool(spellInfo.SchoolMask, spellInfo, caster)) { return(true); } bool isImmuneToAllEffects = true; foreach (SpellEffectInfo spellEffectInfo in spellInfo.Effects) { if (!IsImmuneToSpellEffect(spellEffectInfo, caster)) { isImmuneToAllEffects = false; break; } } if (isImmuneToAllEffects) { return(true); } return(false); }
private SpellCastResult Cast() { ExecutionState = SpellExecutionState.Casting; CastTime = Caster.Spells.ModifySpellCastTime(this, SpellInfo.CastTime); CastTimeLeft = CastTime; // cast if needed, if already casting launch instead, should only be possible with CanCastWhileCasting bool instantCast = CastTime <= 0.0f || Caster.SpellCast.IsCasting || spellValue.CastFlags.HasTargetFlag(SpellCastFlags.CastDirectly); if (casterMovementFlags.IsMoving() && !instantCast && !SpellInfo.HasAttribute(SpellAttributes.CastableWhileMoving)) { return(SpellCastResult.Moving); } if (!spellValue.CastFlags.HasTargetFlag(SpellCastFlags.IgnoreShapeShift) && SpellInfo.CanCancelForm(this)) { Caster.Auras.RemoveAurasWithEffect(AuraEffectType.ShapeShift); } Caster.SpellHistory.StartGlobalCooldown(SpellInfo); if (!spellValue.CastFlags.HasTargetFlag(SpellCastFlags.IgnoreAuraInterruptFlags) && !SpellInfo.HasAttribute(SpellCustomAttributes.DontBreakStealth)) { Caster.Auras.RemoveAurasWithInterrupt(AuraInterruptFlags.Cast); } if (instantCast) { Launch(); } return(SpellCastResult.Success); }
internal void EffectSchoolDamage(EffectSchoolDamage effect, int effectIndex, Unit target, SpellEffectHandleMode mode) { if (mode != SpellEffectHandleMode.HitStart || target == null || !target.IsAlive) { return; } float spellDamage = effect.CalculateSpellDamage(SpellInfo, effectIndex, Caster, target); if (SpellInfo.HasAttribute(SpellCustomAttributes.ShareDamage)) { spellDamage /= Mathf.Min(1, ImplicitTargets.TargetCountForEffect(effectIndex)); } for (var i = 0; i < effect.ConditionalModifiers.Count; i++) { ConditionalModifier modifier = effect.ConditionalModifiers[i]; if (modifier.Condition.With(Caster, target, this).IsApplicableAndValid) { modifier.Modify(ref spellDamage); } } EffectDamage += (int)spellDamage; }
private void ConsumePowers() { if (SpellInfo.HasAttribute(SpellAttributes.RequiresComboPoints)) { if (spellValue.CastFlags.HasTargetFlag(SpellCastFlags.IgnoreComboPoints)) { ConsumedComboPoints = Caster.MaxComboPoints; } else { ConsumedComboPoints = Caster.ComboPoints; Caster.Attributes.SetComboPoints(0); } } if (spellValue.CastFlags.HasTargetFlag(SpellCastFlags.IgnorePowerAndReagentCost)) { return; } foreach ((SpellPowerType, int)powerCost in powerCosts) { if (powerCost.Item1 == SpellPowerType.Health) { if (Caster.IsAlive) { Caster.ModifyHealth(-Mathf.Min(Caster.Health - 1, powerCost.Item2)); } continue; } Caster.Attributes.ModifyPower(powerCost.Item1, -powerCost.Item2); } }
internal bool IsImmunedToDamage(SpellInfo spellInfo, SpellSchoolMask?schoolMaskOverride = null, Unit caster = null) { SpellSchoolMask schoolMask = schoolMaskOverride ?? spellInfo.SchoolMask; if (spellInfo.HasAttribute(SpellAttributes.UnaffectedByInvulnerability) && spellInfo.HasAttribute(SpellAttributes.IgnoreHitResult)) { return(false); } if (spellInfo.HasAttribute(SpellAttributes.UnaffectedBySchoolImmune)) { return(false); } return(IsImmuneToSpellSchool(schoolMask, spellInfo, caster)); }
internal void DoUpdate(int deltaTime) { switch (ExecutionState) { case SpellExecutionState.Casting: CastTimeLeft -= deltaTime; if (CastTimeLeft <= 0) { Caster.SpellCast.HandleSpellCast(this, SpellCast.HandleMode.Finished); Launch(); break; } bool mayBeInterruptedByMove = CastTime - CastTimeLeft > MovementUtils.SpellMovementInterruptThreshold; if (mayBeInterruptedByMove && !SpellInfo.HasAttribute(SpellAttributes.CastableWhileMoving) && Caster.MovementInfo.IsMoving) { Caster.SpellCast.HandleSpellCast(this, SpellCast.HandleMode.Finished); Cancel(); } break; case SpellExecutionState.Processing: bool hasUnprocessed = false; foreach (SpellTargetEntry targetInfo in ImplicitTargets.Entries) { if (targetInfo.Processed) { continue; } targetInfo.Delay -= deltaTime; if (targetInfo.Delay <= 0) { ProcessTarget(targetInfo); } else { hasUnprocessed = true; } } if (!hasUnprocessed) { Finish(); } break; case SpellExecutionState.Completed: goto default; case SpellExecutionState.Preparing: goto default; default: Assert.IsTrue(SpellState == SpellState.Removing, $"Spell {SpellInfo.SpellName} updated in invalid state: {ExecutionState} while not being removed!"); break; } }
internal Spell(Unit caster, SpellInfo info, SpellCastingOptions options) { Logging.LogSpell($"Created new spell, current count: {++SpellAliveCount}"); spellManager = caster.World.SpellManager; spellValue.CastFlags = options.SpellFlags; casterMovementFlags = options.MovementFlags ?? caster.MovementInfo.Flags; SchoolMask = info.SchoolMask; CastTime = CastTimeLeft = EffectDamage = EffectHealing = 0; Caster = OriginalCaster = caster; SpellInfo = info; IsTriggered = spellValue.CastFlags.HasTargetFlag(SpellCastFlags.TriggeredByAura); if (IsTriggered) { spellValue.CastFlags |= SpellCastFlags.IgnoreTargetCheck | SpellCastFlags.IgnoreRangeCheck | SpellCastFlags.IgnoreShapeShift | SpellCastFlags.IgnoreAuraInterruptFlags | SpellCastFlags.IgnoreCasterState; } if (info.HasAttribute(SpellExtraAttributes.CanCastWhileCasting)) { spellValue.CastFlags |= SpellCastFlags.IgnoreCastInProgress | SpellCastFlags.CastDirectly; } if (info.HasAttribute(SpellExtraAttributes.IgnoreGcd)) { spellValue.CastFlags |= SpellCastFlags.IgnoreGcd; } if (info.HasAttribute(SpellExtraAttributes.IgnoreCasterAuras)) { spellValue.CastFlags |= SpellCastFlags.IgnoreCasterAuras; } CanReflect = SpellInfo.DamageClass == SpellDamageClass.Magic && !SpellInfo.HasAttribute(SpellAttributes.CantBeReflected) && !SpellInfo.HasAttribute(SpellAttributes.UnaffectedByInvulnerability) && !SpellInfo.IsPassive && !SpellInfo.IsPositive; ExplicitTargets = options.Targets ?? new SpellExplicitTargets(); ImplicitTargets = new SpellImplicitTargets(this); spellManager.Add(this); }
internal bool IsImmuneToSpell(SpellInfo spellInfo, Unit caster) { if (spellInfo.HasAttribute(SpellAttributes.UnaffectedByInvulnerability)) { return(false); } if (spellInfo.SchoolMask != 0) { SpellSchoolMask immunedSchools = 0; foreach (var schoolImmunityEntry in schoolImmunities) { if (!schoolImmunityEntry.Key.HasAnyFlag(spellInfo.SchoolMask)) { continue; } foreach (SpellInfo immunitySpells in schoolImmunityEntry.Value) { if (!immunitySpells.IsPositive || !spellInfo.IsPositive || !unit.IsFriendlyTo(caster)) { if (!spellInfo.CanPierceImmuneAura(immunitySpells)) { immunedSchools |= schoolImmunityEntry.Key; } } } } if (immunedSchools.HasTargetFlag(spellInfo.SchoolMask)) { return(true); } } bool isImmuneToAllEffects = true; foreach (SpellEffectInfo spellEffectInfo in spellInfo.Effects) { if (!IsImmuneToSpellEffect(spellEffectInfo, caster)) { isImmuneToAllEffects = false; break; } } if (isImmuneToAllEffects) { return(true); } return(false); }
private SpellCastResult ValidateCasterState() { if (spellValue.CastFlags.HasTargetFlag(SpellCastFlags.IgnoreCasterState)) { return(SpellCastResult.Success); } if (SpellInfo.HasAttribute(SpellAttributes.CantBeUsedInCombat) && Caster.Combat.InCombat) { return(SpellCastResult.AffectingCombat); } return(SpellCastResult.Success); }
internal void StartGlobalCooldown(SpellInfo spellInfo) { if (spellInfo.HasAttribute(SpellExtraAttributes.DoesNotTriggerGcd)) { return; } if (GlobalCooldownLeft > spellInfo.GlobalCooldownTime) { return; } GlobalCooldown = spellInfo.GlobalCooldownTime; GlobalCooldownLeft = GlobalCooldown; casterState.GlobalCooldown.CooldownTime = GlobalCooldown; casterState.GlobalCooldown.ServerFrame = BoltNetwork.ServerFrame; }
private void PrepareExplicitTarget() { ExplicitTargets.Source = Caster.Position; // initializes client-provided targets, corrects and automatically attempts to set required target. bool targetsUnits = SpellInfo.ExplicitCastTargets.HasAnyFlag(SpellCastTargetFlags.UnitMask); if (ExplicitTargets.Target != null && !targetsUnits) { ExplicitTargets.Target = null; } if (SpellInfo.ExplicitTargetType == SpellExplicitTargetType.Caster) { ExplicitTargets.Target = Caster; } // try to select correct unit target if not provided by client if (ExplicitTargets.Target == null && targetsUnits) { // try to use player selection as target, it has to be valid target for the spell if (Caster is Player playerCaster && playerCaster.Target is Unit playerTarget) { if (SpellInfo.CheckExplicitTarget(Caster, playerTarget) == SpellCastResult.Success) { ExplicitTargets.Target = playerTarget; } } // didn't find anything, try to use self as target if (ExplicitTargets.Target == null && SpellInfo.ExplicitCastTargets.HasAnyFlag(SpellCastTargetFlags.UnitAlly)) { ExplicitTargets.Target = Caster; } } if (ExplicitTargets.Target != null && SpellInfo.HasAttribute(SpellCustomAttributes.LaunchSourceIsExplicit)) { ExplicitTargets.Source = ExplicitTargets.Target.Position; } }
internal void CalculateSpellDamageTaken(ref SpellDamageInfo damageInfo) { if (damageInfo.Damage == 0 || !damageInfo.Target.IsAlive) { return; } Unit caster = damageInfo.Caster; Unit target = damageInfo.Target; SpellInfo spellInfo = damageInfo.SpellInfo; damageInfo.UpdateDamage(caster.Spells.SpellDamageBonusDone(target, spellInfo, damageInfo.Damage, damageInfo.SpellDamageType)); damageInfo.UpdateDamage(target.Spells.SpellDamageBonusTaken(caster, spellInfo, damageInfo.Damage, damageInfo.SpellDamageType)); if (!spellInfo.HasAttribute(SpellExtraAttributes.FixedDamage) && damageInfo.HasCrit) { uint criticalDamage = CalculateSpellCriticalDamage(spellInfo, damageInfo.Damage); damageInfo.UpdateOriginalDamage(criticalDamage); } HandleAbsorb(ref damageInfo); }
private void ConsumePowers() { if (SpellInfo.HasAttribute(SpellAttributes.RequiresComboPoints)) { ConsumedComboPoints = Caster.ComboPoints; Caster.Attributes.SetComboPoints(0); } foreach ((SpellPowerType, int)powerCost in powerCosts) { if (powerCost.Item1 == SpellPowerType.Health) { if (Caster.IsAlive) { Caster.ModifyHealth(-Mathf.Min(Caster.Health - 1, powerCost.Item2)); } continue; } Caster.Attributes.ModifyPower(powerCost.Item1, -powerCost.Item2); } }
private SpellCastResult Cast() { ExecutionState = SpellExecutionState.Casting; CastTime = Caster.Spells.ModifySpellCastTime(this, SpellInfo.CastTime); CastTimeLeft = CastTime; // cast if needed, if already casting launch instead, should only be possible with CanCastWhileCasting bool instantCast = CastTime <= 0.0f || Caster.SpellCast.IsCasting || spellCastFlags.HasTargetFlag(SpellCastFlags.CastDirectly); if (casterMovementFlags.IsMoving() && !instantCast && !SpellInfo.HasAttribute(SpellAttributes.CastableWhileMoving)) { return(SpellCastResult.Moving); } Caster.SpellHistory.StartGlobalCooldown(SpellInfo); if (instantCast) { Launch(); } return(SpellCastResult.Success); }
private SpellCastResult ValidateCast() { if (spellCastFlags.HasTargetFlag(SpellCastFlags.TriggeredByAura)) { return(SpellCastResult.Success); } if (Caster.HasState(UnitControlState.Stunned) && !SpellInfo.HasAttribute(SpellExtraAttributes.UsableWhileStunned)) { return(SpellCastResult.Stunned); } if (Caster.HasState(UnitControlState.Confused) && !SpellInfo.HasAttribute(SpellExtraAttributes.UsableWhileConfused)) { return(SpellCastResult.Confused); } if (Caster.HasState(UnitControlState.Fleeing) && !SpellInfo.HasAttribute(SpellExtraAttributes.UsableWhileFeared)) { return(SpellCastResult.Fleeing); } if (SpellInfo.PreventionType != 0) { if (SpellInfo.PreventionType.HasTargetFlag(SpellPreventionType.Pacify) && Caster.HasFlag(UnitFlags.Pacified)) { return(SpellCastResult.Silenced); } if (SpellInfo.PreventionType.HasTargetFlag(SpellPreventionType.Silence) && Caster.HasFlag(UnitFlags.Silenced)) { return(SpellCastResult.Pacified); } } // check death state if (!Caster.IsAlive && !SpellInfo.IsPassive() && !SpellInfo.HasAttribute(SpellAttributes.CastableWhileDead)) { return(SpellCastResult.CasterDead); } // check cooldowns to prevent cheating if (!SpellInfo.IsPassive() && !Caster.SpellHistory.IsReady(SpellInfo)) { return(SpellCastResult.NotReady); } // check global cooldown if (!spellCastFlags.HasTargetFlag(SpellCastFlags.IgnoreGcd) && Caster.SpellHistory.HasGlobalCooldown) { return(SpellCastResult.NotReady); } // check if already casting if (Caster.SpellCast.IsCasting && !SpellInfo.HasAttribute(SpellExtraAttributes.CanCastWhileCasting)) { return(SpellCastResult.NotReady); } SpellCastResult castResult = ValidateRange(); if (castResult != SpellCastResult.Success) { return(castResult); } if (!spellCastFlags.HasTargetFlag(SpellCastFlags.IgnoreTargetCheck)) { castResult = SpellInfo.CheckExplicitTarget(Caster, ExplicitTargets.Target); if (castResult != SpellCastResult.Success) { return(castResult); } if (ExplicitTargets.Target != null) { castResult = SpellInfo.CheckTarget(Caster, ExplicitTargets.Target, this, false); if (castResult != SpellCastResult.Success) { return(castResult); } } } return(SpellCastResult.Success); }
private SpellCastResult ValidateAuras() { if (spellCastFlags.HasTargetFlag(SpellCastFlags.IgnoreCasterAuras)) { return(SpellCastResult.Success); } bool usableWhileStunned = SpellInfo.HasAttribute(SpellExtraAttributes.UsableWhileStunned); bool usableWhileConfused = SpellInfo.HasAttribute(SpellExtraAttributes.UsableWhileConfused); bool usableWhileFeared = SpellInfo.HasAttribute(SpellExtraAttributes.UsableWhileFeared); if (Caster.HasFlag(UnitFlags.Stunned)) { if (usableWhileStunned) { SpellCastResult result = ValidateMechanics(AuraEffectType.StunState); if (result != SpellCastResult.Success) { return(result); } } else if (!WillCancelStun()) { return(SpellCastResult.Stunned); } } if (SpellInfo.PreventionType != 0) { if (SpellInfo.PreventionType.HasTargetFlag(SpellPreventionType.Pacify) && Caster.HasFlag(UnitFlags.Pacified) && !WillCancelPacify()) { return(SpellCastResult.Pacified); } if (SpellInfo.PreventionType.HasTargetFlag(SpellPreventionType.Silence) && Caster.HasFlag(UnitFlags.Silenced) && !WillCancelSilence()) { return(SpellCastResult.Silenced); } } if (Caster.HasFlag(UnitFlags.Fleeing)) { if (usableWhileFeared) { SpellCastResult result = ValidateMechanics(AuraEffectType.ModFear); if (result != SpellCastResult.Success) { return(result); } } else if (!WillCancelFear()) { return(SpellCastResult.Fleeing); } } if (Caster.HasFlag(UnitFlags.Confused)) { if (usableWhileConfused) { SpellCastResult result = ValidateMechanics(AuraEffectType.ConfusionState); if (result != SpellCastResult.Success) { return(result); } } else if (!WillCancelConfuse()) { return(SpellCastResult.Confused); } } return(SpellCastResult.Success); bool WillCancelStun() { return(SpellInfo.CanCancelAuraType(AuraEffectType.StunState, Caster) && SpellInfo.CanCancelAuraType(AuraEffectType.Strangulate, Caster)); } bool WillCancelSilence() { return(SpellInfo.CanCancelAuraType(AuraEffectType.Silence, Caster) && SpellInfo.CanCancelAuraType(AuraEffectType.SilencePacify, Caster)); } bool WillCancelPacify() { return(SpellInfo.CanCancelAuraType(AuraEffectType.Pacify, Caster) && SpellInfo.CanCancelAuraType(AuraEffectType.SilencePacify, Caster)); } bool WillCancelFear() { return(SpellInfo.CanCancelAuraType(AuraEffectType.ModFear, Caster)); } bool WillCancelConfuse() { return(SpellInfo.CanCancelAuraType(AuraEffectType.ConfusionState, Caster)); } }
private float CalculateSpellCriticalChance(Unit victim, SpellInfo spellInfo, SpellSchoolMask schoolMask, Spell spell = null) { if (spellInfo.HasAttribute(SpellAttributes.CantCrit) || spellInfo.DamageClass == SpellDamageClass.None) { return(0.0f); } if (spellInfo.HasAttribute(SpellAttributes.AlwaysCrit)) { return(100.0f); } float critChance = unit.CritPercentage; if (victim == null) { return(Mathf.Max(critChance, 0.0f)); } switch (spellInfo.DamageClass) { case SpellDamageClass.Magic: if (!spellInfo.IsPositive) { critChance += victim.Auras.TotalAuraModifier(AuraEffectType.ModAttackerSpellCritChance); } IReadOnlyList <AuraEffect> spellCritAuras = unit.GetAuraEffects(AuraEffectType.OverrideSpellCritCalculation); if (spellCritAuras != null) { for (int i = 0; i < spellCritAuras.Count; i++) { if (spellCritAuras[i].EffectInfo is AuraEffectInfoOverrideSpellCritCalculation effectInfo) { effectInfo.ModifySpellCrit(unit, victim, spell, ref critChance); } } } goto default; case SpellDamageClass.Melee: if (!spellInfo.IsPositive) { critChance += victim.Auras.TotalAuraModifier(AuraEffectType.ModAttackerMeleeCritChance); } goto default; case SpellDamageClass.Ranged: if (!spellInfo.IsPositive) { critChance += victim.Auras.TotalAuraModifier(AuraEffectType.ModAttackerRangedCritChance); } goto default; default: critChance += victim.Auras.TotalAuraModifierForCaster(AuraEffectType.ModAttackerSpellCritChanceForCaster, unit.Id); critChance += victim.Auras.TotalAuraModifier(AuraEffectType.ModAttackerSpellAndWeaponCritChance); break; } return(Mathf.Max(critChance, 0.0f)); }
private SpellCastResult ValidateCast() { if (spellCastFlags.HasTargetFlag(SpellCastFlags.TriggeredByAura)) { return(SpellCastResult.Success); } if (Caster is Player player && !player.PlayerSpells.HasKnownSpell(SpellInfo)) { return(SpellCastResult.NotKnown); } // check death state if (!Caster.IsAlive && !SpellInfo.IsPassive && !SpellInfo.HasAttribute(SpellAttributes.CastableWhileDead)) { return(SpellCastResult.CasterDead); } // check cooldowns to prevent cheating if (!SpellInfo.IsPassive && !Caster.SpellHistory.IsReady(SpellInfo)) { return(SpellCastResult.NotReady); } // check global cooldown if (!spellCastFlags.HasTargetFlag(SpellCastFlags.IgnoreGcd) && Caster.SpellHistory.HasGlobalCooldown) { return(SpellCastResult.NotReady); } // check if already casting if (Caster.SpellCast.IsCasting && !SpellInfo.HasAttribute(SpellExtraAttributes.CanCastWhileCasting)) { return(SpellCastResult.NotReady); } SpellCastResult castResult; if (!spellCastFlags.HasTargetFlag(SpellCastFlags.IgnoreTargetCheck)) { castResult = SpellInfo.CheckExplicitTarget(Caster, ExplicitTargets.Target); if (castResult != SpellCastResult.Success) { return(castResult); } if (ExplicitTargets.Target != null) { castResult = SpellInfo.CheckTarget(Caster, ExplicitTargets.Target, this, false); if (castResult != SpellCastResult.Success) { return(castResult); } } } castResult = ValidateRange(); if (castResult != SpellCastResult.Success) { return(castResult); } castResult = ValidateAuras(); if (castResult != SpellCastResult.Success) { return(castResult); } return(SpellCastResult.Success); }