diff --git a/Source/Framework/Constants/CliDBConst.cs b/Source/Framework/Constants/CliDBConst.cs index 89ce2da7d..a8542b962 100644 --- a/Source/Framework/Constants/CliDBConst.cs +++ b/Source/Framework/Constants/CliDBConst.cs @@ -1973,12 +1973,28 @@ namespace Framework.Constants public enum SpellEffectAttributes { None = 0, - UnaffectedByInvulnerability = 0x01, // not cancelled by immunities - NoScaleWithStack = 0x40, + NoImmunity = 0x01, /*NYI*/ // not cancelled by immunities + PositionIsFacingRelative = 0x02, /*NYI*/ + JumpChargeUnitMeleeRange = 0x04, /*NYI*/ + JumpChargeUnitStrictPathCheck = 0x08, /*NYI*/ + ExcludeOwnParty = 0x10, /*NYI*/ + AlwaysAoeLineOfSight = 0x20, + SuppressPointsStacking = 0x40, ChainFromInitialTarget = 0x80, - StackAuraAmountOnRecast = 0x8000, // refreshing periodic auras with this attribute will add remaining damage to new aura - AllowAnyExplicitTarget = 0x100000, - IgnoreDuringCooldownTimeRateCalculation = 0x800000 + UncontrolledNoBackwards = 0x100, /*NYI*/ + AuraPointsStack = 0x000200, /*NYI*/ // refreshing periodic auras with this attribute will add remaining damage to new aura + NoCopyDamageInterruptsOrProcs = 0x400, /*NYI*/ + AddTargetCombatReachToAOE = 0x800, /*NYI*/ + IsHarmful = 0x1000, + ForceScaleToOverrideCameraMinHeight = 0x2000, /*NYI*/ + PlayersOnly = 0x004000, + ComputePointsOnlyAtCastTime = 0x8000, /*NYI*/ + EnforceLineOfSightToChainTargets = 0x10000, + AreaEffectsUseTargetRadius = 0x20000, /*NYI*/ + TeleportWithVehicle = 0x40000, /*NYI*/ + ScalePointsByChallengeModeDamageScaler = 0x80000, /*NYI*/ + DontFailSpellOnTargetingFailure = 0x100000, /*NYI*/ + IgnoreDuringCooldownTimeRateCalculation = 0x800000, /*NYI*/ } public enum SpellProcsPerMinuteModType : byte diff --git a/Source/Game/Maps/GridNotifiers.cs b/Source/Game/Maps/GridNotifiers.cs index 8b208b7b0..a1b2e217c 100644 --- a/Source/Game/Maps/GridNotifiers.cs +++ b/Source/Game/Maps/GridNotifiers.cs @@ -1337,7 +1337,7 @@ namespace Game.Maps { GridMapTypeMask i_mapTypeMask; List i_objects; - PhaseShift i_phaseShift; + public PhaseShift i_phaseShift; ICheck i_check; public WorldObjectListSearcher(WorldObject searcher, List objects, ICheck check, GridMapTypeMask mapTypeMask = GridMapTypeMask.All) diff --git a/Source/Game/Spells/Auras/Aura.cs b/Source/Game/Spells/Auras/Aura.cs index d7b1aca8e..d1781278b 100644 --- a/Source/Game/Spells/Auras/Aura.cs +++ b/Source/Game/Spells/Auras/Aura.cs @@ -2748,7 +2748,7 @@ namespace Game.Spells if (GetUnitOwner().HasUnitState(UnitState.Isolated)) continue; - List units = new(); + List units = new(); var condList = spellEffectInfo.ImplicitTargetConditions; float radius = spellEffectInfo.CalcRadius(refe); @@ -2804,20 +2804,25 @@ namespace Game.Spells if (selectionType != SpellTargetCheckTypes.Default) { - WorldObjectSpellAreaTargetCheck check = new(radius, GetUnitOwner(), refe, GetUnitOwner(), GetSpellInfo(), selectionType, condList, SpellTargetObjectTypes.Unit); - UnitListSearcher searcher = new(GetUnitOwner(), units, check); - Cell.VisitAllObjects(GetUnitOwner(), searcher, radius + extraSearchRadius); + var containerTypeMask = Spell.GetSearcherTypeMask(GetSpellInfo(), spellEffectInfo, SpellTargetObjectTypes.Unit, condList); + if (containerTypeMask != 0) + { + WorldObjectSpellAreaTargetCheck check = new(radius, GetUnitOwner(), refe, GetUnitOwner(), GetSpellInfo(), selectionType, condList, SpellTargetObjectTypes.Unit); + WorldObjectListSearcher searcher = new(GetUnitOwner(), units, check, containerTypeMask); + searcher.i_phaseShift = PhasingHandler.GetAlwaysVisiblePhaseShift(); + Spell.SearchTargets(searcher, containerTypeMask, GetUnitOwner(), GetUnitOwner(), radius + extraSearchRadius); - // by design WorldObjectSpellAreaTargetCheck allows not-in-world units (for spells) but for auras it is not acceptable - units.RemoveAll(unit => !unit.IsSelfOrInSameMap(GetUnitOwner())); + // by design WorldObjectSpellAreaTargetCheck allows not-in-world units (for spells) but for auras it is not acceptable + units.RemoveAll(unit => !unit.IsSelfOrInSameMap(GetUnitOwner())); + } } - foreach (Unit unit in units) + foreach (WorldObject unit in units) { if (!targets.ContainsKey(unit)) - targets[unit] = 0; + targets[unit.ToUnit()] = 0; - targets[unit] |= 1u << (int)spellEffectInfo.EffectIndex; + targets[unit.ToUnit()] |= 1u << (int)spellEffectInfo.EffectIndex; } } } diff --git a/Source/Game/Spells/Auras/AuraEffect.cs b/Source/Game/Spells/Auras/AuraEffect.cs index 6ba7bc1f8..32aadbecd 100644 --- a/Source/Game/Spells/Auras/AuraEffect.cs +++ b/Source/Game/Spells/Auras/AuraEffect.cs @@ -116,7 +116,7 @@ namespace Game.Spells } GetBase().CallScriptEffectCalcAmountHandlers(this, ref amount, ref m_canBeRecalculated); - if (!GetSpellEffectInfo().EffectAttributes.HasFlag(SpellEffectAttributes.NoScaleWithStack)) + if (!GetSpellEffectInfo().EffectAttributes.HasFlag(SpellEffectAttributes.SuppressPointsStacking)) amount *= GetBase().GetStackAmount(); _estimatedAmount = CalculateEstimatedAmount(caster, amount); @@ -126,7 +126,7 @@ namespace Game.Spells public static float? CalculateEstimatedAmount(Unit caster, Unit target, SpellInfo spellInfo, SpellEffectInfo spellEffectInfo, int amount, byte stack, AuraEffect aurEff) { - uint stackAmountForBonuses = !spellEffectInfo.EffectAttributes.HasFlag(SpellEffectAttributes.NoScaleWithStack) ? stack : 1u; + uint stackAmountForBonuses = !spellEffectInfo.EffectAttributes.HasFlag(SpellEffectAttributes.SuppressPointsStacking) ? stack : 1u; switch (spellEffectInfo.ApplyAuraName) { @@ -5057,7 +5057,7 @@ namespace Game.Spells CleanDamage cleanDamage = new(0, 0, WeaponAttackType.BaseAttack, MeleeHitOutcome.Normal); - uint stackAmountForBonuses = !GetSpellEffectInfo().EffectAttributes.HasFlag(SpellEffectAttributes.NoScaleWithStack) ? GetBase().GetStackAmount() : 1u; + uint stackAmountForBonuses = !GetSpellEffectInfo().EffectAttributes.HasFlag(SpellEffectAttributes.SuppressPointsStacking) ? GetBase().GetStackAmount() : 1u; // ignore non positive values (can be result apply spellmods to aura damage uint damage = (uint)Math.Max(GetAmount(), 0); @@ -5184,7 +5184,7 @@ namespace Game.Spells CleanDamage cleanDamage = new(0, 0, GetSpellInfo().GetAttackType(), MeleeHitOutcome.Normal); - uint stackAmountForBonuses = !GetSpellEffectInfo().EffectAttributes.HasFlag(SpellEffectAttributes.NoScaleWithStack) ? GetBase().GetStackAmount() : 1u; + uint stackAmountForBonuses = !GetSpellEffectInfo().EffectAttributes.HasFlag(SpellEffectAttributes.SuppressPointsStacking) ? GetBase().GetStackAmount() : 1u; // ignore negative values (can be result apply spellmods to aura damage uint damage = (uint)Math.Max(GetAmount(), 0); @@ -5311,7 +5311,7 @@ namespace Game.Spells if (GetBase().IsPermanent() && target.IsFullHealth()) return; - uint stackAmountForBonuses = !GetSpellEffectInfo().EffectAttributes.HasFlag(SpellEffectAttributes.NoScaleWithStack) ? GetBase().GetStackAmount() : 1u; + uint stackAmountForBonuses = !GetSpellEffectInfo().EffectAttributes.HasFlag(SpellEffectAttributes.SuppressPointsStacking) ? GetBase().GetStackAmount() : 1u; // ignore negative values (can be result apply spellmods to aura damage uint damage = (uint)Math.Max(GetAmount(), 0); diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index 2f8741249..4ed897e79 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -398,6 +398,7 @@ namespace Game.Spells spellEffectInfo.ImplicitTargetConditions == effects[j].ImplicitTargetConditions && spellEffectInfo.CalcRadius(m_caster, SpellTargetIndex.TargetA) == effects[j].CalcRadius(m_caster, SpellTargetIndex.TargetA) && spellEffectInfo.CalcRadius(m_caster, SpellTargetIndex.TargetB) == effects[j].CalcRadius(m_caster, SpellTargetIndex.TargetB) && + spellEffectInfo.EffectAttributes.HasFlag(SpellEffectAttributes.PlayersOnly) == effects[j].EffectAttributes.HasFlag(SpellEffectAttributes.PlayersOnly) && CheckScriptEffectImplicitTargets(spellEffectInfo.EffectIndex, (uint)j)) { effectMask |= 1u << j; @@ -658,7 +659,7 @@ namespace Game.Spells } } - WorldObject target = SearchNearbyTarget(range, targetType.GetObjectType(), targetType.GetCheckType(), condList); + WorldObject target = SearchNearbyTarget(spellEffectInfo, range, targetType.GetObjectType(), targetType.GetCheckType(), condList); float randomRadius = 0.0f; switch (targetType.GetTarget()) { @@ -782,7 +783,7 @@ namespace Game.Spells var condList = spellEffectInfo.ImplicitTargetConditions; float radius = spellEffectInfo.CalcRadius(m_caster, targetIndex) * m_spellValue.RadiusMod; - GridMapTypeMask containerTypeMask = GetSearcherTypeMask(objectType, condList); + GridMapTypeMask containerTypeMask = GetSearcherTypeMask(m_spellInfo, spellEffectInfo, objectType, condList); if (containerTypeMask != 0) { float extraSearchRadius = radius > 0.0f ? SharedConst.ExtraCellSearchRadius : 0.0f; @@ -892,15 +893,15 @@ namespace Game.Spells if (!m_caster.IsUnit() || !m_caster.ToUnit().IsInRaidWith(targetedUnit)) targets.Add(m_targets.GetUnitTarget()); else - SearchAreaTargets(targets, radius, targetedUnit, referer, targetType.GetObjectType(), targetType.GetCheckType(), spellEffectInfo.ImplicitTargetConditions); + SearchAreaTargets(targets, spellEffectInfo, radius, targetedUnit, referer, targetType.GetObjectType(), targetType.GetCheckType(), spellEffectInfo.ImplicitTargetConditions); } break; case Targets.UnitCasterAndSummons: targets.Add(m_caster); - SearchAreaTargets(targets, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), spellEffectInfo.ImplicitTargetConditions); + SearchAreaTargets(targets, spellEffectInfo, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), spellEffectInfo.ImplicitTargetConditions); break; default: - SearchAreaTargets(targets, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), spellEffectInfo.ImplicitTargetConditions); + SearchAreaTargets(targets, spellEffectInfo, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), spellEffectInfo.ImplicitTargetConditions); break; } @@ -1484,7 +1485,7 @@ namespace Game.Spells var condList = spellEffectInfo.ImplicitTargetConditions; float radius = spellEffectInfo.CalcRadius(m_caster, targetIndex) * m_spellValue.RadiusMod; - GridMapTypeMask containerTypeMask = GetSearcherTypeMask(objectType, condList); + GridMapTypeMask containerTypeMask = GetSearcherTypeMask(m_spellInfo, spellEffectInfo, objectType, condList); if (containerTypeMask != 0) { WorldObjectSpellLineTargetCheck check = new(m_caster, dst, m_spellInfo.Width != 0 ? m_spellInfo.Width : m_caster.GetCombatReach(), radius, m_caster, m_spellInfo, selectionType, condList, objectType); @@ -1625,7 +1626,7 @@ namespace Game.Spells } } - public GridMapTypeMask GetSearcherTypeMask(SpellTargetObjectTypes objType, List condList) + public static GridMapTypeMask GetSearcherTypeMask(SpellInfo spellInfo, SpellEffectInfo spellEffectInfo, SpellTargetObjectTypes objType, List condList) { // this function selects which containers need to be searched for spell target GridMapTypeMask retMask = GridMapTypeMask.All; @@ -1650,11 +1651,11 @@ namespace Game.Spells break; } - if (m_spellInfo.HasAttribute(SpellAttr3.OnlyOnPlayer)) + if (spellInfo.HasAttribute(SpellAttr3.OnlyOnPlayer) || spellEffectInfo.EffectAttributes.HasFlag(SpellEffectAttributes.PlayersOnly)) retMask &= GridMapTypeMask.Corpse | GridMapTypeMask.Player; - if (m_spellInfo.HasAttribute(SpellAttr3.OnlyOnGhosts)) + if (spellInfo.HasAttribute(SpellAttr3.OnlyOnGhosts)) retMask &= GridMapTypeMask.Player; - if (m_spellInfo.HasAttribute(SpellAttr5.NotOnPlayer)) + if (spellInfo.HasAttribute(SpellAttr5.NotOnPlayer)) retMask &= ~GridMapTypeMask.Player; if (condList != null) @@ -1662,7 +1663,7 @@ namespace Game.Spells return retMask; } - void SearchTargets(Notifier notifier, GridMapTypeMask containerMask, WorldObject referer, Position pos, float radius) + public static void SearchTargets(Notifier notifier, GridMapTypeMask containerMask, WorldObject referer, Position pos, float radius) { if (containerMask == 0) return; @@ -1688,20 +1689,21 @@ namespace Game.Spells } } - WorldObject SearchNearbyTarget(float range, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, List condList) + WorldObject SearchNearbyTarget(SpellEffectInfo spellEffectInfo, float range, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, List condList) { - GridMapTypeMask containerTypeMask = GetSearcherTypeMask(objectType, condList); + GridMapTypeMask containerTypeMask = GetSearcherTypeMask(m_spellInfo, spellEffectInfo, objectType, condList); if (containerTypeMask == 0) return null; + var check = new WorldObjectSpellNearbyTargetCheck(range, m_caster, m_spellInfo, selectionType, condList, objectType); var searcher = new WorldObjectLastSearcher(m_caster, check, containerTypeMask); SearchTargets(searcher, containerTypeMask, m_caster, m_caster.GetPosition(), range); return searcher.GetTarget(); } - void SearchAreaTargets(List targets, float range, Position position, WorldObject referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, List condList) + void SearchAreaTargets(List targets, SpellEffectInfo spellEffectInfo, float range, Position position, WorldObject referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, List condList) { - var containerTypeMask = GetSearcherTypeMask(objectType, condList); + var containerTypeMask = GetSearcherTypeMask(m_spellInfo, spellEffectInfo, objectType, condList); if (containerTypeMask == 0) return; @@ -1750,7 +1752,7 @@ namespace Game.Spells WorldObject chainSource = m_spellInfo.HasAttribute(SpellAttr2.ChainFromCaster) ? m_caster : target; List tempTargets = new(); - SearchAreaTargets(tempTargets, searchRadius, chainSource, m_caster, objectType, selectType, spellEffectInfo.ImplicitTargetConditions); + SearchAreaTargets(tempTargets, spellEffectInfo, searchRadius, chainSource, m_caster, objectType, selectType, spellEffectInfo.ImplicitTargetConditions); tempTargets.Remove(target); // remove targets which are always invalid for chain spells @@ -1761,7 +1763,7 @@ namespace Game.Spells while (chainTargets != 0) { // try to get unit for next chain jump - WorldObject found = null; + WorldObject foundObj = null; // get unit with highest hp deficit in dist if (isChainHeal) { @@ -1772,9 +1774,9 @@ namespace Game.Spells if (unitTarget != null) { uint deficit = (uint)(unitTarget.GetMaxHealth() - unitTarget.GetHealth()); - if ((deficit > maxHPDeficit || found == null) && chainSource.IsWithinDist(unitTarget, jumpRadius) && IsWithinLOS(chainSource, unitTarget, false, ModelIgnoreFlags.M2)) + if ((deficit > maxHPDeficit || foundObj == null) && chainSource.IsWithinDist(unitTarget, jumpRadius) && IsWithinLOS(chainSource, unitTarget, false, ModelIgnoreFlags.M2)) { - found = obj; + foundObj = obj; maxHPDeficit = deficit; } } @@ -1785,24 +1787,28 @@ namespace Game.Spells { foreach (var obj in tempTargets) { - if (found == null) - { - if (chainSource.IsWithinDist(obj, jumpRadius) && IsWithinLOS(chainSource, obj, false, ModelIgnoreFlags.M2)) - found = obj; - } - else if (chainSource.GetDistanceOrder(obj, found) && IsWithinLOS(chainSource, obj, false, ModelIgnoreFlags.M2)) - found = obj; + bool isBestDistanceMatch = foundObj != null ? chainSource.GetDistanceOrder(obj, foundObj) : chainSource.IsWithinDist(obj, jumpRadius); + if (!isBestDistanceMatch) + continue; + + if (!IsWithinLOS(chainSource, obj, false, ModelIgnoreFlags.M2)) + continue; + + if (spellEffectInfo.EffectAttributes.HasFlag(SpellEffectAttributes.EnforceLineOfSightToChainTargets) && !IsWithinLOS(m_caster, obj, false, ModelIgnoreFlags.M2)) + continue; + + foundObj = obj; } } // not found any valid target - chain ends - if (found == null) + if (foundObj == null) break; if (!m_spellInfo.HasAttribute(SpellAttr2.ChainFromCaster) && !spellEffectInfo.EffectAttributes.HasFlag(SpellEffectAttributes.ChainFromInitialTarget)) - chainSource = found; + chainSource = foundObj; - targets.Add(found); - tempTargets.Remove(found); + targets.Add(foundObj); + tempTargets.Remove(foundObj); --chainTargets; } } @@ -7118,7 +7124,7 @@ namespace Game.Spells } default: { - if (losPosition == null || m_spellInfo.HasAttribute(SpellAttr5.AlwaysAoeLineOfSight)) + if (losPosition == null || m_spellInfo.HasAttribute(SpellAttr5.AlwaysAoeLineOfSight) || spellEffectInfo.EffectAttributes.HasFlag(SpellEffectAttributes.AlwaysAoeLineOfSight)) { // Get GO cast coordinates if original caster . GO WorldObject caster = null; diff --git a/Source/Game/Spells/SpellInfo.cs b/Source/Game/Spells/SpellInfo.cs index 5a8194fa9..3ecf5f9fa 100644 --- a/Source/Game/Spells/SpellInfo.cs +++ b/Source/Game/Spells/SpellInfo.cs @@ -3379,6 +3379,9 @@ namespace Game.Spells if (spellInfo.HasAttribute(SpellAttr4.AuraIsBuff)) return true; + if (effect.EffectAttributes.HasFlag(SpellEffectAttributes.IsHarmful)) + return false; + visited.Add(Tuple.Create(spellInfo, effect.EffectIndex)); //We need scaling level info for some auras that compute bp 0 or positive but should be debuffs