From 5869fbaee066e4b774a244a8afe06d2843a18616 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Thu, 1 Feb 2024 18:59:34 -0500 Subject: [PATCH] Core/Spells: Named and implemented most of SpellAttr8 Port From (https://github.com/TrinityCore/TrinityCore/commit/dc9361fcc2eb16a59b52dfd8b0d47dfc1bf639be) --- .../Framework/Constants/Spells/SpellConst.cs | 72 ++++---- Source/Game/Entities/Object/WorldObject.cs | 13 +- Source/Game/Entities/Player/Player.Spells.cs | 8 +- Source/Game/Entities/Unit/Unit.Spells.cs | 10 +- Source/Game/Handlers/SpellHandler.cs | 5 +- .../Game/Networking/Packets/SpellPackets.cs | 4 +- Source/Game/Spells/Auras/Aura.cs | 2 +- Source/Game/Spells/Auras/AuraEffect.cs | 20 ++- Source/Game/Spells/Spell.cs | 154 ++++++++++++++---- Source/Game/Spells/SpellInfo.cs | 31 +++- 10 files changed, 232 insertions(+), 87 deletions(-) diff --git a/Source/Framework/Constants/Spells/SpellConst.cs b/Source/Framework/Constants/Spells/SpellConst.cs index fd2de3f97..3669e1399 100644 --- a/Source/Framework/Constants/Spells/SpellConst.cs +++ b/Source/Framework/Constants/Spells/SpellConst.cs @@ -1927,38 +1927,38 @@ namespace Framework.Constants } public enum SpellAttr8 : uint { - CantMiss = 0x01, // 0 - Unk1 = 0x02, // 1 - Unk2 = 0x04, // 2 - Unk3 = 0x08, // 3 - Unk4 = 0x10, // 4 - Unk5 = 0x20, // 5 - Unk6 = 0x40, // 6 - Unk7 = 0x80, // 7 - AffectPartyAndRaid = 0x100, // 8 - PeriodicCanCrit = 0x200, // 9 - NameChangedDuringTransofrm = 0x400, // 10 - Unk11 = 0x800, // 11 - AuraSendAmount = 0x1000, // 12 Aura Must Have Flag AflagAnyEffectAmountSent To Send Amount - Unk13 = 0x2000, // 13 - Unk14 = 0x4000, // 14 - WaterMount = 0x8000, // 15 - Unk16 = 0x10000, // 16 - HasteAffectsDuration = 0x20000, // 17 Haste Affects Duration - RememberSpells = 0x40000, // 18 - UseComboPointsOnAnyTarget = 0x80000, // 19 - ArmorSpecialization = 0x100000, // 20 - Unk21 = 0x200000, // 21 - Unk22 = 0x400000, // 22 - BattleResurrection = 0x800000, // 23 - HealingSpell = 0x1000000, // 24 - Unk25 = 0x2000000, // 25 - RaidMarker = 0x4000000, // 26 Probably Spell No Need Learn To Cast - Unk27 = 0x8000000, // 27 - NotInBgOrArena = 0x10000000, // 28 - MasteryAffectPoints = 0x20000000, // 29 - Unk30 = 0x40000000, // 30 - AttackIgnoreImmuneToPCFlag = 0x80000000 // 31 + NoAttackBlock = 0x01, // No Attack Block + IgnoreDynamicObjectCaster = 0x02, /*Nyi*/ // Ignore Dynamic Object Caster + RemoveOutsideDungeonsAndRaids = 0x04, // Remove Outside Dungeons And Raids + OnlyTargetIfSameCreator = 0x08, // Only Target If Same Creator + CanHitAoeUntargetable = 0x10, /*Nyi - No Aoe Immunity Implementation*/ // Can Hit Aoe Untargetable + AllowWhileCharmed = 0x20, /*Nyi - Not Implementable Currently, Charming Replaces Ai*/ // Allow While Charmed + AuraRequiredByClient = 0x40, /*Nyi - We Send All Auras To Client*/ // Aura Required By Client + IgnoreSanctuary = 0x80, // Ignore Sanctuary + UseTargetsLevelForSpellScaling = 0x100, // Use Target'S Level For Spell Scaling + PeriodicCanCrit = 0x200, // Periodic Can Crit + MirrorCreatureName = 0x400, // Mirror Creature Name Description Transform Auras Also Override Name (Handled Clientside) + OnlyPlayersCanCastThisSpell = 0x800, // Only Players Can Cast This Spell + AuraPointsOnClient = 0x1000, // Aura Points On Client + NotInSpellbookUntilLearned = 0x2000, // Not In Spellbook Until Learned Description Hides Autolearned Spell From Spellbook Before Learning (Handled Clientside) + TargetProcsOnCaster = 0x4000, // Target Procs On Caster Description Target (Taken) Procs Happen On Caster (Actor) Instead Of Aura Target (Action Target) + RequiresLocationToBeOnLiquidSurface = 0x8000, // Requires Location To Be On Liquid Surface + OnlyTargetOwnSummons = 0x10000, // Only Target Own Summons + HasteAffectsDuration = 0x20000, // Haste Affects Duration + IgnoreSpellcastOverrideCost = 0x40000, // Ttile Ignore Spellcast Override Cost + AllowTargetsHiddenBySpawnTracking = 0x80000, /*Nyi - No Spawn Tracking Implementation*/ // Allow Targets Hidden By Spawn Tracking + RequiresEquippedInvTypes = 0x100000, // Requires Equipped Inv Types + NoSummonDestFromClientTargetingPathingRequirement = 0x200000, /*Nyi - Vald Path To A Spell Dest Is Not Required Currently If The Dest Comes From Client*/ // No 'Summon + Dest From Client' Targeting Pathing Requirement + MeleeHasteAffectsPeriodic = 0x400000, // Melee Haste Affects Periodic + EnforceInCombatRessurectionLimit = 0x800000, // Enforce In Combat Ressurection Limit Description Used To Limit The Number Of Resurrections In Boss Encounters + HealPrediction = 0x1000000, // Heal Prediction + NoLevelUpToast = 0x2000000, // No Level Up Toast + SkipIsKnownCheck = 0x4000000, // Skip Is Known Check + AiFaceTarget = 0x8000000, /*Nyi - Unknown Facing Conditions, Needs Research*/ // Ai Face Target + NotInBattleground = 0x10000000, // Not In Battleground + MasteryAffectsPoints = 0x20000000, // Mastery Affects Points + DisplayLargeAuraIconOnUnitFramesBossAura = 0x40000000, // Display Large Aura Icon On Unit Frames (Boss Aura) + AttackIgnoreImmuneToPcFlag = 0x80000000 // Can Attack Immunepc Description Do Not Check UnitFlagImmuneToPc In Isvalidattacktarget } public enum SpellAttr9 : uint { @@ -2973,4 +2973,12 @@ namespace Framework.Constants TargetA = 0, TargetB = 1 } + + public enum SpellHealPredictionType : byte + { + Target = 0, + TargetAndCaster = 1, + TargetAndBeacon = 2, + TargetParty = 3, + } } diff --git a/Source/Game/Entities/Object/WorldObject.cs b/Source/Game/Entities/Object/WorldObject.cs index e2a979d31..6ee556edd 100644 --- a/Source/Game/Entities/Object/WorldObject.cs +++ b/Source/Game/Entities/Object/WorldObject.cs @@ -2004,7 +2004,7 @@ namespace Game.Entities if (spellInfo == null || duration < 0) return; - if (spellInfo.IsChanneled() && !spellInfo.HasAttribute(SpellAttr5.SpellHasteAffectsPeriodic)) + if (spellInfo.IsChanneled() && !spellInfo.HasAttribute(SpellAttr5.SpellHasteAffectsPeriodic) && !spellInfo.HasAttribute(SpellAttr8.MeleeHasteAffectsPeriodic)) return; // called from caster @@ -2594,7 +2594,7 @@ namespace Game.Entities if (!unitTarget.HasUnitFlag(UnitFlags.PlayerControlled) && unitOrOwner.IsImmuneToNPC()) return false; - if (bySpell == null || !bySpell.HasAttribute(SpellAttr8.AttackIgnoreImmuneToPCFlag)) + if (bySpell == null || !bySpell.HasAttribute(SpellAttr8.AttackIgnoreImmuneToPcFlag)) { if (unitOrOwner.HasUnitFlag(UnitFlags.PlayerControlled) && unitTarget.IsImmuneToPC()) return false; @@ -2664,7 +2664,8 @@ namespace Game.Entities // PvP case - can't attack when attacker or target are in sanctuary // however, 13850 client doesn't allow to attack when one of the unit's has sanctuary flag and is pvp - if (unitTarget != null && unitTarget.HasUnitFlag(UnitFlags.PlayerControlled) && unitOrOwner != null && unitOrOwner.HasUnitFlag(UnitFlags.PlayerControlled) && (unitTarget.IsInSanctuary() || unitOrOwner.IsInSanctuary())) + if (unitTarget != null && unitTarget.HasUnitFlag(UnitFlags.PlayerControlled) && unitOrOwner != null && unitOrOwner.HasUnitFlag(UnitFlags.PlayerControlled) + && (unitTarget.IsInSanctuary() || unitOrOwner.IsInSanctuary()) && (bySpell == null || bySpell.HasAttribute(SpellAttr8.IgnoreSanctuary))) return false; // additional checks - only PvP case @@ -2738,7 +2739,7 @@ namespace Game.Entities { if (unit != null && unit.HasUnitFlag(UnitFlags.PlayerControlled)) { - if (bySpell == null || !bySpell.HasAttribute(SpellAttr8.AttackIgnoreImmuneToPCFlag)) + if (bySpell == null || !bySpell.HasAttribute(SpellAttr8.AttackIgnoreImmuneToPcFlag)) if (unitTarget != null && unitTarget.IsImmuneToPC()) return false; } @@ -2771,7 +2772,7 @@ namespace Game.Entities return false; // can't assist player out of sanctuary from sanctuary if has pvp enabled - if (unitTarget.IsPvP()) + if (unitTarget.IsPvP() && (bySpell == null || bySpell.HasAttribute(SpellAttr8.IgnoreSanctuary))) if (unit.IsInSanctuary() && !unitTarget.IsInSanctuary()) return false; } @@ -2836,7 +2837,7 @@ namespace Game.Entities { return spellInfo.GetSpellXSpellVisualId(this); } - + public List GetGameObjectListWithEntryInGrid(uint entry = 0, float maxSearchRange = 250.0f) { List gameobjectList = new(); diff --git a/Source/Game/Entities/Player/Player.Spells.cs b/Source/Game/Entities/Player/Player.Spells.cs index 4ca6bb57c..49b8097fd 100644 --- a/Source/Game/Entities/Player/Player.Spells.cs +++ b/Source/Game/Entities/Player/Player.Spells.cs @@ -274,7 +274,7 @@ namespace Game.Entities return false; } - public override SpellInfo GetCastSpellInfo(SpellInfo spellInfo) + public override SpellInfo GetCastSpellInfo(SpellInfo spellInfo, TriggerCastFlags triggerFlag) { var overrides = m_overrideSpells.LookupByKey(spellInfo.Id); if (!overrides.Empty()) @@ -283,11 +283,11 @@ namespace Game.Entities { SpellInfo newInfo = Global.SpellMgr.GetSpellInfo(spellId, GetMap().GetDifficultyID()); if (newInfo != null) - return GetCastSpellInfo(newInfo); + return GetCastSpellInfo(newInfo, triggerFlag); } } - return base.GetCastSpellInfo(spellInfo); + return base.GetCastSpellInfo(spellInfo, triggerFlag); } public void SetOverrideSpellsId(uint overrideSpellsId) { SetUpdateFieldValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.OverrideSpellsID), overrideSpellsId); } @@ -1778,7 +1778,7 @@ namespace Game.Entities } case ItemClass.Armor: { - if (!spellInfo.HasAttribute(SpellAttr8.ArmorSpecialization)) + if (!spellInfo.HasAttribute(SpellAttr8.RequiresEquippedInvTypes)) { // most used check: shield only if ((spellInfo.EquippedItemSubClassMask & (1 << (int)ItemSubClassArmor.Shield)) != 0) diff --git a/Source/Game/Entities/Unit/Unit.Spells.cs b/Source/Game/Entities/Unit/Unit.Spells.cs index 1d7955db4..a2918841a 100644 --- a/Source/Game/Entities/Unit/Unit.Spells.cs +++ b/Source/Game/Entities/Unit/Unit.Spells.cs @@ -774,7 +774,7 @@ namespace Game.Entities bool canDodge = !spellInfo.HasAttribute(SpellAttr7.NoAttackDodge); bool canParry = !spellInfo.HasAttribute(SpellAttr7.NoAttackParry); - bool canBlock = true; + bool canBlock = !spellInfo.HasAttribute(SpellAttr8.NoAttackBlock); // if victim is casting or cc'd it can't avoid attacks if (victim.IsNonMeleeSpellCast(false, false, true) || victim.HasUnitState(UnitState.Controlled)) @@ -892,7 +892,7 @@ namespace Game.Entities spell.Finish(result); } - public virtual SpellInfo GetCastSpellInfo(SpellInfo spellInfo) + public virtual SpellInfo GetCastSpellInfo(SpellInfo spellInfo, TriggerCastFlags triggerFlag) { SpellInfo findMatchingAuraEffectIn(AuraType type) { @@ -903,7 +903,13 @@ namespace Game.Entities { SpellInfo info = Global.SpellMgr.GetSpellInfo((uint)auraEffect.GetAmount(), GetMap().GetDifficultyID()); if (info != null) + { + if (auraEffect.GetSpellInfo().HasAttribute(SpellAttr8.IgnoreSpellcastOverrideCost)) + triggerFlag |= TriggerCastFlags.IgnorePowerAndReagentCost; + return info; + + } } } diff --git a/Source/Game/Handlers/SpellHandler.cs b/Source/Game/Handlers/SpellHandler.cs index da1224776..d9f018c47 100644 --- a/Source/Game/Handlers/SpellHandler.cs +++ b/Source/Game/Handlers/SpellHandler.cs @@ -308,11 +308,10 @@ namespace Game SpellCastTargets targets = new(caster, cast.Cast); // check known spell or raid marker spell (which not requires player to know it) - if (caster.IsTypeId(TypeId.Player) && !caster.ToPlayer().HasActiveSpell(spellInfo.Id) && !spellInfo.HasEffect(SpellEffectName.ChangeRaidMarker) && !spellInfo.HasAttribute(SpellAttr8.RaidMarker)) + if (caster.IsTypeId(TypeId.Player) && !caster.ToPlayer().HasActiveSpell(spellInfo.Id) && !spellInfo.HasAttribute(SpellAttr8.SkipIsKnownCheck)) { bool allow = false; - // allow casting of unknown spells for special lock cases GameObject go = targets.GetGOTarget(); if (go != null) @@ -331,7 +330,7 @@ namespace Game } // Check possible spell cast overrides - spellInfo = caster.GetCastSpellInfo(spellInfo); + spellInfo = caster.GetCastSpellInfo(spellInfo, triggerFlag); if (spellInfo.IsPassive()) return; diff --git a/Source/Game/Networking/Packets/SpellPackets.cs b/Source/Game/Networking/Packets/SpellPackets.cs index 1adc2958e..eb7a309e7 100644 --- a/Source/Game/Networking/Packets/SpellPackets.cs +++ b/Source/Game/Networking/Packets/SpellPackets.cs @@ -1819,12 +1819,12 @@ namespace Game.Networking.Packets { public ObjectGuid BeaconGUID; public uint Points; - public byte Type; + public SpellHealPredictionType Type; public void Write(WorldPacket data) { data.WriteUInt32(Points); - data.WriteUInt8(Type); + data.WriteUInt8((byte)Type); data.WritePackedGuid(BeaconGUID); } } diff --git a/Source/Game/Spells/Auras/Aura.cs b/Source/Game/Spells/Auras/Aura.cs index 026defaa3..8b1c6274b 100644 --- a/Source/Game/Spells/Auras/Aura.cs +++ b/Source/Game/Spells/Auras/Aura.cs @@ -143,7 +143,7 @@ namespace Game.Spells bool effectNeedsAmount(AuraEffect effect) => effect != null && (GetEffectsToApply() & (1 << (int)effect.GetEffIndex())) != 0 && Aura.EffectTypeNeedsSendingAmount(effect.GetAuraType()); - if (GetBase().GetSpellInfo().HasAttribute(SpellAttr8.AuraSendAmount) || GetBase().GetAuraEffects().Any(effectNeedsAmount)) + if (GetBase().GetSpellInfo().HasAttribute(SpellAttr8.AuraPointsOnClient) || GetBase().GetAuraEffects().Any(effectNeedsAmount)) _flags |= AuraFlags.Scalable; } diff --git a/Source/Game/Spells/Auras/AuraEffect.cs b/Source/Game/Spells/Auras/AuraEffect.cs index 2c147d8c9..13312097f 100644 --- a/Source/Game/Spells/Auras/AuraEffect.cs +++ b/Source/Game/Spells/Auras/AuraEffect.cs @@ -61,7 +61,7 @@ namespace Game.Spells // default amount calculation int amount = 0; - if (!m_spellInfo.HasAttribute(SpellAttr8.MasteryAffectPoints) || MathFunctions.fuzzyEq(GetSpellEffectInfo().BonusCoefficient, 0.0f)) + if (!m_spellInfo.HasAttribute(SpellAttr8.MasteryAffectsPoints) || MathFunctions.fuzzyEq(GetSpellEffectInfo().BonusCoefficient, 0.0f)) amount = GetSpellEffectInfo().CalcValue(caster, m_baseAmount, GetBase().GetOwner().ToUnit(), GetBase().GetCastItemId(), GetBase().GetCastItemLevel()); else if (caster != null && caster.IsTypeId(TypeId.Player)) amount = (int)(caster.ToPlayer().m_activePlayerData.Mastery * GetSpellEffectInfo().BonusCoefficient); @@ -169,6 +169,8 @@ namespace Game.Spells caster.ModSpellDurationTime(spellInfo, ref period); else if (spellInfo.HasAttribute(SpellAttr5.SpellHasteAffectsPeriodic)) period = (int)(period * caster.m_unitData.ModCastingSpeed); + else if (spellInfo.HasAttribute(SpellAttr8.MeleeHasteAffectsPeriodic)) + period = (int)(period * caster.m_unitData.ModHaste); if (period == 0) return 0.0f; @@ -179,7 +181,7 @@ namespace Game.Spells return totalTicks * CalculateEstimatedAmount(caster, target, spellInfo, spellEffectInfo, (int)amount, stack, null).GetValueOrDefault(amount); } - + public uint GetTotalTicks() { uint totalTicks = 0; @@ -252,6 +254,8 @@ namespace Game.Spells caster.ModSpellDurationTime(m_spellInfo, ref _period); else if (m_spellInfo.HasAttribute(SpellAttr5.SpellHasteAffectsPeriodic)) _period = (int)(_period * caster.m_unitData.ModCastingSpeed); + else if (m_spellInfo.HasAttribute(SpellAttr8.MeleeHasteAffectsPeriodic)) + _period = (int)(_period * caster.m_unitData.ModHaste); } } else // prevent infinite loop on Update @@ -365,7 +369,7 @@ namespace Game.Spells HandleEffect(aurApp, handleMask, true, triggeredBy); } - if (GetSpellInfo().HasAttribute(SpellAttr8.AuraSendAmount) || Aura.EffectTypeNeedsSendingAmount(GetAuraType())) + if (GetSpellInfo().HasAttribute(SpellAttr8.AuraPointsOnClient) || Aura.EffectTypeNeedsSendingAmount(GetAuraType())) GetBase().SetNeedClientUpdateForTargets(); } @@ -2519,7 +2523,7 @@ namespace Game.Spells if (!apply && !target.IsFlying()) target.GetMotionMaster().MoveFall(); } - + [AuraEffectHandler(AuraType.ModStunDisableGravity)] void HandleAuraModStunAndDisableGravity(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) { @@ -4826,7 +4830,7 @@ namespace Game.Spells uint casterType = (uint)GetMiscValue(); if (casterType > 0) caster = GetCaster(); - + caster?.CastSpell(aurApp.GetTarget(), GetSpellEffectInfo().TriggerSpell, new CastSpellExtraArgs(this)); } @@ -5558,6 +5562,8 @@ namespace Game.Spells { Unit triggerCaster = aurApp.GetTarget(); Unit triggerTarget = eventInfo.GetProcTarget(); + if (GetSpellInfo().HasAttribute(SpellAttr8.TargetProcsOnCaster) && eventInfo.GetTypeMask().HasFlag(ProcFlags.TakenHitMask)) + triggerTarget = eventInfo.GetActor(); uint triggerSpellId = GetSpellEffectInfo().TriggerSpell; if (triggerSpellId == 0) @@ -5580,6 +5586,8 @@ namespace Game.Spells { Unit triggerCaster = aurApp.GetTarget(); Unit triggerTarget = eventInfo.GetProcTarget(); + if (GetSpellInfo().HasAttribute(SpellAttr8.TargetProcsOnCaster) && eventInfo.GetTypeMask().HasFlag(ProcFlags.TakenHitMask)) + triggerTarget = eventInfo.GetActor(); uint triggerSpellId = GetSpellEffectInfo().TriggerSpell; if (triggerSpellId == 0) @@ -5993,7 +6001,7 @@ namespace Game.Spells playerTarget.ReplaceAllRequiredMountCapabilityFlags((byte)mountCapabilityFlags); } } - + [AuraEffectHandler(AuraType.SuppressItemPassiveEffectBySpellLabel)] void HandleSuppressItemPassiveEffectBySpellLabel(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) { diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index a408a647e..3e1f7a5f1 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -305,6 +305,21 @@ namespace Game.Spells } } } + + if (m_targets.HasDst()) + { + if (m_spellInfo.HasAttribute(SpellAttr8.RequiresLocationToBeOnLiquidSurface)) + { + ZLiquidStatus status = m_caster.GetMap().GetLiquidStatus(m_caster.GetPhaseShift(), m_targets.GetDstPos().GetPositionX(), m_targets.GetDstPos().GetPositionY(), m_targets.GetDstPos().GetPositionZ(), LiquidHeaderTypeFlags.AllLiquids); + if (!status.HasAnyFlag(ZLiquidStatus.WaterWalk | ZLiquidStatus.InWater)) + { + SendCastResult(SpellCastResult.NoLiquid); + Finish(SpellCastResult.NoLiquid); + return; + } + } + } + ulong dstDelay = CalculateDelayMomentForDst(m_spellInfo.LaunchDelay); if (dstDelay != 0) m_delayMoment = dstDelay; @@ -3806,6 +3821,9 @@ namespace Game.Spells if (HasPowerTypeCost(PowerType.Runes)) castFlags |= SpellCastFlags.NoGCD; // not needed, but Blizzard sends it + if (m_spellInfo.HasAttribute(SpellAttr8.HealPrediction) && m_casttime != 0 && m_caster.IsUnit()) + castFlags |= SpellCastFlags.HealPrediction; + SpellStart packet = new(); SpellCastData castData = packet.Cast; @@ -3874,13 +3892,7 @@ namespace Game.Spells castData.Immunities.Value = (uint)mechanicImmunityMask; } - /** @todo implement heal prediction packet data - if (castFlags & CAST_FLAG_HEAL_PREDICTION) - { - castData.Predict.BeconGUID = ?? - castData.Predict.Points = 0; - castData.Predict.Type = 0; - }**/ + UpdateSpellHealPrediction(castData.Predict, false); m_caster.SendMessageToSet(packet, true); } @@ -4093,6 +4105,77 @@ namespace Game.Spells return ammoDisplayID; } + static (int, SpellHealPredictionType) CalcPredictedHealing(SpellInfo spellInfo, Unit unitCaster, Unit target, uint castItemEntry, int castItemLevel, Spell spell, bool withPeriodic) + { + int points = 0; + SpellHealPredictionType type = SpellHealPredictionType.Target; + foreach (SpellEffectInfo spellEffectInfo in spellInfo.GetEffects()) + { + switch (spellEffectInfo.Effect) + { + case SpellEffectName.Heal: + case SpellEffectName.HealPct: + points += unitCaster.SpellHealingBonusDone(target, spellInfo, spellEffectInfo.CalcValue(unitCaster, null, target, castItemEntry, castItemLevel), DamageEffectType.Direct, spellEffectInfo, 1, spell); + + if (target != unitCaster && (spellEffectInfo.TargetA.GetTarget() == Targets.UnitCaster || spellEffectInfo.TargetB.GetTarget() == Targets.UnitCaster)) + type = SpellHealPredictionType.TargetAndCaster; // Binding Heal-like spells + else if (spellEffectInfo.TargetA.GetCheckType() == SpellTargetCheckTypes.Party || spellEffectInfo.TargetB.GetCheckType() == SpellTargetCheckTypes.Party) + type = SpellHealPredictionType.TargetParty; // Prayer of Healing (old party-wide targeting) + break; + default: + break; + } + + if (withPeriodic) + { + switch (spellEffectInfo.ApplyAuraName) + { + case AuraType.PeriodicHeal: + case AuraType.ObsModHealth: + points += (int)(unitCaster.SpellHealingBonusDone(target, spellInfo, spellEffectInfo.CalcValue(unitCaster, null, target, castItemEntry, castItemLevel), DamageEffectType.Direct, spellEffectInfo, 1, spell) * spellInfo.GetMaxTicks()); + break; + case AuraType.PeriodicTriggerSpell: + SpellInfo triggered = Global.SpellMgr.GetSpellInfo(spellEffectInfo.TriggerSpell, spellInfo.Difficulty); + if (triggered != null) + points += CalcPredictedHealing(triggered, unitCaster, target, castItemEntry, castItemLevel, null, withPeriodic).Item1; + break; + default: + break; + } + } + } + + return (points, type); + } + + void UpdateSpellHealPrediction(SpellHealPrediction healPrediction, bool withPeriodic) + { + healPrediction.BeaconGUID = ObjectGuid.Empty; + healPrediction.Points = 0; + healPrediction.Type = SpellHealPredictionType.Target; + + Unit unitCaster = m_caster.ToUnit(); + Unit target = m_targets.GetUnitTarget(); + if (target != null) + { + var (points, type) = CalcPredictedHealing(m_spellInfo, unitCaster, target, m_castItemEntry, m_castItemLevel, this, withPeriodic); + healPrediction.Points = (uint)points; + healPrediction.Type = type; + } + + uint beaconSpellId = 53651; + + if (healPrediction.Type == SpellHealPredictionType.Target && unitCaster.HasAura(beaconSpellId, unitCaster.GetGUID())) + { + var beacon = unitCaster.GetSingleCastAuras().Find(aura => aura.GetSpellInfo().GetEffects().Count > 1 && aura.GetSpellInfo().GetEffect(1).TriggerSpell == beaconSpellId); + if (beacon != null) + { + healPrediction.BeaconGUID = beacon.GetOwner().GetGUID(); + healPrediction.Type = SpellHealPredictionType.TargetAndBeacon; + } + } + } + void SendSpellExecuteLog() { if (_executeLogEffects.Empty()) @@ -4256,25 +4339,6 @@ namespace Game.Spells if (unitCaster == null) return; - SpellChannelStart spellChannelStart = new(); - spellChannelStart.CasterGUID = unitCaster.GetGUID(); - spellChannelStart.SpellID = (int)m_spellInfo.Id; - spellChannelStart.Visual = m_SpellVisual; - spellChannelStart.ChannelDuration = duration; - - uint schoolImmunityMask = unitCaster.GetSchoolImmunityMask(); - ulong mechanicImmunityMask = unitCaster.GetMechanicImmunityMask(); - - if (schoolImmunityMask != 0 || mechanicImmunityMask != 0) - { - SpellChannelStartInterruptImmunities interruptImmunities = new(); - interruptImmunities.SchoolImmunities = (int)schoolImmunityMask; - interruptImmunities.Immunities = (int)mechanicImmunityMask; - - spellChannelStart.InterruptImmunities = interruptImmunities; - } - unitCaster.SendMessageToSet(spellChannelStart, true); - m_timer = (int)duration; if (!m_targets.HasDst()) @@ -4320,6 +4384,37 @@ namespace Game.Spells unitCaster.SetChannelSpellId(m_spellInfo.Id); unitCaster.SetChannelVisual(m_SpellVisual); + + SpellChannelStart spellChannelStart = new(); + spellChannelStart.CasterGUID = unitCaster.GetGUID(); + spellChannelStart.SpellID = (int)m_spellInfo.Id; + spellChannelStart.Visual = m_SpellVisual; + spellChannelStart.ChannelDuration = duration; + + uint schoolImmunityMask = unitCaster.GetSchoolImmunityMask(); + ulong mechanicImmunityMask = unitCaster.GetMechanicImmunityMask(); + + if (schoolImmunityMask != 0 || mechanicImmunityMask != 0) + { + SpellChannelStartInterruptImmunities interruptImmunities = new(); + interruptImmunities.SchoolImmunities = (int)schoolImmunityMask; + interruptImmunities.Immunities = (int)mechanicImmunityMask; + + spellChannelStart.InterruptImmunities = interruptImmunities; + } + + if (m_spellInfo.HasAttribute(SpellAttr8.HealPrediction) && m_casttime != 0 && m_caster.IsUnit()) + { + SpellTargetedHealPrediction healPrediction = new(); + if (unitCaster.m_unitData.ChannelObjects.Size() == 1 && unitCaster.m_unitData.ChannelObjects[0].IsUnit()) + healPrediction.TargetGUID = unitCaster.m_unitData.ChannelObjects[0]; + + UpdateSpellHealPrediction(healPrediction.Predict, true); + + spellChannelStart.HealPrediction = healPrediction; + } + + unitCaster.SendMessageToSet(spellChannelStart, true); } void SendResurrectRequest(Player target) @@ -4746,6 +4841,9 @@ namespace Game.Spells return SpellCastResult.CustomError; } + if (m_spellInfo.HasAttribute(SpellAttr8.OnlyPlayersCanCastThisSpell) && !m_caster.IsPlayer()) + return SpellCastResult.CasterAurastate; + // Check global cooldown if (strict && !Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreGCD) && HasGlobalCooldown()) return !m_spellInfo.HasAttribute(SpellAttr0.CooldownOnEvent) ? SpellCastResult.NotReady : SpellCastResult.DontReport; @@ -7195,7 +7293,7 @@ namespace Game.Spells bool IsNeedSendToClient() { return m_SpellVisual.SpellXSpellVisualID != 0 || m_SpellVisual.ScriptVisualID != 0 || m_spellInfo.IsChanneled() || - (m_spellInfo.HasAttribute(SpellAttr8.AuraSendAmount)) || m_spellInfo.HasHitDelay() || (m_triggeredByAuraSpell == null && !IsTriggered()) || + (m_spellInfo.HasAttribute(SpellAttr8.AuraPointsOnClient)) || m_spellInfo.HasHitDelay() || (m_triggeredByAuraSpell == null && !IsTriggered()) || m_spellInfo.HasAttribute(SpellAttr7.AlwaysCastLog); } @@ -8474,7 +8572,7 @@ namespace Game.Spells if (unit.IsAlive() != IsAlive) return; - if (spell.GetState() == SpellState.Delayed && !spell.IsPositive() && (GameTime.GetGameTimeMS() - TimeDelay) <= unit.LastSanctuaryTime) + if (!spell.m_spellInfo.HasAttribute(SpellAttr8.IgnoreSanctuary) && spell.GetState() == SpellState.Delayed && !spell.IsPositive() && (GameTime.GetGameTimeMS() - TimeDelay) <= unit.LastSanctuaryTime) return; // No missinfo in that case if (_spellHitTarget != null) diff --git a/Source/Game/Spells/SpellInfo.cs b/Source/Game/Spells/SpellInfo.cs index 720e134dd..5b49d671d 100644 --- a/Source/Game/Spells/SpellInfo.cs +++ b/Source/Game/Spells/SpellInfo.cs @@ -877,6 +877,18 @@ namespace Game.Spells return SpellCastResult.NotInRaidInstance; } + if (HasAttribute(SpellAttr8.RemoveOutsideDungeonsAndRaids)) + { + if (mapEntry == null || !mapEntry.IsDungeon()) + return SpellCastResult.TargetNotInInstance; + } + + if (HasAttribute(SpellAttr8.NotInBattleground)) + { + if (mapEntry == null || mapEntry.IsBattleground()) + return SpellCastResult.NotInBattleground; + } + // DB base check (if non empty then must fit at least single for allow) var saBounds = Global.SpellMgr.GetSpellAreaMapBounds(Id); if (!saBounds.Empty()) @@ -993,6 +1005,10 @@ namespace Game.Spells Unit unitTarget = target.ToUnit(); + if (HasAttribute(SpellAttr8.OnlyTargetIfSameCreator)) + if (caster != target && caster.GetGUID() != target.GetOwnerGUID()) + return SpellCastResult.BadTargets; + // creature/player specific target checks if (unitTarget != null) { @@ -1046,6 +1062,10 @@ namespace Game.Spells } } } + + if (HasAttribute(SpellAttr8.OnlyTargetOwnSummons)) + if (!unitTarget.IsSummon() || unitTarget.ToTempSummon().GetSummonerGUID() != caster.GetGUID()) + return SpellCastResult.BadTargets; } // corpse specific target checks else if (target.IsTypeId(TypeId.Corpse)) @@ -1137,7 +1157,7 @@ namespace Game.Spells if (HasEffect(SpellEffectName.SelfResurrect) || HasEffect(SpellEffectName.Resurrect)) return SpellCastResult.TargetCannotBeResurrected; - if (HasAttribute(SpellAttr8.BattleResurrection)) + if (HasAttribute(SpellAttr8.EnforceInCombatRessurectionLimit)) { Map map = caster.GetMap(); if (map != null) @@ -4248,7 +4268,7 @@ namespace Game.Spells if (Scaling.Coefficient != 0.0f) { uint level = _spellInfo.SpellLevel; - if (target != null && _spellInfo.IsPositiveEffect(EffectIndex) && (Effect == SpellEffectName.ApplyAura)) + if (target != null && _spellInfo.HasAttribute(SpellAttr8.UseTargetsLevelForSpellScaling)) level = target.GetLevel(); else if (caster != null && caster.IsUnit()) level = caster.ToUnit().GetLevel(); @@ -4333,7 +4353,12 @@ namespace Game.Spells if (contentTuning != null) expansion = contentTuning.ExpansionID; - uint level = caster != null && caster.IsUnit() ? caster.ToUnit().GetLevel() : 1; + uint level = 1; + if (target != null && _spellInfo.HasAttribute(SpellAttr8.UseTargetsLevelForSpellScaling)) + level = target.GetLevel(); + else if (caster != null && caster.IsUnit()) + level = caster.ToUnit().GetLevel(); + tempValue = Global.DB2Mgr.EvaluateExpectedStat(stat, level, expansion, 0, Class.None, 0) * BasePoints / 100.0f; }