From 7c812ae904b28397f7475acfd3ef4d9af270b204 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Tue, 6 Feb 2024 17:21:07 -0500 Subject: [PATCH] Core/Creatures: Move immunities to separate table and implemented setting immunties to dispel, spell effects, aura types, aoe and chain targeting Port From (https://github.com/TrinityCore/TrinityCore/commit/f70a5817e1c07891185d716611d45f50b1c73b78) --- Source/Framework/Collections/BitSet.cs | 2 + .../Framework/Constants/Spells/SpellConst.cs | 9 ++- .../Database/Databases/WorldDatabase.cs | 2 +- Source/Framework/Util/CollectionExtensions.cs | 7 ++ Source/Game/Chat/Commands/NPCCommands.cs | 13 ++-- .../Game/Entities/Creature/Creature.Fields.cs | 1 + Source/Game/Entities/Creature/Creature.cs | 59 ++++++++++------- Source/Game/Entities/Creature/CreatureData.cs | 3 +- Source/Game/Entities/Pet.cs | 2 +- Source/Game/Entities/Unit/Unit.Movement.cs | 4 +- Source/Game/Globals/ObjectManager.cs | 9 ++- .../Game/Networking/Packets/SpellPackets.cs | 24 +++---- Source/Game/Spells/Spell.cs | 49 ++++++++++---- Source/Game/Spells/SpellInfo.cs | 64 +++++++++--------- Source/Game/Spells/SpellManager.cs | 66 ++++++++++++++++++- 15 files changed, 216 insertions(+), 98 deletions(-) diff --git a/Source/Framework/Collections/BitSet.cs b/Source/Framework/Collections/BitSet.cs index 442ace371..15ddea444 100644 --- a/Source/Framework/Collections/BitSet.cs +++ b/Source/Framework/Collections/BitSet.cs @@ -45,6 +45,8 @@ namespace System.Collections _version = 0; } + public BitSet(ulong value) : this(new uint[] { (uint)(value & (ulong)uint.MaxValue), (uint)(value >> 32) }) { } + public BitSet(BitSet bits) { if (bits == null) diff --git a/Source/Framework/Constants/Spells/SpellConst.cs b/Source/Framework/Constants/Spells/SpellConst.cs index c9a0ec521..5003375f0 100644 --- a/Source/Framework/Constants/Spells/SpellConst.cs +++ b/Source/Framework/Constants/Spells/SpellConst.cs @@ -260,6 +260,7 @@ namespace Framework.Constants Enrage = 9, ZGTicket = 10, OldUnused = 11, + Max, AllMask = ((1 << Magic) | (1 << Curse) | (1 << Disease) | (1 << Poison)) } @@ -1783,7 +1784,7 @@ namespace Framework.Constants IgnoreCasterAndTargetRestrictions = 0x10000000, /*Nyi*/ // Ignore Caster & Target Restrictions IgnoreCasterModifiers = 0x20000000, // Ignore Caster Modifiers DoNotDisplayRange = 0x40000000, // Do Not Display Range (Client Only) - NotOnAoeImmune = 0x80000000 /*Nyi, No Aoe Immunity Implementation*/ // Not On Aoe Immune + NotOnAoeImmune = 0x80000000 // Not On Aoe Immune } public enum SpellAttr4 : uint { @@ -2981,4 +2982,10 @@ namespace Framework.Constants TargetAndBeacon = 2, TargetParty = 3, } + + public enum WorldObjectSpellAreaTargetSearchReason + { + Area, + Chain + } } diff --git a/Source/Framework/Database/Databases/WorldDatabase.cs b/Source/Framework/Database/Databases/WorldDatabase.cs index b7e7c0c03..16cc9dd7c 100644 --- a/Source/Framework/Database/Databases/WorldDatabase.cs +++ b/Source/Framework/Database/Databases/WorldDatabase.cs @@ -46,7 +46,7 @@ namespace Framework.Database PrepareStatement(WorldStatements.SEL_CREATURE_ADDON_BY_GUID, "SELECT guid FROM creature_addon WHERE guid = ?"); PrepareStatement(WorldStatements.DEL_CREATURE, "DELETE FROM creature WHERE guid = ?"); PrepareStatement(WorldStatements.SEL_COMMANDS, "SELECT name, help FROM command"); - PrepareStatement(WorldStatements.SEL_CREATURE_TEMPLATE, "SELECT entry, KillCredit1, KillCredit2, name, femaleName, subname, TitleAlt, IconName, RequiredExpansion, VignetteID, faction, npcflag, speed_walk, speed_run, scale, Classification, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, unit_flags3, family, trainer_class, type, VehicleId, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, ExperienceModifier, RacialLeader, movementId, WidgetSetID, WidgetSetUnitConditionID, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName, StringId FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ? OR 1 = ?"); + PrepareStatement(WorldStatements.SEL_CREATURE_TEMPLATE, "SELECT entry, KillCredit1, KillCredit2, name, femaleName, subname, TitleAlt, IconName, RequiredExpansion, VignetteID, faction, npcflag, speed_walk, speed_run, scale, Classification, dmgschool, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, unit_flags3, family, trainer_class, type, VehicleId, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, ExperienceModifier, RacialLeader, movementId, WidgetSetID, WidgetSetUnitConditionID, RegenHealth, CreatureImmunitiesId, flags_extra, ScriptName, StringId FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ? OR 1 = ?"); PrepareStatement(WorldStatements.SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id = ?"); PrepareStatement(WorldStatements.SEL_GAMEOBJECT_NEAREST, "SELECT guid, id, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM gameobject WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? ORDER BY order_"); PrepareStatement(WorldStatements.SEL_CREATURE_NEAREST, "SELECT guid, id, position_x, position_y, position_z, map, (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) AS order_ FROM creature WHERE map = ? AND (POW(position_x - ?, 2) + POW(position_y - ?, 2) + POW(position_z - ?, 2)) <= ? ORDER BY order_"); diff --git a/Source/Framework/Util/CollectionExtensions.cs b/Source/Framework/Util/CollectionExtensions.cs index 5cfd81772..a8be08bd0 100644 --- a/Source/Framework/Util/CollectionExtensions.cs +++ b/Source/Framework/Util/CollectionExtensions.cs @@ -186,6 +186,13 @@ namespace System.Collections.Generic return blockValues; } + public static uint ToUInt(this BitSet array) + { + uint[] blockValues = new uint[array.Length / 32 + 1]; + array.CopyTo(blockValues, 0); + return blockValues[0]; + } + public static void Clear(this Array array) { Array.Clear(array, 0, array.Length); diff --git a/Source/Game/Chat/Commands/NPCCommands.cs b/Source/Game/Chat/Commands/NPCCommands.cs index 6e304ad5b..2cc83ca84 100644 --- a/Source/Game/Chat/Commands/NPCCommands.cs +++ b/Source/Game/Chat/Commands/NPCCommands.cs @@ -83,7 +83,10 @@ namespace Game.Chat uint faction = target.GetFaction(); ulong npcflags = (ulong)target.m_unitData.NpcFlags[1] << 32 | target.m_unitData.NpcFlags[0]; - ulong mechanicImmuneMask = cInfo.MechanicImmuneMask; + ulong mechanicImmuneMask = 0; + CreatureImmunities immunities = Global.SpellMgr.GetCreatureImmunities(cInfo.CreatureImmunitiesId); + if (immunities != null) + mechanicImmuneMask = immunities.Mechanic.ToUInt(); uint displayid = target.GetDisplayId(); uint nativeid = target.GetNativeDisplayId(); uint entry = target.GetEntry(); @@ -110,17 +113,17 @@ namespace Game.Chat handler.SendSysMessage(CypherStrings.NpcinfoUnitFieldFlags, (uint)target.m_unitData.Flags); foreach (UnitFlags value in Enum.GetValues(typeof(UnitFlags))) if (target.HasUnitFlag(value)) - handler.SendSysMessage("{0} (0x{1:X})", (UnitFlags)value, value); + handler.SendSysMessage("{0} (0x{1:X})", value, value); handler.SendSysMessage(CypherStrings.NpcinfoUnitFieldFlags2, (uint)target.m_unitData.Flags2); foreach (UnitFlags2 value in Enum.GetValues(typeof(UnitFlags2))) if (target.HasUnitFlag2(value)) - handler.SendSysMessage("{0} (0x{1:X})", (UnitFlags2)value, value); + handler.SendSysMessage("{0} (0x{1:X})", value, value); handler.SendSysMessage(CypherStrings.NpcinfoUnitFieldFlags3, (uint)target.m_unitData.Flags3); foreach (UnitFlags3 value in Enum.GetValues(typeof(UnitFlags3))) if (target.HasUnitFlag3(value)) - handler.SendSysMessage("{0} (0x{1:X})", (UnitFlags3)value, value); + handler.SendSysMessage("{0} (0x{1:X})", value, value); handler.SendSysMessage(CypherStrings.NpcinfoDynamicFlags, target.GetDynamicFlags()); handler.SendSysMessage(CypherStrings.CommandRawpawntimes, defRespawnDelayStr, curRespawnDelayStr); @@ -153,7 +156,7 @@ namespace Game.Chat if (npcflags.HasAnyFlag(value)) handler.SendSysMessage("{0} (0x{1:X})", (NPCFlags)value, value); - handler.SendSysMessage(CypherStrings.NpcinfoMechanicImmune, mechanicImmuneMask); + handler.SendSysMessage(CypherStrings.NpcinfoMechanicImmune, $"0x{mechanicImmuneMask:X}"); foreach (int value in Enum.GetValues(typeof(Mechanics))) if (Convert.ToBoolean(mechanicImmuneMask & (1ul << (value - 1)))) handler.SendSysMessage("{0} (0x{1:X})", (Mechanics)value, value); diff --git a/Source/Game/Entities/Creature/Creature.Fields.cs b/Source/Game/Entities/Creature/Creature.Fields.cs index 0256143d3..57ae6adb0 100644 --- a/Source/Game/Entities/Creature/Creature.Fields.cs +++ b/Source/Game/Entities/Creature/Creature.Fields.cs @@ -29,6 +29,7 @@ namespace Game.Entities bool _isMissingCanSwimFlagOutOfCombat; + int _creatureImmunitiesId; uint _gossipMenuId; uint? _trainerId; float _sparringHealthPct; diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index 7d0a0ad8a..b82b3b634 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -397,7 +397,7 @@ namespace Game.Entities LoadCreaturesAddon(); LoadCreaturesSparringHealth(); - LoadTemplateImmunities(); + LoadTemplateImmunities(cInfo.CreatureImmunitiesId); GetThreatManager().EvaluateSuppressed(); //We must update last scriptId or it looks like we reloaded a script, breaking some things such as gossip temporarily @@ -2177,38 +2177,47 @@ namespace Game.Entities ForcedDespawn((uint)msTimeToDespawn.TotalMilliseconds, forceRespawnTimer); } - public void LoadTemplateImmunities() + public void LoadTemplateImmunities(int creatureImmunitiesId) { // uint32 max used for "spell id", the immunity system will not perform SpellInfo checks against invalid spells // used so we know which immunities were loaded from template uint placeholderSpellId = uint.MaxValue; - // unapply template immunities (in case we're updating entry) - for (uint i = 0; i < (int)Mechanics.Max; ++i) - ApplySpellImmune(placeholderSpellId, SpellImmunity.Mechanic, i, false); - - for (var i = (int)SpellSchools.Normal; i < (int)SpellSchools.Max; ++i) - ApplySpellImmune(placeholderSpellId, SpellImmunity.School, 1u << i, false); - - // don't inherit immunities for hunter pets - if (GetOwnerGUID().IsPlayer() && IsHunterPet()) - return; - - ulong mechanicMask = GetCreatureTemplate().MechanicImmuneMask; - if (mechanicMask != 0) + void applyCreatureImmunities(CreatureImmunities immunities, bool apply) { - for (uint i = 0 + 1; i < (int)Mechanics.Max; ++i) - { - if ((mechanicMask & (1ul << ((int)i - 1))) != 0) - ApplySpellImmune(placeholderSpellId, SpellImmunity.Mechanic, i, true); - } + for (var i = 0; i < immunities.School.Count; ++i) + if (immunities.School[i]) + ApplySpellImmune(placeholderSpellId, SpellImmunity.School, 1u << i, apply); + + for (int i = 0; i < immunities.DispelType.Count; ++i) + if (immunities.DispelType[i]) + ApplySpellImmune(placeholderSpellId, SpellImmunity.Dispel, (uint)i, apply); + + for (var i = 0; i < immunities.Mechanic.Count; ++i) + if (immunities.Mechanic[i]) + ApplySpellImmune(placeholderSpellId, SpellImmunity.Mechanic, (uint)i, apply); + + foreach (SpellEffectName effect in immunities.Effect) + ApplySpellImmune(placeholderSpellId, SpellImmunity.Effect, effect, apply); + + foreach (AuraType aura in immunities.Aura) + ApplySpellImmune(placeholderSpellId, SpellImmunity.State, aura, apply); } - uint schoolMask = GetCreatureTemplate().SpellSchoolImmuneMask; - if (schoolMask != 0) - for (var i = (int)SpellSchools.Normal; i <= (int)SpellSchools.Max; ++i) - if ((schoolMask & (1 << i)) != 0) - ApplySpellImmune(placeholderSpellId, SpellImmunity.School, 1u << i, true); + // unapply template immunities (in case we're updating entry) + CreatureImmunities immunities = Global.SpellMgr.GetCreatureImmunities(_creatureImmunitiesId); + if (immunities != null) + applyCreatureImmunities(immunities, false); + + // apply new immunities + immunities = Global.SpellMgr.GetCreatureImmunities(creatureImmunitiesId); + if (immunities != null) + { + _creatureImmunitiesId = creatureImmunitiesId; + applyCreatureImmunities(immunities, true); + } + else + _creatureImmunitiesId = 0; } public override bool IsImmunedToSpellEffect(SpellInfo spellInfo, SpellEffectInfo spellEffectInfo, WorldObject caster, bool requireImmunityPurgesEffectAttribute = false) diff --git a/Source/Game/Entities/Creature/CreatureData.cs b/Source/Game/Entities/Creature/CreatureData.cs index afa72c955..ed167b757 100644 --- a/Source/Game/Entities/Creature/CreatureData.cs +++ b/Source/Game/Entities/Creature/CreatureData.cs @@ -55,8 +55,7 @@ namespace Game.Entities public int WidgetSetID; public int WidgetSetUnitConditionID; public bool RegenHealth; - public ulong MechanicImmuneMask; - public uint SpellSchoolImmuneMask; + public int CreatureImmunitiesId; public CreatureFlagsExtra FlagsExtra; public uint ScriptID; public string StringId; diff --git a/Source/Game/Entities/Pet.cs b/Source/Game/Entities/Pet.cs index 3a12d4ccd..2d3add007 100644 --- a/Source/Game/Entities/Pet.cs +++ b/Source/Game/Entities/Pet.cs @@ -370,7 +370,7 @@ namespace Game.Entities owner.DisablePetControlsOnMount(ReactStates.Passive, CommandStates.Follow); // must be after SetMinion (owner guid check) - LoadTemplateImmunities(); + LoadTemplateImmunities(0); m_loading = false; }); diff --git a/Source/Game/Entities/Unit/Unit.Movement.cs b/Source/Game/Entities/Unit/Unit.Movement.cs index a35760513..5f9213c86 100644 --- a/Source/Game/Entities/Unit/Unit.Movement.cs +++ b/Source/Game/Entities/Unit/Unit.Movement.cs @@ -562,8 +562,8 @@ namespace Game.Entities Creature creature1 = ToCreature(); if (creature1 != null) { - ulong immuneMask = creature1.GetCreatureTemplate().MechanicImmuneMask; - if (Convert.ToBoolean(immuneMask & (1 << ((int)Mechanics.Snare - 1))) || Convert.ToBoolean(immuneMask & (1 << ((int)Mechanics.Daze - 1)))) + CreatureImmunities immunities = Global.SpellMgr.GetCreatureImmunities(creature1.GetCreatureTemplate().CreatureImmunitiesId); + if (immunities != null && (immunities.Mechanic[(int)Mechanics.Snare] || immunities.Mechanic[(int)Mechanics.Daze])) break; } diff --git a/Source/Game/Globals/ObjectManager.cs b/Source/Game/Globals/ObjectManager.cs index 55cc44e28..d06a57b70 100644 --- a/Source/Game/Globals/ObjectManager.cs +++ b/Source/Game/Globals/ObjectManager.cs @@ -1840,11 +1840,10 @@ namespace Game creature.WidgetSetID = fields.Read(41); creature.WidgetSetUnitConditionID = fields.Read(42); creature.RegenHealth = fields.Read(43); - creature.MechanicImmuneMask = fields.Read(44); - creature.SpellSchoolImmuneMask = fields.Read(45); - creature.FlagsExtra = (CreatureFlagsExtra)fields.Read(46); - creature.ScriptID = GetScriptId(fields.Read(47)); - creature.StringId = fields.Read(48); + creature.CreatureImmunitiesId = fields.Read(44); + creature.FlagsExtra = (CreatureFlagsExtra)fields.Read(45); + creature.ScriptID = GetScriptId(fields.Read(46)); + creature.StringId = fields.Read(47); creatureTemplateStorage[entry] = creature; } diff --git a/Source/Game/Networking/Packets/SpellPackets.cs b/Source/Game/Networking/Packets/SpellPackets.cs index 6a74413d3..09d929d9c 100644 --- a/Source/Game/Networking/Packets/SpellPackets.cs +++ b/Source/Game/Networking/Packets/SpellPackets.cs @@ -1810,18 +1810,6 @@ namespace Game.Networking.Packets } } - public struct CreatureImmunities - { - public uint School; - public uint Value; - - public void Write(WorldPacket data) - { - data.WriteUInt32(School); - data.WriteUInt32(Value); - } - } - public struct SpellHealPrediction { public ObjectGuid BeaconGUID; @@ -1915,6 +1903,18 @@ namespace Game.Networking.Packets public List TargetPoints = new(); public CreatureImmunities Immunities; public SpellHealPrediction Predict; + + public struct CreatureImmunities + { + public uint School; + public uint Value; + + public void Write(WorldPacket data) + { + data.WriteUInt32(School); + data.WriteUInt32(Value); + } + } } public struct LearnedSpellInfo diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index c61517759..fb015dd7e 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -909,15 +909,15 @@ namespace Game.Spells if (!m_caster.IsUnit() || !m_caster.ToUnit().IsInRaidWith(targetedUnit)) targets.Add(m_targets.GetUnitTarget()); else - SearchAreaTargets(targets, spellEffectInfo, radius, targetedUnit, referer, targetType.GetObjectType(), targetType.GetCheckType(), spellEffectInfo.ImplicitTargetConditions); + SearchAreaTargets(targets, spellEffectInfo, radius, targetedUnit, referer, targetType.GetObjectType(), targetType.GetCheckType(), spellEffectInfo.ImplicitTargetConditions, WorldObjectSpellAreaTargetSearchReason.Area); } break; case Targets.UnitCasterAndSummons: targets.Add(m_caster); - SearchAreaTargets(targets, spellEffectInfo, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), spellEffectInfo.ImplicitTargetConditions); + SearchAreaTargets(targets, spellEffectInfo, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), spellEffectInfo.ImplicitTargetConditions, WorldObjectSpellAreaTargetSearchReason.Area); break; default: - SearchAreaTargets(targets, spellEffectInfo, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), spellEffectInfo.ImplicitTargetConditions); + SearchAreaTargets(targets, spellEffectInfo, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), spellEffectInfo.ImplicitTargetConditions, WorldObjectSpellAreaTargetSearchReason.Area); break; } @@ -1713,14 +1713,15 @@ namespace Game.Spells return searcher.GetTarget(); } - void SearchAreaTargets(List targets, SpellEffectInfo spellEffectInfo, float range, Position position, WorldObject referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, List condList) + void SearchAreaTargets(List targets, SpellEffectInfo spellEffectInfo, float range, Position position, WorldObject referer, + SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, List condList, WorldObjectSpellAreaTargetSearchReason searchReason) { var containerTypeMask = GetSearcherTypeMask(m_spellInfo, spellEffectInfo, objectType, condList); if (containerTypeMask == 0) return; float extraSearchRadius = range > 0.0f ? SharedConst.ExtraCellSearchRadius : 0.0f; - var check = new WorldObjectSpellAreaTargetCheck(range, position, m_caster, referer, m_spellInfo, selectionType, condList, objectType); + var check = new WorldObjectSpellAreaTargetCheck(range, position, m_caster, referer, m_spellInfo, selectionType, condList, objectType, searchReason); var searcher = new WorldObjectListSearcher(m_caster, targets, check, containerTypeMask); SearchTargets(searcher, containerTypeMask, m_caster, position, range + extraSearchRadius); } @@ -1764,7 +1765,7 @@ namespace Game.Spells WorldObject chainSource = m_spellInfo.HasAttribute(SpellAttr2.ChainFromCaster) ? m_caster : target; List tempTargets = new(); - SearchAreaTargets(tempTargets, spellEffectInfo, searchRadius, chainSource, m_caster, objectType, selectType, spellEffectInfo.ImplicitTargetConditions); + SearchAreaTargets(tempTargets, spellEffectInfo, searchRadius, chainSource, m_caster, objectType, selectType, spellEffectInfo.ImplicitTargetConditions, WorldObjectSpellAreaTargetSearchReason.Chain); tempTargets.Remove(target); // remove targets which are always invalid for chain spells @@ -3996,7 +3997,7 @@ namespace Game.Spells foreach (var targetInfo in m_UniqueTargetInfo) { if (targetInfo.EffectMask == 0) // No effect apply - all immuned add state - // possibly SPELL_MISS_IMMUNE2 for this?? + // possibly SPELL_MISS_IMMUNE2 for this?? targetInfo.MissCondition = SpellMissInfo.Immune2; if (targetInfo.MissCondition == SpellMissInfo.None || (targetInfo.MissCondition == SpellMissInfo.Block && !m_spellInfo.HasAttribute(SpellAttr3.CompletelyBlocked))) // Add only hits and partial blocked @@ -9155,21 +9156,24 @@ namespace Game.Spells { float _range; Position _position; + WorldObjectSpellAreaTargetSearchReason _searchReason; - public WorldObjectSpellAreaTargetCheck(float range, Position position, WorldObject caster, WorldObject referer, SpellInfo spellInfo, SpellTargetCheckTypes selectionType, List condList, SpellTargetObjectTypes objectType) + public WorldObjectSpellAreaTargetCheck(float range, Position position, WorldObject caster, WorldObject referer, SpellInfo spellInfo, SpellTargetCheckTypes selectionType, List condList, SpellTargetObjectTypes objectType, + WorldObjectSpellAreaTargetSearchReason searchReason = WorldObjectSpellAreaTargetSearchReason.Area) : base(caster, referer, spellInfo, selectionType, condList, objectType) { _range = range; _position = position; - + _searchReason = searchReason; } public override bool Invoke(WorldObject target) { - if (target.ToGameObject() != null) + GameObject gameObjectTarget = target.ToGameObject(); + if (gameObjectTarget != null) { // isInRange including the dimension of the GO - bool isInRange = target.ToGameObject().IsInRange(_position.GetPositionX(), _position.GetPositionY(), _position.GetPositionZ(), _range); + bool isInRange = gameObjectTarget.IsInRange(_position.GetPositionX(), _position.GetPositionY(), _position.GetPositionZ(), _range); if (!isInRange) return false; } @@ -9178,6 +9182,29 @@ namespace Game.Spells bool isInsideCylinder = target.IsWithinDist2d(_position, _range) && Math.Abs(target.GetPositionZ() - _position.GetPositionZ()) <= _range; if (!isInsideCylinder) return false; + + Creature creatureTarget = target.ToCreature(); + if (creatureTarget != null) + { + CreatureImmunities immunities = Global.SpellMgr.GetCreatureImmunities(creatureTarget.GetCreatureTemplate().CreatureImmunitiesId); + if (immunities != null) + { + switch (_searchReason) + { + case WorldObjectSpellAreaTargetSearchReason.Area: + if (immunities.ImmuneAoE) + return false; + break; + case WorldObjectSpellAreaTargetSearchReason.Chain: + if (immunities.ImmuneChain) + return false; + break; + default: + break; + } + + } + } } return base.Invoke(target); diff --git a/Source/Game/Spells/SpellInfo.cs b/Source/Game/Spells/SpellInfo.cs index af854e6ac..e917ab6fd 100644 --- a/Source/Game/Spells/SpellInfo.cs +++ b/Source/Game/Spells/SpellInfo.cs @@ -12,6 +12,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using Framework.Collections; namespace Game.Spells { @@ -1105,6 +1106,13 @@ namespace Game.Spells if (HasAttribute(SpellAttr5.NotOnPlayerControlledNpc) && unitTarget.IsControlledByPlayer()) return SpellCastResult.TargetIsPlayerControlled; + + if (HasAttribute(SpellAttr3.NotOnAoeImmune)) + { + CreatureImmunities immunities = Global.SpellMgr.GetCreatureImmunities(unitTarget.ToCreature().GetCreatureTemplate().CreatureImmunitiesId); + if (immunities != null && immunities.ImmuneAoE) + return SpellCastResult.BadTargets; + } } else if (HasAttribute(SpellAttr5.NotOnPlayer)) return SpellCastResult.TargetIsPlayer; @@ -2134,7 +2142,7 @@ namespace Game.Spells uint schoolImmunityMask = 0; uint applyHarmfulAuraImmunityMask = 0; ulong mechanicImmunityMask = 0; - uint dispelImmunity = 0; + uint dispelImmunityMask = 0; uint damageImmunityMask = 0; int miscVal = effect.MiscValue; @@ -2146,6 +2154,18 @@ namespace Game.Spells { case AuraType.MechanicImmunityMask: { + CreatureImmunities creatureImmunities = Global.SpellMgr.GetCreatureImmunities(miscVal); + if (creatureImmunities != null) + { + schoolImmunityMask |= creatureImmunities.School.ToUInt(); + dispelImmunityMask |= creatureImmunities.DispelType.ToUInt(); + mechanicImmunityMask |= creatureImmunities.Mechanic.ToUInt(); + foreach (SpellEffectName effectType in creatureImmunities.Effect) + immuneInfo.SpellEffectImmune.Add(effectType); + foreach (AuraType aura in creatureImmunities.Aura) + immuneInfo.AuraTypeImmune.Add(aura); + } + switch (miscVal) { case 96: // Free Friend, Uncontrollable Frenzy, Warlord's Presence @@ -2286,29 +2306,6 @@ namespace Game.Spells default: break; } - - if (immuneInfo.AuraTypeImmune.Empty()) - { - if (miscVal.HasAnyFlag(1 << 10)) - immuneInfo.AuraTypeImmune.Add(AuraType.ModStun); - if (miscVal.HasAnyFlag(1 << 1)) - immuneInfo.AuraTypeImmune.Add(AuraType.Transform); - - // These flag can be recognized wrong: - if (miscVal.HasAnyFlag(1 << 6)) - immuneInfo.AuraTypeImmune.Add(AuraType.ModDecreaseSpeed); - if (miscVal.HasAnyFlag(1 << 0)) - { - immuneInfo.AuraTypeImmune.Add(AuraType.ModRoot); - immuneInfo.AuraTypeImmune.Add(AuraType.ModRoot2); - } - if (miscVal.HasAnyFlag(1 << 2)) - immuneInfo.AuraTypeImmune.Add(AuraType.ModConfuse); - if (miscVal.HasAnyFlag(1 << 9)) - immuneInfo.AuraTypeImmune.Add(AuraType.ModFear); - if (miscVal.HasAnyFlag(1 << 7)) - immuneInfo.AuraTypeImmune.Add(AuraType.ModDisarm); - } break; } case AuraType.MechanicImmunity: @@ -2370,7 +2367,7 @@ namespace Game.Spells } case AuraType.DispelImmunity: { - dispelImmunity = (uint)miscVal; + dispelImmunityMask = 1u << miscVal; break; } default: @@ -2380,7 +2377,7 @@ namespace Game.Spells immuneInfo.SchoolImmuneMask = schoolImmunityMask; immuneInfo.ApplyHarmfulAuraImmuneMask = applyHarmfulAuraImmunityMask; immuneInfo.MechanicImmuneMask = mechanicImmunityMask; - immuneInfo.DispelImmune = dispelImmunity; + immuneInfo.DispelImmuneMask = dispelImmunityMask; immuneInfo.DamageSchoolMask = damageImmunityMask; _allowedMechanicMask |= immuneInfo.MechanicImmuneMask; @@ -2513,17 +2510,20 @@ namespace Game.Spells } } - uint dispelImmunity = immuneInfo.DispelImmune; + uint dispelImmunity = immuneInfo.DispelImmuneMask; if (dispelImmunity != 0) { - target.ApplySpellImmune(Id, SpellImmunity.Dispel, dispelImmunity, apply); + for (int i = 0; i < (int)DispelType.Max; ++i) + if ((dispelImmunity & (1u << i)) != 0) + target.ApplySpellImmune(Id, SpellImmunity.Dispel, (uint)i, apply); if (apply && HasAttribute(SpellAttr1.ImmunityPurgesEffect)) { target.RemoveAppliedAuras(aurApp => { SpellInfo spellInfo = aurApp.GetBase().GetSpellInfo(); - if ((uint)spellInfo.Dispel == dispelImmunity) + uint dispelMask = spellInfo.GetDispelMask(); + if ((dispelMask & dispelImmunity) == dispelMask) return true; return false; @@ -2582,7 +2582,7 @@ namespace Game.Spells if ((mechanicImmunity & (1ul << (int)auraSpellInfo.Mechanic)) != 0) return true; - uint dispelImmunity = immuneInfo.DispelImmune; + uint dispelImmunity = immuneInfo.DispelImmuneMask; if (dispelImmunity != 0) if ((uint)auraSpellInfo.Dispel == dispelImmunity) return true; @@ -3883,7 +3883,7 @@ namespace Game.Spells public void _UnloadImplicitTargetConditionLists() { // find the same instances of ConditionList and delete them. - foreach (SpellEffectInfo effect in _effects) + foreach (SpellEffectInfo effect in _effects) effect.ImplicitTargetConditions = null; } @@ -5294,7 +5294,7 @@ namespace Game.Spells public uint SchoolImmuneMask; public uint ApplyHarmfulAuraImmuneMask; public ulong MechanicImmuneMask; - public uint DispelImmune; + public uint DispelImmuneMask; public uint DamageSchoolMask; public List AuraTypeImmune = new(); diff --git a/Source/Game/Spells/SpellManager.cs b/Source/Game/Spells/SpellManager.cs index d703c3c7c..ea56b63c9 100644 --- a/Source/Game/Spells/SpellManager.cs +++ b/Source/Game/Spells/SpellManager.cs @@ -1,6 +1,7 @@ // Copyright (c) CypherCore All rights reserved. // Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information. +using Framework.Collections; using Framework.Constants; using Framework.Database; using Framework.Dynamic; @@ -12,6 +13,7 @@ using Game.Miscellaneous; using Game.Movement; using Game.Spells; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -603,6 +605,11 @@ namespace Game.Entities return mSpellAreaForAreaMap.LookupByKey(area_id); } + public CreatureImmunities GetCreatureImmunities(int creatureImmunitiesId) + { + return mCreatureImmunities.LookupByKey(creatureImmunitiesId); + } + public SpellInfo GetSpellInfo(uint spellId, Difficulty difficulty) { var list = mSpellInfoMap.LookupByKey(spellId); @@ -4502,6 +4509,52 @@ namespace Game.Entities { uint oldMSTime = Time.GetMSTime(); + mCreatureImmunities.Clear(); + + // 0 1 2 3 4 5 6 7 + SQLResult result = DB.World.Query("SELECT ID, SchoolMask, DispelTypeMask, MechanicsMask, Effects, Auras, ImmuneAoE, ImmuneChain FROM creature_immunities"); + if (!result.IsEmpty()) + { + do + { + int id = result.Read(0); + byte school = result.Read(1); + ushort dispelType = result.Read(2); + ulong mechanics = result.Read(3); + + CreatureImmunities immunities = mCreatureImmunities[id]; + immunities.School = new BitSet(new uint[] { school }); + immunities.DispelType = new BitSet(new uint[] { dispelType }); + immunities.Mechanic = new BitSet(mechanics); + immunities.ImmuneAoE = result.Read(6); + immunities.ImmuneChain = result.Read(7); + + if (immunities.School.ToUInt() != school) + Log.outError(LogFilter.Sql, $"Invalid value in `SchoolMask` {school} for creature immunities {id}, truncated"); + if (immunities.DispelType.ToUInt() != dispelType) + Log.outError(LogFilter.Sql, $"Invalid value in `DispelTypeMask` {dispelType} for creature immunities {id}, truncated"); + if (immunities.Mechanic.ToUInt() != mechanics) + Log.outError(LogFilter.Sql, $"Invalid value in `MechanicsMask` {mechanics} for creature immunities {id}, truncated"); + + foreach (string token in new StringArray(result.Read(4), ',')) + { + if (uint.TryParse(token, out uint effect) && effect != 0 && effect < (int)SpellEffectName.TotalSpellEffects) + immunities.Effect.Add((SpellEffectName)effect); + else + Log.outError(LogFilter.Sql, $"Invalid effect type in `Effects` {token} for creature immunities {id}, skipped"); + } + + foreach (string token in new StringArray(result.Read(5), ',')) + { + if (uint.TryParse(token, out uint aura) && aura != 0 && aura < (int)AuraType.Total) + immunities.Aura.Add((AuraType)aura); + else + Log.outError(LogFilter.Sql, $"Invalid aura type in `Auras` {token} for creature immunities {id}, skipped"); + } + } + while (result.NextRow()); + } + foreach (SpellInfo spellInfo in mSpellInfoMap.Values) { if (spellInfo == null) @@ -4811,6 +4864,7 @@ namespace Game.Entities MultiMap mSpellSameEffectStack = new(); List mServersideSpellNames = new(); Dictionary<(uint id, Difficulty difficulty), SpellProcEntry> mSpellProcMap = new(); + Dictionary mCreatureImmunities = new(); Dictionary mSpellThreatMap = new(); Dictionary mSpellPetAuraMap = new(); MultiMap<(SpellLinkedType, uint), int> mSpellLinkedMap = new(); @@ -4920,7 +4974,6 @@ namespace Game.Entities } } - public class PetDefaultSpellsEntry { public uint[] spellid = new uint[4]; @@ -5097,4 +5150,15 @@ namespace Game.Entities public float target_Z; public float target_Orientation; } + + public class CreatureImmunities + { + public BitSet School = new((int)SpellSchools.Max); + public BitSet DispelType = new((int)Framework.Constants.DispelType.Max); + public BitSet Mechanic = new((int)Mechanics.Max); + public List Effect = new(); + public List Aura = new(); + public bool ImmuneAoE; // NYI + public bool ImmuneChain; // NYI + } }