diff --git a/Source/Game/Chat/Commands/SpellCommands.cs b/Source/Game/Chat/Commands/SpellCommands.cs index bde040d48..e2f8fa3c0 100644 --- a/Source/Game/Chat/Commands/SpellCommands.cs +++ b/Source/Game/Chat/Commands/SpellCommands.cs @@ -89,7 +89,10 @@ namespace Game.Chat if (spellInfo != null) { ObjectGuid castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, target.GetMapId(), spellId, target.GetMap().GenerateLowGuid(HighGuid.Cast)); - Aura.TryRefreshStackOrCreate(spellInfo, castId, SpellConst.MaxEffectMask, target, target, target.GetMap().GetDifficultyID()); + AuraCreateInfo createInfo = new(castId, spellInfo, target.GetMap().GetDifficultyID(), SpellConst.MaxEffectMask, target); + createInfo.SetCaster(target); + + Aura.TryRefreshStackOrCreate(createInfo); } return true; diff --git a/Source/Game/Entities/Pet.cs b/Source/Game/Entities/Pet.cs index c733a5cdc..0a53f9432 100644 --- a/Source/Game/Entities/Pet.cs +++ b/Source/Game/Entities/Pet.cs @@ -910,7 +910,12 @@ namespace Game.Entities var info = effectInfo[key]; ObjectGuid castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, GetMapId(), spellInfo.Id, GetMap().GenerateLowGuid(HighGuid.Cast)); - Aura aura = Aura.TryCreate(spellInfo, castId, key.EffectMask, this, null, difficulty, info.BaseAmounts, null, casterGuid); + + AuraCreateInfo createInfo = new(castId, spellInfo, difficulty, key.EffectMask, this); + createInfo.SetCasterGUID(casterGuid); + createInfo.SetBaseAmount(info.BaseAmounts); + + Aura aura = Aura.TryCreate(createInfo); if (aura != null) { if (!aura.CanBeSaved()) diff --git a/Source/Game/Entities/Player/Player.DB.cs b/Source/Game/Entities/Player/Player.DB.cs index f8e4e3b96..8e111cc29 100644 --- a/Source/Game/Entities/Player/Player.DB.cs +++ b/Source/Game/Entities/Player/Player.DB.cs @@ -542,7 +542,13 @@ namespace Game.Entities AuraLoadEffectInfo info = effectInfo[key]; ObjectGuid castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, GetMapId(), spellInfo.Id, GetMap().GenerateLowGuid(HighGuid.Cast)); - Aura aura = Aura.TryCreate(spellInfo, castId, key.EffectMask, this, null, difficulty, info.BaseAmounts, null, casterGuid, itemGuid, castItemId, castItemLevel); + + AuraCreateInfo createInfo = new(castId, spellInfo, difficulty, key.EffectMask, this); + createInfo.SetCasterGUID(casterGuid); + createInfo.SetBaseAmount(info.BaseAmounts); + createInfo.SetCastItem(itemGuid, castItemId, castItemLevel); + + Aura aura = Aura.TryCreate(createInfo); if (aura != null) { if (!aura.CanBeSaved()) diff --git a/Source/Game/Entities/Unit/Unit.Spells.cs b/Source/Game/Entities/Unit/Unit.Spells.cs index dd8ae9704..bf4f59793 100644 --- a/Source/Game/Entities/Unit/Unit.Spells.cs +++ b/Source/Game/Entities/Unit/Unit.Spells.cs @@ -2885,8 +2885,15 @@ namespace Game.Entities effMask &= ~(uint)(1 << i); } + if (effMask == 0) + return null; + ObjectGuid castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, GetMapId(), spellInfo.Id, GetMap().GenerateLowGuid(HighGuid.Cast)); - Aura aura = Aura.TryRefreshStackOrCreate(spellInfo, castId, effMask, target, this, GetMap().GetDifficultyID()); + + AuraCreateInfo createInfo = new(castId, spellInfo, GetMap().GetDifficultyID(), effMask, target); + createInfo.SetCaster(this); + + Aura aura = Aura.TryRefreshStackOrCreate(createInfo); if (aura != null) { aura.ApplyForTargets(); @@ -2952,15 +2959,21 @@ namespace Game.Entities } else // This can happen during Player._LoadAuras { - int[] bp0 = new int[SpellConst.MaxEffects]; + int[] bp = new int[SpellConst.MaxEffects]; foreach (SpellEffectInfo effect in spellEntry.GetEffects()) { if (effect != null) - bp0[effect.EffectIndex] = effect.BasePoints; + bp[effect.EffectIndex] = effect.BasePoints; } - bp0[i] = seatId; - Aura.TryRefreshStackOrCreate(spellEntry, ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, GetMapId(), spellEntry.Id, GetMap().GenerateLowGuid(HighGuid.Cast)), SpellConst.MaxEffectMask, this, clicker, GetMap().GetDifficultyID(), bp0, null, origCasterGUID); + bp[i] = seatId; + + AuraCreateInfo createInfo = new(ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, GetMapId(), spellEntry.Id, GetMap().GenerateLowGuid(HighGuid.Cast)), spellEntry, GetMap().GetDifficultyID(), SpellConst.MaxEffectMask, this); + createInfo.SetCaster(clicker); + createInfo.SetBaseAmount(bp); + createInfo.SetCasterGUID(origCasterGUID); + + Aura.TryRefreshStackOrCreate(createInfo); } } else @@ -2968,7 +2981,13 @@ namespace Game.Entities if (IsInMap(caster)) caster.CastSpell(target, spellEntry.Id, new CastSpellExtraArgs().SetOriginalCaster(origCasterGUID)); else - Aura.TryRefreshStackOrCreate(spellEntry, ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, GetMapId(), spellEntry.Id, GetMap().GenerateLowGuid(HighGuid.Cast)), SpellConst.MaxEffectMask, this, clicker, GetMap().GetDifficultyID(), null, null, origCasterGUID); + { + AuraCreateInfo createInfo = new(ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, GetMapId(), spellEntry.Id, GetMap().GenerateLowGuid(HighGuid.Cast)), spellEntry, GetMap().GetDifficultyID(), SpellConst.MaxEffectMask, this); + createInfo.SetCaster(clicker); + createInfo.SetCasterGUID(origCasterGUID); + + Aura.TryRefreshStackOrCreate(createInfo); + } } spellClickHandled = true; @@ -3340,7 +3359,11 @@ namespace Game.Entities if (aura.IsSingleTarget()) aura.UnregisterSingleTarget(); - Aura newAura = Aura.TryRefreshStackOrCreate(aura.GetSpellInfo(), aura.GetCastGUID(), effMask, stealer, null, aura.GetCastDifficulty(), baseDamage, null, aura.GetCasterGUID()); + AuraCreateInfo createInfo = new(aura.GetCastGUID(), aura.GetSpellInfo(), aura.GetCastDifficulty(), effMask, stealer); + createInfo.SetCasterGUID(aura.GetCasterGUID()); + createInfo.SetBaseAmount(baseDamage); + + Aura newAura = Aura.TryRefreshStackOrCreate(createInfo); if (newAura != null) { // created aura must not be single target aura, so stealer won't loose it on recast @@ -3431,7 +3454,7 @@ namespace Game.Entities aura.Remove(); } } - // All aura base removes should go threw this function! + // All aura base removes should go through this function! public void RemoveOwnedAura(KeyValuePair keyValuePair, AuraRemoveMode removeMode = AuraRemoveMode.Default) { Aura aura = keyValuePair.Value; @@ -4239,41 +4262,32 @@ namespace Game.Entities } } - public Aura _TryStackingOrRefreshingExistingAura(SpellInfo newAura, uint effMask, Unit caster, int[] baseAmount = null, Item castItem = null, ObjectGuid casterGUID = default, bool resetPeriodicTimer = true, ObjectGuid castItemGuid = default, uint castItemId = 0, int castItemLevel = -1) + public Aura _TryStackingOrRefreshingExistingAura(AuraCreateInfo createInfo) { - Cypher.Assert(!casterGUID.IsEmpty() || caster); + Cypher.Assert(!createInfo.CasterGUID.IsEmpty() || createInfo.Caster); // Check if these can stack anyway - if (casterGUID.IsEmpty() && !newAura.IsStackableOnOneSlotWithDifferentCasters()) - casterGUID = caster.GetGUID(); + if (createInfo.CasterGUID.IsEmpty() && !createInfo.GetSpellInfo().IsStackableOnOneSlotWithDifferentCasters()) + createInfo.CasterGUID = createInfo.Caster.GetGUID(); // passive and Incanter's Absorption and auras with different type can stack with themselves any number of times - if (!newAura.IsMultiSlotAura()) + if (!createInfo.GetSpellInfo().IsMultiSlotAura()) { // check if cast item changed - if (castItem != null) - { - castItemGuid = castItem.GetGUID(); - castItemId = castItem.GetEntry(); - Player owner = castItem.GetOwner(); - if (owner) - castItemLevel = (int)castItem.GetItemLevel(owner); - else if (castItem.GetOwnerGUID() == caster.GetGUID()) - castItemLevel = (int)castItem.GetItemLevel(caster.ToPlayer()); - } + ObjectGuid castItemGUID = createInfo.CastItemGUID; // find current aura from spell and change it's stackamount, or refresh it's duration - Aura foundAura = GetOwnedAura(newAura.Id, casterGUID, (newAura.HasAttribute(SpellCustomAttributes.EnchantProc) ? castItemGuid : ObjectGuid.Empty), 0); + Aura foundAura = GetOwnedAura(createInfo.GetSpellInfo().Id, createInfo.CasterGUID, createInfo.GetSpellInfo().HasAttribute(SpellCustomAttributes.EnchantProc) ? castItemGUID : ObjectGuid.Empty, 0); if (foundAura != null) { // effect masks do not match // extremely rare case // let's just recreate aura - if (effMask != foundAura.GetEffectMask()) + if (createInfo.GetAuraEffectMask() != foundAura.GetEffectMask()) return null; // update basepoints with new values - effect amount will be recalculated in ModStackAmount - foreach (SpellEffectInfo effect in newAura.GetEffects()) + foreach (SpellEffectInfo effect in createInfo.GetSpellInfo().GetEffects()) { if (effect == null) continue; @@ -4283,8 +4297,8 @@ namespace Game.Entities continue; int bp; - if (baseAmount != null) - bp = baseAmount[effect.EffectIndex]; + if (createInfo.BaseAmount != null) + bp = createInfo.BaseAmount[effect.EffectIndex]; else bp = effect.BasePoints; @@ -4292,15 +4306,15 @@ namespace Game.Entities } // correct cast item guid if needed - if (castItemGuid != foundAura.GetCastItemGUID()) + if (castItemGUID != foundAura.GetCastItemGUID()) { - foundAura.SetCastItemGUID(castItemGuid); - foundAura.SetCastItemId(castItemId); - foundAura.SetCastItemLevel(castItemLevel); + foundAura.SetCastItemGUID(castItemGUID); + foundAura.SetCastItemId(createInfo.CastItemId); + foundAura.SetCastItemLevel(createInfo.CastItemLevel); } // try to increase stack amount - foundAura.ModStackAmount(1, AuraRemoveMode.Default, resetPeriodicTimer); + foundAura.ModStackAmount(1, AuraRemoveMode.Default, createInfo.ResetPeriodicTimer); return foundAura; } } diff --git a/Source/Game/Scripting/SpellScript.cs b/Source/Game/Scripting/SpellScript.cs index adde03e64..ad5803485 100644 --- a/Source/Game/Scripting/SpellScript.cs +++ b/Source/Game/Scripting/SpellScript.cs @@ -724,18 +724,22 @@ namespace Game.Scripting void PreventHitHeal() { SetHitHeal(0); } public Spell GetSpell() { return m_spell; } // returns current spell hit target aura - public Aura GetHitAura() + public Aura GetHitAura(bool dynObjAura = false) { if (!IsInTargetHook()) { Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.GetHitAura was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); return null; } - if (m_spell.m_spellAura == null) + + Aura aura = m_spell.spellAura; + if (dynObjAura) + aura = m_spell.dynObjAura; + + if (aura == null || aura.IsRemoved()) return null; - if (m_spell.m_spellAura.IsRemoved()) - return null; - return m_spell.m_spellAura; + + return aura; } // prevents applying aura on current spell hit target public void PreventHitAura() @@ -745,8 +749,14 @@ namespace Game.Scripting Log.outError(LogFilter.Scripts, "Script: `{0}` Spell: `{1}`: function SpellScript.PreventHitAura was called, but function has no effect in current hook!", m_scriptName, m_scriptSpellId); return; } - if (m_spell.m_spellAura != null) - m_spell.m_spellAura.Remove(); + + UnitAura unitAura = m_spell.spellAura; + if (unitAura != null) + unitAura.Remove(); + + DynObjAura dynAura = m_spell.dynObjAura; + if (dynAura != null) + dynAura.Remove(); } // prevents effect execution on current spell hit target diff --git a/Source/Game/Spells/Auras/Aura.cs b/Source/Game/Spells/Auras/Aura.cs index 5a94fcfe5..c17f80e2f 100644 --- a/Source/Game/Spells/Auras/Aura.cs +++ b/Source/Game/Spells/Auras/Aura.cs @@ -189,6 +189,37 @@ namespace Game.Spells SetNeedClientUpdate(); } + public void UpdateApplyEffectMask(uint newEffMask) + { + if (_effectsToApply == newEffMask) + return; + + uint removeEffMask = (_effectsToApply ^ newEffMask) & (~newEffMask); + uint addEffMask = (_effectsToApply ^ newEffMask) & (~_effectsToApply); + + // quick check, removes application completely + if (removeEffMask == _effectsToApply && addEffMask == 0) + { + _target._UnapplyAura(this, AuraRemoveMode.Default); + return; + } + + for (uint i = 0; i < SpellConst.MaxEffects; ++i) + { + // update real effects only if they were applied already + if ((_effectMask & (1 << (int)i)) == 0) + continue; + + if ((removeEffMask & (1 << (int)i)) != 0) + _HandleEffect(i, false); + + if ((addEffMask & (1 << (int)i)) != 0) + _HandleEffect(i, true); + } + + _effectsToApply = newEffMask; + } + public void SetNeedClientUpdate() { if (_needClientUpdate || GetRemoveMode() != AuraRemoveMode.None) @@ -294,21 +325,21 @@ namespace Game.Spells { const int UPDATE_TARGET_MAP_INTERVAL = 500; - public Aura(SpellInfo spellproto, ObjectGuid castId, WorldObject owner, Unit caster, Difficulty castDifficulty, Item castItem, ObjectGuid casterGUID, ObjectGuid castItemGuid, uint castItemId, int castItemLevel) + public Aura(AuraCreateInfo createInfo) { - m_spellInfo = spellproto; - m_castDifficulty = castDifficulty; - m_castGuid = castId; - m_casterGuid = !casterGUID.IsEmpty() ? casterGUID : caster.GetGUID(); - m_castItemGuid = castItem != null ? castItem.GetGUID() : castItemGuid; - m_castItemId = castItem != null ? castItem.GetEntry() : castItemId; - m_castItemLevel = castItemLevel; - m_spellVisual = new SpellCastVisual(caster ? caster.GetCastSpellXSpellVisualId(spellproto) : spellproto.GetSpellXSpellVisualId(), 0); + m_spellInfo = createInfo._spellInfo; + m_castDifficulty = createInfo._castDifficulty; + m_castGuid = createInfo._castId; + m_casterGuid = createInfo.CasterGUID.IsEmpty() ? createInfo.Caster.GetGUID() : createInfo.CasterGUID; + m_castItemGuid = createInfo.CastItemGUID; + m_castItemId = createInfo.CastItemId; + m_castItemLevel = createInfo.CastItemLevel; + m_spellVisual = new SpellCastVisual(createInfo.Caster ? createInfo.Caster.GetCastSpellXSpellVisualId(createInfo._spellInfo) : createInfo._spellInfo.GetSpellXSpellVisualId(), 0); m_applyTime = GameTime.GetGameTime(); - m_owner = owner; + m_owner = createInfo._owner; m_timeCla = 0; m_updateTargetMapInterval = 0; - m_casterLevel = caster != null ? caster.GetLevel() : m_spellInfo.SpellLevel; + m_casterLevel = createInfo.Caster ? createInfo.Caster.GetLevel() : m_spellInfo.SpellLevel; m_procCharges = 0; m_stackAmount = 1; m_isRemoved = false; @@ -324,9 +355,9 @@ namespace Game.Spells if (!m_periodicCosts.Empty()) m_timeCla = 1 * Time.InMilliseconds; - m_maxDuration = CalcMaxDuration(caster); + m_maxDuration = CalcMaxDuration(createInfo.Caster); m_duration = m_maxDuration; - m_procCharges = CalcMaxCharges(caster); + m_procCharges = CalcMaxCharges(createInfo.Caster); m_isUsingCharges = m_procCharges != 0; // m_casterLevel = cast item level/caster level, caster level should be saved to db, confirmed with sniffs } @@ -411,7 +442,7 @@ namespace Game.Spells Cypher.Assert(app == auraApp); m_applications.Remove(target.GetGUID()); - m_removedApplications.Add(auraApp); + _removedApplications.Add(auraApp); // reset cooldown state for spells if (caster != null && GetSpellInfo().IsCooldownStartedOnEvent()) @@ -465,9 +496,17 @@ namespace Game.Spells { // needs readding - remove now, will be applied in next update cycle // (dbcs do not have auras which apply on same type of targets but have different radius, so this is not really needed) - if (app.Value.GetEffectMask() != existing.Value || !CanBeAppliedOn(existing.Key)) + if (!CanBeAppliedOn(existing.Key)) + { targetsToRemove.Add(app.Value.GetTarget()); - // nothing todo - aura already applied + continue; + } + + // needs to add/remove effects from application, don't remove from map so it gets updated + if (app.Value.GetEffectMask() != existing.Value) + continue; + + // nothing to do - aura already applied // remove from auras to register list targets.Remove(existing.Key); } @@ -475,28 +514,6 @@ namespace Game.Spells // register auras for units foreach (var unit in targets.Keys.ToList()) { - // aura mustn't be already applied on target - AuraApplication aurApp = GetApplicationOfTarget(unit.GetGUID()); - if (aurApp != null) - { - // the core created 2 different units with same guid - // this is a major failue, which i can't fix right now - // let's remove one unit from aura list - // this may cause area aura "bouncing" between 2 units after each update - // but because we know the reason of a crash we can remove the assertion for now - if (aurApp.GetTarget() != unit) - { - // remove from auras to register list - targets.Remove(unit); - continue; - } - else - { - // ok, we have one unit twice in target map (impossible, but...) - Cypher.Assert(false); - } - } - var value = targets[unit]; bool addUnit = true; @@ -506,8 +523,9 @@ namespace Game.Spells if (unit.IsImmunedToSpellEffect(GetSpellInfo(), effIndex, caster)) value &= ~(1u << effIndex); } - if (value == 0 || unit.IsImmunedToSpell(GetSpellInfo(), caster) - || !CanBeAppliedOn(unit)) + + + if (value == 0 || unit.IsImmunedToSpell(GetSpellInfo(), caster) || !CanBeAppliedOn(unit)) addUnit = false; if (addUnit && !unit.IsHighestExclusiveAura(this, true)) @@ -541,6 +559,7 @@ namespace Game.Spells } } } + if (!addUnit) targets.Remove(unit); else @@ -553,6 +572,14 @@ namespace Game.Spells m_owner.GetName(), m_owner.IsInWorld ? (int)m_owner.GetMap().GetId() : -1, unit.GetName(), unit.IsInWorld ? (int)unit.GetMap().GetId() : -1); } + + AuraApplication aurApp = GetApplicationOfTarget(unit.GetGUID()); + if (aurApp != null) + { + // aura is already applied, this means we need to update effects of current application + unit._UnapplyAura(aurApp, AuraRemoveMode.Default); + } + unit._CreateAuraApplication(this, value); } } @@ -854,17 +881,17 @@ namespace Game.Spells Unit caster = GetCaster(); List applications = GetApplicationList(); - foreach (var appt in applications) - if (!appt.HasRemoveMode()) - HandleAuraSpecificMods(appt, caster, false, true); + foreach (var aurApp in applications) + if (!aurApp.HasRemoveMode()) + HandleAuraSpecificMods(aurApp, caster, false, true); - foreach (AuraEffect effect in GetAuraEffects()) - if (effect != null) - effect.ChangeAmount(effect.CalculateAmount(caster), false, true); + foreach (AuraEffect aurEff in GetAuraEffects()) + if (aurEff != null) + aurEff.ChangeAmount(aurEff.CalculateAmount(caster), false, true); - foreach (var app in applications) - if (!app.HasRemoveMode()) - HandleAuraSpecificMods(app, caster, true, true); + foreach (var aurApp in applications) + if (!aurApp.HasRemoveMode()) + HandleAuraSpecificMods(aurApp, caster, true, true); SetNeedClientUpdateForTargets(); } @@ -956,6 +983,14 @@ namespace Game.Spells return GetSpellInfo().IsDeathPersistent(); } + public bool IsRemovedOnShapeLost(Unit target) + { + return GetCasterGUID() == target.GetGUID() + && m_spellInfo.Stances != 0 + && !m_spellInfo.HasAttribute(SpellAttr2.NotNeedShapeshift) + && !m_spellInfo.HasAttribute(SpellAttr0.NotShapeshift); + } + public bool CanBeSaved() { if (IsPassive()) @@ -1135,10 +1170,10 @@ namespace Game.Spells public List GetApplicationList() { var applicationList = new List(); - foreach (var app in m_applications.Values) + foreach (var aurApp in m_applications.Values) { - if (app.GetEffectMask() != 0) - applicationList.Add(app); + if (aurApp.GetEffectMask() != 0) + applicationList.Add(aurApp); } return applicationList; @@ -1816,7 +1851,7 @@ namespace Game.Spells void _DeleteRemovedApplications() { - m_removedApplications.Clear(); + _removedApplications.Clear(); } public void LoadScripts() @@ -2308,13 +2343,6 @@ namespace Game.Spells public byte GetStackAmount() { return m_stackAmount; } public byte GetCasterLevel() { return (byte)m_casterLevel; } - public bool IsRemovedOnShapeLost(Unit target) - { - return GetCasterGUID() == target.GetGUID() - && m_spellInfo.Stances != 0 - && !m_spellInfo.HasAttribute(SpellAttr2.NotNeedShapeshift) - && !m_spellInfo.HasAttribute(SpellAttr0.NotShapeshift); - } public bool IsRemoved() { return m_isRemoved; } public bool IsSingleTarget() { return m_isSingleTarget; } @@ -2334,9 +2362,10 @@ namespace Game.Spells public uint GetEffectMask() { uint effMask = 0; - foreach (AuraEffect effect in GetAuraEffects()) - if (effect != null) - effMask |= (uint)(1 << (int)effect.GetEffIndex()); + foreach (AuraEffect aurEff in GetAuraEffects()) + if (aurEff != null) + effMask |= (uint)(1 << (int)aurEff.GetEffIndex()); + return effMask; } @@ -2354,6 +2383,11 @@ namespace Game.Spells public void SetLastProcAttemptTime(DateTime lastProcAttemptTime) { m_lastProcAttemptTime = lastProcAttemptTime; } public void SetLastProcSuccessTime(DateTime lastProcSuccessTime) { m_lastProcSuccessTime = lastProcSuccessTime; } + public UnitAura ToUnitAura() { if (GetAuraType() == AuraObjectType.Unit) return (UnitAura)this; else return null; } + + public DynObjAura ToDynObjAura() { if (GetAuraType() == AuraObjectType.DynObj) return (DynObjAura)this; else return null; } + + //Static Methods public static uint BuildEffectMaskForOwner(SpellInfo spellProto, uint availableEffectMask, WorldObject owner) { @@ -2382,22 +2416,23 @@ namespace Game.Spells } return (effMask & availableEffectMask); } - public static Aura TryRefreshStackOrCreate(SpellInfo spellproto, ObjectGuid castId, uint tryEffMask, WorldObject owner, Unit caster, Difficulty castDifficulty, int[] baseAmount = null, Item castItem = null, ObjectGuid casterGUID = default, bool resetPeriodicTimer = true, ObjectGuid castItemGuid = default, uint castItemId = 0, int castItemLevel = -1) + public static Aura TryRefreshStackOrCreate(AuraCreateInfo createInfo) { - return TryRefreshStackOrCreate(spellproto, castId, tryEffMask, owner, caster, castDifficulty, out _, baseAmount, castItem, casterGUID, resetPeriodicTimer, castItemGuid, castItemId, castItemLevel); - } - public static Aura TryRefreshStackOrCreate(SpellInfo spellproto, ObjectGuid castId, uint tryEffMask, WorldObject owner, Unit caster, Difficulty castDifficulty, out bool refresh, int[] baseAmount, Item castItem = null, ObjectGuid casterGUID = default, bool resetPeriodicTimer = true, ObjectGuid castItemGuid = default, uint castItemId = 0, int castItemLevel = -1) - { - Cypher.Assert(spellproto != null); - Cypher.Assert(owner != null); - Cypher.Assert(caster || !casterGUID.IsEmpty()); - Cypher.Assert(tryEffMask <= SpellConst.MaxEffectMask); - refresh = false; + Cypher.Assert(createInfo.Caster != null || !createInfo.CasterGUID.IsEmpty()); + + createInfo.IsRefresh = false; + + createInfo._auraEffectMask = BuildEffectMaskForOwner(createInfo.GetSpellInfo(), createInfo.GetAuraEffectMask(), createInfo.GetOwner()); + createInfo._targetEffectMask &= createInfo._auraEffectMask; + + uint effMask = createInfo._auraEffectMask; + if (createInfo._targetEffectMask != 0) + effMask = createInfo._targetEffectMask; - uint effMask = BuildEffectMaskForOwner(spellproto, tryEffMask, owner); if (effMask == 0) return null; - Aura foundAura = owner.ToUnit()._TryStackingOrRefreshingExistingAura(spellproto, effMask, caster, baseAmount, castItem, casterGUID, resetPeriodicTimer, castItemGuid, castItemId, castItemLevel); + + Aura foundAura = createInfo.GetOwner().ToUnit()._TryStackingOrRefreshingExistingAura(createInfo); if (foundAura != null) { // we've here aura, which script triggered removal after modding stack amount @@ -2405,65 +2440,95 @@ namespace Game.Spells if (foundAura.IsRemoved()) return null; - refresh = true; + createInfo.IsRefresh = true; + + // add owner + Unit unit = createInfo.GetOwner().ToUnit(); + + // check effmask on owner application (if existing) + AuraApplication aurApp = foundAura.GetApplicationOfTarget(unit.GetGUID()); + if (aurApp != null) + aurApp.UpdateApplyEffectMask(effMask); return foundAura; } else - return Create(spellproto, castId, effMask, owner, caster, castDifficulty, baseAmount, castItem, casterGUID, castItemGuid, castItemId, castItemLevel); + return Create(createInfo); } - public static Aura TryCreate(SpellInfo spellproto, ObjectGuid castId, uint tryEffMask, WorldObject owner, Unit caster, Difficulty castDifficulty, int[] baseAmount, Item castItem = null, ObjectGuid casterGUID = default, ObjectGuid castItemGuid = default, uint castItemId = 0, int castItemLevel = -1) + + public static Aura TryCreate(AuraCreateInfo createInfo) { - Cypher.Assert(spellproto != null); - Cypher.Assert(owner != null); - Cypher.Assert(caster != null || !casterGUID.IsEmpty()); - Cypher.Assert(tryEffMask <= SpellConst.MaxEffectMask); - uint effMask = BuildEffectMaskForOwner(spellproto, tryEffMask, owner); + Cypher.Assert(createInfo.Caster != null || !createInfo.CasterGUID.IsEmpty()); + + uint effMask = createInfo._auraEffectMask; + if (createInfo._targetEffectMask != 0) + effMask = createInfo._targetEffectMask; + + effMask = BuildEffectMaskForOwner(createInfo.GetSpellInfo(), effMask, createInfo.GetOwner()); if (effMask == 0) return null; - return Create(spellproto, castId, effMask, owner, caster, castDifficulty, baseAmount, castItem, casterGUID, castItemGuid, castItemId, castItemLevel); + + return Create(createInfo); } - public static Aura Create(SpellInfo spellproto, ObjectGuid castId, uint effMask, WorldObject owner, Unit caster, Difficulty castDifficulty, int[] baseAmount, Item castItem, ObjectGuid casterGUID, ObjectGuid castItemGuid, uint castItemId, int castItemLevel) + + public static Aura Create(AuraCreateInfo createInfo) { - Cypher.Assert(effMask != 0); - Cypher.Assert(spellproto != null); - Cypher.Assert(owner != null); - Cypher.Assert(caster != null || !casterGUID.IsEmpty()); - Cypher.Assert(effMask <= SpellConst.MaxEffectMask); + Cypher.Assert(createInfo.Caster != null || !createInfo.CasterGUID.IsEmpty()); + // try to get caster of aura - if (!casterGUID.IsEmpty()) + if (!createInfo.CasterGUID.IsEmpty()) { - if (owner.GetGUID() == casterGUID) - caster = owner.ToUnit(); + if (createInfo.GetOwner().GetGUID() == createInfo.CasterGUID) + createInfo.Caster = createInfo.GetOwner().ToUnit(); else - caster = Global.ObjAccessor.GetUnit(owner, casterGUID); + createInfo.Caster = Global.ObjAccessor.GetUnit(createInfo.GetOwner(), createInfo.CasterGUID); } else - casterGUID = caster.GetGUID(); + createInfo.CasterGUID = createInfo.Caster.GetGUID(); // check if aura can be owned by owner - if (owner.IsTypeMask(TypeMask.Unit)) - if (!owner.IsInWorld || owner.ToUnit().IsDuringRemoveFromWorld()) + if (createInfo.GetOwner().IsTypeMask(TypeMask.Unit)) + if (!createInfo.GetOwner().IsInWorld || createInfo.GetOwner().ToUnit().IsDuringRemoveFromWorld()) // owner not in world so don't allow to own not self casted single target auras - if (casterGUID != owner.GetGUID() && spellproto.IsSingleTarget()) + if (createInfo.CasterGUID != createInfo.GetOwner().GetGUID() && createInfo.GetSpellInfo().IsSingleTarget()) return null; Aura aura; - switch (owner.GetTypeId()) + switch (createInfo.GetOwner().GetTypeId()) { case TypeId.Unit: case TypeId.Player: - aura = new UnitAura(spellproto, castId, effMask, owner, caster, castDifficulty, baseAmount, castItem, casterGUID, castItemGuid, castItemId, castItemLevel); + aura = new UnitAura(createInfo); + + // aura can be removed in Unit::_AddAura call + if (aura.IsRemoved()) + return null; + + // add owner + uint effMask = createInfo._auraEffectMask; + if (createInfo._targetEffectMask != 0) + effMask = createInfo._targetEffectMask; + + effMask = BuildEffectMaskForOwner(createInfo.GetSpellInfo(), effMask, createInfo.GetOwner()); + Cypher.Assert(effMask != 0); + + Unit unit = createInfo.GetOwner().ToUnit(); + aura.ToUnitAura().AddStaticApplication(unit, effMask); break; case TypeId.DynamicObject: - aura = new DynObjAura(spellproto, castId, effMask, owner, caster, castDifficulty, baseAmount, castItem, casterGUID, castItemGuid, castItemId, castItemLevel); + createInfo._auraEffectMask = BuildEffectMaskForOwner(createInfo.GetSpellInfo(), createInfo._auraEffectMask, createInfo.GetOwner()); + Cypher.Assert(createInfo._auraEffectMask != 0); + + aura = new DynObjAura(createInfo); break; default: Cypher.Assert(false); return null; } - // aura can be removed in Unit:_AddAura call + + // scripts, etc. if (aura.IsRemoved()) return null; + return aura; } @@ -2504,19 +2569,18 @@ namespace Game.Spells DateTime m_lastProcAttemptTime; DateTime m_lastProcSuccessTime; - List m_removedApplications = new(); + List _removedApplications = new(); #endregion } public class UnitAura : Aura { - public UnitAura(SpellInfo spellproto, ObjectGuid castId, uint effMask, WorldObject owner, Unit caster, Difficulty castDifficulty, int[] baseAmount, Item castItem, ObjectGuid casterGUID, ObjectGuid castItemGuid, uint castItemId, int castItemLevel) - : base(spellproto, castId, owner, caster, castDifficulty, castItem, casterGUID, castItemGuid, castItemId, castItemLevel) + public UnitAura(AuraCreateInfo createInfo) : base(createInfo) { m_AuraDRGroup = DiminishingGroup.None; LoadScripts(); - _InitEffects(effMask, caster, baseAmount); - GetUnitOwner()._AddAura(this, caster); + _InitEffects(createInfo._auraEffectMask, createInfo.Caster, createInfo.BaseAmount); + GetUnitOwner()._AddAura(this, createInfo.Caster); } public override void _ApplyForTarget(Unit target, Unit caster, AuraApplication aurApp) @@ -2548,114 +2612,129 @@ namespace Game.Spells if (refe == null) refe = GetUnitOwner(); + // add non area aura targets + // static applications go through spell system first, so we assume they meet conditions + foreach (var targetPair in _staticApplications) + { + Unit target = Global.ObjAccessor.GetUnit(GetUnitOwner(), targetPair.Key); + if (target != null) + targets.Add(target, targetPair.Value); + } + foreach (SpellEffectInfo effect in GetSpellInfo().GetEffects()) { if (effect == null || !HasEffect(effect.EffectIndex)) continue; - List targetList = new(); - var condList = effect.ImplicitTargetConditions; - // non-area aura + // area auras only if (effect.Effect == SpellEffectName.ApplyAura) - { - if (condList == null || Global.ConditionMgr.IsObjectMeetToConditions(GetUnitOwner(), refe, condList)) - targetList.Add(GetUnitOwner()); - } - else - { - // skip area update if owner is not in world! - if (!GetUnitOwner().IsInWorld) - continue; + continue; - if (GetUnitOwner().HasUnitState(UnitState.Isolated)) - continue; + // skip area update if owner is not in world! + if (!GetUnitOwner().IsInWorld) + continue; - float radius = effect.CalcRadius(caster); - SpellTargetCheckTypes selectionType = SpellTargetCheckTypes.Default; - switch (effect.Effect) + if (GetUnitOwner().HasUnitState(UnitState.Isolated)) + continue; + + List units = new(); + var condList = effect.ImplicitTargetConditions; + + float radius = effect.CalcRadius(refe); + SpellTargetCheckTypes selectionType = SpellTargetCheckTypes.Default; + switch (effect.Effect) + { + case SpellEffectName.ApplyAreaAuraParty: + case SpellEffectName.ApplyAreaAuraPartyNonrandom: + selectionType = SpellTargetCheckTypes.Party; + break; + case SpellEffectName.ApplyAreaAuraRaid: + selectionType = SpellTargetCheckTypes.Raid; + break; + case SpellEffectName.ApplyAreaAuraFriend: + selectionType = SpellTargetCheckTypes.Ally; + break; + case SpellEffectName.ApplyAreaAuraEnemy: + selectionType = SpellTargetCheckTypes.Enemy; + break; + case SpellEffectName.ApplyAreaAuraPet: + if (condList == null || Global.ConditionMgr.IsObjectMeetToConditions(GetUnitOwner(), refe, condList)) + units.Add(GetUnitOwner()); + goto case SpellEffectName.ApplyAreaAuraOwner; + /* fallthrough */ + case SpellEffectName.ApplyAreaAuraOwner: { - case SpellEffectName.ApplyAreaAuraParty: - case SpellEffectName.ApplyAreaAuraPartyNonrandom: - selectionType = SpellTargetCheckTypes.Party; - break; - case SpellEffectName.ApplyAreaAuraRaid: - selectionType = SpellTargetCheckTypes.Raid; - break; - case SpellEffectName.ApplyAreaAuraFriend: - selectionType = SpellTargetCheckTypes.Ally; - break; - case SpellEffectName.ApplyAreaAuraEnemy: - selectionType = SpellTargetCheckTypes.Enemy; - break; - case SpellEffectName.ApplyAreaAuraPet: - if (condList == null || Global.ConditionMgr.IsObjectMeetToConditions(GetUnitOwner(), refe, condList)) - targetList.Add(GetUnitOwner()); - goto case SpellEffectName.ApplyAreaAuraOwner; - case SpellEffectName.ApplyAreaAuraOwner: - { - Unit owner = GetUnitOwner().GetCharmerOrOwner(); - if (owner != null) - if (GetUnitOwner().IsWithinDistInMap(owner, radius)) - if (condList == null || Global.ConditionMgr.IsObjectMeetToConditions(owner, refe, condList)) - targetList.Add(owner); - break; - } - case SpellEffectName.ApplyAuraOnPet: - { - Unit pet = Global.ObjAccessor.GetUnit(GetUnitOwner(), GetUnitOwner().GetPetGUID()); - if (pet != null) - if (condList == null || Global.ConditionMgr.IsObjectMeetToConditions(pet, refe, condList)) - targetList.Add(pet); - break; - } - case SpellEffectName.ApplyAreaAuraSummons: - { - if (condList == null || Global.ConditionMgr.IsObjectMeetToConditions(GetUnitOwner(), refe, condList)) - targetList.Add(GetUnitOwner()); - - selectionType = SpellTargetCheckTypes.Summoned; - break; - } + Unit owner = GetUnitOwner().GetCharmerOrOwner(); + if (owner != null) + if (GetUnitOwner().IsWithinDistInMap(owner, radius)) + if (condList == null || Global.ConditionMgr.IsObjectMeetToConditions(owner, refe, condList)) + units.Add(owner); + break; } - - if (selectionType != SpellTargetCheckTypes.Default) + case SpellEffectName.ApplyAuraOnPet: { - WorldObjectSpellAreaTargetCheck check = new(radius, GetUnitOwner(), refe, GetUnitOwner(), GetSpellInfo(), selectionType, condList, SpellTargetObjectTypes.Unit); - UnitListSearcher searcher = new(GetUnitOwner(), targetList, check); - Cell.VisitAllObjects(GetUnitOwner(), searcher, radius); - - // by design WorldObjectSpellAreaTargetCheck allows not-in-world units (for spells) but for auras it is not acceptable - targetList.RemoveAll(unit => !unit.IsSelfOrInSameMap(GetUnitOwner())); + Unit pet = Global.ObjAccessor.GetUnit(GetUnitOwner(), GetUnitOwner().GetPetGUID()); + if (pet != null) + if (condList == null || Global.ConditionMgr.IsObjectMeetToConditions(pet, refe, condList)) + units.Add(pet); + break; + } + case SpellEffectName.ApplyAreaAuraSummons: + { + if (condList == null || Global.ConditionMgr.IsObjectMeetToConditions(GetUnitOwner(), refe, condList)) + units.Add(GetUnitOwner()); + selectionType = SpellTargetCheckTypes.Summoned; + break; } } - foreach (var unit in targetList) + if (selectionType != SpellTargetCheckTypes.Default) { - if (targets.ContainsKey(unit)) - targets[unit] |= (1u << (int)effect.EffectIndex); - else - targets[unit] = (1u << (int)effect.EffectIndex); + WorldObjectSpellAreaTargetCheck check = new(radius, GetUnitOwner(), refe, GetUnitOwner(), GetSpellInfo(), selectionType, condList, SpellTargetObjectTypes.Unit); + UnitListSearcher searcher = new(GetUnitOwner(), units, check); + Cell.VisitAllObjects(GetUnitOwner(), searcher, radius); + + // 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) + targets[unit] |= 1u << (int)effect.EffectIndex; } } + public void AddStaticApplication(Unit target, uint effMask) + { + // only valid for non-area auras + foreach (SpellEffectInfo effect in GetSpellInfo().GetEffects()) + { + if (effect != null && (effMask & (1u << (int)effect.EffectIndex)) != 0 && effect.Effect != SpellEffectName.ApplyAura) + effMask &= ~(1u << (int)effect.EffectIndex); + } + + if (effMask == 0) + return; + + _staticApplications[target.GetGUID()] |= effMask; + } + // Allow Apply Aura Handler to modify and access m_AuraDRGroup public void SetDiminishGroup(DiminishingGroup group) { m_AuraDRGroup = group; } public DiminishingGroup GetDiminishGroup() { return m_AuraDRGroup; } DiminishingGroup m_AuraDRGroup; // Diminishing + Dictionary _staticApplications = new(); // non-area auras } public class DynObjAura : Aura { - public DynObjAura(SpellInfo spellproto, ObjectGuid castId, uint effMask, WorldObject owner, Unit caster, Difficulty castDifficulty, int[] baseAmount, Item castItem, ObjectGuid casterGUID, ObjectGuid castItemGuid, uint castItemId, int castItemLevel) - : base(spellproto, castId, owner, caster, castDifficulty, castItem, casterGUID, castItemGuid, castItemId, castItemLevel) + public DynObjAura(AuraCreateInfo createInfo) : base(createInfo) { LoadScripts(); Cypher.Assert(GetDynobjOwner() != null); Cypher.Assert(GetDynobjOwner().IsInWorld); - Cypher.Assert(GetDynobjOwner().GetMap() == caster.GetMap()); - _InitEffects(effMask, caster, baseAmount); + Cypher.Assert(GetDynobjOwner().GetMap() == createInfo.Caster.GetMap()); + _InitEffects(createInfo._auraEffectMask, createInfo.Caster, createInfo.BaseAmount); GetDynobjOwner().SetAura(this); } @@ -2693,12 +2772,7 @@ namespace Game.Spells targetList.RemoveAll(unit => !unit.IsSelfOrInSameMap(GetDynobjOwner())); foreach (var unit in targetList) - { - if (targets.ContainsKey(unit)) - targets[unit] |= (1u << (int)effect.EffectIndex); - else - targets[unit] = (1u << (int)effect.EffectIndex); - } + targets[unit] |= 1u << (int)effect.EffectIndex; } } } @@ -2774,4 +2848,51 @@ namespace Game.Spells public int[] Amounts = new int[SpellConst.MaxEffects]; public int[] BaseAmounts = new int[SpellConst.MaxEffects]; } + + public class AuraCreateInfo + { + public ObjectGuid CasterGUID; + public Unit Caster; + public int[] BaseAmount; + public ObjectGuid CastItemGUID; + public uint CastItemId = 0; + public int CastItemLevel = -1; + public bool IsRefresh; + public bool ResetPeriodicTimer = true; + + internal ObjectGuid _castId; + internal SpellInfo _spellInfo; + internal Difficulty _castDifficulty; + internal uint _auraEffectMask; + internal WorldObject _owner; + + internal uint _targetEffectMask; + + public AuraCreateInfo(ObjectGuid castId, SpellInfo spellInfo, Difficulty castDifficulty, uint auraEffMask, WorldObject owner) + { + _castId = castId; + _spellInfo = spellInfo; + _castDifficulty = castDifficulty; + _auraEffectMask = auraEffMask; + _owner = owner; + + Cypher.Assert(spellInfo != null); + Cypher.Assert(auraEffMask != 0); + Cypher.Assert(owner != null); + + Cypher.Assert(auraEffMask <= SpellConst.MaxEffectMask); + } + + public void SetCasterGUID(ObjectGuid guid) { CasterGUID = guid; } + public void SetCaster(Unit caster) { Caster = caster; } + public void SetBaseAmount(int[] bp) { BaseAmount = bp; } + public void SetCastItem(ObjectGuid guid, uint itemId, int itemLevel) { CastItemGUID = guid; CastItemId = itemId; CastItemLevel = itemLevel; } + public void SetPeriodicReset(bool reset) { ResetPeriodicTimer = reset; } + public void SetOwnerEffectMask(uint effMask) { _targetEffectMask = effMask; } + public void SetAuraEffectMask(uint effMask) { _auraEffectMask = effMask; } + + public SpellInfo GetSpellInfo() { return _spellInfo; } + public uint GetAuraEffectMask() { return _auraEffectMask; } + public WorldObject GetOwner() { return _owner; } + } } diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index f70246efa..cf0a4fc65 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -1938,8 +1938,6 @@ namespace Game.Spells ProcFlags procVictim = m_procVictim; ProcFlagsHit hitMask = ProcFlagsHit.None; - m_spellAura = null; - //Spells with this flag cannot trigger if effect is cast on self bool canEffectTrigger = !m_spellInfo.HasAttribute(SpellAttr3.CantTriggerProc) && unitTarget.CanProc() && (CanExecuteTriggersOnHit(mask) || missInfo == SpellMissInfo.Immune || missInfo == SpellMissInfo.Immune2); @@ -2228,10 +2226,7 @@ namespace Game.Spells } } - uint aura_effmask = 0; - foreach (SpellEffectInfo effect in m_spellInfo.GetEffects()) - if (effect != null && (Convert.ToBoolean(effectMask & (1 << (int)effect.EffectIndex)) && effect.IsUnitOwnedAuraEffect())) - aura_effmask |= (1u << (int)effect.EffectIndex); + uint aura_effmask = Aura.BuildEffectMaskForOwner(m_spellInfo, effectMask, unit); // Get Data Needed for Diminishing Returns, some effects may have multiple auras, so this must be done on spell hit, not aura add DiminishingGroup diminishGroup = m_spellInfo.GetDiminishingReturnsGroupForSpell(); @@ -2256,19 +2251,35 @@ namespace Game.Spells basePoints[auraSpellEffect.EffectIndex] = (m_spellValue.CustomBasePointsMask & (1 << (int)auraSpellEffect.EffectIndex)) != 0 ? m_spellValue.EffectBasePoints[auraSpellEffect.EffectIndex] : auraSpellEffect.CalcBaseValue(m_originalCaster, unit, m_castItemEntry, m_castItemLevel); - bool refresh; - bool resetPeriodicTimer = !_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.DontResetPeriodicTimer); - m_spellAura = Aura.TryRefreshStackOrCreate(m_spellInfo, m_castId, effectMask, unit, - m_originalCaster, GetCastDifficulty(), out refresh, basePoints, m_CastItem, ObjectGuid.Empty, resetPeriodicTimer, ObjectGuid.Empty, m_castItemEntry, m_castItemLevel); - if (m_spellAura != null) + AuraCreateInfo createInfo = null; + if (spellAura == null) + { + bool resetPeriodicTimer = !_triggeredCastFlags.HasFlag(TriggerCastFlags.DontResetPeriodicTimer); + uint allAuraEffectMask = Aura.BuildEffectMaskForOwner(m_spellInfo, SpellConst.MaxEffectMask, unit); + + createInfo = new(m_castId, m_spellInfo, GetCastDifficulty(), allAuraEffectMask, unit); + createInfo.SetCaster(m_originalCaster); + createInfo.SetBaseAmount(basePoints); + createInfo.SetCastItem(m_castItemGUID, m_castItemEntry, m_castItemLevel); + createInfo.SetPeriodicReset(resetPeriodicTimer); + createInfo.SetOwnerEffectMask(aura_effmask); + + Aura aura = Aura.TryRefreshStackOrCreate(createInfo); + if (aura != null) + spellAura = aura.ToUnitAura(); + } + else + spellAura.AddStaticApplication(unit, aura_effmask); + + if (spellAura != null) { // Set aura stack amount to desired value if (m_spellValue.AuraStackAmount > 1) { - if (!refresh) - m_spellAura.SetStackAmount((byte)m_spellValue.AuraStackAmount); + if (createInfo != null && createInfo.IsRefresh) + spellAura.SetStackAmount((byte)m_spellValue.AuraStackAmount); else - m_spellAura.ModStackAmount(m_spellValue.AuraStackAmount); + spellAura.ModStackAmount(m_spellValue.AuraStackAmount); } // Now Reduce spell duration using data received at spell hit @@ -2287,12 +2298,13 @@ namespace Game.Spells } } - int duration = m_spellAura.GetMaxDuration(); + int duration = spellAura.GetMaxDuration(); // unit is immune to aura if it was diminished to 0 duration if (!positive && !unit.ApplyDiminishingToDuration(m_spellInfo, ref duration, m_originalCaster, diminishLevel)) { - m_spellAura.Remove(); + spellAura.Remove(); + spellAura = null; bool found = false; foreach (SpellEffectInfo effect in m_spellInfo.GetEffects()) if (effect != null && (Convert.ToBoolean(effectMask & (1 << (int)effect.EffectIndex)) && effect.Effect != SpellEffectName.ApplyAura)) @@ -2302,7 +2314,7 @@ namespace Game.Spells } else { - ((UnitAura)m_spellAura).SetDiminishGroup(diminishGroup); + spellAura.SetDiminishGroup(diminishGroup); duration = m_originalCaster.ModSpellDuration(m_spellInfo, unit, duration, positive, effectMask); if (duration > 0) @@ -2320,7 +2332,7 @@ namespace Game.Spells { if (effect != null) { - AuraEffect eff = m_spellAura.GetEffect(effect.EffectIndex); + AuraEffect eff = spellAura.GetEffect(effect.EffectIndex); if (eff != null) { int period = eff.GetPeriod(); @@ -2336,12 +2348,11 @@ namespace Game.Spells } } - if (duration != m_spellAura.GetMaxDuration()) + if (duration != spellAura.GetMaxDuration()) { - m_spellAura.SetMaxDuration(duration); - m_spellAura.SetDuration(duration); + spellAura.SetMaxDuration(duration); + spellAura.SetDuration(duration); } - m_spellAura._RegisterForTargets(); } } } @@ -3216,8 +3227,6 @@ namespace Game.Spells void _handle_immediate_phase() { - m_spellAura = null; - // handle some immediate features of the spell here HandleThreatSpells(); @@ -7632,7 +7641,8 @@ namespace Game.Spells SpellEffectHandleMode effectHandleMode; public SpellEffectInfo effectInfo; // used in effects handlers - public Aura m_spellAura; + internal UnitAura spellAura; + internal DynObjAura dynObjAura; // ------------------------------------------- GameObject focusObject; diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs index 8145111db..5deb6cc2f 100644 --- a/Source/Game/Spells/SpellEffects.cs +++ b/Source/Game/Spells/SpellEffects.cs @@ -707,11 +707,10 @@ namespace Game.Spells if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; - if (m_spellAura == null || unitTarget == null) + if (spellAura == null || unitTarget == null) return; - Cypher.Assert(unitTarget == m_spellAura.GetOwner()); - m_spellAura._ApplyEffectForTargets(effIndex); + spellAura._ApplyEffectForTargets(effIndex); } [SpellEffectHandler(SpellEffectName.ApplyAreaAuraEnemy)] @@ -727,10 +726,10 @@ namespace Game.Spells if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; - if (m_spellAura == null || unitTarget == null) + if (spellAura == null || unitTarget == null) return; - Cypher.Assert(unitTarget == m_spellAura.GetOwner()); - m_spellAura._ApplyEffectForTargets(effIndex); + + spellAura._ApplyEffectForTargets(effIndex); } [SpellEffectHandler(SpellEffectName.UnlearnSpecialization)] @@ -1150,30 +1149,46 @@ namespace Game.Spells if (effectHandleMode != SpellEffectHandleMode.Hit) return; - if (m_spellAura == null) + // only handle at last effect + for (uint i = effIndex + 1; i < SpellConst.MaxEffects; ++i) { - Unit caster = m_caster.GetEntry() == SharedConst.WorldTrigger ? m_originalCaster : m_caster; - float radius = effectInfo.CalcRadius(caster); - - // Caster not in world, might be spell triggered from aura removal - if (!caster.IsInWorld) - return; - DynamicObject dynObj = new(false); - if (!dynObj.CreateDynamicObject(caster.GetMap().GenerateLowGuid(HighGuid.DynamicObject), caster, m_spellInfo, destTarget, radius, DynamicObjectType.AreaSpell, m_SpellVisual)) - return; - - Aura aura = Aura.TryCreate(m_spellInfo, m_castId, SpellConst.MaxEffectMask, dynObj, caster, GetCastDifficulty(), m_spellValue.EffectBasePoints, null, ObjectGuid.Empty, ObjectGuid.Empty, m_castItemEntry, m_castItemLevel); - if (aura != null) - { - m_spellAura = aura; - m_spellAura._RegisterForTargets(); - } - else + SpellEffectInfo otherEffect = m_spellInfo.GetEffect(i); + if (otherEffect != null && otherEffect.IsEffect(SpellEffectName.PersistentAreaAura)) return; } - Cypher.Assert(m_spellAura.GetDynobjOwner()); - m_spellAura._ApplyEffectForTargets(effIndex); + Cypher.Assert(dynObjAura == null); + + Unit caster = m_caster.GetEntry() == SharedConst.WorldTrigger ? m_originalCaster : m_caster; + float radius = effectInfo.CalcRadius(caster); + + // Caster not in world, might be spell triggered from aura removal + if (!caster.IsInWorld) + return; + + DynamicObject dynObj = new(false); + if (!dynObj.CreateDynamicObject(caster.GetMap().GenerateLowGuid(HighGuid.DynamicObject), caster, m_spellInfo, destTarget, radius, DynamicObjectType.AreaSpell, m_SpellVisual)) + { + dynObj.Dispose(); + return; + } + + AuraCreateInfo createInfo = new(m_castId, m_spellInfo, GetCastDifficulty(), SpellConst.MaxEffectMask, dynObj); + createInfo.SetCaster(caster); + createInfo.SetBaseAmount(m_spellValue.EffectBasePoints); + createInfo.SetCastItem(m_castItemGUID, m_castItemEntry, m_castItemLevel); + + Aura aura = Aura.TryCreate(createInfo); + if (aura != null) + { + dynObjAura = aura.ToDynObjAura(); + dynObjAura._RegisterForTargets(); + } + else + return; + + Cypher.Assert(dynObjAura.GetDynobjOwner()); + dynObjAura._ApplyEffectForTargets(effIndex); } [SpellEffectHandler(SpellEffectName.Energize)]