/* * Copyright (C) 2012-2020 CypherCore * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using Framework.Constants; using Framework.Dynamic; using Game.BattleGrounds; using Game.BattlePets; using Game.Combat; using Game.DataStorage; using Game.Entities; using Game.Garrisons; using Game.Groups; using Game.Guilds; using Game.Loots; using Game.Maps; using Game.Movement; using Game.Networking.Packets; using System; using System.Collections.Generic; using System.Linq; using System.Numerics; namespace Game.Spells { public partial class Spell { [SpellEffectHandler(SpellEffectName.None)] [SpellEffectHandler(SpellEffectName.Portal)] [SpellEffectHandler(SpellEffectName.BindSight)] [SpellEffectHandler(SpellEffectName.CallPet)] [SpellEffectHandler(SpellEffectName.PortalTeleport)] [SpellEffectHandler(SpellEffectName.Dodge)] [SpellEffectHandler(SpellEffectName.Evade)] [SpellEffectHandler(SpellEffectName.Weapon)] [SpellEffectHandler(SpellEffectName.Defense)] [SpellEffectHandler(SpellEffectName.SpellDefense)] [SpellEffectHandler(SpellEffectName.Language)] [SpellEffectHandler(SpellEffectName.Spawn)] [SpellEffectHandler(SpellEffectName.Stealth)] [SpellEffectHandler(SpellEffectName.Detect)] [SpellEffectHandler(SpellEffectName.ForceCriticalHit)] [SpellEffectHandler(SpellEffectName.Attack)] [SpellEffectHandler(SpellEffectName.ThreatAll)] [SpellEffectHandler(SpellEffectName.Effect112)] [SpellEffectHandler(SpellEffectName.TeleportGraveyard)] [SpellEffectHandler(SpellEffectName.Effect122)] [SpellEffectHandler(SpellEffectName.Effect175)] [SpellEffectHandler(SpellEffectName.Effect178)] void EffectUnused() { } void EffectResurrectNew() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (corpseTarget == null && unitTarget == null) return; Player player = null; if (corpseTarget) player = Global.ObjAccessor.FindPlayer(corpseTarget.GetOwnerGUID()); else if (unitTarget) player = unitTarget.ToPlayer(); if (player == null || player.IsAlive() || !player.IsInWorld) return; if (player.IsResurrectRequested()) // already have one active request return; int health = damage; int mana = effectInfo.MiscValue; ExecuteLogEffectResurrect(effectInfo.Effect, player); player.SetResurrectRequestData(m_caster, (uint)health, (uint)mana, 0); SendResurrectRequest(player); } [SpellEffectHandler(SpellEffectName.Instakill)] void EffectInstaKill() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsAlive()) return; if (unitTarget.IsTypeId(TypeId.Player)) if (unitTarget.ToPlayer().GetCommandStatus(PlayerCommandStates.God)) return; if (m_caster == unitTarget) // prevent interrupt message Finish(); SpellInstakillLog data = new(); data.Target = unitTarget.GetGUID(); data.Caster = m_caster.GetGUID(); data.SpellID = m_spellInfo.Id; m_caster.SendMessageToSet(data, true); Unit.Kill(GetUnitCasterForEffectHandlers(), unitTarget, false); } [SpellEffectHandler(SpellEffectName.EnvironmentalDamage)] void EffectEnvironmentalDMG() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsAlive()) return; // CalcAbsorbResist already in Player::EnvironmentalDamage if (unitTarget.IsTypeId(TypeId.Player)) unitTarget.ToPlayer().EnvironmentalDamage(EnviromentalDamage.Fire, (uint)damage); else { Unit unitCaster = GetUnitCasterForEffectHandlers(); DamageInfo damageInfo = new(unitCaster, unitTarget, (uint)damage, m_spellInfo, m_spellInfo.GetSchoolMask(), DamageEffectType.SpellDirect, WeaponAttackType.BaseAttack); Unit.CalcAbsorbResist(damageInfo); SpellNonMeleeDamage log = new(unitCaster, unitTarget, m_spellInfo, m_SpellVisual, m_spellInfo.GetSchoolMask(), m_castId); log.damage = damageInfo.GetDamage(); log.originalDamage = (uint)damage; log.absorb = damageInfo.GetAbsorb(); log.resist = damageInfo.GetResist(); if (unitCaster != null) unitCaster.SendSpellNonMeleeDamageLog(log); } } [SpellEffectHandler(SpellEffectName.SchoolDamage)] void EffectSchoolDmg() { if (effectHandleMode != SpellEffectHandleMode.LaunchTarget) return; if (unitTarget != null && unitTarget.IsAlive()) { bool apply_direct_bonus = true; // Meteor like spells (divided damage to targets) if (m_spellInfo.HasAttribute(SpellCustomAttributes.ShareDamage)) { long count = GetUnitTargetCountForEffect(effectInfo.EffectIndex); // divide to all targets if (count != 0) damage /= (int)count; } Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster != null && apply_direct_bonus) { uint bonus = unitCaster.SpellDamageBonusDone(unitTarget, m_spellInfo, (uint)damage, DamageEffectType.SpellDirect, effectInfo); damage = (int)(bonus + (bonus * _variance)); damage = (int)unitTarget.SpellDamageBonusTaken(unitCaster, m_spellInfo, (uint)damage, DamageEffectType.SpellDirect); } m_damage += damage; } } [SpellEffectHandler(SpellEffectName.Dummy)] void EffectDummy() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null && gameObjTarget == null && itemTarget == null && corpseTarget == null) return; // pet auras if (m_caster.GetTypeId() == TypeId.Player) { PetAura petSpell = Global.SpellMgr.GetPetAura(m_spellInfo.Id, (byte)effectInfo.EffectIndex); if (petSpell != null) { m_caster.ToPlayer().AddPetAura(petSpell); return; } } // normal DB scripted effect Log.outDebug(LogFilter.Spells, "Spell ScriptStart spellid {0} in EffectDummy({1})", m_spellInfo.Id, effectInfo.EffectIndex); m_caster.GetMap().ScriptsStart(ScriptsType.Spell, (uint)((int)m_spellInfo.Id | (int)(effectInfo.EffectIndex << 24)), m_caster, unitTarget); } [SpellEffectHandler(SpellEffectName.TriggerSpell)] [SpellEffectHandler(SpellEffectName.TriggerSpellWithValue)] void EffectTriggerSpell() { if (effectHandleMode != SpellEffectHandleMode.LaunchTarget && effectHandleMode != SpellEffectHandleMode.Launch) return; uint triggered_spell_id = effectInfo.TriggerSpell; // @todo move those to spell scripts if (effectInfo.Effect == SpellEffectName.TriggerSpell && effectHandleMode == SpellEffectHandleMode.LaunchTarget) { // special cases switch (triggered_spell_id) { // Demonic Empowerment -- succubus case 54437: { unitTarget.RemoveMovementImpairingAuras(true); unitTarget.RemoveAurasByType(AuraType.ModStalked); unitTarget.RemoveAurasByType(AuraType.ModStun); // Cast Lesser Invisibility unitTarget.CastSpell(unitTarget, 7870, new CastSpellExtraArgs(this)); return; } // Brittle Armor - (need add max stack of 24575 Brittle Armor) case 29284: { // Brittle Armor SpellInfo spell = Global.SpellMgr.GetSpellInfo(24575, GetCastDifficulty()); if (spell == null) return; for (uint j = 0; j < spell.StackAmount; ++j) m_caster.CastSpell(unitTarget, spell.Id, new CastSpellExtraArgs(this)); return; } // Mercurial Shield - (need add max stack of 26464 Mercurial Shield) case 29286: { // Mercurial Shield SpellInfo spell = Global.SpellMgr.GetSpellInfo(26464, GetCastDifficulty()); if (spell == null) return; for (uint j = 0; j < spell.StackAmount; ++j) m_caster.CastSpell(unitTarget, spell.Id, new CastSpellExtraArgs(this)); return; } } } if (triggered_spell_id == 0) { Log.outWarn(LogFilter.Spells, $"Spell::EffectTriggerSpell: Spell {m_spellInfo.Id} [EffectIndex: {effectInfo.EffectIndex}] does not have triggered spell."); return; } // normal case SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(triggered_spell_id, GetCastDifficulty()); if (spellInfo == null) { Log.outDebug(LogFilter.Spells, "Spell.EffectTriggerSpell spell {0} tried to trigger unknown spell {1}", m_spellInfo.Id, triggered_spell_id); return; } SpellCastTargets targets = new(); if (effectHandleMode == SpellEffectHandleMode.LaunchTarget) { if (!spellInfo.NeedsToBeTriggeredByCaster(m_spellInfo)) return; targets.SetUnitTarget(unitTarget); } else //if (effectHandleMode == SpellEffectHandleMode.Launch) { if (spellInfo.NeedsToBeTriggeredByCaster(m_spellInfo) && effectInfo.GetProvidedTargetMask().HasAnyFlag(SpellCastTargetFlags.UnitMask)) return; if (spellInfo.GetExplicitTargetMask().HasAnyFlag(SpellCastTargetFlags.DestLocation)) targets.SetDst(m_targets); Unit target = m_targets.GetUnitTarget(); if (target != null) targets.SetUnitTarget(target); else { Unit unit = m_caster.ToUnit(); if (unit != null) targets.SetUnitTarget(unit); else { GameObject go = m_caster.ToGameObject(); if (go != null) targets.SetGOTarget(go); } } } TimeSpan delay = TimeSpan.Zero; if (effectInfo.Effect == SpellEffectName.TriggerSpell) delay = TimeSpan.FromMilliseconds(effectInfo.MiscValue); CastSpellExtraArgs args = new(TriggerCastFlags.FullMask); args.SetOriginalCaster(m_originalCasterGUID); args.SetTriggeringSpell(this); // set basepoints for trigger with value effect if (effectInfo.Effect == SpellEffectName.TriggerSpellWithValue) for (int i = 0; i < SpellConst.MaxEffects; ++i) args.AddSpellMod(SpellValueMod.BasePoint0 + i, damage); m_caster.m_Events.AddEventAtOffset(() => { // original caster guid only for GO cast m_caster.CastSpell(targets, triggered_spell_id, args); }, delay); } [SpellEffectHandler(SpellEffectName.TriggerMissile)] [SpellEffectHandler(SpellEffectName.TriggerMissileSpellWithValue)] void EffectTriggerMissileSpell() { if (effectHandleMode != SpellEffectHandleMode.HitTarget && effectHandleMode != SpellEffectHandleMode.Hit) return; uint triggered_spell_id = effectInfo.TriggerSpell; if (triggered_spell_id == 0) { Log.outWarn(LogFilter.Spells, $"Spell::EffectTriggerMissileSpell: Spell {m_spellInfo.Id} [EffectIndex: {effectInfo.EffectIndex}] does not have triggered spell."); return; } // normal case SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(triggered_spell_id, GetCastDifficulty()); if (spellInfo == null) { Log.outDebug(LogFilter.Spells, "Spell.EffectTriggerMissileSpell spell {0} tried to trigger unknown spell {1}", m_spellInfo.Id, triggered_spell_id); return; } SpellCastTargets targets = new(); if (effectHandleMode == SpellEffectHandleMode.HitTarget) { if (!spellInfo.NeedsToBeTriggeredByCaster(m_spellInfo)) return; targets.SetUnitTarget(unitTarget); } else //if (effectHandleMode == SpellEffectHandleMode.Hit) { if (spellInfo.NeedsToBeTriggeredByCaster(m_spellInfo) && effectInfo.GetProvidedTargetMask().HasAnyFlag(SpellCastTargetFlags.UnitMask)) return; if (spellInfo.GetExplicitTargetMask().HasAnyFlag(SpellCastTargetFlags.DestLocation)) targets.SetDst(m_targets); Unit unit = m_caster.ToUnit(); if (unit != null) targets.SetUnitTarget(unit); else { GameObject go = m_caster.ToGameObject(); if (go != null) targets.SetGOTarget(go); } } CastSpellExtraArgs args = new(TriggerCastFlags.FullMask); args.SetOriginalCaster(m_originalCasterGUID); args.SetTriggeringSpell(this); // set basepoints for trigger with value effect if (effectInfo.Effect == SpellEffectName.TriggerMissileSpellWithValue) for (int i = 0; i < SpellConst.MaxEffects; ++i) args.AddSpellMod(SpellValueMod.BasePoint0 + i, damage); // original caster guid only for GO cast m_caster.CastSpell(targets, spellInfo.Id, args); } [SpellEffectHandler(SpellEffectName.ForceCast)] [SpellEffectHandler(SpellEffectName.ForceCastWithValue)] [SpellEffectHandler(SpellEffectName.ForceCast2)] void EffectForceCast() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null) return; uint triggered_spell_id = effectInfo.TriggerSpell; if (triggered_spell_id == 0) { Log.outWarn(LogFilter.Spells, $"Spell::EffectForceCast: Spell {m_spellInfo.Id} [EffectIndex: {effectInfo.EffectIndex}] does not have triggered spell."); return; } // normal case SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(triggered_spell_id, GetCastDifficulty()); if (spellInfo == null) { Log.outError(LogFilter.Spells, "Spell.EffectForceCast of spell {0}: triggering unknown spell id {1}", m_spellInfo.Id, triggered_spell_id); return; } if (effectInfo.Effect == SpellEffectName.ForceCast && damage != 0) { switch (m_spellInfo.Id) { case 52588: // Skeletal Gryphon Escape case 48598: // Ride Flamebringer Cue unitTarget.RemoveAura((uint)damage); break; case 52463: // Hide In Mine Car case 52349: // Overtake { CastSpellExtraArgs args1 = new(TriggerCastFlags.FullMask); args1.SetOriginalCaster(m_originalCasterGUID); args1.SetTriggeringSpell(this); args1.AddSpellMod(SpellValueMod.BasePoint0, damage); unitTarget.CastSpell(unitTarget, spellInfo.Id, args1); return; } } } switch (spellInfo.Id) { case 72298: // Malleable Goo Summon unitTarget.CastSpell(unitTarget, spellInfo.Id, new CastSpellExtraArgs(TriggerCastFlags.FullMask) .SetOriginalCaster(m_originalCasterGUID) .SetTriggeringSpell(this)); return; } CastSpellExtraArgs args = new(TriggerCastFlags.FullMask); args.SetTriggeringSpell(this); // set basepoints for trigger with value effect if (effectInfo.Effect == SpellEffectName.ForceCastWithValue) for (int i = 0; i < SpellConst.MaxEffects; ++i) args.AddSpellMod(SpellValueMod.BasePoint0 + i, damage); unitTarget.CastSpell(m_caster, spellInfo.Id, args); } [SpellEffectHandler(SpellEffectName.TriggerSpell2)] void EffectTriggerRitualOfSummoning() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; uint triggered_spell_id = effectInfo.TriggerSpell; if (triggered_spell_id == 0) { Log.outWarn(LogFilter.Spells, $"Spell::EffectTriggerRitualOfSummoning: Spell {m_spellInfo.Id} [EffectIndex: {effectInfo.EffectIndex}] does not have triggered spell."); return; } SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(triggered_spell_id, GetCastDifficulty()); if (spellInfo == null) { Log.outError(LogFilter.Spells, $"EffectTriggerRitualOfSummoning of spell {m_spellInfo.Id}: triggering unknown spell id {triggered_spell_id}"); return; } Finish(); m_caster.CastSpell((Unit)null, spellInfo.Id, new CastSpellExtraArgs().SetTriggeringSpell(this)); } void CalculateJumpSpeeds(SpellEffectInfo effInfo, float dist, out float speedXY, out float speedZ) { Unit unitCaster = GetUnitCasterForEffectHandlers(); float runSpeed = unitCaster.IsControlledByPlayer() ? SharedConst.playerBaseMoveSpeed[(int)UnitMoveType.Run] : SharedConst.baseMoveSpeed[(int)UnitMoveType.Run]; Creature creature = unitCaster.ToCreature(); if (creature != null) runSpeed *= creature.GetCreatureTemplate().SpeedRun; float multiplier = effInfo.Amplitude; if (multiplier <= 0.0f) multiplier = 1.0f; speedXY = Math.Min(runSpeed * 3.0f * multiplier, Math.Max(28.0f, unitCaster.GetSpeed(UnitMoveType.Run) * 4.0f)); float duration = dist / speedXY; float durationSqr = duration * duration; float minHeight = effInfo.MiscValue != 0 ? effInfo.MiscValue / 10.0f : 0.5f; // Lower bound is blizzlike float maxHeight = effInfo.MiscValueB != 0 ? effInfo.MiscValueB / 10.0f : 1000.0f; // Upper bound is unknown float height; if (durationSqr < minHeight * 8 / MotionMaster.gravity) height = minHeight; else if (durationSqr > maxHeight * 8 / MotionMaster.gravity) height = maxHeight; else height = (float)(MotionMaster.gravity * durationSqr / 8); speedZ = MathF.Sqrt((float)(2 * MotionMaster.gravity * height)); } [SpellEffectHandler(SpellEffectName.Jump)] void EffectJump() { if (effectHandleMode != SpellEffectHandleMode.LaunchTarget) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; if (unitCaster.IsInFlight()) return; if (unitTarget == null) return; float speedXY, speedZ; CalculateJumpSpeeds(effectInfo, unitCaster.GetExactDist2d(unitTarget), out speedXY, out speedZ); JumpArrivalCastArgs arrivalCast = new(); arrivalCast.SpellId = effectInfo.TriggerSpell; arrivalCast.Target = unitTarget.GetGUID(); unitCaster.GetMotionMaster().MoveJump(unitTarget, speedXY, speedZ, EventId.Jump, false, arrivalCast); } [SpellEffectHandler(SpellEffectName.JumpDest)] void EffectJumpDest() { if (effectHandleMode != SpellEffectHandleMode.Launch) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; if (unitCaster.IsInFlight()) return; if (!m_targets.HasDst()) return; float speedXY, speedZ; CalculateJumpSpeeds(effectInfo, unitCaster.GetExactDist2d(destTarget), out speedXY, out speedZ); JumpArrivalCastArgs arrivalCast = new(); arrivalCast.SpellId = effectInfo.TriggerSpell; unitCaster.GetMotionMaster().MoveJump(destTarget, speedXY, speedZ, EventId.Jump, !m_targets.GetObjectTargetGUID().IsEmpty(), arrivalCast); } [SpellEffectHandler(SpellEffectName.TeleportUnits)] void EffectTeleportUnits() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || unitTarget.IsInFlight()) return; // If not exist data for dest location - return if (!m_targets.HasDst()) { Log.outError(LogFilter.Spells, "Spell.EffectTeleportUnits - does not have a destination for spellId {0}.", m_spellInfo.Id); return; } // Init dest coordinates WorldLocation targetDest = new(destTarget); if (targetDest.GetMapId() == 0xFFFFFFFF) targetDest.SetMapId(unitTarget.GetMapId()); if (targetDest.GetOrientation() == 0 && m_targets.GetUnitTarget()) targetDest.SetOrientation(m_targets.GetUnitTarget().GetOrientation()); Player player = unitTarget.ToPlayer(); if (player != null) { // Custom loading screen uint customLoadingScreenId = (uint)effectInfo.MiscValue; if (customLoadingScreenId != 0) player.SendPacket(new CustomLoadScreen(m_spellInfo.Id, customLoadingScreenId)); } if (targetDest.GetMapId() == unitTarget.GetMapId()) unitTarget.NearTeleportTo(targetDest, unitTarget == m_caster); else if (player != null) player.TeleportTo(targetDest, unitTarget == m_caster ? TeleportToOptions.Spell : 0); else { Log.outError(LogFilter.Spells, "Spell.EffectTeleportUnits - spellId {0} attempted to teleport creature to a different map.", m_spellInfo.Id); return; } } [SpellEffectHandler(SpellEffectName.TeleportWithSpellVisualKitLoadingScreen)] void EffectTeleportUnitsWithVisualLoadingScreen() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget) return; // If not exist data for dest location - return if (!m_targets.HasDst()) { Log.outError(LogFilter.Spells, $"Spell::EffectTeleportUnitsWithVisualLoadingScreen - does not have a destination for spellId {m_spellInfo.Id}."); return; } // Init dest coordinates WorldLocation targetDest = new(destTarget); if (targetDest.GetMapId() == 0xFFFFFFFF) targetDest.SetMapId(unitTarget.GetMapId()); if (targetDest.GetOrientation() == 0 && m_targets.GetUnitTarget()) targetDest.SetOrientation(m_targets.GetUnitTarget().GetOrientation()); if (effectInfo.MiscValueB != 0) { Player playerTarget = unitTarget.ToPlayer(); if (playerTarget != null) playerTarget.SendPacket(new SpellVisualLoadScreen(effectInfo.MiscValueB, effectInfo.MiscValue)); } unitTarget.m_Events.AddEventAtOffset(new DelayedSpellTeleportEvent(unitTarget, targetDest, unitTarget == m_caster ? TeleportToOptions.Spell : 0, m_spellInfo.Id), TimeSpan.FromMilliseconds(effectInfo.MiscValue)); } [SpellEffectHandler(SpellEffectName.ApplyAura)] [SpellEffectHandler(SpellEffectName.ApplyAuraOnPet)] void EffectApplyAura() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (spellAura == null || unitTarget == null) return; // register target/effect on aura AuraApplication aurApp = spellAura.GetApplicationOfTarget(unitTarget.GetGUID()); if (aurApp == null) aurApp = unitTarget._CreateAuraApplication(spellAura, 1u << (int)effectInfo.EffectIndex); else aurApp.UpdateApplyEffectMask(aurApp.GetEffectsToApply() | 1u << (int)effectInfo.EffectIndex); } [SpellEffectHandler(SpellEffectName.UnlearnSpecialization)] void EffectUnlearnSpecialization() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; Player player = unitTarget.ToPlayer(); uint spellToUnlearn = effectInfo.TriggerSpell; player.RemoveSpell(spellToUnlearn); Log.outDebug(LogFilter.Spells, "Spell: Player {0} has unlearned spell {1} from NpcGUID: {2}", player.GetGUID().ToString(), spellToUnlearn, m_caster.GetGUID().ToString()); } [SpellEffectHandler(SpellEffectName.PowerDrain)] void EffectPowerDrain() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (effectInfo.MiscValue < 0 || effectInfo.MiscValue >= (byte)PowerType.Max) return; PowerType powerType = (PowerType)effectInfo.MiscValue; if (unitTarget == null || !unitTarget.IsAlive() || unitTarget.GetPowerType() != powerType || damage < 0) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); // add spell damage bonus if (unitCaster != null) { uint bonus = unitCaster.SpellDamageBonusDone(unitTarget, m_spellInfo, (uint)damage, DamageEffectType.SpellDirect, effectInfo); damage = (int)(bonus + (bonus * _variance)); damage = (int)unitTarget.SpellDamageBonusTaken(unitCaster, m_spellInfo, (uint)damage, DamageEffectType.SpellDirect); } int newDamage = -(unitTarget.ModifyPower(powerType, -damage)); // Don't restore from self drain float gainMultiplier = 0.0f; if (unitCaster != null && unitCaster != unitTarget) { gainMultiplier = effectInfo.CalcValueMultiplier(unitCaster, this); int gain = (int)(newDamage * gainMultiplier); unitCaster.EnergizeBySpell(unitCaster, m_spellInfo, gain, powerType); } ExecuteLogEffectTakeTargetPower(effectInfo.Effect, unitTarget, powerType, (uint)newDamage, gainMultiplier); } [SpellEffectHandler(SpellEffectName.SendEvent)] void EffectSendEvent() { // we do not handle a flag dropping or clicking on flag in Battlegroundby sendevent system if (effectHandleMode != SpellEffectHandleMode.HitTarget && effectHandleMode != SpellEffectHandleMode.Hit) return; WorldObject target = null; // call events for object target if present if (effectHandleMode == SpellEffectHandleMode.HitTarget) { if (unitTarget != null) target = unitTarget; else if (gameObjTarget != null) target = gameObjTarget; else if (corpseTarget != null) target = corpseTarget; } else // if (effectHandleMode == SpellEffectHandleMode.Hit) { // let's prevent executing effect handler twice in case when spell effect is capable of targeting an object // this check was requested by scripters, but it has some downsides: // now it's impossible to script (using sEventScripts) a cast which misses all targets // or to have an ability to script the moment spell hits dest (in a case when there are object targets present) if (effectInfo.GetProvidedTargetMask().HasAnyFlag(SpellCastTargetFlags.UnitMask | SpellCastTargetFlags.GameobjectMask)) return; // some spells have no target entries in dbc and they use focus target if (focusObject != null) target = focusObject; // @todo there should be a possibility to pass dest target to event script } Log.outDebug(LogFilter.Spells, "Spell ScriptStart {0} for spellid {1} in EffectSendEvent ", effectInfo.MiscValue, m_spellInfo.Id); ZoneScript zoneScript = m_caster.GetZoneScript(); InstanceScript instanceScript = m_caster.GetInstanceScript(); if (zoneScript != null) zoneScript.ProcessEvent(target, (uint)effectInfo.MiscValue, m_caster); else if (instanceScript != null) // needed in case Player is the caster instanceScript.ProcessEvent(target, (uint)effectInfo.MiscValue, m_caster); m_caster.GetMap().ScriptsStart(ScriptsType.Event, (uint)effectInfo.MiscValue, m_caster, target); } [SpellEffectHandler(SpellEffectName.PowerBurn)] void EffectPowerBurn() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (effectInfo.MiscValue < 0 || effectInfo.MiscValue >= (int)PowerType.Max) return; PowerType powerType = (PowerType)effectInfo.MiscValue; if (unitTarget == null || !unitTarget.IsAlive() || unitTarget.GetPowerType() != powerType || damage < 0) return; int newDamage = -(unitTarget.ModifyPower(powerType, -damage)); // NO - Not a typo - EffectPowerBurn uses effect value multiplier - not effect damage multiplier float dmgMultiplier = effectInfo.CalcValueMultiplier(GetUnitCasterForEffectHandlers(), this); // add log data before multiplication (need power amount, not damage) ExecuteLogEffectTakeTargetPower(effectInfo.Effect, unitTarget, powerType, (uint)newDamage, 0.0f); newDamage = (int)(newDamage * dmgMultiplier); m_damage += newDamage; } [SpellEffectHandler(SpellEffectName.Heal)] void EffectHeal() { if (effectHandleMode != SpellEffectHandleMode.LaunchTarget) return; if (unitTarget == null || !unitTarget.IsAlive() || damage < 0) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); // Skip if m_originalCaster not available if (unitCaster == null) return; int addhealth = damage; // Vessel of the Naaru (Vial of the Sunwell trinket) ///@todo: move this to scripts if (m_spellInfo.Id == 45064) { // Amount of heal - depends from stacked Holy Energy int damageAmount = 0; AuraEffect aurEff = unitCaster.GetAuraEffect(45062, 0); if (aurEff != null) { damageAmount += aurEff.GetAmount(); unitCaster.RemoveAurasDueToSpell(45062); } addhealth += damageAmount; } // Death Pact - return pct of max health to caster else if (m_spellInfo.SpellFamilyName == SpellFamilyNames.Deathknight && m_spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x00080000u)) addhealth = (int)unitCaster.SpellHealingBonusDone(unitTarget, m_spellInfo, (uint)unitCaster.CountPctFromMaxHealth(damage), DamageEffectType.Heal, effectInfo); else { uint bonus = unitCaster.SpellHealingBonusDone(unitTarget, m_spellInfo, (uint)addhealth, DamageEffectType.Heal, effectInfo); addhealth = (int)(bonus + (uint)(bonus * _variance)); } addhealth = (int)unitTarget.SpellHealingBonusTaken(unitCaster, m_spellInfo, (uint)addhealth, DamageEffectType.Heal); // Remove Grievious bite if fully healed if (unitTarget.HasAura(48920) && ((uint)(unitTarget.GetHealth() + (ulong)addhealth) >= unitTarget.GetMaxHealth())) unitTarget.RemoveAura(48920); m_healing += addhealth; } [SpellEffectHandler(SpellEffectName.HealPct)] void EffectHealPct() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsAlive() || damage < 0) return; uint heal = (uint)unitTarget.CountPctFromMaxHealth(damage); Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster) { heal = unitCaster.SpellHealingBonusDone(unitTarget, m_spellInfo, heal, DamageEffectType.Heal, effectInfo); heal = unitTarget.SpellHealingBonusTaken(unitCaster, m_spellInfo, heal, DamageEffectType.Heal); } m_healing += (int)heal; } [SpellEffectHandler(SpellEffectName.HealMechanical)] void EffectHealMechanical() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsAlive() || damage < 0) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); uint heal = (uint)damage; if (unitCaster) heal = unitCaster.SpellHealingBonusDone(unitTarget, m_spellInfo, heal, DamageEffectType.Heal, effectInfo); heal += (uint)(heal * _variance); if (unitCaster) heal = unitTarget.SpellHealingBonusTaken(unitCaster, m_spellInfo, heal, DamageEffectType.Heal); m_healing += (int)heal; } [SpellEffectHandler(SpellEffectName.HealthLeech)] void EffectHealthLeech() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsAlive() || damage < 0) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); uint bonus = 0; if (unitCaster != null) unitCaster.SpellDamageBonusDone(unitTarget, m_spellInfo, (uint)damage, DamageEffectType.SpellDirect, effectInfo); damage = (int)(bonus + (bonus * _variance)); if (unitCaster != null) damage = (int)unitTarget.SpellDamageBonusTaken(unitCaster, m_spellInfo, (uint)damage, DamageEffectType.SpellDirect); Log.outDebug(LogFilter.Spells, "HealthLeech :{0}", damage); float healMultiplier = effectInfo.CalcValueMultiplier(unitCaster, this); m_damage += damage; DamageInfo damageInfo = new(unitCaster, unitTarget, (uint)damage, m_spellInfo, m_spellInfo.GetSchoolMask(), DamageEffectType.Direct, WeaponAttackType.BaseAttack); Unit.CalcAbsorbResist(damageInfo); uint absorb = damageInfo.GetAbsorb(); damage -= (int)absorb; // get max possible damage, don't count overkill for heal uint healthGain = (uint)(-unitTarget.GetHealthGain(-damage) * healMultiplier); if (unitCaster != null && unitCaster.IsAlive()) { healthGain = unitCaster.SpellHealingBonusDone(unitCaster, m_spellInfo, healthGain, DamageEffectType.Heal, effectInfo); healthGain = unitCaster.SpellHealingBonusTaken(unitCaster, m_spellInfo, healthGain, DamageEffectType.Heal); HealInfo healInfo = new(unitCaster, unitCaster, healthGain, m_spellInfo, m_spellSchoolMask); unitCaster.HealBySpell(healInfo); } } public void DoCreateItem(uint itemId, ItemContext context = 0, List bonusListIds = null) { if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; Player player = unitTarget.ToPlayer(); uint newitemid = itemId; ItemTemplate pProto = Global.ObjectMgr.GetItemTemplate(newitemid); if (pProto == null) { player.SendEquipError(InventoryResult.ItemNotFound); return; } uint num_to_add = (uint)damage; if (num_to_add < 1) num_to_add = 1; if (num_to_add > pProto.GetMaxStackSize()) num_to_add = pProto.GetMaxStackSize(); // this is bad, should be done using spell_loot_template (and conditions) // the chance of getting a perfect result float perfectCreateChance = 0.0f; // the resulting perfect item if successful uint perfectItemType = itemId; // get perfection capability and chance if (SkillPerfectItems.CanCreatePerfectItem(player, m_spellInfo.Id, ref perfectCreateChance, ref perfectItemType)) if (RandomHelper.randChance(perfectCreateChance)) // if the roll succeeds... newitemid = perfectItemType; // the perfect item replaces the regular one // init items_count to 1, since 1 item will be created regardless of specialization int items_count = 1; // the chance to create additional items float additionalCreateChance = 0.0f; // the maximum number of created additional items byte additionalMaxNum = 0; // get the chance and maximum number for creating extra items if (SkillExtraItems.CanCreateExtraItems(player, m_spellInfo.Id, ref additionalCreateChance, ref additionalMaxNum)) { // roll with this chance till we roll not to create or we create the max num while (RandomHelper.randChance(additionalCreateChance) && items_count <= additionalMaxNum) ++items_count; } // really will be created more items num_to_add *= (uint)items_count; // can the player store the new item? List dest = new(); uint no_space; InventoryResult msg = player.CanStoreNewItem(ItemConst.NullBag, ItemConst.NullSlot, dest, newitemid, num_to_add, out no_space); if (msg != InventoryResult.Ok) { // convert to possible store amount if (msg == InventoryResult.InvFull || msg == InventoryResult.ItemMaxCount) num_to_add -= no_space; else { // if not created by another reason from full inventory or unique items amount limitation player.SendEquipError(msg, null, null, newitemid); return; } } if (num_to_add != 0) { // create the new item and store it Item pItem = player.StoreNewItem(dest, newitemid, true, ItemEnchantmentManager.GenerateItemRandomBonusListId(newitemid), null, context, bonusListIds); // was it successful? return error if not if (pItem == null) { player.SendEquipError(InventoryResult.ItemNotFound); return; } // set the "Crafted by ..." property of the item if (pItem.GetTemplate().HasSignature()) pItem.SetCreator(player.GetGUID()); // send info to the client player.SendNewItem(pItem, num_to_add, true, true); if (pItem.GetQuality() > ItemQuality.Epic || (pItem.GetQuality() == ItemQuality.Epic && pItem.GetItemLevel(player) >= GuildConst.MinNewsItemLevel)) { Guild guild = player.GetGuild(); if (guild != null) guild.AddGuildNews(GuildNews.ItemCrafted, player.GetGUID(), 0, pProto.GetId()); } // we succeeded in creating at least one item, so a levelup is possible player.UpdateCraftSkill(m_spellInfo.Id); } } [SpellEffectHandler(SpellEffectName.CreateItem)] void EffectCreateItem() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; DoCreateItem(effectInfo.ItemType, m_spellInfo.HasAttribute(SpellAttr0.Tradespell) ? ItemContext.TradeSkill : ItemContext.None); ExecuteLogEffectCreateItem(effectInfo.Effect, effectInfo.ItemType); } [SpellEffectHandler(SpellEffectName.CreateLoot)] void EffectCreateItem2() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; Player player = unitTarget.ToPlayer(); ItemContext context = m_spellInfo.HasAttribute(SpellAttr0.Tradespell) ? ItemContext.TradeSkill : ItemContext.None; // Pick a random item from spell_loot_template if (m_spellInfo.IsLootCrafting()) { player.AutoStoreLoot(m_spellInfo.Id, LootStorage.Spell, context, false, true); player.UpdateCraftSkill(m_spellInfo.Id); } else // If there's no random loot entries for this spell, pick the item associated with this spell { uint itemId = effectInfo.ItemType; if (itemId != 0) DoCreateItem(itemId, context); } // @todo ExecuteLogEffectCreateItem(i, GetEffect(i].ItemType); } [SpellEffectHandler(SpellEffectName.CreateRandomItem)] void EffectCreateRandomItem() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; Player player = unitTarget.ToPlayer(); // create some random items player.AutoStoreLoot(m_spellInfo.Id, LootStorage.Spell, m_spellInfo.HasAttribute(SpellAttr0.Tradespell) ? ItemContext.TradeSkill : ItemContext.None); // @todo ExecuteLogEffectCreateItem(i, GetEffect(i].ItemType); } [SpellEffectHandler(SpellEffectName.PersistentAreaAura)] void EffectPersistentAA() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; // only handle at last effect for (uint i = effectInfo.EffectIndex + 1; i < m_spellInfo.GetEffects().Count; ++i) if (m_spellInfo.GetEffect(i).IsEffect(SpellEffectName.PersistentAreaAura)) return; Cypher.Assert(dynObjAura == null); float radius = effectInfo.CalcRadius(unitCaster); // Caster not in world, might be spell triggered from aura removal if (!unitCaster.IsInWorld) return; DynamicObject dynObj = new(false); if (!dynObj.CreateDynamicObject(unitCaster.GetMap().GenerateLowGuid(HighGuid.DynamicObject), unitCaster, m_spellInfo, destTarget, radius, DynamicObjectType.AreaSpell, m_SpellVisual)) { dynObj.Dispose(); return; } AuraCreateInfo createInfo = new(m_castId, m_spellInfo, GetCastDifficulty(), SpellConst.MaxEffectMask, dynObj); createInfo.SetCaster(unitCaster); 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(effectInfo.EffectIndex); } [SpellEffectHandler(SpellEffectName.Energize)] void EffectEnergize() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null || unitTarget == null) return; if (!unitTarget.IsAlive()) return; if (effectInfo.MiscValue < 0 || effectInfo.MiscValue >= (byte)PowerType.Max) return; PowerType power = (PowerType)effectInfo.MiscValue; if (unitTarget.GetMaxPower(power) == 0) return; // Some level depends spells switch (m_spellInfo.Id) { case 24571: // Blood Fury // Instantly increases your rage by ${(300-10*$max(0,$PL-60))/10}. damage -= 10 * (int)Math.Max(0, Math.Min(30, unitCaster.GetLevel() - 60)); break; case 24532: // Burst of Energy // Instantly increases your energy by ${60-4*$max(0,$min(15,$PL-60))}. damage -= 4 * (int)Math.Max(0, Math.Min(15, unitCaster.GetLevel() - 60)); break; case 67490: // Runic Mana Injector (mana gain increased by 25% for engineers - 3.2.0 patch change) { Player player = unitCaster.ToPlayer(); if (player != null) if (player.HasSkill(SkillType.Engineering)) MathFunctions.AddPct(ref damage, 25); break; } default: break; } unitCaster.EnergizeBySpell(unitTarget, m_spellInfo, damage, power); } [SpellEffectHandler(SpellEffectName.EnergizePct)] void EffectEnergizePct() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null || unitTarget == null) return; if (!unitTarget.IsAlive()) return; if (effectInfo.MiscValue < 0 || effectInfo.MiscValue >= (byte)PowerType.Max) return; PowerType power = (PowerType)effectInfo.MiscValue; uint maxPower = (uint)unitTarget.GetMaxPower(power); if (maxPower == 0) return; int gain = (int)MathFunctions.CalculatePct(maxPower, damage); unitCaster.EnergizeBySpell(unitTarget, m_spellInfo, gain, power); } void SendLoot(ObjectGuid guid, LootType loottype) { Player player = m_caster.ToPlayer(); if (player == null) return; if (gameObjTarget != null) { // Players shouldn't be able to loot gameobjects that are currently despawned if (!gameObjTarget.IsSpawned() && !player.IsGameMaster()) { Log.outError(LogFilter.Spells, "Possible hacking attempt: Player {0} [{1}] tried to loot a gameobject [{2}] which is on respawn time without being in GM mode!", player.GetName(), player.GetGUID().ToString(), gameObjTarget.GetGUID().ToString()); return; } // special case, already has GossipHello inside so return and avoid calling twice if (gameObjTarget.GetGoType() == GameObjectTypes.Goober) { gameObjTarget.Use(player); return; } player.PlayerTalkClass.ClearMenus(); if (gameObjTarget.GetAI().OnGossipHello(player)) return; switch (gameObjTarget.GetGoType()) { case GameObjectTypes.Door: case GameObjectTypes.Button: gameObjTarget.UseDoorOrButton(0, false, player); return; case GameObjectTypes.QuestGiver: player.PrepareGossipMenu(gameObjTarget, gameObjTarget.GetGoInfo().QuestGiver.gossipID, true); player.SendPreparedGossip(gameObjTarget); return; case GameObjectTypes.SpellFocus: // triggering linked GO uint trapEntry = gameObjTarget.GetGoInfo().SpellFocus.linkedTrap; if (trapEntry != 0) gameObjTarget.TriggeringLinkedGameObject(trapEntry, player); return; case GameObjectTypes.Chest: // @todo possible must be moved to loot release (in different from linked triggering) if (gameObjTarget.GetGoInfo().Chest.triggeredEvent != 0) { Log.outDebug(LogFilter.Spells, "Chest ScriptStart id {0} for GO {1}", gameObjTarget.GetGoInfo().Chest.triggeredEvent, gameObjTarget.GetSpawnId()); player.GetMap().ScriptsStart(ScriptsType.Event, gameObjTarget.GetGoInfo().Chest.triggeredEvent, player, gameObjTarget); } // triggering linked GO uint _trapEntry = gameObjTarget.GetGoInfo().Chest.linkedTrap; if (_trapEntry != 0) gameObjTarget.TriggeringLinkedGameObject(_trapEntry, player); break; // Don't return, let loots been taken default: break; } } // Send loot player.SendLoot(guid, loottype); } [SpellEffectHandler(SpellEffectName.OpenLock)] void EffectOpenLock() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!m_caster.IsTypeId(TypeId.Player)) { Log.outDebug(LogFilter.Spells, "WORLD: Open Lock - No Player Caster!"); return; } Player player = m_caster.ToPlayer(); uint lockId; ObjectGuid guid; // Get lockId if (gameObjTarget != null) { GameObjectTemplate goInfo = gameObjTarget.GetGoInfo(); if (goInfo.GetNoDamageImmune() != 0 && player.HasUnitFlag(UnitFlags.Immune)) return; // Arathi Basin banner opening. // @todo Verify correctness of this check if ((goInfo.type == GameObjectTypes.Button && goInfo.Button.noDamageImmune != 0) || (goInfo.type == GameObjectTypes.Goober && goInfo.Goober.requireLOS != 0)) { //CanUseBattlegroundObject() already called in CheckCast() // in Battlegroundcheck Battleground bg = player.GetBattleground(); if (bg) { bg.EventPlayerClickedOnFlag(player, gameObjTarget); return; } } else if (goInfo.type == GameObjectTypes.FlagStand) { //CanUseBattlegroundObject() already called in CheckCast() // in Battlegroundcheck Battleground bg = player.GetBattleground(); if (bg) { if (bg.GetTypeID(true) == BattlegroundTypeId.EY) bg.EventPlayerClickedOnFlag(player, gameObjTarget); return; } } else if (goInfo.type == GameObjectTypes.NewFlag) { gameObjTarget.Use(player); return; } else if (m_spellInfo.Id == 1842 && gameObjTarget.GetGoInfo().type == GameObjectTypes.Trap && gameObjTarget.GetOwner() != null) { gameObjTarget.SetLootState(LootState.JustDeactivated); return; } // @todo Add script for spell 41920 - Filling, becouse server it freze when use this spell // handle outdoor pvp object opening, return true if go was registered for handling // these objects must have been spawned by outdoorpvp! else if (gameObjTarget.GetGoInfo().type == GameObjectTypes.Goober && Global.OutdoorPvPMgr.HandleOpenGo(player, gameObjTarget)) return; lockId = goInfo.GetLockId(); guid = gameObjTarget.GetGUID(); } else if (itemTarget != null) { lockId = itemTarget.GetTemplate().GetLockID(); guid = itemTarget.GetGUID(); } else { Log.outDebug(LogFilter.Spells, "WORLD: Open Lock - No GameObject/Item Target!"); return; } SkillType skillId = SkillType.None; int reqSkillValue = 0; int skillValue = 0; SpellCastResult res = CanOpenLock(effectInfo, lockId, ref skillId, ref reqSkillValue, ref skillValue); if (res != SpellCastResult.SpellCastOk) { SendCastResult(res); return; } if (gameObjTarget != null) SendLoot(guid, LootType.Skinning); else if (itemTarget != null) { itemTarget.AddItemFlag(ItemFieldFlags.Unlocked); itemTarget.SetState(ItemUpdateState.Changed, itemTarget.GetOwner()); } // not allow use skill grow at item base open if (m_CastItem == null && skillId != SkillType.None) { // update skill if really known uint pureSkillValue = player.GetPureSkillValue(skillId); if (pureSkillValue != 0) { if (gameObjTarget != null) { // Allow one skill-up until respawned if (!gameObjTarget.IsInSkillupList(player.GetGUID()) && player.UpdateGatherSkill(skillId, pureSkillValue, (uint)reqSkillValue)) gameObjTarget.AddToSkillupList(player.GetGUID()); } else if (itemTarget != null) { // Do one skill-up player.UpdateGatherSkill(skillId, pureSkillValue, (uint)reqSkillValue); } } } ExecuteLogEffectOpenLock(effectInfo.Effect, gameObjTarget != null ? gameObjTarget : (WorldObject)itemTarget); } [SpellEffectHandler(SpellEffectName.SummonChangeItem)] void EffectSummonChangeItem() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; if (!m_caster.IsTypeId(TypeId.Player)) return; Player player = m_caster.ToPlayer(); // applied only to using item if (m_CastItem == null) return; // ... only to item in own inventory/bank/equip_slot if (m_CastItem.GetOwnerGUID() != player.GetGUID()) return; uint newitemid = effectInfo.ItemType; if (newitemid == 0) return; ushort pos = m_CastItem.GetPos(); Item pNewItem = Item.CreateItem(newitemid, 1, m_CastItem.GetContext(), player); if (pNewItem == null) return; for (var j = EnchantmentSlot.Perm; j <= EnchantmentSlot.Temp; ++j) if (m_CastItem.GetEnchantmentId(j) != 0) pNewItem.SetEnchantment(j, m_CastItem.GetEnchantmentId(j), m_CastItem.GetEnchantmentDuration(j), (uint)m_CastItem.GetEnchantmentCharges(j)); if (m_CastItem.m_itemData.Durability < m_CastItem.m_itemData.MaxDurability) { double lossPercent = 1 - m_CastItem.m_itemData.Durability / m_CastItem.m_itemData.MaxDurability; player.DurabilityLoss(pNewItem, lossPercent); } if (player.IsInventoryPos(pos)) { List dest = new(); InventoryResult msg = player.CanStoreItem(m_CastItem.GetBagSlot(), m_CastItem.GetSlot(), dest, pNewItem, true); if (msg == InventoryResult.Ok) { player.DestroyItem(m_CastItem.GetBagSlot(), m_CastItem.GetSlot(), true); // prevent crash at access and unexpected charges counting with item update queue corrupt if (m_CastItem == m_targets.GetItemTarget()) m_targets.SetItemTarget(null); m_CastItem = null; m_castItemGUID.Clear(); m_castItemEntry = 0; m_castItemLevel = -1; player.StoreItem(dest, pNewItem, true); player.SendNewItem(pNewItem, 1, true, false); player.ItemAddedQuestCheck(newitemid, 1); return; } } else if (Player.IsBankPos(pos)) { List dest = new(); InventoryResult msg = player.CanBankItem(m_CastItem.GetBagSlot(), m_CastItem.GetSlot(), dest, pNewItem, true); if (msg == InventoryResult.Ok) { player.DestroyItem(m_CastItem.GetBagSlot(), m_CastItem.GetSlot(), true); // prevent crash at access and unexpected charges counting with item update queue corrupt if (m_CastItem == m_targets.GetItemTarget()) m_targets.SetItemTarget(null); m_CastItem = null; m_castItemGUID.Clear(); m_castItemEntry = 0; m_castItemLevel = -1; player.BankItem(dest, pNewItem, true); return; } } else if (Player.IsEquipmentPos(pos)) { ushort dest; player.DestroyItem(m_CastItem.GetBagSlot(), m_CastItem.GetSlot(), true); InventoryResult msg = player.CanEquipItem(m_CastItem.GetSlot(), out dest, pNewItem, true); if (msg == InventoryResult.Ok || msg == InventoryResult.ClientLockedOut) { if (msg == InventoryResult.ClientLockedOut) dest = EquipmentSlot.MainHand; // prevent crash at access and unexpected charges counting with item update queue corrupt if (m_CastItem == m_targets.GetItemTarget()) m_targets.SetItemTarget(null); m_CastItem = null; m_castItemGUID.Clear(); m_castItemEntry = 0; m_castItemLevel = -1; player.EquipItem(dest, pNewItem, true); player.AutoUnequipOffhandIfNeed(); player.SendNewItem(pNewItem, 1, true, false); player.ItemAddedQuestCheck(newitemid, 1); return; } } } [SpellEffectHandler(SpellEffectName.Proficiency)] void EffectProficiency() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; if (!m_caster.IsTypeId(TypeId.Player)) return; Player p_target = m_caster.ToPlayer(); uint subClassMask = (uint)m_spellInfo.EquippedItemSubClassMask; if (m_spellInfo.EquippedItemClass == ItemClass.Weapon && !Convert.ToBoolean(p_target.GetWeaponProficiency() & subClassMask)) { p_target.AddWeaponProficiency(subClassMask); p_target.SendProficiency(ItemClass.Weapon, p_target.GetWeaponProficiency()); } if (m_spellInfo.EquippedItemClass == ItemClass.Armor && !Convert.ToBoolean(p_target.GetArmorProficiency() & subClassMask)) { p_target.AddArmorProficiency(subClassMask); p_target.SendProficiency(ItemClass.Armor, p_target.GetArmorProficiency()); } } [SpellEffectHandler(SpellEffectName.Summon)] void EffectSummonType() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; uint entry = (uint)effectInfo.MiscValue; if (entry == 0) return; SummonPropertiesRecord properties = CliDB.SummonPropertiesStorage.LookupByKey(effectInfo.MiscValueB); if (properties == null) { Log.outError(LogFilter.Spells, "EffectSummonType: Unhandled summon type {0}", effectInfo.MiscValueB); return; } WorldObject caster = m_caster; if (m_originalCaster) caster = m_originalCaster; ObjectGuid privateObjectOwner = caster.GetGUID(); if (!properties.GetFlags().HasAnyFlag(SummonPropertiesFlags.OnlyVisibleToSummoner | SummonPropertiesFlags.OnlyVisibleToSummonerGroup)) privateObjectOwner = ObjectGuid.Empty; if (caster.IsPrivateObject()) privateObjectOwner = caster.GetPrivateObjectOwner(); if (properties.GetFlags().HasFlag(SummonPropertiesFlags.OnlyVisibleToSummonerGroup)) if (caster.IsPlayer() && m_originalCaster.ToPlayer().GetGroup()) privateObjectOwner = caster.ToPlayer().GetGroup().GetGUID(); int duration = m_spellInfo.CalcDuration(caster); Unit unitCaster = GetUnitCasterForEffectHandlers(); TempSummon summon = null; // determine how many units should be summoned uint numSummons; // some spells need to summon many units, for those spells number of summons is stored in effect value // however so far noone found a generic check to find all of those (there's no related data in summonproperties.dbc // and in spell attributes, possibly we need to add a table for those) // so here's a list of MiscValueB values, which is currently most generic check switch (effectInfo.MiscValueB) { case 64: case 61: case 1101: case 66: case 648: case 2301: case 1061: case 1261: case 629: case 181: case 715: case 1562: case 833: case 1161: case 713: numSummons = (uint)(damage > 0 ? damage : 1); break; default: numSummons = 1; break; } switch (properties.Control) { case SummonCategory.Wild: case SummonCategory.Ally: case SummonCategory.Unk: if (properties.GetFlags().HasFlag(SummonPropertiesFlags.JoinSummonerSpawnGroup)) { SummonGuardian(effectInfo, entry, properties, numSummons, privateObjectOwner); break; } switch (properties.Title) { case SummonTitle.Pet: case SummonTitle.Guardian: case SummonTitle.Runeblade: case SummonTitle.Minion: SummonGuardian(effectInfo, entry, properties, numSummons, privateObjectOwner); break; // Summons a vehicle, but doesn't force anyone to enter it (see SUMMON_CATEGORY_VEHICLE) case SummonTitle.Vehicle: case SummonTitle.Mount: { if (unitCaster == null) return; summon = unitCaster.GetMap().SummonCreature(entry, destTarget, properties, (uint)duration, unitCaster, m_spellInfo.Id); break; } case SummonTitle.LightWell: case SummonTitle.Totem: { if (unitCaster == null) return; summon = unitCaster.GetMap().SummonCreature(entry, destTarget, properties, (uint)duration, unitCaster, m_spellInfo.Id, 0, privateObjectOwner); if (summon == null || !summon.IsTotem()) return; if (damage != 0) // if not spell info, DB values used { summon.SetMaxHealth((uint)damage); summon.SetHealth((uint)damage); } break; } case SummonTitle.Companion: { if (unitCaster == null) return; summon = unitCaster.GetMap().SummonCreature(entry, destTarget, properties, (uint)duration, unitCaster, m_spellInfo.Id, 0, privateObjectOwner); if (summon == null || !summon.HasUnitTypeMask(UnitTypeMask.Minion)) return; summon.SetImmuneToAll(true); break; } default: { float radius = effectInfo.CalcRadius(); TempSummonType summonType = (duration == 0) ? TempSummonType.DeadDespawn : TempSummonType.TimedDespawn; for (uint count = 0; count < numSummons; ++count) { Position pos; if (count == 0) pos = destTarget; else // randomize position for multiple summons pos = caster.GetRandomPoint(destTarget, radius); summon = caster.SummonCreature(entry, pos, summonType, TimeSpan.FromMilliseconds(duration), 0, m_spellInfo.Id, privateObjectOwner); if (summon == null) continue; if (properties.Control == SummonCategory.Ally) summon.SetOwnerGUID(caster.GetGUID()); uint faction = properties.Faction; if (properties.GetFlags().HasFlag(SummonPropertiesFlags.UseSummonerFaction)) // TODO: Determine priority between faction and flag { WorldObject summoner = summon.GetSummoner(); if (summoner != null) faction = summoner.GetFaction(); } if (faction != 0) summon.SetFaction(faction); ExecuteLogEffectSummonObject(effectInfo.Effect, summon); } return; } }//switch break; case SummonCategory.Pet: SummonGuardian(effectInfo, entry, properties, numSummons, privateObjectOwner); break; case SummonCategory.Puppet: { if (unitCaster == null) return; summon = unitCaster.GetMap().SummonCreature(entry, destTarget, properties, (uint)duration, unitCaster, m_spellInfo.Id, 0, privateObjectOwner); break; } case SummonCategory.Vehicle: { if (unitCaster == null) return; // Summoning spells (usually triggered by npc_spellclick) that spawn a vehicle and that cause the clicker // to cast a ride vehicle spell on the summoned unit. summon = unitCaster.GetMap().SummonCreature(entry, destTarget, properties, (uint)duration, unitCaster, m_spellInfo.Id); if (summon == null || !summon.IsVehicle()) return; // The spell that this effect will trigger. It has SPELL_AURA_CONTROL_VEHICLE uint spellId = SharedConst.VehicleSpellRideHardcoded; int basePoints = effectInfo.CalcValue(); if (basePoints > SharedConst.MaxVehicleSeats) { SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo((uint)basePoints, GetCastDifficulty()); if (spellInfo != null && spellInfo.HasAura(AuraType.ControlVehicle)) spellId = spellInfo.Id; } CastSpellExtraArgs args = new(TriggerCastFlags.FullMask); args.SetTriggeringSpell(this); // if we have small value, it indicates seat position if (basePoints > 0 && basePoints < SharedConst.MaxVehicleSeats) args.AddSpellMod(SpellValueMod.BasePoint0, basePoints); unitCaster.CastSpell(summon, spellId, args); break; } } if (summon != null) { summon.SetCreatorGUID(caster.GetGUID()); ExecuteLogEffectSummonObject(effectInfo.Effect, summon); } } [SpellEffectHandler(SpellEffectName.LearnSpell)] void EffectLearnSpell() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null) return; if (!unitTarget.IsTypeId(TypeId.Player)) { if (unitTarget.IsPet()) EffectLearnPetSpell(); return; } Player player = unitTarget.ToPlayer(); if (m_CastItem != null && effectInfo.TriggerSpell == 0) { foreach (var itemEffect in m_CastItem.GetEffects()) { if (itemEffect.TriggerType != ItemSpelltriggerType.LearnSpellId) continue; bool dependent = false; var speciesEntry = Global.SpellMgr.GetBattlePetSpecies((uint)itemEffect.SpellID); if (speciesEntry != null) { player.GetSession().GetBattlePetMgr().AddPet(speciesEntry.Id, BattlePetMgr.SelectPetDisplay(speciesEntry), BattlePetMgr.RollPetBreed(speciesEntry.Id), BattlePetMgr.GetDefaultPetQuality(speciesEntry.Id)); // If the spell summons a battle pet, we fake that it has been learned and the battle pet is added // marking as dependent prevents saving the spell to database (intended) dependent = true; } player.LearnSpell((uint)itemEffect.SpellID, dependent); } } if (effectInfo.TriggerSpell != 0) { player.LearnSpell(effectInfo.TriggerSpell, false); Log.outDebug(LogFilter.Spells, $"Spell: {player.GetGUID()} has learned spell {effectInfo.TriggerSpell} from {m_caster.GetGUID()}"); } } [SpellEffectHandler(SpellEffectName.Dispel)] void EffectDispel() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null) return; // Create dispel mask by dispel type uint dispel_type = (uint)effectInfo.MiscValue; uint dispelMask = SpellInfo.GetDispelMask((DispelType)dispel_type); List dispelList = unitTarget.GetDispellableAuraList(m_caster, dispelMask, targetMissInfo == SpellMissInfo.Reflect); if (dispelList.Empty()) return; int remaining = dispelList.Count; // Ok if exist some buffs for dispel try dispel it List successList = new(); DispelFailed dispelFailed = new(); dispelFailed.CasterGUID = m_caster.GetGUID(); dispelFailed.VictimGUID = unitTarget.GetGUID(); dispelFailed.SpellID = m_spellInfo.Id; // dispel N = damage buffs (or while exist buffs for dispel) for (int count = 0; count < damage && remaining > 0;) { // Random select buff for dispel var dispelableAura = dispelList[RandomHelper.IRand(0, remaining - 1)]; if (dispelableAura.RollDispel()) { var successAura = successList.Find(dispelAura => { if (dispelAura.GetAura().GetId() == dispelableAura.GetAura().GetId() && dispelAura.GetAura().GetCaster() == dispelableAura.GetAura().GetCaster()) return true; return false; }); if (successAura == null) successList.Add(new DispelableAura(dispelableAura.GetAura(), 0, 1)); else successAura.IncrementCharges(); if (!dispelableAura.DecrementCharge()) { --remaining; dispelList[remaining] = dispelableAura; } } else { dispelFailed.FailedSpells.Add(dispelableAura.GetAura().GetId()); } ++count; } if (!dispelFailed.FailedSpells.Empty()) m_caster.SendMessageToSet(dispelFailed, true); if (successList.Empty()) return; SpellDispellLog spellDispellLog = new(); spellDispellLog.IsBreak = false; // TODO: use me spellDispellLog.IsSteal = false; spellDispellLog.TargetGUID = unitTarget.GetGUID(); spellDispellLog.CasterGUID = m_caster.GetGUID(); spellDispellLog.DispelledBySpellID = m_spellInfo.Id; foreach (var dispelableAura in successList) { var dispellData = new SpellDispellData(); dispellData.SpellID = dispelableAura.GetAura().GetId(); dispellData.Harmful = false; // TODO: use me unitTarget.RemoveAurasDueToSpellByDispel(dispelableAura.GetAura().GetId(), m_spellInfo.Id, dispelableAura.GetAura().GetCasterGUID(), m_caster, dispelableAura.GetDispelCharges()); spellDispellLog.DispellData.Add(dispellData); } m_caster.SendMessageToSet(spellDispellLog, true); CallScriptSuccessfulDispel(effectInfo.EffectIndex); } [SpellEffectHandler(SpellEffectName.DualWield)] void EffectDualWield() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; unitTarget.SetCanDualWield(true); if (unitTarget.IsTypeId(TypeId.Unit)) unitTarget.ToCreature().UpdateDamagePhysical(WeaponAttackType.OffAttack); } [SpellEffectHandler(SpellEffectName.Distract)] void EffectDistract() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; // Check for possible target if (unitTarget == null || unitTarget.IsEngaged()) return; // target must be OK to do this if (unitTarget.HasUnitState(UnitState.Confused | UnitState.Stunned | UnitState.Fleeing)) return; unitTarget.GetMotionMaster().MoveDistract((uint)(damage * Time.InMilliseconds), unitTarget.GetAbsoluteAngle(destTarget)); } [SpellEffectHandler(SpellEffectName.Pickpocket)] void EffectPickPocket() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!m_caster.IsTypeId(TypeId.Player)) return; // victim must be creature and attackable if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Unit) || m_caster.IsFriendlyTo(unitTarget)) return; // victim have to be alive and humanoid or undead if (unitTarget.IsAlive() && (unitTarget.GetCreatureTypeMask() & (uint)CreatureType.MaskHumanoidOrUndead) != 0) m_caster.ToPlayer().SendLoot(unitTarget.GetGUID(), LootType.Pickpocketing); } [SpellEffectHandler(SpellEffectName.AddFarsight)] void EffectAddFarsight() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Player player = m_caster.ToPlayer(); if (player == null) return; float radius = effectInfo.CalcRadius(); int duration = m_spellInfo.CalcDuration(m_caster); // Caster not in world, might be spell triggered from aura removal if (!player.IsInWorld) return; DynamicObject dynObj = new(true); if (!dynObj.CreateDynamicObject(player.GetMap().GenerateLowGuid(HighGuid.DynamicObject), player, m_spellInfo, destTarget, radius, DynamicObjectType.FarsightFocus, m_SpellVisual)) return; dynObj.SetDuration(duration); dynObj.SetCasterViewpoint(); } [SpellEffectHandler(SpellEffectName.UntrainTalents)] void EffectUntrainTalents() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || m_caster.IsTypeId(TypeId.Player)) return; ObjectGuid guid = m_caster.GetGUID(); if (!guid.IsEmpty()) // the trainer is the caster unitTarget.ToPlayer().SendRespecWipeConfirm(guid, unitTarget.ToPlayer().GetNextResetTalentsCost()); } [SpellEffectHandler(SpellEffectName.TeleportUnitsFaceCaster)] void EffectTeleUnitsFaceCaster() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null) return; if (unitTarget.IsInFlight()) return; float dis = effectInfo.CalcRadius(m_caster); float fx, fy, fz; m_caster.GetClosePoint(out fx, out fy, out fz, unitTarget.GetCombatReach(), dis); unitTarget.NearTeleportTo(fx, fy, fz, -m_caster.GetOrientation(), unitTarget == m_caster); } [SpellEffectHandler(SpellEffectName.SkillStep)] void EffectLearnSkill() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget.IsTypeId(TypeId.Player)) return; if (damage < 1) return; uint skillid = (uint)effectInfo.MiscValue; SkillRaceClassInfoRecord rcEntry = Global.DB2Mgr.GetSkillRaceClassInfo(skillid, unitTarget.GetRace(), unitTarget.GetClass()); if (rcEntry == null) return; SkillTiersEntry tier = Global.ObjectMgr.GetSkillTier(rcEntry.SkillTierID); if (tier == null) return; ushort skillval = unitTarget.ToPlayer().GetPureSkillValue((SkillType)skillid); unitTarget.ToPlayer().SetSkill(skillid, (uint)damage, Math.Max(skillval, (ushort)1), tier.Value[damage - 1]); } [SpellEffectHandler(SpellEffectName.PlayMovie)] void EffectPlayMovie() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget.IsTypeId(TypeId.Player)) return; uint movieId = (uint)effectInfo.MiscValue; if (!CliDB.MovieStorage.ContainsKey(movieId)) return; unitTarget.ToPlayer().SendMovieStart(movieId); } [SpellEffectHandler(SpellEffectName.TradeSkill)] void EffectTradeSkill() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; if (!m_caster.IsTypeId(TypeId.Player)) return; // uint skillid = GetEffect(i].MiscValue; // ushort skillmax = unitTarget.ToPlayer().(skillid); // m_caster.ToPlayer().SetSkill(skillid, skillval?skillval:1, skillmax+75); } [SpellEffectHandler(SpellEffectName.EnchantItem)] void EffectEnchantItemPerm() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (itemTarget == null) return; Player player = m_caster.ToPlayer(); if (player == null) return; // Handle vellums if (itemTarget.IsVellum()) { // destroy one vellum from stack uint count = 1; player.DestroyItemCount(itemTarget, ref count, true); unitTarget = player; // and add a scroll damage = 1; DoCreateItem(effectInfo.ItemType, m_spellInfo.HasAttribute(SpellAttr0.Tradespell) ? ItemContext.TradeSkill : ItemContext.None); itemTarget = null; m_targets.SetItemTarget(null); } else { // do not increase skill if vellum used if (!(m_CastItem && m_CastItem.GetTemplate().HasFlag(ItemFlags.NoReagentCost))) player.UpdateCraftSkill(m_spellInfo.Id); uint enchant_id = (uint)effectInfo.MiscValue; if (enchant_id == 0) return; SpellItemEnchantmentRecord pEnchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id); if (pEnchant == null) return; // item can be in trade slot and have owner diff. from caster Player item_owner = itemTarget.GetOwner(); if (item_owner == null) return; if (item_owner != player && player.GetSession().HasPermission(RBACPermissions.LogGmTrade)) { Log.outCommand(player.GetSession().GetAccountId(), "GM {0} (Account: {1}) enchanting(perm): {2} (Entry: {3}) for player: {4} (Account: {5})", player.GetName(), player.GetSession().GetAccountId(), itemTarget.GetTemplate().GetName(), itemTarget.GetEntry(), item_owner.GetName(), item_owner.GetSession().GetAccountId()); } // remove old enchanting before applying new if equipped item_owner.ApplyEnchantment(itemTarget, EnchantmentSlot.Perm, false); itemTarget.SetEnchantment(EnchantmentSlot.Perm, enchant_id, 0, 0, m_caster.GetGUID()); // add new enchanting if equipped item_owner.ApplyEnchantment(itemTarget, EnchantmentSlot.Perm, true); item_owner.RemoveTradeableItem(itemTarget); itemTarget.ClearSoulboundTradeable(item_owner); } } [SpellEffectHandler(SpellEffectName.EnchantItemPrismatic)] void EffectEnchantItemPrismatic() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (itemTarget == null) return; Player player = m_caster.ToPlayer(); if (player == null) return; uint enchantId = (uint)effectInfo.MiscValue; if (enchantId == 0) return; SpellItemEnchantmentRecord enchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchantId); if (enchant == null) return; // support only enchantings with add socket in this slot { bool add_socket = false; for (byte i = 0; i < ItemConst.MaxItemEnchantmentEffects; ++i) { if (enchant.Effect[i] == ItemEnchantmentType.PrismaticSocket) { add_socket = true; break; } } if (!add_socket) { Log.outError(LogFilter.Spells, "Spell.EffectEnchantItemPrismatic: attempt apply enchant spell {0} with SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC ({1}) but without ITEM_ENCHANTMENT_TYPE_PRISMATIC_SOCKET ({2}), not suppoted yet.", m_spellInfo.Id, SpellEffectName.EnchantItemPrismatic, ItemEnchantmentType.PrismaticSocket); return; } } // item can be in trade slot and have owner diff. from caster Player item_owner = itemTarget.GetOwner(); if (item_owner == null) return; if (item_owner != player && player.GetSession().HasPermission(RBACPermissions.LogGmTrade)) { Log.outCommand(player.GetSession().GetAccountId(), "GM {0} (Account: {1}) enchanting(perm): {2} (Entry: {3}) for player: {4} (Account: {5})", player.GetName(), player.GetSession().GetAccountId(), itemTarget.GetTemplate().GetName(), itemTarget.GetEntry(), item_owner.GetName(), item_owner.GetSession().GetAccountId()); } // remove old enchanting before applying new if equipped item_owner.ApplyEnchantment(itemTarget, EnchantmentSlot.Prismatic, false); itemTarget.SetEnchantment(EnchantmentSlot.Prismatic, enchantId, 0, 0, m_caster.GetGUID()); // add new enchanting if equipped item_owner.ApplyEnchantment(itemTarget, EnchantmentSlot.Prismatic, true); item_owner.RemoveTradeableItem(itemTarget); itemTarget.ClearSoulboundTradeable(item_owner); } [SpellEffectHandler(SpellEffectName.EnchantItemTemporary)] void EffectEnchantItemTmp() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (itemTarget == null) return; Player player = m_caster.ToPlayer(); if (player == null) return; uint enchant_id = (uint)effectInfo.MiscValue; if (enchant_id == 0) { Log.outError(LogFilter.Spells, "Spell {0} Effect {1} (SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY) have 0 as enchanting id", m_spellInfo.Id, effectInfo.EffectIndex); return; } SpellItemEnchantmentRecord pEnchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id); if (pEnchant == null) { Log.outError(LogFilter.Spells, "Spell {0} Effect {1} (SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY) have not existed enchanting id {2}", m_spellInfo.Id, effectInfo.EffectIndex, enchant_id); return; } // select enchantment duration uint duration; // rogue family enchantments exception by duration if (m_spellInfo.Id == 38615) duration = 1800; // 30 mins // other rogue family enchantments always 1 hour (some have spell damage=0, but some have wrong data in EffBasePoints) else if (m_spellInfo.SpellFamilyName == SpellFamilyNames.Rogue) duration = 3600; // 1 hour // shaman family enchantments else if (m_spellInfo.SpellFamilyName == SpellFamilyNames.Shaman) duration = 3600; // 30 mins // other cases with this SpellVisual already selected else if (m_spellInfo.GetSpellVisual() == 215) duration = 1800; // 30 mins // some fishing pole bonuses except Glow Worm which lasts full hour else if (m_spellInfo.GetSpellVisual() == 563 && m_spellInfo.Id != 64401) duration = 600; // 10 mins else if (m_spellInfo.Id == 29702) duration = 300; // 5 mins else if (m_spellInfo.Id == 37360) duration = 300; // 5 mins // default case else duration = 3600; // 1 hour // item can be in trade slot and have owner diff. from caster Player item_owner = itemTarget.GetOwner(); if (item_owner == null) return; if (item_owner != player && player.GetSession().HasPermission(RBACPermissions.LogGmTrade)) { Log.outCommand(player.GetSession().GetAccountId(), "GM {0} (Account: {1}) enchanting(temp): {2} (Entry: {3}) for player: {4} (Account: {5})", player.GetName(), player.GetSession().GetAccountId(), itemTarget.GetTemplate().GetName(), itemTarget.GetEntry(), item_owner.GetName(), item_owner.GetSession().GetAccountId()); } // remove old enchanting before applying new if equipped item_owner.ApplyEnchantment(itemTarget, EnchantmentSlot.Temp, false); itemTarget.SetEnchantment(EnchantmentSlot.Temp, enchant_id, duration * 1000, 0, m_caster.GetGUID()); // add new enchanting if equipped item_owner.ApplyEnchantment(itemTarget, EnchantmentSlot.Temp, true); } [SpellEffectHandler(SpellEffectName.Tamecreature)] void EffectTameCreature() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null || !unitCaster.GetPetGUID().IsEmpty()) return; if (unitTarget == null) return; if (!unitTarget.IsTypeId(TypeId.Unit)) return; Creature creatureTarget = unitTarget.ToCreature(); if (creatureTarget.IsPet()) return; if (unitCaster.GetClass() != Class.Hunter) return; // cast finish successfully Finish(); Pet pet = unitCaster.CreateTamedPetFrom(creatureTarget, m_spellInfo.Id); if (pet == null) // in very specific state like near world end/etc. return; // "kill" original creature creatureTarget.DespawnOrUnsummon(); uint level = (creatureTarget.GetLevelForTarget(m_caster) < (m_caster.GetLevelForTarget(creatureTarget) - 5)) ? (m_caster.GetLevelForTarget(creatureTarget) - 5) : creatureTarget.GetLevelForTarget(m_caster); // prepare visual effect for levelup pet.SetLevel(level - 1); // add to world pet.GetMap().AddToMap(pet.ToCreature()); // visual effect for levelup pet.SetLevel(level); // caster have pet now unitCaster.SetMinion(pet, true); if (m_caster.IsTypeId(TypeId.Player)) { pet.SavePetToDB(PetSaveMode.AsCurrent); unitCaster.ToPlayer().PetSpellInitialize(); } } [SpellEffectHandler(SpellEffectName.SummonPet)] void EffectSummonPet() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Player owner = null; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster != null) { owner = unitCaster.ToPlayer(); if (owner == null && unitCaster.IsTotem()) owner = unitCaster.GetCharmerOrOwnerPlayerOrPlayerItself(); } uint petentry = (uint)effectInfo.MiscValue; if (owner == null) { SummonPropertiesRecord properties = CliDB.SummonPropertiesStorage.LookupByKey(67); if (properties != null) SummonGuardian(effectInfo, petentry, properties, 1, ObjectGuid.Empty); return; } Pet OldSummon = owner.GetPet(); // if pet requested type already exist if (OldSummon != null) { if (petentry == 0 || OldSummon.GetEntry() == petentry) { // pet in corpse state can't be summoned if (OldSummon.IsDead()) return; Cypher.Assert(OldSummon.GetMap() == owner.GetMap()); float px, py, pz; owner.GetClosePoint(out px, out py, out pz, OldSummon.GetCombatReach()); OldSummon.NearTeleportTo(px, py, pz, OldSummon.GetOrientation()); if (owner.IsTypeId(TypeId.Player) && OldSummon.IsControlled()) owner.ToPlayer().PetSpellInitialize(); return; } if (owner.IsTypeId(TypeId.Player)) owner.ToPlayer().RemovePet(OldSummon, PetSaveMode.NotInSlot, false); else return; } PetSaveMode? petSlot = null; if (petentry == 0) petSlot = (PetSaveMode)damage; float x, y, z; owner.GetClosePoint(out x, out y, out z, owner.GetCombatReach()); Pet pet = owner.SummonPet(petentry, petSlot, x, y, z, owner.Orientation, 0, out bool isNew); if (pet == null) return; if (isNew) { if (m_caster.IsCreature()) { if (m_caster.ToCreature().IsTotem()) pet.SetReactState(ReactStates.Aggressive); else pet.SetReactState(ReactStates.Defensive); } pet.SetCreatedBySpell(m_spellInfo.Id); // generate new name for summon pet string new_name = Global.ObjectMgr.GeneratePetName(petentry); if (!string.IsNullOrEmpty(new_name)) pet.SetName(new_name); } ExecuteLogEffectSummonObject(effectInfo.Effect, pet); } [SpellEffectHandler(SpellEffectName.LearnPetSpell)] void EffectLearnPetSpell() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null) return; if (unitTarget.ToPlayer() != null) { EffectLearnSpell(); return; } Pet pet = unitTarget.ToPet(); if (pet == null) return; SpellInfo learn_spellproto = Global.SpellMgr.GetSpellInfo(effectInfo.TriggerSpell, Difficulty.None); if (learn_spellproto == null) return; pet.LearnSpell(learn_spellproto.Id); pet.SavePetToDB(PetSaveMode.AsCurrent); pet.GetOwner().PetSpellInitialize(); } [SpellEffectHandler(SpellEffectName.AttackMe)] void EffectTaunt() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; // this effect use before aura Taunt apply for prevent taunt already attacking target // for spell as marked "non effective at already attacking target" if (!unitTarget || unitTarget.IsTotem()) { SendCastResult(SpellCastResult.DontReport); return; } // Hand of Reckoning can hit some entities that can't have a threat list (including players' pets) if (m_spellInfo.Id == 62124) if (!unitTarget.IsPlayer() && unitTarget.GetTarget() != unitCaster.GetGUID()) unitCaster.CastSpell(unitTarget, 67485, true); if (!unitTarget.CanHaveThreatList()) { SendCastResult(SpellCastResult.DontReport); return; } ThreatManager mgr = unitTarget.GetThreatManager(); if (mgr.GetCurrentVictim() == unitCaster) { SendCastResult(SpellCastResult.DontReport); return; } if (!mgr.IsThreatListEmpty()) // Set threat equal to highest threat currently on target mgr.MatchUnitThreatToHighestThreat(unitCaster); } [SpellEffectHandler(SpellEffectName.WeaponDamageNoSchool)] [SpellEffectHandler(SpellEffectName.WeaponPercentDamage)] [SpellEffectHandler(SpellEffectName.WeaponDamage)] [SpellEffectHandler(SpellEffectName.NormalizedWeaponDmg)] void EffectWeaponDmg() { if (effectHandleMode != SpellEffectHandleMode.LaunchTarget) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; if (unitTarget == null || !unitTarget.IsAlive()) return; // multiple weapon dmg effect workaround // execute only the last weapon damage // and handle all effects at once for (var j = effectInfo.EffectIndex + 1; j < m_spellInfo.GetEffects().Count; ++j) { switch (m_spellInfo.GetEffect(j).Effect) { case SpellEffectName.WeaponDamage: case SpellEffectName.WeaponDamageNoSchool: case SpellEffectName.NormalizedWeaponDmg: case SpellEffectName.WeaponPercentDamage: return; // we must calculate only at last weapon effect } } // some spell specific modifiers float totalDamagePercentMod = 1.0f; // applied to final bonus+weapon damage int fixed_bonus = 0; int spell_bonus = 0; // bonus specific for spell switch (m_spellInfo.SpellFamilyName) { case SpellFamilyNames.Shaman: { // Skyshatter Harness item set bonus // Stormstrike AuraEffect aurEff = unitCaster.IsScriptOverriden(m_spellInfo, 5634); if (aurEff != null) unitCaster.CastSpell((WorldObject)null, 38430, new CastSpellExtraArgs(aurEff)); break; } } bool normalized = false; float weaponDamagePercentMod = 1.0f; foreach (var spellEffectInfo in m_spellInfo.GetEffects()) { switch (spellEffectInfo.Effect) { case SpellEffectName.WeaponDamage: case SpellEffectName.WeaponDamageNoSchool: fixed_bonus += CalculateDamage(spellEffectInfo, unitTarget); break; case SpellEffectName.NormalizedWeaponDmg: fixed_bonus += CalculateDamage(spellEffectInfo, unitTarget); normalized = true; break; case SpellEffectName.WeaponPercentDamage: MathFunctions.ApplyPct(ref weaponDamagePercentMod, CalculateDamage(spellEffectInfo, unitTarget)); break; default: break; // not weapon damage effect, just skip } } // if (addPctMods) { percent mods are added in Unit::CalculateDamage } else { percent mods are added in Unit::MeleeDamageBonusDone } // this distinction is neccessary to properly inform the client about his autoattack damage values from Script_UnitDamage bool addPctMods = !m_spellInfo.HasAttribute(SpellAttr6.IgnoreCasterDamageModifiers) && m_spellSchoolMask.HasAnyFlag(SpellSchoolMask.Normal); if (addPctMods) { UnitMods unitMod; switch (m_attackType) { default: case WeaponAttackType.BaseAttack: unitMod = UnitMods.DamageMainHand; break; case WeaponAttackType.OffAttack: unitMod = UnitMods.DamageOffHand; break; case WeaponAttackType.RangedAttack: unitMod = UnitMods.DamageRanged; break; } float weapon_total_pct = unitCaster.GetPctModifierValue(unitMod, UnitModifierPctType.Total); if (fixed_bonus != 0) fixed_bonus = (int)(fixed_bonus * weapon_total_pct); if (spell_bonus != 0) spell_bonus = (int)(spell_bonus * weapon_total_pct); } uint weaponDamage = unitCaster.CalculateDamage(m_attackType, normalized, addPctMods); // Sequence is important foreach (var spellEffectInfo in m_spellInfo.GetEffects()) { // We assume that a spell have at most one fixed_bonus // and at most one weaponDamagePercentMod switch (spellEffectInfo.Effect) { case SpellEffectName.WeaponDamage: case SpellEffectName.WeaponDamageNoSchool: case SpellEffectName.NormalizedWeaponDmg: weaponDamage += (uint)fixed_bonus; break; case SpellEffectName.WeaponPercentDamage: weaponDamage = (uint)(weaponDamage * weaponDamagePercentMod); break; default: break; // not weapon damage effect, just skip } } weaponDamage += (uint)spell_bonus; weaponDamage = (uint)(weaponDamage * totalDamagePercentMod); // prevent negative damage weaponDamage = Math.Max(weaponDamage, 0); // Add melee damage bonuses (also check for negative) weaponDamage = unitCaster.MeleeDamageBonusDone(unitTarget, weaponDamage, m_attackType, DamageEffectType.SpellDirect, m_spellInfo, effectInfo); m_damage += (int)unitTarget.MeleeDamageBonusTaken(unitCaster, weaponDamage, m_attackType, DamageEffectType.SpellDirect, m_spellInfo); } [SpellEffectHandler(SpellEffectName.Threat)] void EffectThreat() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null || !unitCaster.IsAlive()) return; if (unitTarget == null) return; if (!unitTarget.CanHaveThreatList()) return; unitTarget.GetThreatManager().AddThreat(unitCaster, damage, m_spellInfo, true); } [SpellEffectHandler(SpellEffectName.HealMaxHealth)] void EffectHealMaxHealth() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; if (unitTarget == null || !unitTarget.IsAlive()) return; int addhealth; // damage == 0 - heal for caster max health if (damage == 0) addhealth = (int)unitCaster.GetMaxHealth(); else addhealth = (int)(unitTarget.GetMaxHealth() - unitTarget.GetHealth()); m_healing += addhealth; } [SpellEffectHandler(SpellEffectName.InterruptCast)] void EffectInterruptCast() { if (effectHandleMode != SpellEffectHandleMode.LaunchTarget) return; if (unitTarget == null || !unitTarget.IsAlive()) return; // @todo not all spells that used this effect apply cooldown at school spells // also exist case: apply cooldown to interrupted cast only and to all spells // there is no CURRENT_AUTOREPEAT_SPELL spells that can be interrupted for (var i = CurrentSpellTypes.Generic; i < CurrentSpellTypes.AutoRepeat; ++i) { Spell spell = unitTarget.GetCurrentSpell(i); if (spell != null) { SpellInfo curSpellInfo = spell.m_spellInfo; // check if we can interrupt spell if ((spell.GetState() == SpellState.Casting || (spell.GetState() == SpellState.Preparing && spell.GetCastTime() > 0.0f)) && curSpellInfo.CanBeInterrupted(m_caster, unitTarget)) { int duration = m_spellInfo.GetDuration(); duration = unitTarget.ModSpellDuration(m_spellInfo, unitTarget, duration, false, 1u << (int)effectInfo.EffectIndex); unitTarget.GetSpellHistory().LockSpellSchool(curSpellInfo.GetSchoolMask(), TimeSpan.FromMilliseconds(duration)); m_hitMask |= ProcFlagsHit.Interrupt; SendSpellInterruptLog(unitTarget, curSpellInfo.Id); unitTarget.InterruptSpell(i, false); } } } } [SpellEffectHandler(SpellEffectName.SummonObjectWild)] void EffectSummonObjectWild() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; WorldObject target = focusObject; if (target == null) target = m_caster; float x, y, z, o; if (m_targets.HasDst()) destTarget.GetPosition(out x, out y, out z, out o); else { m_caster.GetClosePoint(out x, out y, out z, SharedConst.DefaultPlayerBoundingRadius); o = target.GetOrientation(); } Map map = target.GetMap(); Position pos = new(x, y, z, o); Quaternion rotation = Quaternion.CreateFromRotationMatrix(Extensions.fromEulerAnglesZYX(o, 0.0f, 0.0f)); GameObject go = GameObject.CreateGameObject((uint)effectInfo.MiscValue, map, pos, rotation, 255, GameObjectState.Ready); if (!go) return; PhasingHandler.InheritPhaseShift(go, m_caster); int duration = m_spellInfo.CalcDuration(m_caster); go.SetRespawnTime(duration > 0 ? duration / Time.InMilliseconds : 0); go.SetSpellId(m_spellInfo.Id); ExecuteLogEffectSummonObject(effectInfo.Effect, go); // Wild object not have owner and check clickable by players map.AddToMap(go); if (go.GetGoType() == GameObjectTypes.FlagDrop) { Player player = m_caster.ToPlayer(); if (player != null) { Battleground bg = player.GetBattleground(); if (bg) bg.SetDroppedFlagGUID(go.GetGUID(), (player.GetTeam() == Team.Alliance ? TeamId.Horde : TeamId.Alliance)); } } GameObject linkedTrap = go.GetLinkedTrap(); if (linkedTrap) { PhasingHandler.InheritPhaseShift(linkedTrap, m_caster); linkedTrap.SetRespawnTime(duration > 0 ? duration / Time.InMilliseconds : 0); linkedTrap.SetSpellId(m_spellInfo.Id); ExecuteLogEffectSummonObject(effectInfo.Effect, linkedTrap); } } [SpellEffectHandler(SpellEffectName.ScriptEffect)] void EffectScriptEffect() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); // @todo we must implement hunter pet summon at login there (spell 6962) /// @todo: move this to scripts switch (m_spellInfo.SpellFamilyName) { case SpellFamilyNames.Generic: { switch (m_spellInfo.Id) { case 45204: // Clone Me! m_caster.CastSpell(unitTarget, (uint)damage, new CastSpellExtraArgs(true)); break; // Shadow Flame (All script effects, not just end ones to prevent player from dodging the last triggered spell) case 22539: case 22972: case 22975: case 22976: case 22977: case 22978: case 22979: case 22980: case 22981: case 22982: case 22983: case 22984: case 22985: { if (unitTarget == null || !unitTarget.IsAlive()) return; // Onyxia Scale Cloak if (unitTarget.HasAura(22683)) return; // Shadow Flame m_caster.CastSpell(unitTarget, 22682, new CastSpellExtraArgs(this)); return; } // Mug Transformation case 41931: { if (!m_caster.IsTypeId(TypeId.Player)) return; byte bag = 19; byte slot = 0; Item item; while (bag != 0) // 256 = 0 due to var type { item = m_caster.ToPlayer().GetItemByPos(bag, slot); if (item != null && item.GetEntry() == 38587) break; ++slot; if (slot == 39) { slot = 0; ++bag; } } if (bag != 0) { if (m_caster.ToPlayer().GetItemByPos(bag, slot).GetCount() == 1) m_caster.ToPlayer().RemoveItem(bag, slot, true); else m_caster.ToPlayer().GetItemByPos(bag, slot).SetCount(m_caster.ToPlayer().GetItemByPos(bag, slot).GetCount() - 1); // Spell 42518 (Braufest - Gratisprobe des Braufest herstellen) m_caster.CastSpell(m_caster, 42518, new CastSpellExtraArgs(this)); return; } break; } // Brutallus - Burn case 45141: case 45151: { //Workaround for Range ... should be global for every ScriptEffect float radius = effectInfo.CalcRadius(); if (unitTarget != null && unitTarget.IsTypeId(TypeId.Player) && unitTarget.GetDistance(m_caster) >= radius && !unitTarget.HasAura(46394) && unitTarget != m_caster) unitTarget.CastSpell(unitTarget, 46394, new CastSpellExtraArgs(this)); break; } // Emblazon Runeblade case 51770: { if (m_originalCaster == null) return; m_originalCaster.CastSpell(m_originalCaster, (uint)damage, new CastSpellExtraArgs(false)); break; } // Summon Ghouls On Scarlet Crusade case 51904: { if (!m_targets.HasDst()) return; float radius = effectInfo.CalcRadius(); for (byte i = 0; i < 15; ++i) m_caster.CastSpell(m_caster.GetRandomPoint(destTarget, radius), 54522, new CastSpellExtraArgs(this)); break; } case 52173: // Coyote Spirit Despawn case 60243: // Blood Parrot Despawn if (unitTarget.IsTypeId(TypeId.Unit) && unitTarget.ToCreature().IsSummon()) unitTarget.ToTempSummon().UnSummon(); return; case 57347: // Retrieving (Wintergrasp RP-GG pickup spell) { if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Unit) || !m_caster.IsTypeId(TypeId.Player)) return; unitTarget.ToCreature().DespawnOrUnsummon(); return; } case 57349: // Drop RP-GG (Wintergrasp RP-GG at death drop spell) { if (!m_caster.IsTypeId(TypeId.Player)) return; // Delete item from inventory at death m_caster.ToPlayer().DestroyItemCount((uint)damage, 5, true); return; } case 58941: // Rock Shards if (unitTarget != null && m_originalCaster != null) { for (uint i = 0; i < 3; ++i) { m_originalCaster.CastSpell(unitTarget, 58689, new CastSpellExtraArgs(true)); m_originalCaster.CastSpell(unitTarget, 58692, new CastSpellExtraArgs(true)); } if (m_originalCaster.GetMap().GetDifficultyID() == Difficulty.None) { m_originalCaster.CastSpell(unitTarget, 58695, new CastSpellExtraArgs(true)); m_originalCaster.CastSpell(unitTarget, 58696, new CastSpellExtraArgs(true)); } else { m_originalCaster.CastSpell(unitTarget, 60883, new CastSpellExtraArgs(true)); m_originalCaster.CastSpell(unitTarget, 60884, new CastSpellExtraArgs(true)); } } return; case 62482: // Grab Crate { if (unitCaster == null) return; if (unitTarget != null) { Unit seat = unitCaster.GetVehicleBase(); if (seat != null) { Unit parent = seat.GetVehicleBase(); if (parent != null) { // @todo a hack, range = 11, should after some time cast, otherwise too far unitCaster.CastSpell(parent, 62496, new CastSpellExtraArgs(this)); unitTarget.CastSpell(parent, (uint)damage, new CastSpellExtraArgs().SetTriggeringSpell(this)); // DIFFICULTY_NONE, so effect always valid } } } return; } } break; } } // normal DB scripted effect Log.outDebug(LogFilter.Spells, "Spell ScriptStart spellid {0} in EffectScriptEffect({1})", m_spellInfo.Id, effectInfo.EffectIndex); m_caster.GetMap().ScriptsStart(ScriptsType.Spell, (uint)((int)m_spellInfo.Id | (int)(effectInfo.EffectIndex << 24)), m_caster, unitTarget); } [SpellEffectHandler(SpellEffectName.Sanctuary)] [SpellEffectHandler(SpellEffectName.Sanctuary2)] void EffectSanctuary() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null) return; if (unitTarget.IsPlayer() && !unitTarget.GetMap().IsDungeon()) { // stop all pve combat for players outside dungeons, suppress pvp combat unitTarget.CombatStop(false, false); } else { // in dungeons (or for nonplayers), reset this unit on all enemies' threat lists foreach (var pair in unitTarget.GetThreatManager().GetThreatenedByMeList()) pair.Value.ScaleThreat(0.0f); } // makes spells cast before this time fizzle unitTarget.LastSanctuaryTime = GameTime.GetGameTimeMS(); } [SpellEffectHandler(SpellEffectName.Duel)] void EffectDuel() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !m_caster.IsTypeId(TypeId.Player) || !unitTarget.IsTypeId(TypeId.Player)) return; Player caster = m_caster.ToPlayer(); Player target = unitTarget.ToPlayer(); // caster or target already have requested duel if (caster.duel != null || target.duel != null || target.GetSocial() == null || target.GetSocial().HasIgnore(caster.GetGUID(), caster.GetSession().GetAccountGUID())) return; // Players can only fight a duel in zones with this flag AreaTableRecord casterAreaEntry = CliDB.AreaTableStorage.LookupByKey(caster.GetAreaId()); if (casterAreaEntry != null && !casterAreaEntry.HasFlag(AreaFlags.AllowDuels)) { SendCastResult(SpellCastResult.NoDueling); // Dueling isn't allowed here return; } AreaTableRecord targetAreaEntry = CliDB.AreaTableStorage.LookupByKey(target.GetAreaId()); if (targetAreaEntry != null && !targetAreaEntry.HasFlag(AreaFlags.AllowDuels)) { SendCastResult(SpellCastResult.NoDueling); // Dueling isn't allowed here return; } //CREATE DUEL FLAG OBJECT Map map = caster.GetMap(); Position pos = new() { posX = caster.GetPositionX() + (unitTarget.GetPositionX() - caster.GetPositionX()) / 2, posY = caster.GetPositionY() + (unitTarget.GetPositionY() - caster.GetPositionY()) / 2, posZ = caster.GetPositionZ(), Orientation = caster.GetOrientation() }; Quaternion rotation = Quaternion.CreateFromRotationMatrix(Extensions.fromEulerAnglesZYX(pos.GetOrientation(), 0.0f, 0.0f)); GameObject go = GameObject.CreateGameObject((uint)effectInfo.MiscValue, map, pos, rotation, 0, GameObjectState.Ready); if (!go) return; PhasingHandler.InheritPhaseShift(go, caster); go.SetFaction(caster.GetFaction()); go.SetLevel(caster.GetLevel() + 1); int duration = m_spellInfo.CalcDuration(caster); go.SetRespawnTime(duration > 0 ? duration / Time.InMilliseconds : 0); go.SetSpellId(m_spellInfo.Id); ExecuteLogEffectSummonObject(effectInfo.Effect, go); caster.AddGameObject(go); map.AddToMap(go); //END // Send request DuelRequested packet = new(); packet.ArbiterGUID = go.GetGUID(); packet.RequestedByGUID = caster.GetGUID(); packet.RequestedByWowAccount = caster.GetSession().GetAccountGUID(); caster.SendPacket(packet); target.SendPacket(packet); // create duel-info bool isMounted = (GetSpellInfo().Id == 62875); caster.duel = new(target, caster, isMounted); target.duel = new(caster, caster, isMounted); caster.SetDuelArbiter(go.GetGUID()); target.SetDuelArbiter(go.GetGUID()); Global.ScriptMgr.OnPlayerDuelRequest(target, caster); } [SpellEffectHandler(SpellEffectName.Stuck)] void EffectStuck() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; if (!WorldConfig.GetBoolValue(WorldCfg.CastUnstuck)) return; Player player = m_caster.ToPlayer(); if (player == null) return; Log.outDebug(LogFilter.Spells, "Spell Effect: Stuck"); Log.outInfo(LogFilter.Spells, "Player {0} (guid {1}) used auto-unstuck future at map {2} ({3}, {4}, {5})", player.GetName(), player.GetGUID().ToString(), player.GetMapId(), player.GetPositionX(), player.GetPositionY(), player.GetPositionZ()); if (player.IsInFlight()) return; // if player is dead without death timer is teleported to graveyard, otherwise not apply the effect if (player.IsDead()) { if (player.GetDeathTimer() == 0) player.RepopAtGraveyard(); return; } // the player dies if hearthstone is in cooldown, else the player is teleported to home if (player.GetSpellHistory().HasCooldown(8690)) { player.KillSelf(); return; } player.TeleportTo(player.GetHomebind(), TeleportToOptions.Spell); // Stuck spell trigger Hearthstone cooldown SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(8690, GetCastDifficulty()); if (spellInfo == null) return; Spell spell = new(player, spellInfo, TriggerCastFlags.FullMask); spell.SendSpellCooldown(); } [SpellEffectHandler(SpellEffectName.SummonPlayer)] void EffectSummonPlayer() { // workaround - this effect should not use target map if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; unitTarget.ToPlayer().SendSummonRequestFrom(unitCaster); } [SpellEffectHandler(SpellEffectName.ActivateObject)] void EffectActivateObject() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (gameObjTarget == null) return; GameObjectActions action = (GameObjectActions)effectInfo.MiscValue; switch (action) { case GameObjectActions.AnimateCustom0: case GameObjectActions.AnimateCustom1: case GameObjectActions.AnimateCustom2: case GameObjectActions.AnimateCustom3: gameObjTarget.SendCustomAnim((uint)(action - GameObjectActions.AnimateCustom0)); break; case GameObjectActions.Disturb: // What's the difference with Open? case GameObjectActions.Open: { Unit unitCaster = m_caster.ToUnit(); if (unitCaster != null) gameObjTarget.Use(unitCaster); break; } case GameObjectActions.OpenAndUnlock: { Unit unitCaster = m_caster.ToUnit(); if (unitCaster != null) gameObjTarget.UseDoorOrButton(0, false, unitCaster); gameObjTarget.RemoveFlag(GameObjectFlags.Locked); break; } case GameObjectActions.Unlock: gameObjTarget.RemoveFlag(GameObjectFlags.Locked); break; case GameObjectActions.Lock: gameObjTarget.AddFlag(GameObjectFlags.Locked); break; case GameObjectActions.Close: case GameObjectActions.Rebuild: gameObjTarget.ResetDoorOrButton(); break; case GameObjectActions.Despawn: gameObjTarget.DespawnOrUnsummon(); break; case GameObjectActions.MakeInert: gameObjTarget.AddFlag(GameObjectFlags.NotSelectable); break; case GameObjectActions.MakeActive: gameObjTarget.RemoveFlag(GameObjectFlags.NotSelectable); break; case GameObjectActions.CloseAndLock: gameObjTarget.ResetDoorOrButton(); gameObjTarget.AddFlag(GameObjectFlags.Locked); break; case GameObjectActions.Destroy: { Unit unitCaster = m_caster.ToUnit(); if (unitCaster != null) gameObjTarget.UseDoorOrButton(0, true, unitCaster); break; } case GameObjectActions.UseArtKit0: case GameObjectActions.UseArtKit1: case GameObjectActions.UseArtKit2: case GameObjectActions.UseArtKit3: case GameObjectActions.UseArtKit4: { GameObjectTemplateAddon templateAddon = gameObjTarget.GetTemplateAddon(); uint artKitIndex = action != GameObjectActions.UseArtKit4 ? (uint)(action - GameObjectActions.UseArtKit0) : 4; uint artKitValue = 0; if (templateAddon != null) artKitValue = templateAddon.ArtKits[artKitIndex]; if (artKitValue == 0) Log.outError(LogFilter.Sql, $"GameObject {gameObjTarget.GetEntry()} hit by spell {m_spellInfo.Id} needs `artkit{artKitIndex}` in `gameobject_template_addon`"); else gameObjTarget.SetGoArtKit((byte)artKitValue); break; } case GameObjectActions.GoTo1stFloor: case GameObjectActions.GoTo2ndFloor: case GameObjectActions.GoTo3rdFloor: case GameObjectActions.GoTo4thFloor: case GameObjectActions.GoTo5thFloor: case GameObjectActions.GoTo6thFloor: case GameObjectActions.GoTo7thFloor: case GameObjectActions.GoTo8thFloor: case GameObjectActions.GoTo9thFloor: case GameObjectActions.GoTo10thFloor: if (gameObjTarget.GetGoType() == GameObjectTypes.Transport) gameObjTarget.SetTransportState(GameObjectState.TransportStopped, (uint)(action - GameObjectActions.GoTo1stFloor)); else Log.outError(LogFilter.Spells, $"Spell {m_spellInfo.Id} targeted non-transport gameobject for transport only action \"Go to Floor\" {action} in effect {effectInfo.EffectIndex}"); break; case GameObjectActions.PlayAnimKit: gameObjTarget.SetAnimKitId((ushort)effectInfo.MiscValueB, false); break; case GameObjectActions.OpenAndPlayAnimKit: { Unit unitCaster = m_caster.ToUnit(); if (unitCaster != null) gameObjTarget.UseDoorOrButton(0, false, unitCaster); gameObjTarget.SetAnimKitId((ushort)effectInfo.MiscValueB, false); break; } case GameObjectActions.CloseAndPlayAnimKit: gameObjTarget.ResetDoorOrButton(); gameObjTarget.SetAnimKitId((ushort)effectInfo.MiscValueB, false); break; case GameObjectActions.PlayOneShotAnimKit: gameObjTarget.SetAnimKitId((ushort)effectInfo.MiscValueB, true); break; case GameObjectActions.StopAnimKit: gameObjTarget.SetAnimKitId(0, false); break; case GameObjectActions.OpenAndStopAnimKit: { Unit unitCaster = m_caster.ToUnit(); if (unitCaster != null) gameObjTarget.UseDoorOrButton(0, false, unitCaster); gameObjTarget.SetAnimKitId(0, false); break; } case GameObjectActions.CloseAndStopAnimKit: gameObjTarget.ResetDoorOrButton(); gameObjTarget.SetAnimKitId(0, false); break; case GameObjectActions.PlaySpellVisual: gameObjTarget.SetSpellVisualId((ushort)effectInfo.MiscValueB, m_originalCasterGUID); break; case GameObjectActions.StopSpellVisual: gameObjTarget.SetSpellVisualId(0); break; case GameObjectActions.None: Log.outFatal(LogFilter.Spells, $"Spell {m_spellInfo.Id} has action type NONE in effect {effectInfo.EffectIndex}"); break; default: Log.outFatal(LogFilter.Spells, $"Spell {m_spellInfo.Id} has unhandled action {action} in effect {effectInfo.EffectIndex}"); break; } } [SpellEffectHandler(SpellEffectName.ApplyGlyph)] void EffectApplyGlyph() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Player player = m_caster.ToPlayer(); if (player == null) return; List glyphs = player.GetGlyphs(player.GetActiveTalentGroup()); int replacedGlyph = glyphs.Count; for (int i = 0; i < glyphs.Count; ++i) { List activeGlyphBindableSpells = Global.DB2Mgr.GetGlyphBindableSpells(glyphs[i]); if (activeGlyphBindableSpells.Contains(m_misc.SpellId)) { replacedGlyph = i; player.RemoveAurasDueToSpell(CliDB.GlyphPropertiesStorage.LookupByKey(glyphs[i]).SpellID); break; } } uint glyphId = (uint)effectInfo.MiscValue; if (replacedGlyph < glyphs.Count) { if (glyphId != 0) glyphs[replacedGlyph] = glyphId; else glyphs.RemoveAt(replacedGlyph); } else if (glyphId != 0) glyphs.Add(glyphId); player.RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2.ChangeGlyph); GlyphPropertiesRecord glyphProperties = CliDB.GlyphPropertiesStorage.LookupByKey(glyphId); if (glyphProperties != null) player.CastSpell(player, glyphProperties.SpellID, new CastSpellExtraArgs(this)); ActiveGlyphs activeGlyphs = new(); activeGlyphs.Glyphs.Add(new GlyphBinding(m_misc.SpellId, (ushort)glyphId)); activeGlyphs.IsFullUpdate = false; player.SendPacket(activeGlyphs); } [SpellEffectHandler(SpellEffectName.EnchantHeldItem)] void EffectEnchantHeldItem() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; // this is only item spell effect applied to main-hand weapon of target player (players in area) if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; Player item_owner = unitTarget.ToPlayer(); Item item = item_owner.GetItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand); if (item == null) return; // must be equipped if (!item.IsEquipped()) return; if (effectInfo.MiscValue != 0) { uint enchant_id = (uint)effectInfo.MiscValue; int duration = m_spellInfo.GetDuration(); //Try duration index first .. if (duration == 0) duration = damage;//+1; //Base points after .. if (duration == 0) duration = 10 * Time.InMilliseconds; //10 seconds for enchants which don't have listed duration if (m_spellInfo.Id == 14792) // Venomhide Poison duration = 5 * Time.Minute * Time.InMilliseconds; SpellItemEnchantmentRecord pEnchant = CliDB.SpellItemEnchantmentStorage.LookupByKey(enchant_id); if (pEnchant == null) return; // Always go to temp enchantment slot EnchantmentSlot slot = EnchantmentSlot.Temp; // Enchantment will not be applied if a different one already exists if (item.GetEnchantmentId(slot) != 0 && item.GetEnchantmentId(slot) != enchant_id) return; // Apply the temporary enchantment item.SetEnchantment(slot, enchant_id, (uint)duration, 0, m_caster.GetGUID()); item_owner.ApplyEnchantment(item, slot, true); } } [SpellEffectHandler(SpellEffectName.Disenchant)] void EffectDisEnchant() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Player caster = m_caster.ToPlayer(); if (caster != null) { caster.UpdateCraftSkill(m_spellInfo.Id); caster.SendLoot(itemTarget.GetGUID(), LootType.Disenchanting); } // item will be removed at disenchanting end } [SpellEffectHandler(SpellEffectName.Inebriate)] void EffectInebriate() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; Player player = unitTarget.ToPlayer(); byte currentDrunk = player.GetDrunkValue(); int drunkMod = damage; if (currentDrunk + drunkMod > 100) { currentDrunk = 100; if (RandomHelper.randChance() < 25.0f) player.CastSpell(player, 67468, new CastSpellExtraArgs().SetTriggeringSpell(this)); // Drunken Vomit } else currentDrunk += (byte)drunkMod; player.SetDrunkValue(currentDrunk, m_CastItem != null ? m_CastItem.GetEntry() : 0); } [SpellEffectHandler(SpellEffectName.FeedPet)] void EffectFeedPet() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Player player = m_caster.ToPlayer(); if (player == null) return; Item foodItem = itemTarget; if (foodItem == null) return; Pet pet = player.GetPet(); if (pet == null) return; if (!pet.IsAlive()) return; ExecuteLogEffectDestroyItem(effectInfo.Effect, foodItem.GetEntry()); int pct; int levelDiff = (int)pet.GetLevel() - (int)foodItem.GetTemplate().GetBaseItemLevel(); if (levelDiff >= 30) return; else if (levelDiff >= 20) pct = (int)12.5; // we can't pass double so keeping the cast here for future references else if (levelDiff >= 10) pct = 25; else pct = 50; uint count = 1; player.DestroyItemCount(foodItem, ref count, true); // @todo fix crash when a spell has two effects, both pointed at the same item target CastSpellExtraArgs args = new(TriggerCastFlags.FullMask); args.SetTriggeringSpell(this); args.AddSpellMod(SpellValueMod.BasePoint0, pct); m_caster.CastSpell(pet, effectInfo.TriggerSpell, args); } [SpellEffectHandler(SpellEffectName.DismissPet)] void EffectDismissPet() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsPet()) return; Pet pet = unitTarget.ToPet(); ExecuteLogEffectUnsummonObject(effectInfo.Effect, pet); pet.Remove(PetSaveMode.NotInSlot); } [SpellEffectHandler(SpellEffectName.SummonObjectSlot1)] void EffectSummonObject() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; byte slot = (byte)(effectInfo.Effect - SpellEffectName.SummonObjectSlot1); ObjectGuid guid = unitCaster.m_ObjectSlot[slot]; if (!guid.IsEmpty()) { GameObject obj = unitCaster.GetMap().GetGameObject(guid); if (obj != null) { // Recast case - null spell id to make auras not be removed on object remove from world if (m_spellInfo.Id == obj.GetSpellId()) obj.SetSpellId(0); unitCaster.RemoveGameObject(obj, true); } unitCaster.m_ObjectSlot[slot].Clear(); } float x, y, z, o; // If dest location if present if (m_targets.HasDst()) destTarget.GetPosition(out x, out y, out z, out o); // Summon in random point all other units if location present else { unitCaster.GetClosePoint(out x, out y, out z, SharedConst.DefaultPlayerBoundingRadius); o = unitCaster.GetOrientation(); } Map map = m_caster.GetMap(); Position pos = new(x, y, z, o); Quaternion rotation = Quaternion.CreateFromRotationMatrix(Extensions.fromEulerAnglesZYX(o, 0.0f, 0.0f)); GameObject go = GameObject.CreateGameObject((uint)effectInfo.MiscValue, map, pos, rotation, 255, GameObjectState.Ready); if (!go) return; PhasingHandler.InheritPhaseShift(go, m_caster); go.SetFaction(unitCaster.GetFaction()); go.SetLevel(unitCaster.GetLevel()); int duration = m_spellInfo.CalcDuration(m_caster); go.SetRespawnTime(duration > 0 ? duration / Time.InMilliseconds : 0); go.SetSpellId(m_spellInfo.Id); unitCaster.AddGameObject(go); ExecuteLogEffectSummonObject(effectInfo.Effect, go); map.AddToMap(go); unitCaster.m_ObjectSlot[slot] = go.GetGUID(); } [SpellEffectHandler(SpellEffectName.Resurrect)] void EffectResurrect() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (corpseTarget == null && unitTarget == null) return; Player player = null; if (corpseTarget) player = Global.ObjAccessor.FindPlayer(corpseTarget.GetOwnerGUID()); else if (unitTarget) player = unitTarget.ToPlayer(); if (player == null || player.IsAlive() || !player.IsInWorld) return; if (player.IsResurrectRequested()) // already have one active request return; uint health = (uint)player.CountPctFromMaxHealth(damage); uint mana = (uint)MathFunctions.CalculatePct(player.GetMaxPower(PowerType.Mana), damage); ExecuteLogEffectResurrect(effectInfo.Effect, player); player.SetResurrectRequestData(m_caster, health, mana, 0); SendResurrectRequest(player); } [SpellEffectHandler(SpellEffectName.AddExtraAttacks)] void EffectAddExtraAttacks() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget || !unitTarget.IsAlive()) return; if (unitTarget.ExtraAttacks != 0) return; unitTarget.ExtraAttacks = (uint)damage; ExecuteLogEffectExtraAttacks(effectInfo.Effect, unitTarget, (uint)damage); } [SpellEffectHandler(SpellEffectName.Parry)] void EffectParry() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; if (m_caster.IsTypeId(TypeId.Player)) m_caster.ToPlayer().SetCanParry(true); } [SpellEffectHandler(SpellEffectName.Block)] void EffectBlock() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; if (m_caster.IsTypeId(TypeId.Player)) m_caster.ToPlayer().SetCanBlock(true); } [SpellEffectHandler(SpellEffectName.Leap)] void EffectLeap() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || unitTarget.IsInFlight()) return; if (!m_targets.HasDst()) return; Position pos = destTarget.GetPosition(); unitTarget.NearTeleportTo(pos.posX, pos.posY, pos.posZ, pos.Orientation, unitTarget == m_caster); } [SpellEffectHandler(SpellEffectName.Reputation)] void EffectReputation() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; Player player = unitTarget.ToPlayer(); int repChange = damage; int factionId = effectInfo.MiscValue; FactionRecord factionEntry = CliDB.FactionStorage.LookupByKey(factionId); if (factionEntry == null) return; repChange = player.CalculateReputationGain(ReputationSource.Spell, 0, repChange, factionId); player.GetReputationMgr().ModifyReputation(factionEntry, repChange); } [SpellEffectHandler(SpellEffectName.QuestComplete)] void EffectQuestComplete() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; Player player = unitTarget.ToPlayer(); uint questId = (uint)effectInfo.MiscValue; if (questId != 0) { Quest quest = Global.ObjectMgr.GetQuestTemplate(questId); if (quest == null) return; ushort logSlot = player.FindQuestSlot(questId); if (logSlot < SharedConst.MaxQuestLogSize) player.AreaExploredOrEventHappens(questId); else if (quest.HasFlag(QuestFlags.Tracking)) // Check if the quest is used as a serverside flag. player.SetRewardedQuest(questId); // If so, set status to rewarded without broadcasting it to client. } } [SpellEffectHandler(SpellEffectName.ForceDeselect)] void EffectForceDeselect() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; float dist = m_caster.GetVisibilityRange(); // clear focus PacketSenderOwning breakTarget = new(); breakTarget.Data.UnitGUID = m_caster.GetGUID(); breakTarget.Data.Write(); var notifierBreak = new MessageDistDelivererToHostile>(unitCaster, breakTarget, dist); Cell.VisitWorldObjects(m_caster, notifierBreak, dist); // and selection PacketSenderOwning clearTarget = new(); clearTarget.Data.Guid = m_caster.GetGUID(); clearTarget.Data.Write(); var notifierClear = new MessageDistDelivererToHostile>(unitCaster, clearTarget, dist); Cell.VisitWorldObjects(m_caster, notifierClear, dist); // we should also force pets to remove us from current target List attackerSet = new(); foreach (var unit in unitCaster.GetAttackers()) if (unit.GetTypeId() == TypeId.Unit && !unit.CanHaveThreatList()) attackerSet.Add(unit); foreach (var unit in attackerSet) unit.AttackStop(); } [SpellEffectHandler(SpellEffectName.SelfResurrect)] void EffectSelfResurrect() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Player player = m_caster.ToPlayer(); if (player == null || !player.IsInWorld || player.IsAlive()) return; uint health; int mana = 0; // flat case if (damage < 0) { health = (uint)-damage; mana = effectInfo.MiscValue; } // percent case else { health = (uint)player.CountPctFromMaxHealth(damage); if (player.GetMaxPower(PowerType.Mana) > 0) mana = MathFunctions.CalculatePct(player.GetMaxPower(PowerType.Mana), damage); } player.ResurrectPlayer(0.0f); player.SetHealth(health); player.SetPower(PowerType.Mana, mana); player.SetPower(PowerType.Rage, 0); player.SetFullPower(PowerType.Energy); player.SetPower(PowerType.Focus, 0); player.SpawnCorpseBones(); } [SpellEffectHandler(SpellEffectName.Skinning)] void EffectSkinning() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget.IsTypeId(TypeId.Unit)) return; Player player = m_caster.ToPlayer(); if (player == null) return; Creature creature = unitTarget.ToCreature(); int targetLevel = (int)creature.GetLevelForTarget(m_caster); SkillType skill = creature.GetCreatureTemplate().GetRequiredLootSkill(); creature.RemoveUnitFlag(UnitFlags.Skinnable); creature.AddDynamicFlag(UnitDynFlags.Lootable); player.SendLoot(creature.GetGUID(), LootType.Skinning); if (skill == SkillType.Skinning) { int reqValue; if (targetLevel <= 10) reqValue = 1; else if (targetLevel < 20) reqValue = (targetLevel - 10) * 10; else if (targetLevel <= 73) reqValue = targetLevel * 5; else if (targetLevel < 80) reqValue = targetLevel * 10 - 365; else if (targetLevel <= 84) reqValue = targetLevel * 5 + 35; else if (targetLevel <= 87) reqValue = targetLevel * 15 - 805; else if (targetLevel <= 92) reqValue = (targetLevel - 62) * 20; else if (targetLevel <= 104) reqValue = targetLevel * 5 + 175; else if (targetLevel <= 107) reqValue = targetLevel * 15 - 905; else if (targetLevel <= 112) reqValue = (targetLevel - 72) * 20; else if (targetLevel <= 122) reqValue = (targetLevel - 32) * 10; else reqValue = 900; // TODO: Specialize skillid for each expansion // new db field? // tied to one of existing expansion fields in creature_template? // Double chances for elites m_caster.ToPlayer().UpdateGatherSkill(skill, (uint)damage, (uint)reqValue, (uint)(creature.IsElite() ? 2 : 1)); } } [SpellEffectHandler(SpellEffectName.Charge)] void EffectCharge() { if (unitTarget == null) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; if (effectHandleMode == SpellEffectHandleMode.LaunchTarget) { // charge changes fall time if (unitCaster.IsPlayer()) unitCaster.ToPlayer().SetFallInformation(0, m_caster.GetPositionZ()); float speed = MathFunctions.fuzzyGt(m_spellInfo.Speed, 0.0f) ? m_spellInfo.Speed : MotionMaster.SPEED_CHARGE; SpellEffectExtraData spellEffectExtraData = null; if (effectInfo.MiscValueB != 0) { spellEffectExtraData = new SpellEffectExtraData(); spellEffectExtraData.Target = unitTarget.GetGUID(); spellEffectExtraData.SpellVisualId = (uint)effectInfo.MiscValueB; } // Spell is not using explicit target - no generated path if (m_preGeneratedPath == null) { Position pos = unitTarget.GetFirstCollisionPosition(unitTarget.GetCombatReach(), unitTarget.GetRelativeAngle(m_caster.GetPosition())); if (MathFunctions.fuzzyGt(m_spellInfo.Speed, 0.0f) && m_spellInfo.HasAttribute(SpellAttr9.SpecialDelayCalculation)) speed = pos.GetExactDist(m_caster) / speed; unitCaster.GetMotionMaster().MoveCharge(pos.posX, pos.posY, pos.posZ, speed, EventId.Charge, false, unitTarget, spellEffectExtraData); } else { if (MathFunctions.fuzzyGt(m_spellInfo.Speed, 0.0f) && m_spellInfo.HasAttribute(SpellAttr9.SpecialDelayCalculation)) { Vector3 pos = m_preGeneratedPath.GetActualEndPosition(); speed = new Position(pos.X, pos.Y, pos.Z).GetExactDist(m_caster) / speed; } unitCaster.GetMotionMaster().MoveCharge(m_preGeneratedPath, speed, unitTarget, spellEffectExtraData); } } if (effectHandleMode == SpellEffectHandleMode.HitTarget) { // not all charge effects used in negative spells if (!m_spellInfo.IsPositive() && m_caster.IsTypeId(TypeId.Player)) unitCaster.Attack(unitTarget, true); if (effectInfo.TriggerSpell != 0) m_caster.CastSpell(unitTarget, effectInfo.TriggerSpell, new CastSpellExtraArgs(TriggerCastFlags.FullMask) .SetOriginalCaster(m_originalCasterGUID) .SetTriggeringSpell(this)); } } [SpellEffectHandler(SpellEffectName.ChargeDest)] void EffectChargeDest() { if (destTarget == null) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; if (effectHandleMode == SpellEffectHandleMode.Launch) { Position pos = destTarget.GetPosition(); if (!unitCaster.IsWithinLOS(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ())) { float angle = unitCaster.GetRelativeAngle(pos.posX, pos.posY); float dist = unitCaster.GetDistance(pos); pos = unitCaster.GetFirstCollisionPosition(dist, angle); } unitCaster.GetMotionMaster().MoveCharge(pos.posX, pos.posY, pos.posZ); } else if (effectHandleMode == SpellEffectHandleMode.Hit) { if (effectInfo.TriggerSpell != 0) m_caster.CastSpell(destTarget, effectInfo.TriggerSpell, new CastSpellExtraArgs(TriggerCastFlags.FullMask) .SetOriginalCaster(m_originalCasterGUID) .SetTriggeringSpell(this)); } } [SpellEffectHandler(SpellEffectName.KnockBack)] [SpellEffectHandler(SpellEffectName.KnockBackDest)] void EffectKnockBack() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget) return; if (m_caster.GetAffectingPlayer()) { Creature creatureTarget = unitTarget.ToCreature(); if (creatureTarget != null) if (creatureTarget.IsWorldBoss() || creatureTarget.IsDungeonBoss()) return; } // Spells with SPELL_EFFECT_KNOCK_BACK (like Thunderstorm) can't knockback target if target has ROOT/STUN if (unitTarget.HasUnitState(UnitState.Root | UnitState.Stunned)) return; // Instantly interrupt non melee spells being casted if (unitTarget.IsNonMeleeSpellCast(true)) unitTarget.InterruptNonMeleeSpells(true); float ratio = 0.1f; float speedxy = effectInfo.MiscValue * ratio; float speedz = damage * ratio; if (speedxy < 0.01f && speedz < 0.01f) return; Position origin; if (effectInfo.Effect == SpellEffectName.KnockBackDest) { if (m_targets.HasDst()) origin = new(destTarget.GetPosition()); else return; } else //if (effectInfo.Effect == SPELL_EFFECT_KNOCK_BACK) origin = new(m_caster.GetPosition()); unitTarget.KnockbackFrom(origin, speedxy, speedz); Unit.ProcSkillsAndAuras(GetUnitCasterForEffectHandlers(), unitTarget, new ProcFlagsInit(ProcFlags.None), new ProcFlagsInit(ProcFlags.None, ProcFlags2.Knockback), ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.Hit, ProcFlagsHit.None, null, null, null); } [SpellEffectHandler(SpellEffectName.LeapBack)] void EffectLeapBack() { if (effectHandleMode != SpellEffectHandleMode.LaunchTarget) return; if (unitTarget == null) return; float speedxy = effectInfo.MiscValue / 10.0f; float speedz = damage / 10.0f; // Disengage unitTarget.JumpTo(speedxy, speedz, effectInfo.PositionFacing); // changes fall time if (m_caster.GetTypeId() == TypeId.Player) m_caster.ToPlayer().SetFallInformation(0, m_caster.GetPositionZ()); } [SpellEffectHandler(SpellEffectName.ClearQuest)] void EffectQuestClear() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; Player player = unitTarget.ToPlayer(); uint quest_id = (uint)effectInfo.MiscValue; Quest quest = Global.ObjectMgr.GetQuestTemplate(quest_id); if (quest == null) return; QuestStatus oldStatus = player.GetQuestStatus(quest_id); // Player has never done this quest if (oldStatus == QuestStatus.None) return; // remove all quest entries for 'entry' from quest log for (byte slot = 0; slot < SharedConst.MaxQuestLogSize; ++slot) { uint logQuest = player.GetQuestSlotQuestId(slot); if (logQuest == quest_id) { player.SetQuestSlot(slot, 0); // we ignore unequippable quest items in this case, it's still be equipped player.TakeQuestSourceItem(logQuest, false); if (quest.HasFlag(QuestFlags.Pvp)) { player.pvpInfo.IsHostile = player.pvpInfo.IsInHostileArea || player.HasPvPForcingQuest(); player.UpdatePvPState(); } } } player.RemoveActiveQuest(quest_id, false); player.RemoveRewardedQuest(quest_id); Global.ScriptMgr.OnQuestStatusChange(player, quest_id); Global.ScriptMgr.OnQuestStatusChange(player, quest, oldStatus, QuestStatus.None); } [SpellEffectHandler(SpellEffectName.SendTaxi)] void EffectSendTaxi() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; unitTarget.ToPlayer().ActivateTaxiPathTo((uint)effectInfo.MiscValue, m_spellInfo.Id); } [SpellEffectHandler(SpellEffectName.PullTowards)] void EffectPullTowards() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget) return; Position pos = m_caster.GetFirstCollisionPosition(m_caster.GetCombatReach(), m_caster.GetRelativeAngle(unitTarget)); // This is a blizzlike mistake: this should be 2D distance according to projectile motion formulas, but Blizzard erroneously used 3D distance. float distXY = unitTarget.GetExactDist(pos); // Avoid division by 0 if (distXY < 0.001) return; float distZ = pos.GetPositionZ() - unitTarget.GetPositionZ(); float speedXY = effectInfo.MiscValue != 0 ? effectInfo.MiscValue / 10.0f : 30.0f; float speedZ = (float)((2 * speedXY * speedXY * distZ + MotionMaster.gravity * distXY * distXY) / (2 * speedXY * distXY)); if (!float.IsFinite(speedZ)) { Log.outError(LogFilter.Spells, $"Spell {m_spellInfo.Id} with SPELL_EFFECT_PULL_TOWARDS called with invalid speedZ. {GetDebugInfo()}"); return; } unitTarget.JumpTo(speedXY, speedZ, 0.0f, pos); } [SpellEffectHandler(SpellEffectName.PullTowardsDest)] void EffectPullTowardsDest() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget) return; if (!m_targets.HasDst()) { Log.outError(LogFilter.Spells, $"Spell {m_spellInfo.Id} with SPELL_EFFECT_PULL_TOWARDS_DEST has no dest target"); return; } Position pos = m_targets.GetDstPos(); // This is a blizzlike mistake: this should be 2D distance according to projectile motion formulas, but Blizzard erroneously used 3D distance float distXY = unitTarget.GetExactDist(pos); // Avoid division by 0 if (distXY < 0.001) return; float distZ = pos.GetPositionZ() - unitTarget.GetPositionZ(); float speedXY = effectInfo.MiscValue != 0 ? effectInfo.MiscValue / 10.0f : 30.0f; float speedZ = (float)((2 * speedXY * speedXY * distZ + MotionMaster.gravity * distXY * distXY) / (2 * speedXY * distXY)); if (!float.IsFinite(speedZ)) { Log.outError(LogFilter.Spells, $"Spell {m_spellInfo.Id} with SPELL_EFFECT_PULL_TOWARDS_DEST called with invalid speedZ. {GetDebugInfo()}"); return; } unitTarget.JumpTo(speedXY, speedZ, 0.0f, pos); } [SpellEffectHandler(SpellEffectName.ChangeRaidMarker)] void EffectChangeRaidMarker() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Player player = m_caster.ToPlayer(); if (!player || !m_targets.HasDst()) return; Group group = player.GetGroup(); if (!group || (group.IsRaidGroup() && !group.IsLeader(player.GetGUID()) && !group.IsAssistant(player.GetGUID()))) return; float x, y, z; destTarget.GetPosition(out x, out y, out z); group.AddRaidMarker((byte)damage, player.GetMapId(), x, y, z); } [SpellEffectHandler(SpellEffectName.DispelMechanic)] void EffectDispelMechanic() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null) return; int mechanic = effectInfo.MiscValue; List> dispel_list = new(); var auras = unitTarget.GetOwnedAuras(); foreach (var pair in auras) { Aura aura = pair.Value; if (aura.GetApplicationOfTarget(unitTarget.GetGUID()) == null) continue; if (RandomHelper.randChance(aura.CalcDispelChance(unitTarget, !unitTarget.IsFriendlyTo(m_caster)))) if (Convert.ToBoolean(aura.GetSpellInfo().GetAllEffectsMechanicMask() & (1 << mechanic))) dispel_list.Add(new KeyValuePair(aura.GetId(), aura.GetCasterGUID())); } while (!dispel_list.Empty()) { unitTarget.RemoveAura(dispel_list[0].Key, dispel_list[0].Value, 0, AuraRemoveMode.EnemySpell); dispel_list.RemoveAt(0); } } [SpellEffectHandler(SpellEffectName.ResurrectPet)] void EffectResurrectPet() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; if (damage < 0) return; Player player = m_caster.ToPlayer(); if (player == null) return; // Maybe player dismissed dead pet or pet despawned? bool hadPet = true; if (player.GetPet() == null) { PetStable petStable = player.GetPetStable(); var deadPetIndex = Array.FindIndex(petStable.ActivePets, petInfo => petInfo?.Health == 0); PetSaveMode slot = (PetSaveMode)deadPetIndex; player.SummonPet(0, slot, 0f, 0f, 0f, 0f, 0); hadPet = false; } // TODO: Better to fail Hunter's "Revive Pet" at cast instead of here when casting ends Pet pet = player.GetPet(); // Attempt to get current pet if (pet == null || pet.IsAlive()) return; // If player did have a pet before reviving, teleport it if (hadPet) { // Reposition the pet's corpse before reviving so as not to grab aggro // We can use a different, more accurate version of GetClosePoint() since we have a pet // Will be used later to reposition the pet if we have one player.GetClosePoint(out float x, out float y, out float z, pet.GetCombatReach(), SharedConst.PetFollowDist, pet.GetFollowAngle()); pet.NearTeleportTo(x, y, z, player.GetOrientation()); pet.Relocate(x, y, z, player.GetOrientation()); // This is needed so SaveStayPosition() will get the proper coords. } pet.SetDynamicFlags(UnitDynFlags.None); pet.RemoveUnitFlag(UnitFlags.Skinnable); pet.SetDeathState(DeathState.Alive); pet.ClearUnitState(UnitState.AllErasable); pet.SetHealth(pet.CountPctFromMaxHealth(damage)); // Reset things for when the AI to takes over CharmInfo ci = pet.GetCharmInfo(); if (ci != null) { // In case the pet was at stay, we don't want it running back ci.SaveStayPosition(); ci.SetIsAtStay(ci.HasCommandState(CommandStates.Stay)); ci.SetIsFollowing(false); ci.SetIsCommandAttack(false); ci.SetIsCommandFollow(false); ci.SetIsReturning(false); } pet.SavePetToDB(PetSaveMode.AsCurrent); } [SpellEffectHandler(SpellEffectName.DestroyAllTotems)] void EffectDestroyAllTotems() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; int mana = 0; for (byte slot = (int)SummonSlot.Totem; slot < SharedConst.MaxTotemSlot; ++slot) { if (unitCaster.m_SummonSlot[slot].IsEmpty()) continue; Creature totem = unitCaster.GetMap().GetCreature(unitCaster.m_SummonSlot[slot]); if (totem != null && totem.IsTotem()) { uint spell_id = totem.m_unitData.CreatedBySpell; SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spell_id, GetCastDifficulty()); if (spellInfo != null) { var costs = spellInfo.CalcPowerCost(unitCaster, spellInfo.GetSchoolMask()); var m = costs.Find(cost => cost.Power == PowerType.Mana); if (m != null) mana += m.Amount; } totem.ToTotem().UnSummon(); } } MathFunctions.ApplyPct(ref mana, damage); if (mana != 0) { CastSpellExtraArgs args = new(TriggerCastFlags.FullMask); args.SetTriggeringSpell(this); args.AddSpellMod(SpellValueMod.BasePoint0, mana); unitCaster.CastSpell(m_caster, 39104, args); } } [SpellEffectHandler(SpellEffectName.DurabilityDamage)] void EffectDurabilityDamage() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; int slot = effectInfo.MiscValue; // -1 means all player equipped items and -2 all items if (slot < 0) { unitTarget.ToPlayer().DurabilityPointsLossAll(damage, (slot < -1)); ExecuteLogEffectDurabilityDamage(effectInfo.Effect, unitTarget, -1, -1); return; } // invalid slot value if (slot >= InventorySlots.BagEnd) return; Item item = unitTarget.ToPlayer().GetItemByPos(InventorySlots.Bag0, (byte)slot); if (item != null) { unitTarget.ToPlayer().DurabilityPointsLoss(item, damage); ExecuteLogEffectDurabilityDamage(effectInfo.Effect, unitTarget, (int)item.GetEntry(), slot); } } [SpellEffectHandler(SpellEffectName.DurabilityDamagePct)] void EffectDurabilityDamagePCT() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; int slot = effectInfo.MiscValue; // FIXME: some spells effects have value -1/-2 // Possibly its mean -1 all player equipped items and -2 all items if (slot < 0) { unitTarget.ToPlayer().DurabilityLossAll(damage / 100.0f, (slot < -1)); return; } // invalid slot value if (slot >= InventorySlots.BagEnd) return; if (damage <= 0) return; Item item = unitTarget.ToPlayer().GetItemByPos(InventorySlots.Bag0, (byte)slot); if (item != null) unitTarget.ToPlayer().DurabilityLoss(item, damage / 100.0f); } [SpellEffectHandler(SpellEffectName.ModifyThreatPercent)] void EffectModifyThreatPercent() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null || unitTarget == null) return; unitTarget.GetThreatManager().ModifyThreatByPercent(unitCaster, damage); } [SpellEffectHandler(SpellEffectName.TransDoor)] void EffectTransmitted() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; uint name_id = (uint)effectInfo.MiscValue; var overrideSummonedGameObjects = unitCaster.GetAuraEffectsByType(AuraType.OverrideSummonedObject); foreach (AuraEffect aurEff in overrideSummonedGameObjects) { if (aurEff.GetMiscValue() == name_id) { name_id = (uint)aurEff.GetMiscValueB(); break; } } GameObjectTemplate goinfo = Global.ObjectMgr.GetGameObjectTemplate(name_id); if (goinfo == null) { Log.outError(LogFilter.Sql, "Gameobject (Entry: {0}) not exist and not created at spell (ID: {1}) cast", name_id, m_spellInfo.Id); return; } float fx, fy, fz, fo; if (m_targets.HasDst()) destTarget.GetPosition(out fx, out fy, out fz, out fo); //FIXME: this can be better check for most objects but still hack else if (effectInfo.HasRadius() && m_spellInfo.Speed == 0) { float dis = effectInfo.CalcRadius(unitCaster); unitCaster.GetClosePoint(out fx, out fy, out fz, SharedConst.DefaultPlayerBoundingRadius, dis); fo = unitCaster.GetOrientation(); } else { //GO is always friendly to it's creator, get range for friends float min_dis = m_spellInfo.GetMinRange(true); float max_dis = m_spellInfo.GetMaxRange(true); float dis = (float)RandomHelper.NextDouble() * (max_dis - min_dis) + min_dis; unitCaster.GetClosePoint(out fx, out fy, out fz, SharedConst.DefaultPlayerBoundingRadius, dis); fo = unitCaster.GetOrientation(); } Map cMap = unitCaster.GetMap(); // if gameobject is summoning object, it should be spawned right on caster's position if (goinfo.type == GameObjectTypes.Ritual) unitCaster.GetPosition(out fx, out fy, out fz, out fo); Position pos = new(fx, fy, fz, fo); Quaternion rotation = Quaternion.CreateFromRotationMatrix(Extensions.fromEulerAnglesZYX(fo, 0.0f, 0.0f)); GameObject go = GameObject.CreateGameObject(name_id, cMap, pos, rotation, 255, GameObjectState.Ready); if (!go) return; PhasingHandler.InheritPhaseShift(go, m_caster); int duration = m_spellInfo.CalcDuration(m_caster); switch (goinfo.type) { case GameObjectTypes.FishingNode: { go.SetFaction(unitCaster.GetFaction()); ObjectGuid bobberGuid = go.GetGUID(); // client requires fishing bobber guid in channel object slot 0 to be usable unitCaster.SetChannelObject(0, bobberGuid); unitCaster.AddGameObject(go); // will removed at spell cancel // end time of range when possible catch fish (FISHING_BOBBER_READY_TIME..GetDuration(m_spellInfo)) // start time == fish-FISHING_BOBBER_READY_TIME (0..GetDuration(m_spellInfo)-FISHING_BOBBER_READY_TIME) int lastSec = 0; switch (RandomHelper.IRand(0, 2)) { case 0: lastSec = 3; break; case 1: lastSec = 7; break; case 2: lastSec = 13; break; } // Duration of the fishing bobber can't be higher than the Fishing channeling duration duration = Math.Min(duration, duration - lastSec * Time.InMilliseconds + 5 * Time.InMilliseconds); break; } case GameObjectTypes.Ritual: { if (unitCaster.IsPlayer()) { go.AddUniqueUse(unitCaster.ToPlayer()); unitCaster.AddGameObject(go); // will be removed at spell cancel } break; } case GameObjectTypes.DuelArbiter: // 52991 unitCaster.AddGameObject(go); break; case GameObjectTypes.FishingHole: case GameObjectTypes.Chest: default: break; } go.SetRespawnTime(duration > 0 ? duration / Time.InMilliseconds : 0); go.SetOwnerGUID(unitCaster.GetGUID()); go.SetSpellId(m_spellInfo.Id); ExecuteLogEffectSummonObject(effectInfo.Effect, go); Log.outDebug(LogFilter.Spells, "AddObject at SpellEfects.cpp EffectTransmitted"); cMap.AddToMap(go); GameObject linkedTrap = go.GetLinkedTrap(); if (linkedTrap != null) { PhasingHandler.InheritPhaseShift(linkedTrap, m_caster); linkedTrap.SetRespawnTime(duration > 0 ? duration / Time.InMilliseconds : 0); linkedTrap.SetSpellId(m_spellInfo.Id); linkedTrap.SetOwnerGUID(unitCaster.GetGUID()); ExecuteLogEffectSummonObject(effectInfo.Effect, linkedTrap); } } [SpellEffectHandler(SpellEffectName.Prospecting)] void EffectProspecting() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Player player = m_caster.ToPlayer(); if (player == null) return; if (itemTarget == null || !itemTarget.GetTemplate().HasFlag(ItemFlags.IsProspectable)) return; if (itemTarget.GetCount() < 5) return; if (WorldConfig.GetBoolValue(WorldCfg.SkillProspecting)) { uint SkillValue = player.GetPureSkillValue(SkillType.Jewelcrafting); uint reqSkillValue = itemTarget.GetTemplate().GetRequiredSkillRank(); player.UpdateGatherSkill(SkillType.Jewelcrafting, SkillValue, reqSkillValue); } player.SendLoot(itemTarget.GetGUID(), LootType.Prospecting); } [SpellEffectHandler(SpellEffectName.Milling)] void EffectMilling() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Player player = m_caster.ToPlayer(); if (player == null) return; if (itemTarget == null || !itemTarget.GetTemplate().HasFlag(ItemFlags.IsMillable)) return; if (itemTarget.GetCount() < 5) return; if (WorldConfig.GetBoolValue(WorldCfg.SkillMilling)) { uint SkillValue = player.GetPureSkillValue(SkillType.Inscription); uint reqSkillValue = itemTarget.GetTemplate().GetRequiredSkillRank(); player.UpdateGatherSkill(SkillType.Inscription, SkillValue, reqSkillValue); } player.SendLoot(itemTarget.GetGUID(), LootType.Milling); } [SpellEffectHandler(SpellEffectName.Skill)] void EffectSkill() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Log.outDebug(LogFilter.Spells, "WORLD: SkillEFFECT"); } /* There is currently no need for this effect. We handle it in Battleground.cpp If we would handle the resurrection here, the spiritguide would instantly disappear as the player revives, and so we wouldn't see the spirit heal visual effect on the npc. This is why we use a half sec delay between the visual effect and the resurrection itself */ void EffectSpiritHeal() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; } // remove insignia spell effect [SpellEffectHandler(SpellEffectName.SkinPlayerCorpse)] void EffectSkinPlayerCorpse() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Log.outDebug(LogFilter.Spells, "Effect: SkinPlayerCorpse"); Player player = m_caster.ToPlayer(); Player target = null; if (unitTarget != null) target = unitTarget.ToPlayer(); else if (corpseTarget != null) target = Global.ObjAccessor.FindPlayer(corpseTarget.GetOwnerGUID()); if (player == null || target == null || target.IsAlive()) return; target.RemovedInsignia(player); } [SpellEffectHandler(SpellEffectName.StealBeneficialBuff)] void EffectStealBeneficialBuff() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Log.outDebug(LogFilter.Spells, "Effect: StealBeneficialBuff"); if (unitTarget == null || unitTarget == m_caster) // can't steal from self return; List stealList = new(); // Create dispel mask by dispel type uint dispelMask = SpellInfo.GetDispelMask((DispelType)effectInfo.MiscValue); var auras = unitTarget.GetOwnedAuras(); foreach (var map in auras) { Aura aura = map.Value; AuraApplication aurApp = aura.GetApplicationOfTarget(unitTarget.GetGUID()); if (aurApp == null) continue; if (Convert.ToBoolean(aura.GetSpellInfo().GetDispelMask() & dispelMask)) { // Need check for passive? this if (!aurApp.IsPositive() || aura.IsPassive() || aura.GetSpellInfo().HasAttribute(SpellAttr4.NotStealable)) continue; // 2.4.3 Patch Notes: "Dispel effects will no longer attempt to remove effects that have 100% dispel resistance." int chance = aura.CalcDispelChance(unitTarget, !unitTarget.IsFriendlyTo(m_caster)); if (chance == 0) continue; // The charges / stack amounts don't count towards the total number of auras that can be dispelled. // Ie: A dispel on a target with 5 stacks of Winters Chill and a Polymorph has 1 / (1 + 1) . 50% chance to dispell // Polymorph instead of 1 / (5 + 1) . 16%. bool dispelCharges = aura.GetSpellInfo().HasAttribute(SpellAttr7.DispelCharges); byte charges = dispelCharges ? aura.GetCharges() : aura.GetStackAmount(); if (charges > 0) stealList.Add(new DispelableAura(aura, chance, charges)); } } if (stealList.Empty()) return; int remaining = stealList.Count; // Ok if exist some buffs for dispel try dispel it List> successList = new(); DispelFailed dispelFailed = new(); dispelFailed.CasterGUID = m_caster.GetGUID(); dispelFailed.VictimGUID = unitTarget.GetGUID(); dispelFailed.SpellID = m_spellInfo.Id; // dispel N = damage buffs (or while exist buffs for dispel) for (int count = 0; count < damage && remaining > 0;) { // Random select buff for dispel var dispelableAura = stealList[RandomHelper.IRand(0, remaining - 1)]; if (dispelableAura.RollDispel()) { successList.Add(Tuple.Create(dispelableAura.GetAura().GetId(), dispelableAura.GetAura().GetCasterGUID())); if (!dispelableAura.DecrementCharge()) { --remaining; stealList[remaining] = dispelableAura; } } else { dispelFailed.FailedSpells.Add(dispelableAura.GetAura().GetId()); } ++count; } if (!dispelFailed.FailedSpells.Empty()) m_caster.SendMessageToSet(dispelFailed, true); if (successList.Empty()) return; SpellDispellLog spellDispellLog = new(); spellDispellLog.IsBreak = false; // TODO: use me spellDispellLog.IsSteal = true; spellDispellLog.TargetGUID = unitTarget.GetGUID(); spellDispellLog.CasterGUID = m_caster.GetGUID(); spellDispellLog.DispelledBySpellID = m_spellInfo.Id; foreach (var dispell in successList) { var dispellData = new SpellDispellData(); dispellData.SpellID = dispell.Item1; dispellData.Harmful = false; // TODO: use me unitTarget.RemoveAurasDueToSpellBySteal(dispell.Item1, dispell.Item2, m_caster); spellDispellLog.DispellData.Add(dispellData); } m_caster.SendMessageToSet(spellDispellLog, true); } [SpellEffectHandler(SpellEffectName.KillCredit)] void EffectKillCreditPersonal() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; unitTarget.ToPlayer().KilledMonsterCredit((uint)effectInfo.MiscValue); } [SpellEffectHandler(SpellEffectName.KillCredit2)] void EffectKillCredit() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; int creatureEntry = effectInfo.MiscValue; if (creatureEntry != 0) unitTarget.ToPlayer().RewardPlayerAndGroupAtEvent((uint)creatureEntry, unitTarget); } [SpellEffectHandler(SpellEffectName.QuestFail)] void EffectQuestFail() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; unitTarget.ToPlayer().FailQuest((uint)effectInfo.MiscValue); } [SpellEffectHandler(SpellEffectName.QuestStart)] void EffectQuestStart() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget) return; Player player = unitTarget.ToPlayer(); if (!player) return; Quest quest = Global.ObjectMgr.GetQuestTemplate((uint)effectInfo.MiscValue); if (quest != null) { if (!player.CanTakeQuest(quest, false)) return; if (quest.IsAutoAccept() && player.CanAddQuest(quest, false)) { player.AddQuestAndCheckCompletion(quest, null); player.PlayerTalkClass.SendQuestGiverQuestDetails(quest, player.GetGUID(), true, true); } else player.PlayerTalkClass.SendQuestGiverQuestDetails(quest, player.GetGUID(), true, false); } } [SpellEffectHandler(SpellEffectName.CreateTamedPet)] void EffectCreateTamedPet() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player) || !unitTarget.GetPetGUID().IsEmpty() || unitTarget.GetClass() != Class.Hunter) return; uint creatureEntry = (uint)effectInfo.MiscValue; Pet pet = unitTarget.CreateTamedPetFrom(creatureEntry, m_spellInfo.Id); if (pet == null) return; // relocate float px, py, pz; unitTarget.GetClosePoint(out px, out py, out pz, pet.GetCombatReach(), SharedConst.PetFollowDist, pet.GetFollowAngle()); pet.Relocate(px, py, pz, unitTarget.GetOrientation()); // add to world pet.GetMap().AddToMap(pet.ToCreature()); // unitTarget has pet now unitTarget.SetMinion(pet, true); if (unitTarget.IsTypeId(TypeId.Player)) { pet.SavePetToDB(PetSaveMode.AsCurrent); unitTarget.ToPlayer().PetSpellInitialize(); } } [SpellEffectHandler(SpellEffectName.DiscoverTaxi)] void EffectDiscoverTaxi() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; uint nodeid = (uint)effectInfo.MiscValue; if (CliDB.TaxiNodesStorage.ContainsKey(nodeid)) unitTarget.ToPlayer().GetSession().SendDiscoverNewTaxiNode(nodeid); } [SpellEffectHandler(SpellEffectName.TitanGrip)] void EffectTitanGrip() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; if (m_caster.IsTypeId(TypeId.Player)) m_caster.ToPlayer().SetCanTitanGrip(true, (uint)effectInfo.MiscValue); } [SpellEffectHandler(SpellEffectName.RedirectThreat)] void EffectRedirectThreat() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; if (unitTarget != null) unitCaster.GetThreatManager().RegisterRedirectThreat(m_spellInfo.Id, unitTarget.GetGUID(), (uint)damage); } [SpellEffectHandler(SpellEffectName.GameObjectDamage)] void EffectGameObjectDamage() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (gameObjTarget == null) return; FactionTemplateRecord casterFaction = m_caster.GetFactionTemplateEntry(); FactionTemplateRecord targetFaction = CliDB.FactionTemplateStorage.LookupByKey(gameObjTarget.GetFaction()); // Do not allow to damage GO's of friendly factions (ie: Wintergrasp Walls/Ulduar Storm Beacons) if (targetFaction == null || (casterFaction != null && !casterFaction.IsFriendlyTo(targetFaction))) gameObjTarget.ModifyHealth(-damage, m_caster, GetSpellInfo().Id); } [SpellEffectHandler(SpellEffectName.GameobjectRepair)] void EffectGameObjectRepair() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (gameObjTarget == null) return; gameObjTarget.ModifyHealth(damage, m_caster); } [SpellEffectHandler(SpellEffectName.GameobjectSetDestructionState)] void EffectGameObjectSetDestructionState() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (gameObjTarget == null) return; gameObjTarget.SetDestructibleState((GameObjectDestructibleState)effectInfo.MiscValue, m_caster, true); } void SummonGuardian(SpellEffectInfo effect, uint entry, SummonPropertiesRecord properties, uint numGuardians, ObjectGuid privateObjectOwner) { Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; if (unitCaster.IsTotem()) unitCaster = unitCaster.ToTotem().GetOwner(); // in another case summon new float radius = 5.0f; int duration = m_spellInfo.CalcDuration(m_originalCaster); //TempSummonType summonType = (duration == 0) ? TempSummonType.DeadDespawn : TempSummonType.TimedDespawn; Map map = unitCaster.GetMap(); for (uint count = 0; count < numGuardians; ++count) { Position pos; if (count == 0) pos = destTarget; else // randomize position for multiple summons pos = unitCaster.GetRandomPoint(destTarget, radius); TempSummon summon = map.SummonCreature(entry, pos, properties, (uint)duration, unitCaster, m_spellInfo.Id, 0, privateObjectOwner); if (summon == null) return; if (summon.HasUnitTypeMask(UnitTypeMask.Guardian)) { uint level = summon.GetLevel(); if (properties != null && !properties.GetFlags().HasFlag(SummonPropertiesFlags.UseCreatureLevel)) level = unitCaster.GetLevel(); // level of pet summoned using engineering item based at engineering skill level if (m_CastItem && unitCaster.IsPlayer()) { ItemTemplate proto = m_CastItem.GetTemplate(); if (proto != null) { if (proto.GetRequiredSkill() == (uint)SkillType.Engineering) { ushort skill202 = unitCaster.ToPlayer().GetSkillValue(SkillType.Engineering); if (skill202 != 0) level = skill202 / 5u; } } } ((Guardian)summon).InitStatsForLevel(level); } if (summon.HasUnitTypeMask(UnitTypeMask.Minion) && m_targets.HasDst()) ((Minion)summon).SetFollowAngle(unitCaster.GetAbsoluteAngle(summon.GetPosition())); if (summon.GetEntry() == 27893) { VisibleItem weapon = m_caster.ToPlayer().m_playerData.VisibleItems[EquipmentSlot.MainHand]; if (weapon.ItemID != 0) { summon.SetDisplayId(11686); summon.SetVirtualItem(0, weapon.ItemID, weapon.ItemAppearanceModID, weapon.ItemVisual); } else summon.SetDisplayId(1126); } ExecuteLogEffectSummonObject(effect.Effect, summon); } } [SpellEffectHandler(SpellEffectName.AllowRenamePet)] void EffectRenamePet() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Unit) || !unitTarget.IsPet() || unitTarget.ToPet().GetPetType() != PetType.Hunter) return; unitTarget.AddPetFlag(UnitPetFlags.CanBeRenamed); } [SpellEffectHandler(SpellEffectName.PlayMusic)] void EffectPlayMusic() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; uint soundid = (uint)effectInfo.MiscValue; if (!CliDB.SoundKitStorage.ContainsKey(soundid)) { Log.outError(LogFilter.Spells, "EffectPlayMusic: Sound (Id: {0}) not exist in spell {1}.", soundid, m_spellInfo.Id); return; } unitTarget.ToPlayer().SendPacket(new PlayMusic(soundid)); } [SpellEffectHandler(SpellEffectName.TalentSpecSelect)] void EffectActivateSpec() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; Player player = unitTarget.ToPlayer(); uint specID = m_misc.SpecializationId; ChrSpecializationRecord spec = CliDB.ChrSpecializationStorage.LookupByKey(specID); // Safety checks done in Spell::CheckCast if (!spec.IsPetSpecialization()) player.ActivateTalentGroup(spec); else player.GetPet().SetSpecialization(specID); } [SpellEffectHandler(SpellEffectName.PlaySound)] void EffectPlaySound() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget) return; Player player = unitTarget.ToPlayer(); if (!player) return; switch (m_spellInfo.Id) { case 91604: // Restricted Flight Area player.GetSession().SendNotification(CypherStrings.ZoneNoflyzone); break; default: break; } uint soundId = (uint)effectInfo.MiscValue; if (!CliDB.SoundKitStorage.ContainsKey(soundId)) { Log.outError(LogFilter.Spells, "EffectPlaySound: Sound (Id: {0}) not exist in spell {1}.", soundId, m_spellInfo.Id); return; } player.PlayDirectSound(soundId, player); } [SpellEffectHandler(SpellEffectName.RemoveAura)] [SpellEffectHandler(SpellEffectName.RemoveAura2)] void EffectRemoveAura() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null) return; // there may be need of specifying casterguid of removed auras unitTarget.RemoveAurasDueToSpell(effectInfo.TriggerSpell); } [SpellEffectHandler(SpellEffectName.DamageFromMaxHealthPCT)] void EffectDamageFromMaxHealthPCT() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null) return; m_damage += (int)unitTarget.CountPctFromMaxHealth(damage); } [SpellEffectHandler(SpellEffectName.GiveCurrency)] void EffectGiveCurrency() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; if (!CliDB.CurrencyTypesStorage.ContainsKey(effectInfo.MiscValue)) return; unitTarget.ToPlayer().ModifyCurrency((CurrencyTypes)effectInfo.MiscValue, damage); } [SpellEffectHandler(SpellEffectName.CastButton)] void EffectCastButtons() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Player player = m_caster.ToPlayer(); if (player == null) return; int button_id = effectInfo.MiscValue + 132; int n_buttons = effectInfo.MiscValueB; for (; n_buttons != 0; --n_buttons, ++button_id) { ActionButton ab = player.GetActionButton((byte)button_id); if (ab == null || ab.GetButtonType() != ActionButtonType.Spell) continue; //! Action button data is unverified when it's set so it can be "hacked" //! to contain invalid spells, so filter here. uint spell_id = ab.GetAction(); if (spell_id == 0) continue; SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spell_id, GetCastDifficulty()); if (spellInfo == null) continue; if (!player.HasSpell(spell_id) || player.GetSpellHistory().HasCooldown(spell_id)) continue; if (!spellInfo.HasAttribute(SpellAttr9.SummonPlayerTotem)) continue; CastSpellExtraArgs args = new(TriggerCastFlags.IgnoreGCD | TriggerCastFlags.IgnoreCastInProgress | TriggerCastFlags.CastDirectly | TriggerCastFlags.DontReportCastError); args.OriginalCastId = m_castId; args.CastDifficulty = GetCastDifficulty(); m_caster.CastSpell(m_caster, spellInfo.Id, args); } } [SpellEffectHandler(SpellEffectName.RechargeItem)] void EffectRechargeItem() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null) return; Player player = unitTarget.ToPlayer(); if (player == null) return; Item item = player.GetItemByEntry(effectInfo.ItemType); if (item != null) { foreach (ItemEffectRecord itemEffect in item.GetEffects()) if (itemEffect.LegacySlotIndex <= item.m_itemData.SpellCharges.GetSize()) item.SetSpellCharges(itemEffect.LegacySlotIndex, itemEffect.Charges); item.SetState(ItemUpdateState.Changed, player); } } [SpellEffectHandler(SpellEffectName.Bind)] void EffectBind() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; Player player = unitTarget.ToPlayer(); WorldLocation homeLoc = new(); uint areaId = player.GetAreaId(); if (effectInfo.MiscValue != 0) areaId = (uint)effectInfo.MiscValue; if (m_targets.HasDst()) homeLoc.WorldRelocate(destTarget); else { homeLoc.Relocate(player.GetPosition()); homeLoc.SetMapId(player.GetMapId()); } player.SetHomebind(homeLoc, areaId); player.SendBindPointUpdate(); Log.outDebug(LogFilter.Spells, $"EffectBind: New homebind: {homeLoc}, AreaId: {areaId}"); // zone update player.SendPlayerBound(m_caster.GetGUID(), areaId); } [SpellEffectHandler(SpellEffectName.TeleportToReturnPoint)] void EffectTeleportToReturnPoint() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Player player = unitTarget.ToPlayer(); if (player != null) { WorldLocation dest = player.GetStoredAuraTeleportLocation((uint)effectInfo.MiscValue); if (dest != null) player.TeleportTo(dest, unitTarget == m_caster ? TeleportToOptions.Spell | TeleportToOptions.NotLeaveCombat : 0); } } [SpellEffectHandler(SpellEffectName.SummonRafFriend)] void EffectSummonRaFFriend() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!m_caster.IsTypeId(TypeId.Player) || unitTarget == null || !unitTarget.IsTypeId(TypeId.Player)) return; m_caster.CastSpell(unitTarget, effectInfo.TriggerSpell, new CastSpellExtraArgs(this)); } [SpellEffectHandler(SpellEffectName.UnlockGuildVaultTab)] void EffectUnlockGuildVaultTab() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; // Safety checks done in Spell.CheckCast Player caster = m_caster.ToPlayer(); Guild guild = caster.GetGuild(); if (guild != null) guild.HandleBuyBankTab(caster.GetSession(), (byte)(damage - 1)); // Bank tabs start at zero internally } [SpellEffectHandler(SpellEffectName.SummonPersonalGameobject)] void EffectSummonPersonalGameObject() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; uint goId = (uint)effectInfo.MiscValue; if (goId == 0) return; float x, y, z, o; if (m_targets.HasDst()) destTarget.GetPosition(out x, out y, out z, out o); else { m_caster.GetClosePoint(out x, out y, out z, SharedConst.DefaultPlayerBoundingRadius); o = m_caster.GetOrientation(); } Map map = m_caster.GetMap(); Position pos = new(x, y, z, o); Quaternion rot = Quaternion.CreateFromRotationMatrix(Extensions.fromEulerAnglesZYX(o, 0.0f, 0.0f)); GameObject go = GameObject.CreateGameObject(goId, map, pos, rot, 255, GameObjectState.Ready); if (!go) { Log.outWarn(LogFilter.Spells, $"SpellEffect Failed to summon personal gameobject. SpellId {m_spellInfo.Id}, effect {effectInfo.EffectIndex}"); return; } PhasingHandler.InheritPhaseShift(go, m_caster); int duration = m_spellInfo.CalcDuration(m_caster); go.SetRespawnTime(duration > 0 ? duration / Time.InMilliseconds : 0); go.SetSpellId(m_spellInfo.Id); go.SetPrivateObjectOwner(m_caster.GetGUID()); ExecuteLogEffectSummonObject(effectInfo.Effect, go); map.AddToMap(go); GameObject linkedTrap = go.GetLinkedTrap(); if (linkedTrap != null) { PhasingHandler.InheritPhaseShift(linkedTrap, m_caster); linkedTrap.SetRespawnTime(duration > 0 ? duration / Time.InMilliseconds : 0); linkedTrap.SetSpellId(m_spellInfo.Id); ExecuteLogEffectSummonObject(effectInfo.Effect, linkedTrap); } } [SpellEffectHandler(SpellEffectName.ResurrectWithAura)] void EffectResurrectWithAura() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsInWorld) return; Player target = unitTarget.ToPlayer(); if (target == null) return; if (unitTarget.IsAlive()) return; if (target.IsResurrectRequested()) // already have one active request return; uint health = (uint)target.CountPctFromMaxHealth(damage); uint mana = (uint)MathFunctions.CalculatePct(target.GetMaxPower(PowerType.Mana), damage); uint resurrectAura = 0; if (Global.SpellMgr.HasSpellInfo(effectInfo.TriggerSpell, Difficulty.None)) resurrectAura = effectInfo.TriggerSpell; if (resurrectAura != 0 && target.HasAura(resurrectAura)) return; ExecuteLogEffectResurrect(effectInfo.Effect, target); target.SetResurrectRequestData(m_caster, health, mana, resurrectAura); SendResurrectRequest(target); } [SpellEffectHandler(SpellEffectName.CreateAreaTrigger)] void EffectCreateAreaTrigger() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null || !m_targets.HasDst()) return; int duration = GetSpellInfo().CalcDuration(GetCaster()); AreaTrigger.CreateAreaTrigger((uint)effectInfo.MiscValue, unitCaster, null, GetSpellInfo(), destTarget.GetPosition(), duration, m_SpellVisual, m_castId); } [SpellEffectHandler(SpellEffectName.RemoveTalent)] void EffectRemoveTalent() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; TalentRecord talent = CliDB.TalentStorage.LookupByKey(m_misc.TalentId); if (talent == null) return; Player player = unitTarget ? unitTarget.ToPlayer() : null; if (player == null) return; player.RemoveTalent(talent); player.SendTalentsInfoData(); } [SpellEffectHandler(SpellEffectName.DestroyItem)] void EffectDestroyItem() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) return; Player player = unitTarget.ToPlayer(); Item item = player.GetItemByEntry(effectInfo.ItemType); if (item) player.DestroyItem(item.GetBagSlot(), item.GetSlot(), true); } [SpellEffectHandler(SpellEffectName.LearnGarrisonBuilding)] void EffectLearnGarrisonBuilding() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) return; Garrison garrison = unitTarget.ToPlayer().GetGarrison(); if (garrison != null) garrison.LearnBlueprint((uint)effectInfo.MiscValue); } [SpellEffectHandler(SpellEffectName.CreateGarrison)] void EffectCreateGarrison() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) return; unitTarget.ToPlayer().CreateGarrison((uint)effectInfo.MiscValue); } [SpellEffectHandler(SpellEffectName.CreateConversation)] void EffectCreateConversation() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null || !m_targets.HasDst()) return; Conversation.CreateConversation((uint)effectInfo.MiscValue, unitCaster, destTarget.GetPosition(), ObjectGuid.Empty, GetSpellInfo()); } [SpellEffectHandler(SpellEffectName.CancelConversation)] void EffectCancelConversation() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget) return; List objs = new(); ObjectEntryAndPrivateOwnerIfExistsCheck check = new(unitTarget.GetGUID(), (uint)effectInfo.MiscValue); WorldObjectListSearcher checker = new(unitTarget, objs, check, GridMapTypeMask.Conversation); Cell.VisitGridObjects(unitTarget, checker, 100.0f); foreach (WorldObject obj in objs) { Conversation convo = obj.ToConversation(); if (convo != null) convo.Remove(); } } [SpellEffectHandler(SpellEffectName.AddGarrisonFollower)] void EffectAddGarrisonFollower() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) return; Garrison garrison = unitTarget.ToPlayer().GetGarrison(); if (garrison != null) garrison.AddFollower((uint)effectInfo.MiscValue); } [SpellEffectHandler(SpellEffectName.CreateHeirloomItem)] void EffectCreateHeirloomItem() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Player player = m_caster.ToPlayer(); if (!player) return; CollectionMgr collectionMgr = player.GetSession().GetCollectionMgr(); if (collectionMgr == null) return; List bonusList = new(); bonusList.Add(collectionMgr.GetHeirloomBonus(m_misc.Data0)); DoCreateItem(m_misc.Data0, ItemContext.None, bonusList); ExecuteLogEffectCreateItem(effectInfo.Effect, m_misc.Data0); } [SpellEffectHandler(SpellEffectName.ActivateGarrisonBuilding)] void EffectActivateGarrisonBuilding() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) return; Garrison garrison = unitTarget.ToPlayer().GetGarrison(); if (garrison != null) garrison.ActivateBuilding((uint)effectInfo.MiscValue); } [SpellEffectHandler(SpellEffectName.GrantBattlepetLevel)] void EffectGrantBattlePetLevel() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Player playerCaster = m_caster.ToPlayer(); if (playerCaster == null) return; if (unitTarget == null || !unitTarget.IsCreature()) return; playerCaster.GetSession().GetBattlePetMgr().GrantBattlePetLevel(unitTarget.GetBattlePetCompanionGUID(), (ushort)damage); } [SpellEffectHandler(SpellEffectName.HealBattlepetPct)] void EffectHealBattlePetPct() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) return; BattlePetMgr battlePetMgr = unitTarget.ToPlayer().GetSession().GetBattlePetMgr(); if (battlePetMgr != null) battlePetMgr.HealBattlePetsPct((byte)damage); } [SpellEffectHandler(SpellEffectName.EnableBattlePets)] void EffectEnableBattlePets() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (unitTarget == null || !unitTarget.IsPlayer()) return; Player player = unitTarget.ToPlayer(); player.AddPlayerFlag(PlayerFlags.PetBattlesUnlocked); player.GetSession().GetBattlePetMgr().UnlockSlot(BattlePetSlots.Slot0); } [SpellEffectHandler(SpellEffectName.ChangeBattlepetQuality)] void EffectChangeBattlePetQuality() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Player playerCaster = m_caster.ToPlayer(); if (playerCaster == null) return; if (unitTarget == null || !unitTarget.IsCreature()) return; var quality = BattlePetBreedQuality.Poor; switch (damage) { case 85: quality = BattlePetBreedQuality.Rare; break; case 75: quality = BattlePetBreedQuality.Uncommon; break; default: // Ignore Epic Battle-Stones break; } playerCaster.GetSession().GetBattlePetMgr().ChangeBattlePetQuality(unitTarget.GetBattlePetCompanionGUID(), quality); } [SpellEffectHandler(SpellEffectName.LaunchQuestChoice)] void EffectLaunchQuestChoice() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget || !unitTarget.IsPlayer()) return; unitTarget.ToPlayer().SendPlayerChoice(GetCaster().GetGUID(), effectInfo.MiscValue); } [SpellEffectHandler(SpellEffectName.UncageBattlepet)] void EffectUncageBattlePet() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; if (!m_CastItem || !m_caster || !m_caster.IsTypeId(TypeId.Player)) return; uint speciesId = m_CastItem.GetModifier(ItemModifier.BattlePetSpeciesId); ushort breed = (ushort)(m_CastItem.GetModifier(ItemModifier.BattlePetBreedData) & 0xFFFFFF); BattlePetBreedQuality quality = (BattlePetBreedQuality)((m_CastItem.GetModifier(ItemModifier.BattlePetBreedData) >> 24) & 0xFF); ushort level = (ushort)m_CastItem.GetModifier(ItemModifier.BattlePetLevel); uint displayId = m_CastItem.GetModifier(ItemModifier.BattlePetDisplayId); BattlePetSpeciesRecord speciesEntry = CliDB.BattlePetSpeciesStorage.LookupByKey(speciesId); if (speciesEntry == null) return; Player player = m_caster.ToPlayer(); BattlePetMgr battlePetMgr = player.GetSession().GetBattlePetMgr(); if (battlePetMgr == null) return; if (battlePetMgr.GetMaxPetLevel() < level) { battlePetMgr.SendError(BattlePetError.TooHighLevelToUncage, speciesEntry.CreatureID); SendCastResult(SpellCastResult.CantAddBattlePet); return; } if (battlePetMgr.HasMaxPetCount(speciesEntry, player.GetGUID())) { battlePetMgr.SendError(BattlePetError.CantHaveMorePetsOfThatType, speciesEntry.CreatureID); SendCastResult(SpellCastResult.CantAddBattlePet); return; } battlePetMgr.AddPet(speciesId, displayId, breed, quality, level); player.SendPlaySpellVisual(player, SharedConst.SpellVisualUncagePet, 0, 0, 0.0f, false); player.DestroyItem(m_CastItem.GetBagSlot(), m_CastItem.GetSlot(), true); m_CastItem = null; } [SpellEffectHandler(SpellEffectName.UpgradeHeirloom)] void EffectUpgradeHeirloom() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Player player = m_caster.ToPlayer(); if (player) { CollectionMgr collectionMgr = player.GetSession().GetCollectionMgr(); if (collectionMgr != null) collectionMgr.UpgradeHeirloom(m_misc.Data0, m_castItemEntry); } } [SpellEffectHandler(SpellEffectName.ApplyEnchantIllusion)] void EffectApplyEnchantIllusion() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!itemTarget) return; Player player = m_caster.ToPlayer(); if (!player || player.GetGUID() != itemTarget.GetOwnerGUID()) return; itemTarget.SetState(ItemUpdateState.Changed, player); itemTarget.SetModifier(ItemModifier.EnchantIllusionAllSpecs, (uint)effectInfo.MiscValue); if (itemTarget.IsEquipped()) player.SetVisibleItemSlot(itemTarget.GetSlot(), itemTarget); player.RemoveTradeableItem(itemTarget); itemTarget.ClearSoulboundTradeable(player); } [SpellEffectHandler(SpellEffectName.UpdatePlayerPhase)] void EffectUpdatePlayerPhase() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) return; PhasingHandler.OnConditionChange(unitTarget); } [SpellEffectHandler(SpellEffectName.UpdateZoneAurasPhases)] void EffectUpdateZoneAurasAndPhases() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget || !unitTarget.IsTypeId(TypeId.Player)) return; unitTarget.ToPlayer().UpdateAreaDependentAuras(unitTarget.GetAreaId()); } [SpellEffectHandler(SpellEffectName.GiveArtifactPower)] void EffectGiveArtifactPower() { if (effectHandleMode != SpellEffectHandleMode.LaunchTarget) return; Player playerCaster = m_caster.ToPlayer(); if (playerCaster == null) return; Aura artifactAura = playerCaster.GetAura(PlayerConst.ArtifactsAllWeaponsGeneralWeaponEquippedPassive); if (artifactAura != null) { Item artifact = playerCaster.GetItemByGuid(artifactAura.GetCastItemGUID()); if (artifact) artifact.GiveArtifactXp((ulong)damage, m_CastItem, (ArtifactCategory)effectInfo.MiscValue); } } [SpellEffectHandler(SpellEffectName.GiveArtifactPowerNoBonus)] void EffectGiveArtifactPowerNoBonus() { if (effectHandleMode != SpellEffectHandleMode.LaunchTarget) return; if (!unitTarget || !m_caster.IsTypeId(TypeId.Player)) return; Aura artifactAura = unitTarget.GetAura(PlayerConst.ArtifactsAllWeaponsGeneralWeaponEquippedPassive); if (artifactAura != null) { Item artifact = unitTarget.ToPlayer().GetItemByGuid(artifactAura.GetCastItemGUID()); if (artifact) artifact.GiveArtifactXp((ulong)damage, m_CastItem, 0); } } [SpellEffectHandler(SpellEffectName.PlaySceneScriptPackage)] void EffectPlaySceneScriptPackage() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; if (!m_caster.IsTypeId(TypeId.Player)) return; m_caster.ToPlayer().GetSceneMgr().PlaySceneByPackageId((uint)effectInfo.MiscValue, SceneFlags.PlayerNonInteractablePhased, destTarget); } bool IsUnitTargetSceneObjectAura(Spell spell, TargetInfo target) { if (target.TargetGUID != spell.GetCaster().GetGUID()) return false; foreach (SpellEffectInfo spellEffectInfo in spell.GetSpellInfo().GetEffects()) if ((target.EffectMask & (1 << (int)spellEffectInfo.EffectIndex)) != 0 && spellEffectInfo.IsUnitOwnedAuraEffect()) return true; return false; } [SpellEffectHandler(SpellEffectName.CreateSceneObject)] void EffectCreateSceneObject() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (!unitCaster || !m_targets.HasDst()) return; SceneObject sceneObject = SceneObject.CreateSceneObject((uint)effectInfo.MiscValue, unitCaster, destTarget.GetPosition(), ObjectGuid.Empty); if (sceneObject != null) { bool hasAuraTargetingCaster = m_UniqueTargetInfo.Any(target => IsUnitTargetSceneObjectAura(this, target)); if (hasAuraTargetingCaster) sceneObject.SetCreatedBySpellCast(m_castId); } } [SpellEffectHandler(SpellEffectName.CreatePersonalSceneObject)] void EffectCreatePrivateSceneObject() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (!unitCaster || !m_targets.HasDst()) return; SceneObject sceneObject = SceneObject.CreateSceneObject((uint)effectInfo.MiscValue, unitCaster, destTarget.GetPosition(), unitCaster.GetGUID()); if (sceneObject != null) { bool hasAuraTargetingCaster = m_UniqueTargetInfo.Any(target => IsUnitTargetSceneObjectAura(this, target)); if (hasAuraTargetingCaster) sceneObject.SetCreatedBySpellCast(m_castId); } } [SpellEffectHandler(SpellEffectName.PlayScene)] void EffectPlayScene() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; if (m_caster.GetTypeId() != TypeId.Player) return; m_caster.ToPlayer().GetSceneMgr().PlayScene((uint)effectInfo.MiscValue, destTarget); } [SpellEffectHandler(SpellEffectName.GiveHonor)] void EffectGiveHonor() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget || unitTarget.GetTypeId() != TypeId.Player) return; PvPCredit packet = new(); packet.Honor = damage; packet.OriginalHonor = damage; Player playerTarget = unitTarget.ToPlayer(); playerTarget.AddHonorXP((uint)damage); playerTarget.SendPacket(packet); } [SpellEffectHandler(SpellEffectName.JumpCharge)] void EffectJumpCharge() { if (effectHandleMode != SpellEffectHandleMode.Launch) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; if (unitCaster.IsInFlight()) return; JumpChargeParams jumpParams = Global.ObjectMgr.GetJumpChargeParams(effectInfo.MiscValue); if (jumpParams == null) return; float speed = jumpParams.Speed; if (jumpParams.TreatSpeedAsMoveTimeSeconds) speed = unitCaster.GetExactDist(destTarget) / jumpParams.Speed; JumpArrivalCastArgs arrivalCast = null; if (effectInfo.TriggerSpell != 0) { arrivalCast = new(); arrivalCast.SpellId = effectInfo.TriggerSpell; } SpellEffectExtraData effectExtra = null; if (jumpParams.SpellVisualId.HasValue || jumpParams.ProgressCurveId.HasValue || jumpParams.ParabolicCurveId.HasValue) { effectExtra = new(); if (jumpParams.SpellVisualId.HasValue) effectExtra.SpellVisualId = jumpParams.SpellVisualId.Value; if (jumpParams.ProgressCurveId.HasValue) effectExtra.ProgressCurveId = jumpParams.ProgressCurveId.Value; if (jumpParams.ParabolicCurveId.HasValue) effectExtra.ParabolicCurveId = jumpParams.ParabolicCurveId.Value; } unitCaster.GetMotionMaster().MoveJumpWithGravity(destTarget, speed, jumpParams.JumpGravity, EventId.Jump, false, arrivalCast, effectExtra); } [SpellEffectHandler(SpellEffectName.LearnTransmogSet)] void EffectLearnTransmogSet() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; if (!unitTarget || !unitTarget.IsPlayer()) return; unitTarget.ToPlayer().GetSession().GetCollectionMgr().AddTransmogSet((uint)effectInfo.MiscValue); } [SpellEffectHandler(SpellEffectName.LearnAzeriteEssencePower)] void EffectLearnAzeriteEssencePower() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Player playerTarget = unitTarget != null ? unitTarget.ToPlayer() : null; if (!playerTarget) return; Item heartOfAzeroth = playerTarget.GetItemByEntry(PlayerConst.ItemIdHeartOfAzeroth, ItemSearchLocation.Everywhere); if (heartOfAzeroth == null) return; AzeriteItem azeriteItem = heartOfAzeroth.ToAzeriteItem(); if (azeriteItem == null) return; // remove old rank and apply new one if (azeriteItem.IsEquipped()) { SelectedAzeriteEssences selectedEssences = azeriteItem.GetSelectedAzeriteEssences(); if (selectedEssences != null) { for (int slot = 0; slot < SharedConst.MaxAzeriteEssenceSlot; ++slot) { if (selectedEssences.AzeriteEssenceID[slot] == effectInfo.MiscValue) { bool major = (AzeriteItemMilestoneType)Global.DB2Mgr.GetAzeriteItemMilestonePower(slot).Type == AzeriteItemMilestoneType.MajorEssence; playerTarget.ApplyAzeriteEssence(azeriteItem, (uint)effectInfo.MiscValue, SharedConst.MaxAzeriteEssenceRank, major, false); playerTarget.ApplyAzeriteEssence(azeriteItem, (uint)effectInfo.MiscValue, (uint)effectInfo.MiscValueB, major, false); break; } } } } azeriteItem.SetEssenceRank((uint)effectInfo.MiscValue, (uint)effectInfo.MiscValueB); azeriteItem.SetState(ItemUpdateState.Changed, playerTarget); } [SpellEffectHandler(SpellEffectName.CreatePrivateConversation)] void EffectCreatePrivateConversation() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null || unitTarget == null || !unitTarget.IsPlayer()) return; Conversation.CreateConversation((uint)effectInfo.MiscValue, unitCaster, unitTarget.GetPosition(), unitTarget.GetGUID(), GetSpellInfo()); } [SpellEffectHandler(SpellEffectName.SendChatMessage)] void EffectSendChatMessage() { if (effectHandleMode != SpellEffectHandleMode.Hit) return; Unit unitCaster = GetUnitCasterForEffectHandlers(); if (unitCaster == null) return; uint broadcastTextId = (uint)effectInfo.MiscValue; if (!CliDB.BroadcastTextStorage.ContainsKey(broadcastTextId)) return; ChatMsg chatType = (ChatMsg)effectInfo.MiscValueB; unitCaster.Talk(broadcastTextId, chatType, Global.CreatureTextMgr.GetRangeForChatType(chatType), unitTarget); } [SpellEffectHandler(SpellEffectName.GrantBattlepetExperience)] void EffectGrantBattlePetExperience() { if (effectHandleMode != SpellEffectHandleMode.HitTarget) return; Player playerCaster = m_caster.ToPlayer(); if (playerCaster == null) return; if (!unitTarget || !unitTarget.IsCreature()) return; playerCaster.GetSession().GetBattlePetMgr().GrantBattlePetExperience(unitTarget.GetBattlePetCompanionGUID(), (ushort)damage, BattlePetXpSource.SpellEffect); } } public class DispelableAura { public DispelableAura(Aura aura, int dispelChance, byte dispelCharges) { _aura = aura; _chance = dispelChance; _charges = dispelCharges; } public bool RollDispel() { return RandomHelper.randChance(_chance); } public Aura GetAura() { return _aura; } public byte GetDispelCharges() { return _charges; } public void IncrementCharges() { ++_charges; } public bool DecrementCharge() { if (_charges == 0) return false; --_charges; return _charges > 0; } Aura _aura; int _chance; byte _charges; } class DelayedSpellTeleportEvent : BasicEvent { Unit _target; WorldLocation _targetDest; TeleportToOptions _options; uint _spellId; public DelayedSpellTeleportEvent(Unit target, WorldLocation targetDest, TeleportToOptions options, uint spellId) { _target = target; _targetDest = targetDest; _options = options; _spellId = spellId; } public override bool Execute(ulong e_time, uint p_time) { if (_targetDest.GetMapId() == _target.GetMapId()) _target.NearTeleportTo(_targetDest, (_options & TeleportToOptions.Spell) != 0); else { Player player = _target.ToPlayer(); if (player != null) player.TeleportTo(_targetDest, _options); else Log.outError(LogFilter.Spells, $"Spell::EffectTeleportUnitsWithVisualLoadingScreen - spellId {_spellId} attempted to teleport creature to a different map."); } return true; } } }