diff --git a/sql/updates/world/custom/custom_2018_07_23_00_world.sql b/sql/updates/world/custom/custom_2018_07_23_00_world.sql new file mode 100644 index 0000000000..54a71e5932 --- /dev/null +++ b/sql/updates/world/custom/custom_2018_07_23_00_world.sql @@ -0,0 +1,16 @@ +DELETE FROM `spell_script_names` WHERE `ScriptName` IN +('spell_mage_mirror_image', +'spell_initialize_images'); + +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(55342, 'spell_mage_mirror_image'), +(58836, 'spell_initialize_images'); + +UPDATE `creature_template` SET `unit_class`= 8, `ScriptName`= 'npc_pet_mage_mirror_image' WHERE `entry` IN (47244, 47243, 31216); + + +DELETE FROM `conditions` WHERE `SourceEntry` IN (58836) AND `SourceTypeOrReferenceId`= 13; +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ScriptName`, `Comment`) VALUES +(13, 1, 58836, 0, 0, 31, 0, 3, 47244, 0, 0, 0, '', 'Initialize Images - Target Mirror Image'), +(13, 1, 58836, 0, 1, 31, 0, 3, 47243, 0, 0, 0, '', 'Initialize Images - Target Mirror Image'), +(13, 1, 58836, 0, 2, 31, 0, 3, 31216, 0, 0, 0, '', 'Initialize Images - Target Mirror Image'); diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index c9b1564fac..a717db56cc 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -999,17 +999,6 @@ bool Guardian::InitStatsForLevel(uint8 petlevel) AddAura(58877, this);//Spirit Hunt, passive, Spirit Wolves' attacks heal them and their master for 150% of damage done. break; } - case 31216: // Mirror Image - { - SetBonusDamage(int32(GetOwner()->SpellBaseDamageBonusDone(SPELL_SCHOOL_MASK_FROST) * 0.33f)); - SetDisplayId(GetOwner()->GetDisplayId()); - if (!pInfo) - { - SetCreateMana(28 + 30*petlevel); - SetCreateHealth(28 + 10*petlevel); - } - break; - } case 27829: // Ebon Gargoyle { if (!pInfo) diff --git a/src/server/game/Entities/Transport/Transport.cpp b/src/server/game/Entities/Transport/Transport.cpp index 31e2c988a2..59f4eb3b54 100644 --- a/src/server/game/Entities/Transport/Transport.cpp +++ b/src/server/game/Entities/Transport/Transport.cpp @@ -427,8 +427,6 @@ TempSummon* Transport::SummonPassenger(uint32 entry, Position const& pos, TempSu mask = UNIT_MASK_MINION; break; default: - if (properties->Flags & 512) // Mirror Image, Summon Gargoyle - mask = UNIT_MASK_GUARDIAN; break; } break; diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index 721d38310b..cb2343bd3d 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -968,10 +968,6 @@ void AuraEffect::UpdatePeriodic(Unit* caster) break; } break; - case SPELLFAMILY_MAGE: - if (GetId() == 55342)// Mirror Image - m_isPeriodic = false; - break; case SPELLFAMILY_DEATHKNIGHT: // Chains of Ice if (GetSpellInfo()->SpellFamilyFlags[1] & 0x00004000) @@ -5120,14 +5116,6 @@ void AuraEffect::HandlePeriodicDummyAuraTick(Unit* target, Unit* caster) const break; } break; - case SPELLFAMILY_MAGE: - { - // Mirror Image - if (GetId() == 55342) - // Set name of summons to name of caster - target->CastSpell((Unit*)nullptr, m_spellInfo->Effects[m_effIndex].TriggerSpell, true); - break; - } case SPELLFAMILY_DRUID: { switch (GetSpellInfo()->Id) diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 3779bed6a8..07e7d8c9e9 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -386,6 +386,13 @@ void Spell::EffectSchoolDMG(SpellEffIndex effIndex) damage = (m_caster->getLevel() - 60) * 4 + 60; break; } + case 88082: // Fireball (Mirror Image) + case 59638: // Frost Bolt (Mirror Image) + case 88084: // Arcane Blast (Mirror Image) + // Mage Mirror Images deal 50% of the coefficient's spell power bonus as bonus damage + if (Unit* owner = m_caster->ToTempSummon()->GetSummoner()) + damage += CalculatePct(owner->SpellDamageBonusDone(unitTarget, m_spellInfo, (uint32)damage, SPELL_DIRECT_DAMAGE, effIndex), 50); + break; } break; } diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index dc5f42685b..4ac78f8518 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -4667,6 +4667,14 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->SpellLevel = 0; }); + // Glyph of Mirror Image + ApplySpellFix({ 63093 }, [](SpellInfo* spellInfo) + { + spellInfo->Effects[EFFECT_0].Effect = SPELL_EFFECT_APPLY_AURA; + spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_DUMMY; + }); + + for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i) { SpellInfo* spellInfo = mSpellInfoMap[i]; diff --git a/src/server/scripts/Pet/pet_mage.cpp b/src/server/scripts/Pet/pet_mage.cpp index 724094043d..15fc5fb3ca 100644 --- a/src/server/scripts/Pet/pet_mage.cpp +++ b/src/server/scripts/Pet/pet_mage.cpp @@ -28,233 +28,129 @@ #include "PetAI.h" #include "ScriptedCreature.h" -enum MageSpells +enum Spells { - SPELL_MAGE_CLONE_ME = 45204, - SPELL_MAGE_MASTERS_THREAT_LIST = 58838, - SPELL_MAGE_FROST_BOLT = 59638, - SPELL_MAGE_FIRE_BLAST = 59637 + SPELL_INHERIT_MASTERS_THREAT_LIST = 58838, + SPELL_PET_HEALTH_SCALING = 61679, + SPELL_CLONE_ME = 45204, + SPELL_COPY_OFFHAND_WEAPON = 45206, + SPELL_COPY_WEAPON = 41055, + SPELL_INITIALIZE_IMAGES = 58836, + + SPELL_FIREBALL = 88082, + SPELL_FROST_BOLT = 59638, + SPELL_ARCANE_BLAST = 88084 }; -enum MirrorImageTimers +enum CreatureIds { - TIMER_MIRROR_IMAGE_INIT = 0, - TIMER_MIRROR_IMAGE_FROST_BOLT = 4000, - TIMER_MIRROR_IMAGE_FIRE_BLAST = 6000 + NPC_MIRROR_IMAGE_FIRE = 47244, + NPC_MIRROR_IMAGE_ARCANE = 47243, + NPC_MIRROR_IMAGE_FROST = 31216 }; -class npc_pet_mage_mirror_image : public CreatureScript +enum Events { - public: - npc_pet_mage_mirror_image() : CreatureScript("npc_pet_mage_mirror_image") { } + EVENT_CAST_ABILITY = 1 +}; - struct npc_pet_mage_mirror_imageAI : CasterAI +struct npc_pet_mage_mirror_image : ScriptedAI +{ + npc_pet_mage_mirror_image(Creature* creature) : ScriptedAI(creature) + { + Initialize(); + } + + void Initialize() + { + _angle = 0.0f; + } + + void AttackStart(Unit* who) override + { + AttackStartCaster(who, 20.0f); + } + + void EnterEvadeMode(EvadeReason /*why*/) override + { + if (Unit* summoner = me->ToTempSummon()->GetSummoner()) + me->GetMotionMaster()->MoveFollow(summoner, PET_FOLLOW_DIST, _angle); + } + + void IsSummonedBy(Unit* summoner) override + { + DoCastAOE(SPELL_INHERIT_MASTERS_THREAT_LIST); + DoCastSelf(SPELL_PET_HEALTH_SCALING); + summoner->CastSpell(me, SPELL_CLONE_ME, true); + DoCast(summoner, SPELL_COPY_OFFHAND_WEAPON); + DoCast(summoner, SPELL_COPY_WEAPON); + + _angle = summoner->GetAngle(me); + me->GetMotionMaster()->MoveFollow(summoner, PET_FOLLOW_DIST, _angle); + } + + void SpellHitTarget(Unit* target, SpellInfo const* spell) override + { + if (spell->Id == SPELL_INHERIT_MASTERS_THREAT_LIST) { - npc_pet_mage_mirror_imageAI(Creature* creature) : CasterAI(creature) { } + Unit* summoner = me->ToTempSummon()->GetSummoner(); + if (!summoner) + return; - void Init() + if (target->IsInCombatWith(summoner)) { - Unit* owner = me->GetCharmerOrOwner(); - - std::list targets; - Trinity::AnyUnfriendlyUnitInObjectRangeCheck u_check(me, me, 30.0f); - Trinity::UnitListSearcher searcher(me, targets, u_check); - Cell::VisitAllObjects(me, searcher, 40.0f); - - Unit* highestThreatUnit = nullptr; - float highestThreat = 0.0f; - Unit* nearestPlayer = nullptr; - for (std::list::const_iterator iter = targets.begin(); iter != targets.end(); ++iter) - { - // Consider only units without CC - if (!(*iter)->HasBreakableByDamageCrowdControlAura((*iter))) - { - // Take first found unit - if (!highestThreatUnit && (*iter)->GetTypeId() != TYPEID_PLAYER) - { - highestThreatUnit = (*iter); - continue; - } - if (!nearestPlayer && ((*iter)->GetTypeId() == TYPEID_PLAYER)) - { - nearestPlayer = (*iter); - continue; - } - // else compare best fit unit with current unit - ThreatContainer::StorageType triggers = (*iter)->getThreatManager().getThreatList(); - for (ThreatContainer::StorageType::const_iterator trig_citr = triggers.begin(); trig_citr != triggers.end(); ++trig_citr) - { - // Try to find threat referenced to owner - if ((*trig_citr)->getTarget() == owner) - { - // Check if best fit hostile unit hs lower threat than this current unit - if (highestThreat < (*trig_citr)->getThreat()) - { - // If so, update best fit unit - highestThreat = (*trig_citr)->getThreat(); - highestThreatUnit = (*iter); - break; - } - } - } - // In case no unit with threat was found so far, always check for nearest unit (only for players) - if ((*iter)->GetTypeId() == TYPEID_PLAYER) - { - // If this player is closer than the previous one, update it - if (me->GetDistance((*iter)->GetPosition()) < me->GetDistance(nearestPlayer->GetPosition())) - nearestPlayer = (*iter); - } - } - } - // Prioritize units with threat referenced to owner - if (highestThreat > 0.0f && highestThreatUnit) - me->Attack(highestThreatUnit, false); - // If there is no such target, try to attack nearest hostile unit if such exists - else if (nearestPlayer) - me->Attack(nearestPlayer, false); + target->AddThreat(me, target->getThreatManager().getThreat(summoner)); + me->SetInCombatWith(target); } - - bool IsInThreatList(Unit* target) - { - Unit* owner = me->GetCharmerOrOwner(); - - std::list targets; - Trinity::AnyUnfriendlyUnitInObjectRangeCheck u_check(me, me, 30.0f); - Trinity::UnitListSearcher searcher(me, targets, u_check); - Cell::VisitAllObjects(me, searcher, 40.0f); - - for (std::list::const_iterator iter = targets.begin(); iter != targets.end(); ++iter) - { - if ((*iter) == target) - { - // Consider only units without CC - if (!(*iter)->HasBreakableByDamageCrowdControlAura((*iter))) - { - ThreatContainer::StorageType triggers = (*iter)->getThreatManager().getThreatList(); - for (ThreatContainer::StorageType::const_iterator trig_citr = triggers.begin(); trig_citr != triggers.end(); ++trig_citr) - { - // Try to find threat referenced to owner - if ((*trig_citr)->getTarget() == owner) - return true; - } - } - } - } - return false; - } - - void InitializeAI() override - { - CasterAI::InitializeAI(); - Unit* owner = me->GetOwner(); - if (!owner) - return; - - // here mirror image casts on summoner spell (not present in client dbc) 49866 - // here should be auras (not present in client dbc): 35657, 35658, 35659, 35660 selfcast by mirror images (stats related?) - // Clone Me! - owner->CastSpell(me, SPELL_MAGE_CLONE_ME, false); - } - - void JustEngagedWith(Unit* who) override - { - if (me->GetVictim() && !me->GetVictim()->HasBreakableByDamageCrowdControlAura(me)) - { - me->CastSpell(who, SPELL_MAGE_FIRE_BLAST, false); - events.ScheduleEvent(SPELL_MAGE_FROST_BOLT, TIMER_MIRROR_IMAGE_INIT); - events.ScheduleEvent(SPELL_MAGE_FIRE_BLAST, TIMER_MIRROR_IMAGE_FIRE_BLAST); - } - else - EnterEvadeMode(EVADE_REASON_OTHER); - } - - void Reset() override - { - events.Reset(); - } - - void UpdateAI(uint32 diff) override - { - Unit* owner = me->GetCharmerOrOwner(); - if (!owner) - return; - - Unit* target = owner->getAttackerForHelper(); - - events.Update(diff); - - // prevent CC interrupts by images - if (me->GetVictim() && me->EnsureVictim()->HasBreakableByDamageCrowdControlAura(me)) - { - me->InterruptNonMeleeSpells(false); - return; - } - - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - // assign target if image doesnt have any or the target is not actual - if (!target || me->GetVictim() != target) - { - Unit* ownerTarget = nullptr; - if (Player* playerOwner = me->GetCharmerOrOwner()->ToPlayer()) - ownerTarget = playerOwner->GetSelectedUnit(); - - // recognize which victim will be choosen - if (ownerTarget && ownerTarget->GetTypeId() == TYPEID_PLAYER) - { - if (!ownerTarget->HasBreakableByDamageCrowdControlAura(ownerTarget)) - me->Attack(ownerTarget, false); - } - else if (ownerTarget && (ownerTarget->GetTypeId() != TYPEID_PLAYER) && IsInThreatList(ownerTarget)) - { - if (!ownerTarget->HasBreakableByDamageCrowdControlAura(ownerTarget)) - me->Attack(ownerTarget, false); - } - else - Init(); - } - - if (uint32 spellId = events.ExecuteEvent()) - { - if (spellId == SPELL_MAGE_FROST_BOLT) - { - events.ScheduleEvent(SPELL_MAGE_FROST_BOLT, TIMER_MIRROR_IMAGE_FROST_BOLT); - DoCastVictim(spellId); - } - else if (spellId == SPELL_MAGE_FIRE_BLAST) - { - DoCastVictim(spellId); - events.ScheduleEvent(SPELL_MAGE_FIRE_BLAST, TIMER_MIRROR_IMAGE_FIRE_BLAST); - } - } - } - - // Do not reload Creature templates on evade mode enter - prevent visual lost - void EnterEvadeMode(EvadeReason /*why*/) override - { - if (me->IsInEvadeMode() || !me->IsAlive()) - return; - - Unit* owner = me->GetCharmerOrOwner(); - - me->CombatStop(true); - if (owner && !me->HasUnitState(UNIT_STATE_FOLLOW)) - { - me->GetMotionMaster()->Clear(false); - me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle(), MOTION_SLOT_ACTIVE); - } - Init(); - } - }; - - CreatureAI* GetAI(Creature* creature) const override - { - return new npc_pet_mage_mirror_imageAI(creature); } + } + + void SpellHit(Unit* caster, SpellInfo const* spell) override + { + if (spell->Id == SPELL_INITIALIZE_IMAGES) + { + _events.ScheduleEvent(EVENT_CAST_ABILITY, 500ms); + if (Unit* target = me->SelectVictim()) + AttackStart(target); + } + } + + void UpdateAI(uint32 diff) override + { + if (!UpdateVictim()) + return; + + _events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + while (uint32 eventId = _events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_CAST_ABILITY: + if (me->GetEntry() == NPC_MIRROR_IMAGE_FROST) + DoCastVictim(SPELL_FROST_BOLT); + if (me->GetEntry() == NPC_MIRROR_IMAGE_FIRE) + DoCastVictim(SPELL_FIREBALL); + else if (me->GetEntry() == NPC_MIRROR_IMAGE_ARCANE) + DoCastVictim(SPELL_ARCANE_BLAST); + + _events.Repeat(3s); + break; + default: + break; + } + } + } + +private: + EventMap _events; + float _angle; }; void AddSC_mage_pet_scripts() { - new npc_pet_mage_mirror_image(); + RegisterCreatureAI(npc_pet_mage_mirror_image); } diff --git a/src/server/scripts/Spells/spell_mage.cpp b/src/server/scripts/Spells/spell_mage.cpp index f0cfda8603..336f85b92d 100644 --- a/src/server/scripts/Spells/spell_mage.cpp +++ b/src/server/scripts/Spells/spell_mage.cpp @@ -84,6 +84,9 @@ enum MageSpells SPELL_MAGE_INCANTERS_ABSORBTION_KNOCKBACK = 86261, SPELL_MAGE_IGNITE = 12654, SPELL_MAGE_MASTER_OF_ELEMENTS_ENERGIZE = 29077, + SPELL_MAGE_MIRROR_IMAGE_TRIGGERED_FIRE = 88092, + SPELL_MAGE_MIRROR_IMAGE_TRIGGERED_ARCANE = 88091, + SPELL_MAGE_MIRROR_IMAGE_TRIGGERED_FROST = 58832, SPELL_MAGE_PERMAFROST = 91394, SPELL_MAGE_PYROBLAST = 11366, SPELL_MAGE_SCORCH = 2948, @@ -134,7 +137,8 @@ enum MageIcons ICON_MAGE_IMPROVED_MANA_GEM = 1036, ICON_MAGE_EARLY_FROST_SKILL = 189, ICON_MAGE_EARLY_FROST = 2114, - SPELL_ICON_MAGE_LIVING_BOMB = 3000 + ICON_MAGE_GLYPH_OF_MIRROR_IMAGE = 331, + SPELL_ICON_MAGE_LIVING_BOMB = 3000, }; enum MiscSpells @@ -2012,6 +2016,97 @@ class spell_mage_pyroblast : public SpellScriptLoader } }; +// 55342 - Mirror Image +class spell_mage_mirror_image : public SpellScript +{ + PrepareSpellScript(spell_mage_mirror_image); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo( + { + SPELL_MAGE_MIRROR_IMAGE_TRIGGERED_FIRE, + SPELL_MAGE_MIRROR_IMAGE_TRIGGERED_ARCANE, + SPELL_MAGE_MIRROR_IMAGE_TRIGGERED_FROST + }); + } + + void HandleDummyEffect(SpellEffIndex /*effIndex*/) + { + Player* player = GetHitPlayer(); + if (!player) + return; + + uint32 spellId = SPELL_MAGE_MIRROR_IMAGE_TRIGGERED_FROST; + if (player->GetDummyAuraEffect(SPELLFAMILY_MAGE, ICON_MAGE_GLYPH_OF_MIRROR_IMAGE, EFFECT_0)) + { + if (player->GetPrimaryTalentTree(player->GetActiveSpec()) == TALENT_TREE_MAGE_FIRE) + spellId = SPELL_MAGE_MIRROR_IMAGE_TRIGGERED_FIRE; + else if (player->GetPrimaryTalentTree(player->GetActiveSpec()) == TALENT_TREE_MAGE_ARCANE) + spellId = SPELL_MAGE_MIRROR_IMAGE_TRIGGERED_ARCANE; + } + + player->CastSpell(player, spellId, true); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_mage_mirror_image::HandleDummyEffect, EFFECT_1, SPELL_EFFECT_DUMMY); + } +}; + +class spell_mage_mirror_image_AurasScript : public AuraScript +{ + PrepareAuraScript(spell_mage_mirror_image_AurasScript); + + void HandleEffectPeriodic(AuraEffect const* aurEff) + { + if (aurEff->GetTickNumber() == 1) + GetTarget()->CastSpell(GetTarget(), GetSpellInfo()->Effects[aurEff->GetEffIndex()].TriggerSpell, true); + } + + void Register() override + { + OnEffectPeriodic += AuraEffectPeriodicFn(spell_mage_mirror_image_AurasScript::HandleEffectPeriodic, EFFECT_2, SPELL_AURA_PERIODIC_DUMMY); + } +}; + +class SummonerCheck +{ + public: + SummonerCheck(Unit* _summoner) : summoner(_summoner) { } + + bool operator()(WorldObject* object) + { + if (Unit* unit = object->ToUnit()) + if (TempSummon* summon = unit->ToTempSummon()) + return (summon->GetSummoner() && summon->GetSummoner() != summoner); + + return false; + } + + private: + Unit* summoner; +}; + +class spell_initialize_images : public SpellScript +{ + PrepareSpellScript(spell_initialize_images); + + void FilterTargets(std::list& targets) + { + if (targets.empty()) + return; + + targets.remove_if(SummonerCheck(GetCaster())); + } + + void Register() override + { + OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_initialize_images::FilterTargets, EFFECT_0, TARGET_UNIT_SRC_AREA_ENTRY); + } +}; + void AddSC_mage_spell_scripts() { new spell_mage_arcane_potency(); @@ -2032,6 +2127,7 @@ void AddSC_mage_spell_scripts() new spell_mage_ice_barrier(); new spell_mage_ignite(); new spell_mage_improved_hot_streak(); + RegisterSpellScript(spell_initialize_images); new spell_mage_glyph_of_ice_block(); new spell_mage_glyph_of_icy_veins(); new spell_mage_glyph_of_polymorph(); @@ -2039,6 +2135,7 @@ void AddSC_mage_spell_scripts() new spell_mage_mage_ward(); new spell_mage_mana_shield(); new spell_mage_master_of_elements(); + RegisterSpellAndAuraScriptPair(spell_mage_mirror_image, spell_mage_mirror_image_AurasScript); new spell_mage_nether_vortex(); new spell_mage_permafrost(); new spell_mage_polymorph();