/* * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * 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 2 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 . */ /* * Ordered alphabetically using scriptname. * Scriptnames of files in this file should be prefixed with "npc_pet_mag_". */ #include "ScriptMgr.h" #include "CellImpl.h" #include "CombatAI.h" #include "GridNotifiersImpl.h" #include "MotionMaster.h" #include "Pet.h" #include "ScriptedCreature.h" enum MageSpells { SPELL_MAGE_CLONE_ME = 45204, SPELL_MAGE_MASTERS_THREAT_LIST = 58838, SPELL_MAGE_FROST_BOLT = 59638, SPELL_MAGE_FIRE_BLAST = 59637 }; enum MirrorImageTimers { TIMER_MIRROR_IMAGE_FIRE_BLAST = 6500 }; struct npc_pet_mage_mirror_image : ScriptedAI { const float CHASE_DISTANCE = 35.0f; npc_pet_mage_mirror_image(Creature* creature) : ScriptedAI(creature) { } void InitializeAI() override { 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, true); } // custom UpdateVictim implementation to handle special target selection // we prioritize between things that are in combat with owner based on the owner's threat to them bool UpdateVictim() { Unit* owner = me->GetOwner(); if (!owner) return false; if (!me->HasUnitState(UNIT_STATE_CASTING) && !me->IsInCombat() && !owner->IsInCombat()) return false; Unit* currentTarget = me->GetVictim(); if (currentTarget && !CanAIAttack(currentTarget)) { me->InterruptNonMeleeSpells(true); // do not finish casting on invalid targets me->AttackStop(); currentTarget = nullptr; } // don't reselect if we're currently casting anyway if (currentTarget && me->HasUnitState(UNIT_STATE_CASTING)) return true; Unit* selectedTarget = nullptr; CombatManager const& mgr = owner->GetCombatManager(); if (mgr.HasPvPCombat()) { // select pvp target float minDistance = 0.0f; for (auto const& pair : mgr.GetPvPCombatRefs()) { Unit* target = pair.second->GetOther(owner); if (target->GetTypeId() != TYPEID_PLAYER) continue; if (!CanAIAttack(target)) continue; float dist = owner->GetDistance(target); if (!selectedTarget || dist < minDistance) { selectedTarget = target; minDistance = dist; } } } if (!selectedTarget) { // select pve target float maxThreat = 0.0f; for (auto const& pair : mgr.GetPvECombatRefs()) { Unit* target = pair.second->GetOther(owner); if (!CanAIAttack(target)) continue; float threat = target->GetThreatManager().GetThreat(owner); if (threat >= maxThreat) { selectedTarget = target; maxThreat = threat; } } } if (!selectedTarget) { EnterEvadeMode(EvadeReason::NoHostiles); return false; } if (selectedTarget != me->GetVictim()) AttackStartCaster(selectedTarget, CHASE_DISTANCE); return true; } void UpdateAI(uint32 diff) override { Unit* owner = me->GetOwner(); if (!owner) { me->DespawnOrUnsummon(); return; } if (_fireBlastTimer) { if (_fireBlastTimer <= diff) _fireBlastTimer = 0; else _fireBlastTimer -= diff; } if (!UpdateVictim()) return; if (me->HasUnitState(UNIT_STATE_CASTING)) return; if (!_fireBlastTimer) { DoCastVictim(SPELL_MAGE_FIRE_BLAST); _fireBlastTimer = TIMER_MIRROR_IMAGE_FIRE_BLAST; } else DoCastVictim(SPELL_MAGE_FROST_BOLT); } bool CanAIAttack(Unit const* who) const override { Unit* owner = me->GetOwner(); return owner && who->IsAlive() && me->IsValidAttackTarget(who) && !who->HasBreakableByDamageCrowdControlAura() && who->IsInCombatWith(owner) && ScriptedAI::CanAIAttack(who); } // 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(); me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle()); } } uint32 _fireBlastTimer = 0; }; void AddSC_mage_pet_scripts() { RegisterCreatureAI(npc_pet_mage_mirror_image); }