diff --git a/Source/Framework/Constants/CliDBConst.cs b/Source/Framework/Constants/CliDBConst.cs index 0b448b3bf..4a003266d 100644 --- a/Source/Framework/Constants/CliDBConst.cs +++ b/Source/Framework/Constants/CliDBConst.cs @@ -1746,6 +1746,7 @@ namespace Framework.Constants None = 0, UnaffectedByInvulnerability = 0x01, // not cancelled by immunities NoScaleWithStack = 0x40, + ChainFromInitialTarget = 0x80, StackAuraAmountOnRecast = 0x8000, // refreshing periodic auras with this attribute will add remaining damage to new aura AllowAnyExplicitTarget = 0x100000, IgnoreDuringCooldownTimeRateCalculation = 0x800000 diff --git a/Source/Framework/Constants/Spells/SpellConst.cs b/Source/Framework/Constants/Spells/SpellConst.cs index ebdf86cf5..78408be66 100644 --- a/Source/Framework/Constants/Spells/SpellConst.cs +++ b/Source/Framework/Constants/Spells/SpellConst.cs @@ -1723,38 +1723,38 @@ namespace Framework.Constants } public enum SpellAttr4 : uint { - IgnoreResistances = 0x01, // 0 Spells With This Attribute Will Completely Ignore The Target'S Resistance (These Spells Can'T Be Resisted) - ProcOnlyOnCaster = 0x02, // 1 Proc Only On Effects With TargetUnitCaster? - AuraExpiresOffline = 0x04, // 2 duration is removed from aura while player is logged out - Unk3 = 0x08, // 3 - Unk4 = 0x10, // 4 This Will No Longer Cause Guards To Attack On Use?? - Unk5 = 0x20, // 5 - NotStealable = 0x40, // 6 Although Such Auras Might Be Dispellable, They Cannot Be Stolen - CanCastWhileCasting = 0x80, // 7 Can be cast while another cast is in progress - see CanCastWhileCasting(SpellRec const*,CGUnit_C *,int &) - FixedDamage = 0x100, // 8 Ignores Taken Percent Damage Mods? - TriggerActivate = 0x200, // 9 Initially Disabled / Trigger Activate From Event (Execute, Riposte, Deep Freeze End Other) - SpellVsExtendCost = 0x400, // 10 Rogue Shiv Have This Flag - Unk11 = 0x800, // 11 - Unk12 = 0x1000, // 12 - CombatLogNoCaster = 0x2000, // 13 No caster object is sent to client combat log - DamageDoesntBreakAuras = 0x4000, // 14 Doesn'T Break Auras By Damage From These Spells - HiddenInSpellbook = 0x8000, // 15 - NotUsableInArenaOrRatedBg = 0x10000, // 16 Cannot Be Used In Both Arenas Or Rated Battlegrounds - UsableInArena = 0x20000, // 17 - AreaTargetChain = 0x40000, // 18 (Nyi)Hits Area Targets One After Another Instead Of All At Once - Unk19 = 0x80000, // 19 Proc Dalayed, After Damage Or Don'T Proc On Absorb? - NotCheckSelfcastPower = 0x100000, // 20 Supersedes Message "More Powerful Spell Applied" For Self Casts. - DontRemoveInArena = 0x200000, // 21 Pally Aura, Dk Presence, Dudu Form, Warrior Stance, Shadowform, Hunter Track - Unk22 = 0x400000, // 22 Seal Of Command (42058,57770) And Gymer'S Smash 55426 - SuppressWeaponProcs = 0x800000, // 23 spells with this flag should not trigger item spells / enchants (mostly in conjunction with SPELL_ATTR0_STOP_ATTACK_TARGET) - Unk24 = 0x1000000, // 24 Some Shoot Spell - IsPetScaling = 0x2000000, // 25 Pet Scaling Auras - CastOnlyInOutland = 0x4000000, // 26 Can Only Be Used In Outland. - Unk27 = 0x8000000, // 27 - Unk28 = 0x10000000, // 28 Aimed Shot - Unk29 = 0x20000000, // 29 - Unk30 = 0x40000000, // 30 - Unk31 = 0x80000000 // 31 Polymorph (Chicken) 228 And Sonic Boom (38052,38488) + NoCastLog = 0x01, // No Cast Log + ClassTriggerOnlyOnTarget = 0x02, // Class Trigger Only On Target + AuraExpiresOffline = 0x04, // Aura Expires Offline Description Debuffs (Except Resurrection Sickness) Will Automatically Do This + NoHelpfulThreat = 0x08, // No Helpful Threat + NoHarmfulThreat = 0x10, // No Harmful Threat + AllowClientTargeting = 0x20, // Allow Client Targeting Description Allows Client To Send Spell Targets For This Spell. Applies Only To Pet Spells, Without This Attribute CmsgPetAction Is Sent Instead Of CmsgPetCastSpell + CannotBeStolen = 0x40, // Cannot Be Stolen + AllowCastWhileCasting = 0x80, // Allow Cast While Casting Description Ignores Already In-Progress Cast And Still Casts + IgnoreDamageTakenModifiers = 0x100, // Ignore Damage Taken Modifiers + CombatFeedbackWhenUsable = 0x200, // Combat Feedback When Usable (Client Only) + WeaponSpeedCostScaling = 0x400, // Weapon Speed Cost Scaling Description Adds 10 To Power Cost For Each 1s Of Weapon Speed + NoPartialImmunity = 0x800, // No Partial Immunity + AuraIsBuff = 0x1000, // Aura Is Buff + DoNotLogCaster = 0x2000, // Do Not Log Caster + ReactiveDamageProc = 0x4000, // Reactive Damage Proc Description Damage From Spells With This Attribute Doesn'T Break Auras That Normally Break On Damage Taken + NotInSpellbook = 0x8000, // Not In Spellbook + NotInArenaOrRatedBattleground = 0x10000, // Not In Arena Or Rated Battleground Description Makes Spell Unusable Despite Cd <= 10min + IgnoreDefaultArenaRestrictions = 0x20000, // Ignore Default Arena Restrictions Description Makes Spell Usable Despite Cd > 10min + BouncyChainMissiles = 0x40000, // Bouncy Chain Missiles Description Hits Area Targets Over Time Instead Of All At Once + AllowProcWhileSitting = 0x80000, // Allow Proc While Sitting + AuraNeverBounces = 0x100000, // Aura Never Bounces + AllowEnteringArena = 0x200000, // Allow Entering Arena + ProcSuppressSwingAnim = 0x400000, // Proc Suppress Swing Anim + SuppressWeaponProcs = 0x800000, // Suppress Weapon Procs + AutoRangedCombat = 0x1000000, // Auto Ranged Combat + OwnerPowerScaling = 0x2000000, // Owner Power Scaling + OnlyFlyingAreas = 0x4000000, // Only Flying Areas + ForceDisplayCastbar = 0x8000000, // Force Display Castbar + IgnoreCombatTimer = 0x10000000, // Ignore Combat Timer + AuraBounceFailsSpell = 0x20000000, // Aura Bounce Fails Spell + Obsolete = 0x40000000, // Obsolete + UseFacingFromSpell = 0x80000000 // Use Facing From Spell } public enum SpellAttr5 : uint { @@ -1770,7 +1770,7 @@ namespace Framework.Constants StartPeriodicAtApply = 0x200, // 9 Begin Periodic Tick At Aura Apply HideDuration = 0x400, // 10 Do Not Send Duration To Client AllowTargetOfTargetAsTarget = 0x800, // 11 (Nyi) Uses Target'S Target As Target If Original Target Not Valid (Intervene For Example) - Unk12 = 0x1000, // 12 Cleave Related? + MeleeChainTargeting = 0x1000, // 12 Cleave Related? HasteAffectDuration = 0x2000, // 13 Haste Effects Decrease Duration Of This NotUsableWhileCharmed = 0x4000, // 14 Charmed units cannot cast this spell Unk15 = 0x8000, // 15 Inflits On Multiple Targets? diff --git a/Source/Game/Combat/ThreatManager.cs b/Source/Game/Combat/ThreatManager.cs index 098684cd4..c8c6d2b7a 100644 --- a/Source/Game/Combat/ThreatManager.cs +++ b/Source/Game/Combat/ThreatManager.cs @@ -590,7 +590,7 @@ namespace Game.Combat public void ForwardThreatForAssistingMe(Unit assistant, float baseAmount, SpellInfo spell = null, bool ignoreModifiers = false) { - if (spell != null && spell.HasAttribute(SpellAttr1.NoThreat)) // shortcut, none of the calls would do anything + if (spell != null && (spell.HasAttribute(SpellAttr1.NoThreat) || spell.HasAttribute(SpellAttr4.NoHelpfulThreat))) // shortcut, none of the calls would do anything return; if (_threatenedByMe.Empty()) diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index 5868ec689..60a50a73d 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -2088,30 +2088,6 @@ namespace Game.Entities ApplySpellImmune(placeholderSpellId, SpellImmunity.School, 1u << i, true); } - public override bool IsImmunedToSpell(SpellInfo spellInfo, WorldObject caster) - { - if (spellInfo == null) - return false; - - bool immunedToAllEffects = true; - foreach (var spellEffectInfo in spellInfo.GetEffects()) - { - if (!spellEffectInfo.IsEffect()) - continue; - - if (!IsImmunedToSpellEffect(spellInfo, spellEffectInfo, caster)) - { - immunedToAllEffects = false; - break; - } - } - - if (immunedToAllEffects) - return true; - - return base.IsImmunedToSpell(spellInfo, caster); - } - public override bool IsImmunedToSpellEffect(SpellInfo spellInfo, SpellEffectInfo spellEffectInfo, WorldObject caster) { if (GetCreatureTemplate().CreatureType == CreatureType.Mechanical && spellEffectInfo.IsEffect(SpellEffectName.Heal)) diff --git a/Source/Game/Entities/Unit/Unit.Spells.cs b/Source/Game/Entities/Unit/Unit.Spells.cs index 905e87c90..c469b43a1 100644 --- a/Source/Game/Entities/Unit/Unit.Spells.cs +++ b/Source/Game/Entities/Unit/Unit.Spells.cs @@ -273,8 +273,8 @@ namespace Game.Entities if (cheatDeath.GetMiscValue().HasAnyFlag((int)SpellSchoolMask.Normal)) MathFunctions.AddPct(ref TakenTotalMod, cheatDeath.GetAmount()); - // Spells with SPELL_ATTR4_FIXED_DAMAGE should only benefit from mechanic damage mod auras. - if (!spellProto.HasAttribute(SpellAttr4.FixedDamage)) + // Spells with SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS should only benefit from mechanic damage mod auras. + if (!spellProto.HasAttribute(SpellAttr4.IgnoreDamageTakenModifiers)) { // Versatility Player modOwner = GetSpellModOwner(); @@ -1216,7 +1216,7 @@ namespace Game.Entities } } } - public virtual bool IsImmunedToSpell(SpellInfo spellInfo, WorldObject caster) + public bool IsImmunedToSpell(SpellInfo spellInfo, WorldObject caster) { if (spellInfo == null) return false; @@ -1259,6 +1259,9 @@ namespace Game.Entities immuneToAllEffects = false; break; } + + if (spellInfo.HasAttribute(SpellAttr4.NoPartialImmunity)) + return true; } if (immuneToAllEffects) //Return immune only if the target is immune to all spell effects. @@ -1939,8 +1942,8 @@ namespace Game.Entities SpellSchoolMask damageSchoolMask = damageInfo.schoolMask; - // Spells with SPELL_ATTR4_FIXED_DAMAGE ignore resilience because their damage is based off another spell's damage. - if (!spellInfo.HasAttribute(SpellAttr4.FixedDamage)) + // Spells with SPELL_ATTR4_IGNORE_DAMAGE_TAKEN_MODIFIERS ignore resilience because their damage is based off another spell's damage. + if (!spellInfo.HasAttribute(SpellAttr4.IgnoreDamageTakenModifiers)) { if (IsDamageReducedByArmor(damageSchoolMask, spellInfo)) damage = (int)CalcArmorReducedDamage(damageInfo.attacker, victim, (uint)damage, spellInfo, attackType); @@ -3433,7 +3436,7 @@ namespace Game.Entities RemoveAppliedAuras(aurApp => { Aura aura = aurApp.GetBase(); - return (!aura.GetSpellInfo().HasAttribute(SpellAttr4.DontRemoveInArena) // don't remove stances, shadowform, pally/hunter auras + return (!aura.GetSpellInfo().HasAttribute(SpellAttr4.AllowEnteringArena) // don't remove stances, shadowform, pally/hunter auras && !aura.IsPassive() // don't remove passive auras && (aurApp.IsPositive() || !aura.GetSpellInfo().HasAttribute(SpellAttr3.AllowAuraWhileDead))) || // not negative death persistent auras aura.GetSpellInfo().HasAttribute(SpellAttr5.RemoveEnteringArena); // special marker, always remove diff --git a/Source/Game/Entities/Unit/Unit.cs b/Source/Game/Entities/Unit/Unit.cs index c0983a0ca..87542d536 100644 --- a/Source/Game/Entities/Unit/Unit.cs +++ b/Source/Game/Entities/Unit/Unit.cs @@ -2440,7 +2440,7 @@ namespace Game.Entities // interrupting auras with SpellAuraInterruptFlags.Damage before checking !damage (absorbed damage breaks that type of auras) if (spellProto != null) { - if (!spellProto.HasAttribute(SpellAttr4.DamageDoesntBreakAuras)) + if (!spellProto.HasAttribute(SpellAttr4.ReactiveDamageProc)) victim.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags.Damage, spellProto); } else @@ -2657,7 +2657,7 @@ namespace Game.Entities if (damagetype != DamageEffectType.DOT && damage > 0 && !victim.GetOwnerGUID().IsPlayer() && (spellProto == null || !spellProto.HasAura(AuraType.DamageShield))) victim.ToCreature().SetLastDamagedTime(GameTime.GetGameTime() + SharedConst.MaxAggroResetTime); - if (attacker != null) + if (attacker != null && (spellProto == null || !spellProto.HasAttribute(SpellAttr4.NoHarmfulThreat))) victim.GetThreatManager().AddThreat(attacker, damage, spellProto); } else // victim is a player @@ -3245,13 +3245,6 @@ namespace Game.Entities if (schoolMask.HasAnyFlag(SpellSchoolMask.Holy) && victim.GetTypeId() != TypeId.Unit) return 0; - // Ignore spells that can't be resisted - if (spellInfo != null) - { - if (spellInfo.HasAttribute(SpellAttr4.IgnoreResistances)) - return 0; - } - float averageResist = CalculateAverageResistReduction(attacker, schoolMask, victim, spellInfo); float[] discreteResistProbability = new float[11]; diff --git a/Source/Game/Spells/Auras/Aura.cs b/Source/Game/Spells/Auras/Aura.cs index f660cd286..c77ecb32b 100644 --- a/Source/Game/Spells/Auras/Aura.cs +++ b/Source/Game/Spells/Auras/Aura.cs @@ -1828,6 +1828,10 @@ namespace Game.Spells if (target.GetGUID() != GetCasterGUID()) return 0; + if (!m_spellInfo.HasAttribute(SpellAttr4.AllowProcWhileSitting)) + if (!target.IsStandState()) + return 0; + bool success = RandomHelper.randChance(CalcProcChance(procEntry, eventInfo)); SetLastProcAttemptTime(now); diff --git a/Source/Game/Spells/Auras/AuraEffect.cs b/Source/Game/Spells/Auras/AuraEffect.cs index 092acaa25..e819b57e7 100644 --- a/Source/Game/Spells/Auras/AuraEffect.cs +++ b/Source/Game/Spells/Auras/AuraEffect.cs @@ -5023,14 +5023,14 @@ namespace Game.Spells damage = damageReducedArmor; } - if (!GetSpellInfo().HasAttribute(SpellAttr4.FixedDamage)) + if (!GetSpellInfo().HasAttribute(SpellAttr4.IgnoreDamageTakenModifiers)) { if (GetSpellEffectInfo().IsTargetingArea() || GetSpellEffectInfo().IsAreaAuraEffect() || GetSpellEffectInfo().IsEffect(SpellEffectName.PersistentAreaAura)) damage = (uint)target.CalculateAOEAvoidance((int)damage, (uint)m_spellInfo.SchoolMask, GetBase().GetCastItemGUID()); } int dmg = (int)damage; - if (!GetSpellInfo().HasAttribute(SpellAttr4.FixedDamage) && caster != null && caster.CanApplyResilience()) + if (!GetSpellInfo().HasAttribute(SpellAttr4.IgnoreDamageTakenModifiers) && caster != null && caster.CanApplyResilience()) Unit.ApplyResilience(target, ref dmg); damage = (uint)dmg; @@ -5105,14 +5105,14 @@ namespace Game.Spells damage = damageReducedArmor; } - if (!GetSpellInfo().HasAttribute(SpellAttr4.FixedDamage)) + if (!GetSpellInfo().HasAttribute(SpellAttr4.IgnoreDamageTakenModifiers)) { if (GetSpellEffectInfo().IsTargetingArea() || GetSpellEffectInfo().IsAreaAuraEffect() || GetSpellEffectInfo().IsEffect(SpellEffectName.PersistentAreaAura)) damage = (uint)target.CalculateAOEAvoidance((int)damage, (uint)m_spellInfo.SchoolMask, GetBase().GetCastItemGUID()); } int dmg = (int)damage; - if (!GetSpellInfo().HasAttribute(SpellAttr4.FixedDamage) && caster != null && caster.CanApplyResilience()) + if (!GetSpellInfo().HasAttribute(SpellAttr4.IgnoreDamageTakenModifiers) && caster != null && caster.CanApplyResilience()) Unit.ApplyResilience(target, ref dmg); damage = (uint)dmg; @@ -5284,7 +5284,8 @@ namespace Game.Spells { gainedAmount = caster.ModifyPower(powerType, gainAmount); // energize is not modified by threat modifiers - target.GetThreatManager().AddThreat(caster, gainedAmount * 0.5f, GetSpellInfo(), true); + if (!GetSpellInfo().HasAttribute(SpellAttr4.NoHelpfulThreat)) + target.GetThreatManager().AddThreat(caster, gainedAmount * 0.5f, GetSpellInfo(), true); } // Drain Mana diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index c1ab9fda8..7829b1f51 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -99,7 +99,7 @@ namespace Game.Spells if (info.HasAttribute(SpellAttr2.DoNotReportSpellFailure)) _triggeredCastFlags = _triggeredCastFlags | TriggerCastFlags.DontReportCastError; - if (m_spellInfo.HasAttribute(SpellAttr4.CanCastWhileCasting)) + if (m_spellInfo.HasAttribute(SpellAttr4.AllowCastWhileCasting)) _triggeredCastFlags = _triggeredCastFlags | TriggerCastFlags.IgnoreCastInProgress; effectHandleMode = SpellEffectHandleMode.Launch; @@ -529,6 +529,9 @@ namespace Game.Spells if (target) { SpellDestination dest = new(target); + if (m_spellInfo.HasAttribute(SpellAttr4.UseFacingFromSpell)) + dest.Position.SetOrientation(spellEffectInfo.PositionFacing); + CallScriptDestinationTargetSelectHandlers(ref dest, spellEffectInfo.EffectIndex, targetType); m_targets.SetDst(dest); } @@ -541,6 +544,9 @@ namespace Game.Spells case Targets.DestChannelCaster: { SpellDestination dest = new(channeledSpell.GetCaster()); + if (m_spellInfo.HasAttribute(SpellAttr4.UseFacingFromSpell)) + dest.Position.SetOrientation(spellEffectInfo.PositionFacing); + CallScriptDestinationTargetSelectHandlers(ref dest, spellEffectInfo.EffectIndex, targetType); m_targets.SetDst(dest); break; @@ -607,6 +613,9 @@ namespace Game.Spells if (focusObject != null) { SpellDestination dest = new(focusObject); + if (m_spellInfo.HasAttribute(SpellAttr4.UseFacingFromSpell)) + dest.Position.SetOrientation(spellEffectInfo.PositionFacing); + CallScriptDestinationTargetSelectHandlers(ref dest, spellEffectInfo.EffectIndex, targetType); m_targets.SetDst(dest); } @@ -681,6 +690,9 @@ namespace Game.Spells break; case SpellTargetObjectTypes.Dest: SpellDestination dest = new(target); + if (m_spellInfo.HasAttribute(SpellAttr4.UseFacingFromSpell)) + dest.Position.SetOrientation(spellEffectInfo.PositionFacing); + CallScriptDestinationTargetSelectHandlers(ref dest, spellEffectInfo.EffectIndex, targetType); m_targets.SetDst(dest); break; @@ -849,6 +861,8 @@ namespace Game.Spells if (targetType.GetObjectType() == SpellTargetObjectTypes.UnitAndDest) { SpellDestination dest = new(referer); + if (m_spellInfo.HasAttribute(SpellAttr4.UseFacingFromSpell)) + dest.Position.SetOrientation(spellEffectInfo.PositionFacing); CallScriptDestinationTargetSelectHandlers(ref dest, spellEffectInfo.EffectIndex, targetType); @@ -1064,6 +1078,10 @@ namespace Game.Spells break; } } + + if (m_spellInfo.HasAttribute(SpellAttr4.UseFacingFromSpell)) + dest.Position.SetOrientation(spellEffectInfo.PositionFacing); + CallScriptDestinationTargetSelectHandlers(ref dest, spellEffectInfo.EffectIndex, targetType); m_targets.SetDst(dest); } @@ -1095,6 +1113,9 @@ namespace Game.Spells break; } + if (m_spellInfo.HasAttribute(SpellAttr4.UseFacingFromSpell)) + dest.Position.SetOrientation(spellEffectInfo.PositionFacing); + CallScriptDestinationTargetSelectHandlers(ref dest, spellEffectInfo.EffectIndex, targetType); m_targets.SetDst(dest); } @@ -1134,6 +1155,9 @@ namespace Game.Spells break; } + if (m_spellInfo.HasAttribute(SpellAttr4.UseFacingFromSpell)) + dest.Position.SetOrientation(spellEffectInfo.PositionFacing); + CallScriptDestinationTargetSelectHandlers(ref dest, spellEffectInfo.EffectIndex, targetType); m_targets.ModDst(dest); } @@ -1255,16 +1279,21 @@ namespace Game.Spells m_applyMultiplierMask |= effMask; List targets = new(); - SearchChainTargets(targets, (uint)maxTargets - 1, target, targetType.GetObjectType(), targetType.GetCheckType(), spellEffectInfo.ImplicitTargetConditions, targetType.GetTarget() == Targets.UnitChainhealAlly); + SearchChainTargets(targets, (uint)maxTargets - 1, target, targetType.GetObjectType(), targetType.GetCheckType(), spellEffectInfo, targetType.GetTarget() == Targets.UnitChainhealAlly); // Chain primary target is added earlier CallScriptObjectAreaTargetSelectHandlers(targets, spellEffectInfo.EffectIndex, targetType); + Position losPosition = m_spellInfo.HasAttribute(SpellAttr2.ChainFromCaster) ? m_caster : target; + foreach (var obj in targets) { Unit unitTarget = obj.ToUnit(); if (unitTarget) - AddUnitTarget(unitTarget, effMask, false); + AddUnitTarget(unitTarget, effMask, false, true, losPosition); + + if (!m_spellInfo.HasAttribute(SpellAttr2.ChainFromCaster) && !spellEffectInfo.EffectAttributes.HasFlag(SpellEffectAttributes.ChainFromInitialTarget)) + losPosition = obj; } } } @@ -1360,6 +1389,9 @@ namespace Game.Spells float z = m_targets.GetSrcPos().posZ + bestDist * (a * bestDist + b); SpellDestination dest = new(x, y, z, unitCaster.GetOrientation()); + if (m_spellInfo.HasAttribute(SpellAttr4.UseFacingFromSpell)) + dest.Position.SetOrientation(spellEffectInfo.PositionFacing); + CallScriptDestinationTargetSelectHandlers(ref dest, spellEffectInfo.EffectIndex, targetType); m_targets.ModDst(dest); } @@ -1615,7 +1647,7 @@ namespace Game.Spells SearchTargets(searcher, containerTypeMask, m_caster, position, range); } - void SearchChainTargets(List targets, uint chainTargets, WorldObject target, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectType, List condList, bool isChainHeal) + void SearchChainTargets(List targets, uint chainTargets, WorldObject target, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectType, SpellEffectInfo spellEffectInfo, bool isChainHeal) { // max dist for jump target selection float jumpRadius = 0.0f; @@ -1644,32 +1676,23 @@ namespace Game.Spells if (modOwner) modOwner.ApplySpellMod(m_spellInfo, SpellModOp.ChainJumpDistance, ref jumpRadius, this); - // chain lightning/heal spells and similar - allow to jump at larger distance and go out of los - bool isBouncingFar = (m_spellInfo.HasAttribute(SpellAttr4.AreaTargetChain) - || m_spellInfo.DmgClass == SpellDmgClass.None - || m_spellInfo.DmgClass == SpellDmgClass.Magic); - - // max dist which spell can reach - float searchRadius = jumpRadius; - if (isBouncingFar) - searchRadius *= chainTargets; + float searchRadius; + if (m_spellInfo.HasAttribute(SpellAttr2.ChainFromCaster)) + searchRadius = GetMinMaxRange(false).maxRange; + else if (spellEffectInfo.EffectAttributes.HasFlag(SpellEffectAttributes.ChainFromInitialTarget)) + searchRadius= jumpRadius; + else + searchRadius= jumpRadius * chainTargets; WorldObject chainSource = m_spellInfo.HasAttribute(SpellAttr2.ChainFromCaster) ? m_caster : target; List tempTargets = new(); - SearchAreaTargets(tempTargets, searchRadius, chainSource, m_caster, objectType, selectType, condList); + SearchAreaTargets(tempTargets, searchRadius, chainSource, m_caster, objectType, selectType, spellEffectInfo.ImplicitTargetConditions); tempTargets.Remove(target); // remove targets which are always invalid for chain spells // for some spells allow only chain targets in front of caster (swipe for example) - if (!isBouncingFar) - { - for (var i = 0; i < tempTargets.Count; ++i) - { - var obj = tempTargets[i]; - if (!m_caster.HasInArc(MathFunctions.PI, obj)) - tempTargets.Remove(obj); - } - } + if (m_spellInfo.HasAttribute(SpellAttr5.MeleeChainTargeting)) + tempTargets.RemoveAll(obj => !m_caster.HasInArc(MathF.PI, obj)); while (chainTargets != 0) { @@ -1700,7 +1723,7 @@ namespace Game.Spells { if (found == null) { - if ((!isBouncingFar || chainSource.IsWithinDist(obj, jumpRadius)) && chainSource.IsWithinLOSInMap(obj, LineOfSightChecks.All, ModelIgnoreFlags.M2)) + if (chainSource.IsWithinDist(obj, jumpRadius) && chainSource.IsWithinLOSInMap(obj, LineOfSightChecks.All, ModelIgnoreFlags.M2)) found = obj; } else if (chainSource.GetDistanceOrder(obj, found) && chainSource.IsWithinLOSInMap(obj, LineOfSightChecks.All, ModelIgnoreFlags.M2)) @@ -1711,7 +1734,7 @@ namespace Game.Spells if (found == null) break; - if (!m_spellInfo.HasAttribute(SpellAttr2.ChainFromCaster)) + if (!m_spellInfo.HasAttribute(SpellAttr2.ChainFromCaster) && !spellEffectInfo.EffectAttributes.HasFlag(SpellEffectAttributes.ChainFromInitialTarget)) chainSource = found; targets.Add(found); @@ -1829,17 +1852,33 @@ namespace Game.Spells if (m_caster != target) { float hitDelay = m_spellInfo.LaunchDelay; + WorldObject missileSource = m_caster; + if (m_spellInfo.HasAttribute(SpellAttr4.BouncyChainMissiles)) + { + var previousTargetInfo = m_UniqueTargetInfo.FindLast(target => (target.EffectMask & effectMask) != 0); + if (previousTargetInfo != null) + { + hitDelay = 0.0f; // this is not the first target in chain, LaunchDelay was already included + + WorldObject previousTarget = Global.ObjAccessor.GetWorldObject(m_caster, previousTargetInfo.TargetGUID); + if (previousTarget != null) + missileSource = previousTarget; + + targetInfo.TimeDelay += previousTargetInfo.TimeDelay; + } + } + if (m_spellInfo.HasAttribute(SpellAttr9.SpecialDelayCalculation)) hitDelay += m_spellInfo.Speed; else if (m_spellInfo.Speed > 0.0f) { // calculate spell incoming interval /// @todo this is a hack - float dist = Math.Max(m_caster.GetDistance(target.GetPositionX(), target.GetPositionY(), target.GetPositionZ()), 5.0f); + float dist = Math.Max(missileSource.GetDistance(target.GetPositionX(), target.GetPositionY(), target.GetPositionZ()), 5.0f); hitDelay += dist / m_spellInfo.Speed; } - targetInfo.TimeDelay = (ulong)Math.Floor(hitDelay * 1000.0f); + targetInfo.TimeDelay += (ulong)Math.Floor(hitDelay * 1000.0f); } else targetInfo.TimeDelay = 0L; @@ -2240,7 +2279,7 @@ namespace Game.Spells spellAura = null; } - public void DoTriggersOnSpellHit(Unit unit, uint effMask) + public void DoTriggersOnSpellHit(Unit unit) { // handle SPELL_AURA_ADD_TARGET_TRIGGER auras // this is executed after spell proc spells on target hit @@ -2251,7 +2290,7 @@ namespace Game.Spells int _duration = 0; foreach (var hit in m_hitTriggerSpells) { - if (CanExecuteTriggersOnHit(effMask, hit.triggeredByAura) && RandomHelper.randChance(hit.chance)) + if (CanExecuteTriggersOnHit(unit, hit.triggeredByAura) && RandomHelper.randChance(hit.chance)) { m_caster.CastSpell(unit, hit.triggeredSpell.Id, new CastSpellExtraArgs(TriggerCastFlags.FullMask) .SetTriggeringSpell(this) @@ -2850,7 +2889,7 @@ namespace Game.Spells creatureCaster.ReleaseSpellFocus(this); // Okay, everything is prepared. Now we need to distinguish between immediate and evented delayed spells - if ((m_spellInfo.HasHitDelay() && !m_spellInfo.IsChanneled()) || m_spellInfo.HasAttribute(SpellAttr4.Unk4)) + if ((m_spellInfo.HasHitDelay() && !m_spellInfo.IsChanneled()) || m_spellInfo.HasAttribute(SpellAttr4.NoHarmfulThreat)) { // Remove used for cast item if need (it can be already NULL after TakeReagents call // in case delayed spell remove item at cast delay start @@ -5726,7 +5765,10 @@ namespace Game.Spells } // check if target already has the same type, but more powerful aura - if (nonAuraEffectMask == 0 && (approximateAuraEffectMask & (1 << (int)spellEffectInfo.EffectIndex)) != 0 && !m_spellInfo.IsTargetingArea()) + if (!m_spellInfo.HasAttribute(SpellAttr4.AuraNeverBounces) + && (nonAuraEffectMask == 0 || m_spellInfo.HasAttribute(SpellAttr4.AuraBounceFailsSpell)) + && (approximateAuraEffectMask & (1 << (int)spellEffectInfo.EffectIndex)) != 0 + && !m_spellInfo.IsTargetingArea()) { Unit target = m_targets.GetUnitTarget(); if (target != null) @@ -6012,11 +6054,11 @@ namespace Game.Spells if (isRatedBattleground && m_spellInfo.HasAttribute(SpellAttr9.UsableInRatedBattlegrounds)) return SpellCastResult.SpellCastOk; - if (isArena && m_spellInfo.HasAttribute(SpellAttr4.UsableInArena)) + if (isArena && m_spellInfo.HasAttribute(SpellAttr4.IgnoreDefaultArenaRestrictions)) return SpellCastResult.SpellCastOk; // check NOT_USABLE attributes - if (m_spellInfo.HasAttribute(SpellAttr4.NotUsableInArenaOrRatedBg)) + if (m_spellInfo.HasAttribute(SpellAttr4.NotInArenaOrRatedBattleground)) return isArena ? SpellCastResult.NotInArena : SpellCastResult.NotInBattleground; if (isArena && m_spellInfo.HasAttribute(SpellAttr9.NotUsableInArena)) @@ -7617,15 +7659,20 @@ namespace Game.Spells return true; } - public bool CanExecuteTriggersOnHit(uint effMask, SpellInfo triggeredByAura = null) + public bool CanExecuteTriggersOnHit(Unit unit, SpellInfo triggeredByAura = null) { - bool only_on_caster = (triggeredByAura != null && triggeredByAura.HasAttribute(SpellAttr4.ProcOnlyOnCaster)); - // If triggeredByAura has SPELL_ATTR4_PROC_ONLY_ON_CASTER then it can only proc on a casted spell with TARGET_UNIT_CASTER - foreach (var spellEffectInfo in m_spellInfo.GetEffects()) - { - if ((effMask & (1 << (int)spellEffectInfo.EffectIndex)) != 0 && (!only_on_caster || (spellEffectInfo.TargetA.GetTarget() == Targets.UnitCaster))) - return true; - } + bool onlyOnTarget = triggeredByAura != null && triggeredByAura.HasAttribute(SpellAttr4.ClassTriggerOnlyOnTarget); + if (!onlyOnTarget) + return true; + + // If triggeredByAura has SPELL_ATTR4_CLASS_TRIGGER_ONLY_ON_TARGET then it can only proc on either noncaster units... + if (unit != m_caster) + return true; + + // ... or caster if it is the only target + if (m_UniqueTargetInfo.Count == 1) + return true; + return false; } @@ -8278,9 +8325,7 @@ namespace Game.Spells ProcFlagsHit hitMask = ProcFlagsHit.None; // Spells with this flag cannot trigger if effect is cast on self - bool canEffectTrigger = (!spell.m_spellInfo.HasAttribute(SpellAttr3.SuppressCasterProcs) || !spell.m_spellInfo.HasAttribute(SpellAttr3.SuppressTargetProcs)) - && spell.unitTarget.CanProc() - && (spell.CanExecuteTriggersOnHit(EffectMask) || MissCondition == SpellMissInfo.Immune || MissCondition == SpellMissInfo.Immune2); + bool canEffectTrigger = (!spell.m_spellInfo.HasAttribute(SpellAttr3.SuppressCasterProcs) || !spell.m_spellInfo.HasAttribute(SpellAttr3.SuppressTargetProcs)) && spell.unitTarget.CanProc(); // Trigger info was not filled in Spell::prepareDataForTriggerSystem - we do it now if (canEffectTrigger && !procAttacker && !procVictim) @@ -8516,7 +8561,7 @@ namespace Game.Spells } // Needs to be called after dealing damage/healing to not remove breaking on damage auras - spell.DoTriggersOnSpellHit(_spellHitTarget, EffectMask); + spell.DoTriggersOnSpellHit(_spellHitTarget); } if (_enablePVP) diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs index ffecea77a..b48af05f4 100644 --- a/Source/Game/Spells/SpellEffects.cs +++ b/Source/Game/Spells/SpellEffects.cs @@ -4347,7 +4347,7 @@ namespace Game.Spells if (Convert.ToBoolean(aura.GetSpellInfo().GetDispelMask() & dispelMask)) { // Need check for passive? this - if (!aurApp.IsPositive() || aura.IsPassive() || aura.GetSpellInfo().HasAttribute(SpellAttr4.NotStealable)) + if (!aurApp.IsPositive() || aura.IsPassive() || aura.GetSpellInfo().HasAttribute(SpellAttr4.CannotBeStolen)) continue; // 2.4.3 Patch Notes: "Dispel effects will no longer attempt to remove effects that have 100% dispel resistance." diff --git a/Source/Game/Spells/SpellInfo.cs b/Source/Game/Spells/SpellInfo.cs index 641c151a1..79bdd6917 100644 --- a/Source/Game/Spells/SpellInfo.cs +++ b/Source/Game/Spells/SpellInfo.cs @@ -580,7 +580,7 @@ namespace Game.Spells public bool HasInitialAggro() { - return !(HasAttribute(SpellAttr1.NoThreat) || HasAttribute(SpellAttr2.NoInitialThreat)); + return !(HasAttribute(SpellAttr1.NoThreat) || HasAttribute(SpellAttr2.NoInitialThreat) || HasAttribute(SpellAttr4.NoHarmfulThreat)); } public bool HasHitDelay() @@ -858,7 +858,7 @@ namespace Game.Spells } // continent limitation (virtual continent) - if (HasAttribute(SpellAttr4.CastOnlyInOutland)) + if (HasAttribute(SpellAttr4.OnlyFlyingAreas)) { uint mountFlags = 0; if (player && player.HasAuraType(AuraType.MountRestrictions)) @@ -2859,7 +2859,7 @@ namespace Game.Spells bool initiallyNegative = powerCost < 0; // Shiv - costs 20 + weaponSpeed*10 energy (apply only to non-triggered spell with energy cost) - if (HasAttribute(SpellAttr4.SpellVsExtendCost)) + if (HasAttribute(SpellAttr4.WeaponSpeedCostScaling)) { uint speed = 0; SpellShapeshiftFormRecord ss = CliDB.SpellShapeshiftFormStorage.LookupByKey(unitCaster.GetShapeshiftForm()); @@ -3323,6 +3323,9 @@ namespace Game.Spells if (spellInfo.HasAttribute(SpellAttr0.AuraIsDebuff)) return false; + if (spellInfo.HasAttribute(SpellAttr4.AuraIsBuff)) + return true; + 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 diff --git a/Source/Game/Spells/SpellManager.cs b/Source/Game/Spells/SpellManager.cs index 013e01e98..c7148a192 100644 --- a/Source/Game/Spells/SpellManager.cs +++ b/Source/Game/Spells/SpellManager.cs @@ -3156,7 +3156,7 @@ namespace Game.Entities 60864 // Jaws of Death }, spellInfo => { - spellInfo.AttributesEx4 |= SpellAttr4.FixedDamage; + spellInfo.AttributesEx4 |= SpellAttr4.IgnoreDamageTakenModifiers; }); // Howl of Azgalor @@ -3824,12 +3824,6 @@ namespace Game.Entities }); }); - // Coldflame (Lord Marrowgar) - ApplySpellFix(new[] { 69146 }, spellInfo => - { - spellInfo.AttributesEx4 &= ~SpellAttr4.IgnoreResistances; - }); - // Shadow's Fate ApplySpellFix(new[] { 71169 }, spellInfo => {