diff --git a/Source/Framework/Constants/ScriptsConst.cs b/Source/Framework/Constants/ScriptsConst.cs index a003e2806..bd15065a2 100644 --- a/Source/Framework/Constants/ScriptsConst.cs +++ b/Source/Framework/Constants/ScriptsConst.cs @@ -42,7 +42,8 @@ namespace Framework.Constants CheckCast, BeforeCast, OnCast, - AfterCast + AfterCast, + CalcCritChance } // AuraScript interface - enum used for runtime checks of script function calls @@ -57,6 +58,7 @@ namespace Framework.Constants EffectCalcAmount, EffectCalcPeriodic, EffectCalcSpellmod, + EffectCalcCritChance, EffectAbsorb, EffectAfterAbsorb, EffectManaShield, diff --git a/Source/Game/Entities/Unit/Unit.Spells.cs b/Source/Game/Entities/Unit/Unit.Spells.cs index d53dc279b..06bed5a05 100644 --- a/Source/Game/Entities/Unit/Unit.Spells.cs +++ b/Source/Game/Entities/Unit/Unit.Spells.cs @@ -647,13 +647,14 @@ namespace Game.Entities return (uint)Math.Max(heal, 0.0f); } - public bool IsSpellCrit(Unit victim, SpellInfo spellProto, SpellSchoolMask schoolMask, WeaponAttackType attackType = WeaponAttackType.BaseAttack) + public bool IsSpellCrit(Unit victim, Spell spell, AuraEffect aurEff, SpellSchoolMask schoolMask, WeaponAttackType attackType = WeaponAttackType.BaseAttack) { - return RandomHelper.randChance(GetUnitSpellCriticalChance(victim, spellProto, schoolMask, attackType)); + return RandomHelper.randChance(GetUnitSpellCriticalChance(victim, spell, aurEff, schoolMask, attackType)); } - public float GetUnitSpellCriticalChance(Unit victim, SpellInfo spellProto, SpellSchoolMask schoolMask, WeaponAttackType attackType = WeaponAttackType.BaseAttack) + public float GetUnitSpellCriticalChance(Unit victim, Spell spell, AuraEffect aurEff, SpellSchoolMask schoolMask, WeaponAttackType attackType = WeaponAttackType.BaseAttack) { + SpellInfo spellProto = spell.GetSpellInfo() ?? aurEff.GetSpellInfo(); //! Mobs can't crit with spells. Player Totems can //! Fire Elemental (from totem) can too - but this part is a hack and needs more research if (GetGUID().IsCreatureOrVehicle() && !(IsTotem() && GetOwnerGUID().IsPlayer()) && GetEntry() != 15438) @@ -779,6 +780,12 @@ namespace Game.Entities crit_chance += victim.GetTotalAuraModifier(AuraType.ModCritChanceForCasterPet, aurEff => aurEff.GetCasterGUID() == tempSummon.GetSummonerGUID()); } + // call script handlers + if (spell) + spell.CallScriptCalcCritChanceHandlers(victim, ref crit_chance); + else + aurEff.GetBase().CallScriptEffectCalcCritChanceHandlers(aurEff, aurEff.GetBase().GetApplicationOfTarget(victim.GetGUID()), victim, ref crit_chance); + return Math.Max(crit_chance, 0.0f); } diff --git a/Source/Game/Scripting/SpellScript.cs b/Source/Game/Scripting/SpellScript.cs index dcfe52a3c..f62744721 100644 --- a/Source/Game/Scripting/SpellScript.cs +++ b/Source/Game/Scripting/SpellScript.cs @@ -151,6 +151,7 @@ namespace Game.Scripting public delegate void SpellEffectFnType(uint index); public delegate void SpellBeforeHitFnType(SpellMissInfo missInfo); public delegate void SpellHitFnType(); + public delegate void SpellOnCalcCritChanceFnType(Unit victim, ref float chance); public delegate void SpellCastFnType(); public delegate void SpellObjectAreaTargetSelectFnType(List targets); public delegate void SpellObjectTargetSelectFnType(ref WorldObject targets); @@ -243,6 +244,20 @@ namespace Game.Scripting SpellHitFnType _pHitHandlerScript; } + public class OnCalcCritChanceHandler + { + public OnCalcCritChanceHandler(SpellOnCalcCritChanceFnType onCalcCritChanceHandlerScript) + { + _onCalcCritChanceHandlerScript = onCalcCritChanceHandlerScript; + } + public void Call(Unit victim, ref float critChance) + { + _onCalcCritChanceHandlerScript(victim, ref critChance); + } + + SpellOnCalcCritChanceFnType _onCalcCritChanceHandlerScript; + } + public class TargetHook : EffectHook { public TargetHook(uint effectIndex, Targets targetType, bool area, bool dest = false) : base(effectIndex) @@ -463,6 +478,9 @@ namespace Game.Scripting public List OnHit = new(); public List AfterHit = new(); + // where function is void function(Unit victim, ref float critChance) + public List OnCalcCritChance = new(); + // where function is void function(List targets) public List OnObjectAreaTargetSelect = new(); @@ -771,6 +789,7 @@ namespace Game.Scripting public delegate void AuraEffectCalcAmountDelegate(AuraEffect aura, ref int amount, ref bool canBeRecalculated); public delegate void AuraEffectCalcPeriodicDelegate(AuraEffect aura, ref bool isPeriodic, ref int amplitude); public delegate void AuraEffectCalcSpellModDelegate(AuraEffect aura, ref SpellModifier spellMod); + public delegate void AuraEffectCalcCritChanceFnType(AuraEffect aura, Unit victim, ref float critChance); public delegate void AuraEffectAbsorbDelegate(AuraEffect aura, DamageInfo damageInfo, ref uint absorbAmount); public delegate void AuraEffectSplitDelegate(AuraEffect aura, DamageInfo damageInfo, uint splitAmount); public delegate bool AuraCheckProcDelegate(ProcEventInfo info); @@ -889,6 +908,19 @@ namespace Game.Scripting AuraEffectCalcSpellModDelegate pEffectHandlerScript; } + public class EffectCalcCritChanceHandler : EffectBase + { + public EffectCalcCritChanceHandler(AuraEffectCalcCritChanceFnType effectHandlerScript, byte effIndex, AuraType effName) : base(effIndex, effName) + { + _effectHandlerScript = effectHandlerScript; + } + public void Call(AuraEffect aurEff, Unit victim, ref float critChance) + { + _effectHandlerScript(aurEff, victim, ref critChance); + } + + AuraEffectCalcCritChanceFnType _effectHandlerScript; + } public class EffectApplyHandler : EffectBase { public EffectApplyHandler(AuraEffectApplicationModeDelegate _pEffectHandlerScript, byte _effIndex, AuraType _effName, AuraEffectHandleModes _mode) @@ -1074,6 +1106,10 @@ namespace Game.Scripting if (eff.GetAffectedEffectsMask(entry) == 0) Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `DoEffectCalcSpellMod` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + foreach (var eff in DoEffectCalcCritChance) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, $"Spell `{entry.Id}` Effect `{eff}` of script `{m_scriptName}` did not match dbc effect data - handler bound to hook `DoEffectCalcCritChance` of AuraScript won't be executed"); + foreach (var eff in OnEffectAbsorb) if (eff.GetAffectedEffectsMask(entry) == 0) Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `OnEffectAbsorb` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); @@ -1248,6 +1284,11 @@ namespace Game.Scripting // where function is: void function (AuraEffect aurEff, SpellModifier& spellMod); public List DoEffectCalcSpellMod = new(); + // executed when aura effect calculates crit chance for dots and hots + // example: DoEffectCalcCritChance += AuraEffectCalcCritChanceFn(class::function, EffectIndexSpecifier, EffectAuraNameSpecifier); + // where function is: void function (AuraEffect const* aurEff, Unit* victim, float& critChance); + public List DoEffectCalcCritChance = new(); + // executed when absorb aura effect is going to reduce damage // example: OnEffectAbsorb += AuraEffectAbsorbFn(class.function, EffectIndexSpecifier); // where function is: void function (AuraEffect aurEff, DamageInfo& dmgInfo, uint& absorbAmount); diff --git a/Source/Game/Spells/Auras/Aura.cs b/Source/Game/Spells/Auras/Aura.cs index 5bd908ae1..ff4f45e91 100644 --- a/Source/Game/Spells/Auras/Aura.cs +++ b/Source/Game/Spells/Auras/Aura.cs @@ -882,7 +882,7 @@ namespace Game.Spells } return maxStackAmount; } - + public bool ModStackAmount(int num, AuraRemoveMode removeMode = AuraRemoveMode.Default, bool resetPeriodicTimer = true) { int stackAmount = m_stackAmount + num; @@ -1449,7 +1449,7 @@ namespace Game.Spells effect.SetDonePct(caster.SpellDamagePctDone(target, m_spellInfo, DamageEffectType.DOT)); // Calculate done percentage first! effect.SetDamage((int)(caster.SpellDamageBonusDone(target, m_spellInfo, damage, DamageEffectType.DOT, effect.GetSpellEffectInfo(), GetStackAmount()) * effect.GetDonePct())); - effect.SetCritChance(caster.GetUnitSpellCriticalChance(target, m_spellInfo, m_spellInfo.GetSchoolMask())); + effect.SetCritChance(caster.GetUnitSpellCriticalChance(target, null, effect, m_spellInfo.GetSchoolMask())); break; } case AuraType.PeriodicHeal: @@ -1463,7 +1463,7 @@ namespace Game.Spells effect.SetDonePct(caster.SpellHealingPctDone(target, m_spellInfo)); // Calculate done percentage first! effect.SetDamage((int)(caster.SpellHealingBonusDone(target, m_spellInfo, damage, DamageEffectType.DOT, effect.GetSpellEffectInfo(), GetStackAmount()) * effect.GetDonePct())); - effect.SetCritChance(caster.GetUnitSpellCriticalChance(target, m_spellInfo, m_spellInfo.GetSchoolMask())); + effect.SetCritChance(caster.GetUnitSpellCriticalChance(target, null, effect, m_spellInfo.GetSchoolMask())); break; } default: @@ -2074,6 +2074,19 @@ namespace Game.Spells } } + public void CallScriptEffectCalcCritChanceHandlers(AuraEffect aurEff, AuraApplication aurApp, Unit victim, ref float critChance) + { + foreach (AuraScript loadedScript in m_loadedScripts) + { + loadedScript._PrepareScriptCall(AuraScriptHookType.EffectCalcCritChance, aurApp); + foreach (var hook in loadedScript.DoEffectCalcCritChance) + if (hook.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) + hook.Call(aurEff, victim, ref critChance); + + loadedScript._FinishScriptCall(); + } + } + public void CallScriptEffectAbsorbHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, ref uint absorbAmount, ref bool defaultPrevented) { foreach (var auraScript in m_loadedScripts) diff --git a/Source/Game/Spells/Auras/AuraEffect.cs b/Source/Game/Spells/Auras/AuraEffect.cs index 7c0ea6fbd..6bc94eba5 100644 --- a/Source/Game/Spells/Auras/AuraEffect.cs +++ b/Source/Game/Spells/Auras/AuraEffect.cs @@ -4923,7 +4923,7 @@ namespace Game.Spells } } - bool crit = RandomHelper.randChance(isAreaAura ? caster.GetUnitSpellCriticalChance(target, m_spellInfo, m_spellInfo.GetSchoolMask()) : m_critChance); + bool crit = RandomHelper.randChance(isAreaAura ? caster.GetUnitSpellCriticalChance(target, null, this, m_spellInfo.GetSchoolMask()) : m_critChance); if (crit) damage = caster.SpellCriticalDamageBonus(m_spellInfo, damage, target); @@ -5010,7 +5010,7 @@ namespace Game.Spells } } - bool crit = RandomHelper.randChance(isAreaAura ? caster.GetUnitSpellCriticalChance(target, m_spellInfo, m_spellInfo.GetSchoolMask()) : m_critChance); + bool crit = RandomHelper.randChance(isAreaAura ? caster.GetUnitSpellCriticalChance(target, null, this, m_spellInfo.GetSchoolMask()) : m_critChance); if (crit) damage = caster.SpellCriticalDamageBonus(m_spellInfo, damage, target); @@ -5150,7 +5150,7 @@ namespace Game.Spells damage = (int)target.SpellHealingBonusTaken(caster, GetSpellInfo(), (uint)damage, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()); } - bool crit = RandomHelper.randChance(isAreaAura ? caster.GetUnitSpellCriticalChance(target, m_spellInfo, m_spellInfo.GetSchoolMask()) : m_critChance); + bool crit = RandomHelper.randChance(isAreaAura ? caster.GetUnitSpellCriticalChance(target, null, this, m_spellInfo.GetSchoolMask()) : m_critChance); if (crit) damage = caster.SpellCriticalHealingBonus(m_spellInfo, damage, target); diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index 5b6214122..cf0a6cff2 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -6814,7 +6814,7 @@ namespace Game.Spells } } - targetInfo.crit = m_caster.IsSpellCrit(unit, m_spellInfo, m_spellSchoolMask, m_attackType); + targetInfo.crit = m_caster.IsSpellCrit(unit, this, null, m_spellSchoolMask, m_attackType); } SpellCastResult CanOpenLock(uint effIndex, uint lockId, ref SkillType skillId, ref int reqSkillValue, ref int skillValue) @@ -7092,6 +7092,18 @@ namespace Game.Spells } } + public void CallScriptCalcCritChanceHandlers(Unit victim, ref float critChance) + { + foreach (var loadedScript in m_loadedScripts) + { + loadedScript._PrepareScriptCall(SpellScriptHookType.CalcCritChance); + foreach (var hook in loadedScript.OnCalcCritChance) + hook.Call(victim, ref critChance); + + loadedScript._FinishScriptCall(); + } + } + void CallScriptObjectAreaTargetSelectHandlers(List targets, uint effIndex, SpellImplicitTargetInfo targetType) { foreach (var script in m_loadedScripts)