From 6975212812fd7f0d5d9e97cb962fc062f9902d41 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Thu, 14 Sep 2023 04:24:19 -0400 Subject: [PATCH] Core/Spells: Implement sqrt based aoe damage diminishing Port From (https://github.com/TrinityCore/TrinityCore/commit/42a6e0eb1afa431fc1efe7d4c5a62758fed42684) --- .../Framework/Constants/Spells/SpellConst.cs | 2 + Source/Framework/Constants/UnitConst.cs | 4 +- Source/Game/Spells/Spell.cs | 61 +++++++++++++++++-- Source/Game/Spells/SpellEffects.cs | 22 +++++++ Source/Game/Spells/SpellInfo.cs | 52 ++++++++++++++-- 5 files changed, 129 insertions(+), 12 deletions(-) diff --git a/Source/Framework/Constants/Spells/SpellConst.cs b/Source/Framework/Constants/Spells/SpellConst.cs index 13797450a..c057bf03b 100644 --- a/Source/Framework/Constants/Spells/SpellConst.cs +++ b/Source/Framework/Constants/Spells/SpellConst.cs @@ -20,6 +20,8 @@ namespace Framework.Constants public const float TrajectoryMissileSize = 3.0f; + public const int AoeDamageTargetCap = 20; + public const int MaxPowersPerSpell = 5; public const uint VisualKitFood = 406; diff --git a/Source/Framework/Constants/UnitConst.cs b/Source/Framework/Constants/UnitConst.cs index fcbbf0e69..64d269e16 100644 --- a/Source/Framework/Constants/UnitConst.cs +++ b/Source/Framework/Constants/UnitConst.cs @@ -406,7 +406,9 @@ namespace Framework.Constants AuraStack, CritChance, DurationPct, - Duration + Duration, + ParentSpellTargetCount, + ParentSpellTargetIndex } public enum CombatRating diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index 2122ebe4c..dda77ee14 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -2108,6 +2108,23 @@ namespace Game.Spells m_destTargets[effIndex] = dest; } + int GetUnitTargetIndexForEffect(ObjectGuid target, uint effect) + { + int index = 0; + foreach (TargetInfo uniqueTargetInfo in m_UniqueTargetInfo) + { + if (uniqueTargetInfo.MissCondition == SpellMissInfo.None && (uniqueTargetInfo.EffectMask & (1 << (int)effect)) != 0) + { + if (uniqueTargetInfo.TargetGUID == target) + break; + + ++index; + } + } + + return index; + } + public long GetUnitTargetCountForEffect(uint effect) { return m_UniqueTargetInfo.Count(targetInfo => targetInfo.MissCondition == SpellMissInfo.None && (targetInfo.EffectMask & (1 << (int)effect)) != 0); @@ -7277,16 +7294,42 @@ namespace Game.Spells if (m_originalCaster != null && m_damage > 0) { - if (spellEffectInfo.IsTargetingArea() || spellEffectInfo.IsAreaAuraEffect() || spellEffectInfo.IsEffect(SpellEffectName.PersistentAreaAura) || m_spellInfo.HasAttribute(SpellAttr5.TreatAsAreaEffect)) + bool isAoeTarget = spellEffectInfo.IsTargetingArea() || spellEffectInfo.IsAreaAuraEffect() || spellEffectInfo.IsEffect(SpellEffectName.PersistentAreaAura); + if (isAoeTarget || m_spellInfo.HasAttribute(SpellAttr5.TreatAsAreaEffect)) { m_damage = unit.CalculateAOEAvoidance(m_damage, (uint)m_spellInfo.SchoolMask, m_originalCaster.GetGUID()); if (m_originalCaster.IsPlayer()) { - // cap damage of player AOE - long targetAmount = GetUnitTargetCountForEffect(spellEffectInfo.EffectIndex); - if (targetAmount > 20) - m_damage = (int)(m_damage * 20 / targetAmount); + long targetCount = !isAoeTarget && m_spellValue.ParentSpellTargetCount.HasValue ? m_spellValue.ParentSpellTargetCount.Value : GetUnitTargetCountForEffect(spellEffectInfo.EffectIndex); + int targetIndex = !isAoeTarget && m_spellValue.ParentSpellTargetIndex.HasValue ? m_spellValue.ParentSpellTargetIndex.Value : GetUnitTargetIndexForEffect(targetInfo.TargetGUID, spellEffectInfo.EffectIndex); + + // sqrt target cap damage calculation + if (m_spellInfo.SqrtDamageAndHealingDiminishing.MaxTargets != 0 + && targetCount > m_spellInfo.SqrtDamageAndHealingDiminishing.MaxTargets + && targetIndex >= m_spellInfo.SqrtDamageAndHealingDiminishing.NumNonDiminishedTargets) + m_damage = (int)(m_damage * Math.Sqrt((float)m_spellInfo.SqrtDamageAndHealingDiminishing.MaxTargets / Math.Min(SpellConst.AoeDamageTargetCap, targetCount))); + if (targetCount > SpellConst.AoeDamageTargetCap) + m_damage = (int)(m_damage * SpellConst.AoeDamageTargetCap / targetCount); + } + } + } + + if (m_originalCaster && m_healing > 0) + { + bool isAoeTarget = spellEffectInfo.IsTargetingArea() || spellEffectInfo.IsAreaAuraEffect() || spellEffectInfo.IsEffect(SpellEffectName.PersistentAreaAura); + if (isAoeTarget || m_spellInfo.HasAttribute(SpellAttr5.TreatAsAreaEffect)) + { + if (m_originalCaster.IsPlayer()) + { + long targetCount = !isAoeTarget && m_spellValue.ParentSpellTargetCount.HasValue ? m_spellValue.ParentSpellTargetCount.Value : GetUnitTargetCountForEffect(spellEffectInfo.EffectIndex); + int targetIndex = !isAoeTarget && m_spellValue.ParentSpellTargetIndex.HasValue ? m_spellValue.ParentSpellTargetIndex.Value : GetUnitTargetIndexForEffect(targetInfo.TargetGUID, spellEffectInfo.EffectIndex); + + // sqrt target cap healing calculation + if (m_spellInfo.SqrtDamageAndHealingDiminishing.MaxTargets != 0 + && targetCount > m_spellInfo.SqrtDamageAndHealingDiminishing.MaxTargets + && targetIndex >= m_spellInfo.SqrtDamageAndHealingDiminishing.NumNonDiminishedTargets) + m_healing = (int)(m_healing * Math.Sqrt((float)m_spellInfo.SqrtDamageAndHealingDiminishing.MaxTargets / Math.Min(SpellConst.AoeDamageTargetCap, targetCount))); } } @@ -7407,6 +7450,12 @@ namespace Game.Spells case SpellValueMod.Duration: m_spellValue.Duration = value; break; + case SpellValueMod.ParentSpellTargetCount: + m_spellValue.ParentSpellTargetCount = value; + break; + case SpellValueMod.ParentSpellTargetIndex: + m_spellValue.ParentSpellTargetIndex = value; + break; } } @@ -8796,6 +8845,8 @@ namespace Game.Spells public float DurationMul; public float CriticalChance; public int? Duration; + public int? ParentSpellTargetCount; + public int? ParentSpellTargetIndex; } // Spell modifier (used for modify other spells) diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs index 14683ec11..ce3f330f9 100644 --- a/Source/Game/Spells/SpellEffects.cs +++ b/Source/Game/Spells/SpellEffects.cs @@ -255,11 +255,16 @@ namespace Game.Spells } SpellCastTargets targets = new(); + int? targetCount = null; + int? targetIndex = null; if (effectHandleMode == SpellEffectHandleMode.LaunchTarget) { if (!spellInfo.NeedsToBeTriggeredByCaster(m_spellInfo)) return; + targets.SetUnitTarget(unitTarget); + targetCount = (int)GetUnitTargetCountForEffect(effectInfo.EffectIndex); + targetIndex = GetUnitTargetIndexForEffect(unitTarget.GetGUID(), effectInfo.EffectIndex); } else //if (effectHandleMode == SpellEffectHandleMode.Launch) { @@ -320,6 +325,12 @@ namespace Game.Spells for (int i = 0; i < SpellConst.MaxEffects; ++i) args.AddSpellMod(SpellValueMod.BasePoint0 + i, value); + if (targetCount.HasValue) + args.AddSpellMod(SpellValueMod.ParentSpellTargetCount, targetCount.Value); + + if (targetIndex.HasValue) + args.AddSpellMod(SpellValueMod.ParentSpellTargetIndex, targetIndex.Value); + caster.CastSpell(targets, triggerSpell, args); }, delay); } @@ -348,11 +359,16 @@ namespace Game.Spells } SpellCastTargets targets = new(); + int? targetCount = null; + int? targetIndex = null; if (effectHandleMode == SpellEffectHandleMode.HitTarget) { if (!spellInfo.NeedsToBeTriggeredByCaster(m_spellInfo)) return; + targets.SetUnitTarget(unitTarget); + targetCount = (int)GetUnitTargetCountForEffect(effectInfo.EffectIndex); + targetIndex = GetUnitTargetIndexForEffect(unitTarget.GetGUID(), effectInfo.EffectIndex); } else //if (effectHandleMode == SpellEffectHandleMode.Hit) { @@ -381,6 +397,12 @@ namespace Game.Spells for (int i = 0; i < SpellConst.MaxEffects; ++i) args.AddSpellMod(SpellValueMod.BasePoint0 + i, damage); + if (targetCount.HasValue) + args.AddSpellMod(SpellValueMod.ParentSpellTargetCount, targetCount.Value); + + if (targetIndex.HasValue) + args.AddSpellMod(SpellValueMod.ParentSpellTargetIndex, targetIndex.Value); + // original caster guid only for GO cast m_caster.CastSpell(targets, spellInfo.Id, args); } diff --git a/Source/Game/Spells/SpellInfo.cs b/Source/Game/Spells/SpellInfo.cs index 13a71808a..6fcb232a9 100644 --- a/Source/Game/Spells/SpellInfo.cs +++ b/Source/Game/Spells/SpellInfo.cs @@ -2393,6 +2393,38 @@ namespace Game.Spells } } + void _LoadSqrtTargetLimit(int maxTargets, int numNonDiminishedTargets, uint? maxTargetsEffectValueHolder, uint? numNonDiminishedTargetsEffectValueHolder) + { + SqrtDamageAndHealingDiminishing.MaxTargets = maxTargets; + SqrtDamageAndHealingDiminishing.NumNonDiminishedTargets = numNonDiminishedTargets; + + if (maxTargetsEffectValueHolder.HasValue) + { + if (maxTargetsEffectValueHolder < GetEffects().Count) + { + SpellEffectInfo valueHolder = GetEffect(maxTargetsEffectValueHolder.Value); + int expectedValue = valueHolder.CalcBaseValue(null, null, 0, -1); + if (maxTargets != expectedValue) + Log.outError(LogFilter.Spells, $"SpellInfo::_LoadSqrtTargetLimit(maxTargets): Spell {Id} has different value in effect {maxTargetsEffectValueHolder.Value} than expected, recheck target caps (expected {maxTargets}, got {expectedValue})"); + } + else + Log.outError(LogFilter.Spells, $"SpellInfo::_LoadSqrtTargetLimit(maxTargets): Spell {Id} does not have effect {maxTargetsEffectValueHolder.Value}"); + } + + if (numNonDiminishedTargetsEffectValueHolder.HasValue) + { + if (numNonDiminishedTargetsEffectValueHolder < GetEffects().Count) + { + SpellEffectInfo valueHolder = GetEffect(numNonDiminishedTargetsEffectValueHolder.Value); + int expectedValue = valueHolder.CalcBaseValue(null, null, 0, -1); + if (numNonDiminishedTargets != expectedValue) + Log.outError(LogFilter.Spells, $"SpellInfo::_LoadSqrtTargetLimit(numNonDiminishedTargets): Spell {Id} has different value in effect {numNonDiminishedTargetsEffectValueHolder.Value} than expected, recheck target caps (expected {numNonDiminishedTargets}, got {expectedValue})"); + } + else + Log.outError(LogFilter.Spells, $"SpellInfo::_LoadSqrtTargetLimit(numNonDiminishedTargets): Spell {Id} does not have effect {numNonDiminishedTargetsEffectValueHolder.Value}"); + } + } + public void ApplyAllSpellImmunitiesTo(Unit target, SpellEffectInfo spellEffectInfo, bool apply) { ImmunityInfo immuneInfo = spellEffectInfo.GetImmunityInfo(); @@ -2646,7 +2678,7 @@ namespace Game.Spells return mechanicImmunityMask; } - + public float GetMinRange(bool positive = false) { if (RangeEntry == null) @@ -3001,7 +3033,7 @@ namespace Game.Spells return itr; costs.Add(new SpellPowerCost() { Power = powerType, Amount = 0 }); - return costs.Last(); + return costs.Last(); } foreach (SpellPowerRecord power in PowerCosts) @@ -3329,7 +3361,7 @@ namespace Game.Spells bool _isPositiveEffectImpl(SpellInfo spellInfo, SpellEffectInfo effect, List> visited) { - if ( !effect.IsEffect()) + if (!effect.IsEffect()) return true; // attribute may be already set in DB @@ -3619,7 +3651,7 @@ namespace Game.Spells if (_isPositiveTarget(spellTriggeredEffect) && !_isPositiveEffectImpl(spellTriggeredProto, spellTriggeredEffect, visited)) return false; } - } + } break; case AuraType.PeriodicTriggerSpell: case AuraType.ModStun: @@ -3972,8 +4004,8 @@ namespace Game.Spells public uint ExcludeCasterAuraSpell { get; set; } public uint ExcludeTargetAuraSpell { get; set; } public AuraType CasterAuraType { get; set; } - public AuraType TargetAuraType { get; set; } - public AuraType ExcludeCasterAuraType { get; set; } + public AuraType TargetAuraType { get; set; } + public AuraType ExcludeCasterAuraType { get; set; } public AuraType ExcludeTargetAuraType { get; set; } public SpellCastTimesRecord CastTimeEntry { get; set; } public uint RecoveryTime { get; set; } @@ -4032,6 +4064,8 @@ namespace Game.Spells public uint ExplicitTargetMask { get; set; } public SpellChainNode ChainEntry { get; set; } + public SqrtDamageAndHealingDiminishingStruct SqrtDamageAndHealingDiminishing; + List _effects = new(); List _visuals = new(); SpellSpecificType _spellSpecific; @@ -4047,6 +4081,12 @@ namespace Game.Spells public uint MaxScalingLevel; public uint ScalesFromItemLevel; } + + public struct SqrtDamageAndHealingDiminishingStruct + { + public int MaxTargets; // The amount of targets after the damage decreases by the Square Root AOE formula + public int NumNonDiminishedTargets; // The amount of targets that still take the full amount before the damage decreases by the Square Root AOE formula + } } public class SpellEffectInfo