aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/server/game/AI/CreatureAI.cpp9
-rw-r--r--src/server/game/AI/CreatureAI.h2
-rw-r--r--src/server/game/Battlegrounds/Zones/BattlegroundIC.cpp2
-rw-r--r--src/server/game/Battlegrounds/Zones/BattlegroundIC.h8
-rw-r--r--src/server/game/Entities/Creature/Creature.cpp117
-rw-r--r--src/server/game/Entities/Creature/Creature.h7
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp97
-rw-r--r--src/server/game/Entities/Unit/Unit.h42
-rw-r--r--src/server/game/Instances/InstanceScript.cpp12
-rw-r--r--src/server/game/Instances/InstanceScript.h19
-rw-r--r--src/server/game/Maps/AreaBoundary.cpp2
-rw-r--r--src/server/game/Maps/AreaBoundary.h9
-rw-r--r--src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp3
-rw-r--r--src/server/game/Spells/Spell.cpp52
-rw-r--r--src/server/game/Spells/Spell.h1
-rw-r--r--src/server/game/Spells/SpellMgr.cpp9
-rw-r--r--src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp2
-rw-r--r--src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp2
-rw-r--r--src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp9
-rw-r--r--src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp9
-rw-r--r--src/server/scripts/Outland/zone_zangarmarsh.cpp4
-rw-r--r--src/server/scripts/Spells/spell_paladin.cpp69
22 files changed, 348 insertions, 138 deletions
diff --git a/src/server/game/AI/CreatureAI.cpp b/src/server/game/AI/CreatureAI.cpp
index 8ef8940e89f..c254a9124c1 100644
--- a/src/server/game/AI/CreatureAI.cpp
+++ b/src/server/game/AI/CreatureAI.cpp
@@ -204,7 +204,8 @@ void CreatureAI::SetGazeOn(Unit* target)
{
if (me->IsValidAttackTarget(target))
{
- AttackStart(target);
+ if (!me->IsFocusing(nullptr, true))
+ AttackStart(target);
me->SetReactState(REACT_PASSIVE);
}
}
@@ -223,7 +224,8 @@ bool CreatureAI::UpdateVictimWithGaze()
}
if (Unit* victim = me->SelectVictim())
- AttackStart(victim);
+ if (!me->IsFocusing(nullptr, true))
+ AttackStart(victim);
return me->GetVictim() != nullptr;
}
@@ -236,7 +238,8 @@ bool CreatureAI::UpdateVictim()
if (!me->HasReactState(REACT_PASSIVE))
{
if (Unit* victim = me->SelectVictim())
- AttackStart(victim);
+ if (!me->IsFocusing(nullptr, true))
+ AttackStart(victim);
return me->GetVictim() != nullptr;
}
diff --git a/src/server/game/AI/CreatureAI.h b/src/server/game/AI/CreatureAI.h
index 3b7c489e018..f250a79ea25 100644
--- a/src/server/game/AI/CreatureAI.h
+++ b/src/server/game/AI/CreatureAI.h
@@ -80,7 +80,7 @@ class CreatureAI : public UnitAI
Creature* DoSummonFlyer(uint32 entry, WorldObject* obj, float flightZ, float radius = 5.0f, uint32 despawnTime = 30000, TempSummonType summonType = TEMPSUMMON_CORPSE_TIMED_DESPAWN);
bool CheckBoundary(Position* who = nullptr) const;
- void SetBoundary(CreatureBoundary const* boundary) { _boundary = boundary; CheckInRoom(); }
+ void SetBoundary(CreatureBoundary const* boundary) { _boundary = boundary; me->DoImmediateBoundaryCheck(); }
public:
enum EvadeReason
{
diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundIC.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundIC.cpp
index cff02234ab6..52e0deaf86f 100644
--- a/src/server/game/Battlegrounds/Zones/BattlegroundIC.cpp
+++ b/src/server/game/Battlegrounds/Zones/BattlegroundIC.cpp
@@ -365,7 +365,7 @@ bool BattlegroundIC::SetupBattleground()
// setting correct factions for Keep Cannons
for (uint8 i = BG_IC_NPC_KEEP_CANNON_1; i <= BG_IC_NPC_KEEP_CANNON_12; ++i)
GetBGCreature(i)->setFaction(BG_IC_Factions[0]);
- for (uint8 i = BG_IC_NPC_KEEP_CANNON_13; i <= BG_IC_NPC_KEEP_CANNON_25; ++i)
+ for (uint8 i = BG_IC_NPC_KEEP_CANNON_13; i <= BG_IC_NPC_KEEP_CANNON_24; ++i)
GetBGCreature(i)->setFaction(BG_IC_Factions[1]);
// correcting spawn time for keeps bombs
diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundIC.h b/src/server/game/Battlegrounds/Zones/BattlegroundIC.h
index 72e9990b7c1..44666819feb 100644
--- a/src/server/game/Battlegrounds/Zones/BattlegroundIC.h
+++ b/src/server/game/Battlegrounds/Zones/BattlegroundIC.h
@@ -390,7 +390,6 @@ enum BG_IC_NPCs
BG_IC_NPC_KEEP_CANNON_22,
BG_IC_NPC_KEEP_CANNON_23,
BG_IC_NPC_KEEP_CANNON_24,
- BG_IC_NPC_KEEP_CANNON_25,
BG_IC_NPC_SIEGE_ENGINE_A,
BG_IC_NPC_SIEGE_ENGINE_H,
@@ -444,7 +443,7 @@ enum BannersTypes
enum BG_IC_MaxSpawns
{
MAX_NORMAL_GAMEOBJECTS_SPAWNS = BG_IC_GO_DOODAD_ND_WINTERORC_WALL_GATEFX_DOOR03+1,
- MAX_NORMAL_NPCS_SPAWNS = BG_IC_NPC_KEEP_CANNON_25+1,
+ MAX_NORMAL_NPCS_SPAWNS = BG_IC_NPC_KEEP_CANNON_24+1,
MAX_WORKSHOP_SPAWNS = 10,
MAX_DOCKS_SPAWNS = 12,
MAX_SPIRIT_GUIDES_SPAWNS = 7,
@@ -506,9 +505,8 @@ const ICNpc BG_IC_NpcSpawnlocs[MAX_NORMAL_NPCS_SPAWNS] =
{BG_IC_NPC_KEEP_CANNON_20, NPC_KEEP_CANNON, TEAM_HORDE, 1137.72f, -688.517f, 88.4023f, 3.9619f}, // 30
{BG_IC_NPC_KEEP_CANNON_21, NPC_KEEP_CANNON, TEAM_HORDE, 1135.29f, -840.878f, 88.0252f, 2.30383f}, // 31
{BG_IC_NPC_KEEP_CANNON_22, NPC_KEEP_CANNON, TEAM_HORDE, 1144.33f, -833.309f, 87.9268f, 2.14675f}, // 32
- {BG_IC_NPC_KEEP_CANNON_23, NPC_KEEP_CANNON, TEAM_HORDE, 1135.29f, -840.878f, 88.0252f, 2.30383f}, // 33
- {BG_IC_NPC_KEEP_CANNON_24, NPC_KEEP_CANNON, TEAM_HORDE, 1142.59f, -691.946f, 87.9756f, 3.9619f}, // 34
- {BG_IC_NPC_KEEP_CANNON_25, NPC_KEEP_CANNON, TEAM_HORDE, 1166.13f, -858.391f, 87.9653f, 5.63741f} // 35
+ {BG_IC_NPC_KEEP_CANNON_23, NPC_KEEP_CANNON, TEAM_HORDE, 1142.59f, -691.946f, 87.9756f, 3.9619f}, // 33
+ {BG_IC_NPC_KEEP_CANNON_24, NPC_KEEP_CANNON, TEAM_HORDE, 1166.13f, -858.391f, 87.9653f, 5.63741f} // 34
};
const Position BG_IC_WorkshopVehicles[5] =
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp
index 19d64907065..31320e4d65e 100644
--- a/src/server/game/Entities/Creature/Creature.cpp
+++ b/src/server/game/Entities/Creature/Creature.cpp
@@ -200,6 +200,7 @@ m_originalEntry(0), m_homePosition(), m_transportHomePosition(), m_creatureInfo(
TriggerJustRespawned = false;
m_isTempWorldObject = false;
_focusSpell = NULL;
+ _focusDelay = 0;
}
Creature::~Creature()
@@ -1569,7 +1570,9 @@ void Creature::setDeathState(DeathState s)
if (sWorld->getBoolConfig(CONFIG_SAVE_RESPAWN_TIME_IMMEDIATELY) || isWorldBoss())
SaveRespawnTime();
- SetTarget(ObjectGuid::Empty); // remove target selection in any cases (can be set at aura remove in Unit::setDeathState)
+ ReleaseFocus(); // remove spellcast focus (this also clears unit target)
+ SetTarget(ObjectGuid::Empty); // drop target - dead mobs shouldn't ever target things
+
SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
SetUInt32Value(UNIT_FIELD_MOUNTDISPLAYID, 0); // if creature is mounted on a virtual mount, remove it at death
@@ -2688,39 +2691,117 @@ void Creature::SetDisplayId(uint32 modelId)
void Creature::SetTarget(ObjectGuid guid)
{
- if (!_focusSpell)
+ if (!IsFocusing())
SetGuidValue(UNIT_FIELD_TARGET, guid);
}
-void Creature::FocusTarget(Spell const* focusSpell, WorldObject const* target)
+bool Creature::FocusTarget(Spell const* focusSpell, WorldObject const* target)
{
// already focused
if (_focusSpell)
- return;
+ return false;
+
+ if ((!target || target == this) && !focusSpell->GetCastTime()) // instant cast, untargeted (or self-targeted) spell doesn't need any facing updates
+ return false;
_focusSpell = focusSpell;
- SetGuidValue(UNIT_FIELD_TARGET, target->GetGUID());
- if (focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST))
- AddUnitState(UNIT_STATE_ROTATING);
- // Set serverside orientation if needed (needs to be after attribute check)
- SetInFront(target);
+ // "instant" creature casts that require re-targeting will be delayed by a short moment to prevent facing bugs
+ bool shouldDelay = false;
+
+ // set target, then force send update packet to players if it changed to provide appropriate facing
+ ObjectGuid newTarget = target ? target->GetGUID() : ObjectGuid::Empty;
+ if (GetGuidValue(UNIT_FIELD_TARGET) != newTarget)
+ {
+ SetGuidValue(UNIT_FIELD_TARGET, newTarget);
+ if (target)
+ SetFacingToObject(target);
+
+ if ( // here we determine if the (relatively expensive) forced update is worth it, or whether we can afford to wait until the scheduled update tick
+ ( // only require instant update for spells that actually have a visual
+ focusSpell->GetSpellInfo()->SpellVisual[0] ||
+ focusSpell->GetSpellInfo()->SpellVisual[1]
+ ) && (
+ !focusSpell->GetCastTime() || // if the spell is instant cast
+ focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST) // client gets confused if we attempt to turn at the regularly scheduled update packet
+ )
+ )
+ {
+ const MapRefManager& mapPlayers = GetMap()->GetPlayers();
+ for (MapRefManager::const_iterator it = mapPlayers.begin(); it != mapPlayers.end(); ++it)
+ if (Player* player = (*it).GetSource())
+ {
+ // only update players that can both see us, and are actually in combat with us (this is a performance tradeoff)
+ if (player->CanSeeOrDetect(this, false, true) && IsInCombatWith(player))
+ {
+ SendUpdateToPlayer(player);
+ shouldDelay = true;
+ }
+ }
+ if (shouldDelay)
+ shouldDelay = !(focusSpell->IsTriggered() || focusSpell->GetCastTime() || focusSpell->GetSpellInfo()->IsChanneled());
+
+ }
+ }
+
+ // tell the creature that it should reacquire its current target after the cast is done (this is handled in ::Attack)
+ MustReacquireTarget();
+
+ bool canTurnDuringCast = !focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST);
+ // Face the target - we need to do this before the unit state is modified for no-turn spells
+ if (target)
+ SetInFront(target);
+ else if (!canTurnDuringCast)
+ if(Unit* victim = GetVictim())
+ SetInFront(victim); // ensure server-side orientation is correct at beginning of cast
+
+ if (!canTurnDuringCast)
+ AddUnitState(UNIT_STATE_CANNOT_TURN);
+
+ return shouldDelay;
+}
+
+bool Creature::IsFocusing(Spell const* focusSpell, bool withDelay)
+{
+ if (!IsAlive()) // dead creatures cannot focus
+ {
+ ReleaseFocus(nullptr, false);
+ return false;
+ }
+
+ if (focusSpell && (focusSpell != _focusSpell))
+ return false;
+
+ if (!_focusSpell)
+ {
+ if (!withDelay || !_focusDelay)
+ return false;
+ if (GetMSTimeDiffToNow(_focusDelay) > 1000) // @todo figure out if we can get rid of this magic number somehow
+ {
+ _focusDelay = 0; // save checks in the future
+ return false;
+ }
+ }
+
+ return true;
}
-void Creature::ReleaseFocus(Spell const* focusSpell)
+void Creature::ReleaseFocus(Spell const* focusSpell, bool withDelay)
{
+ if (!_focusSpell)
+ return;
+
// focused to something else
- if (focusSpell != _focusSpell)
+ if (focusSpell && focusSpell != _focusSpell)
return;
- _focusSpell = NULL;
- if (Unit* victim = GetVictim())
- SetGuidValue(UNIT_FIELD_TARGET, victim->GetGUID());
- else
- SetGuidValue(UNIT_FIELD_TARGET, ObjectGuid::Empty);
+ SetGuidValue(UNIT_FIELD_TARGET, ObjectGuid::Empty);
+
+ if (_focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST))
+ ClearUnitState(UNIT_STATE_CANNOT_TURN);
- if (focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST))
- ClearUnitState(UNIT_STATE_ROTATING);
+ _focusSpell = nullptr;
+ _focusDelay = withDelay ? getMSTime() : 0; // don't allow re-target right away to prevent visual bugs
}
void Creature::StartPickPocketRefillTimer()
diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h
index 92522a6a770..3ee1ba7db7b 100644
--- a/src/server/game/Entities/Creature/Creature.h
+++ b/src/server/game/Entities/Creature/Creature.h
@@ -605,6 +605,7 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject
float GetRespawnRadius() const { return m_respawnradius; }
void SetRespawnRadius(float dist) { m_respawnradius = dist; }
+ void DoImmediateBoundaryCheck() { m_boundaryCheckTime = 0; }
uint32 GetCombatPulseDelay() const { return m_combatPulseDelay; }
void SetCombatPulseDelay(uint32 delay) // (secs) interval at which the creature pulses the entire zone into combat (only works in dungeons)
{
@@ -671,8 +672,9 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject
// Handling caster facing during spellcast
void SetTarget(ObjectGuid guid) override;
- void FocusTarget(Spell const* focusSpell, WorldObject const* target);
- void ReleaseFocus(Spell const* focusSpell);
+ bool FocusTarget(Spell const* focusSpell, WorldObject const* target);
+ bool IsFocusing(Spell const* focusSpell = nullptr, bool withDelay = false);
+ void ReleaseFocus(Spell const* focusSpell = nullptr, bool withDelay = true);
CreatureTextRepeatIds GetTextRepeatGroup(uint8 textGroup);
void SetTextRepeatId(uint8 textGroup, uint8 id);
@@ -745,6 +747,7 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject
bool TriggerJustRespawned;
Spell const* _focusSpell; ///> Locks the target during spell cast for proper facing
+ uint32 _focusDelay;
CreatureTextRepeatGroup m_textRepeat;
};
diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp
index 06ff5d527eb..a826f841058 100644
--- a/src/server/game/Entities/Unit/Unit.cpp
+++ b/src/server/game/Entities/Unit/Unit.cpp
@@ -157,6 +157,28 @@ ProcEventInfo::ProcEventInfo(Unit* actor, Unit* actionTarget, Unit* procTarget,
_damageInfo(damageInfo), _healInfo(healInfo)
{ }
+SpellInfo const* ProcEventInfo::GetSpellInfo() const
+{
+ if (_spell)
+ return _spell->GetSpellInfo();
+ if (_damageInfo)
+ return _damageInfo->GetSpellInfo();
+ if (_healInfo)
+ return _healInfo->GetSpellInfo();
+ return nullptr;
+}
+
+SpellSchoolMask ProcEventInfo::GetSchoolMask() const
+{
+ if (_spell)
+ return _spell->GetSpellInfo()->GetSchoolMask();
+ if (_damageInfo)
+ return _damageInfo->GetSchoolMask();
+ if (_healInfo)
+ return _healInfo->GetSchoolMask();
+ return SPELL_SCHOOL_MASK_NONE;
+}
+
Unit::Unit(bool isWorldObject) :
WorldObject(isWorldObject), m_movedPlayer(NULL), m_lastSanctuaryTime(0),
IsAIEnabled(false), NeedChangeAI(false), LastCharmerGUID(),
@@ -223,7 +245,8 @@ Unit::Unit(bool isWorldObject) :
for (uint8 i = 0; i < MAX_STATS; ++i)
m_createStats[i] = 0.0f;
- m_attacking = NULL;
+ m_attacking = nullptr;
+ m_shouldReacquireTarget = false;
m_modMeleeHitChance = 0.0f;
m_modRangedHitChance = 0.0f;
m_modSpellHitChance = 0.0f;
@@ -238,7 +261,7 @@ Unit::Unit(bool isWorldObject) :
for (uint8 i = 0; i < MAX_MOVE_TYPE; ++i)
m_speed_rate[i] = 1.0f;
- m_charmInfo = NULL;
+ m_charmInfo = nullptr;
_redirectThreadInfo = RedirectThreatInfo();
@@ -1956,6 +1979,9 @@ void Unit::AttackerStateUpdate (Unit* victim, WeaponAttackType attType, bool ext
if (attType != BASE_ATTACK && attType != OFF_ATTACK)
return; // ignore ranged case
+ if (GetTypeId() == TYPEID_UNIT && !HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED))
+ SetFacingToObject(victim); // update client side facing to face the target (prevents visual glitches when casting untargeted spells)
+
// melee attack spell cast at main hand attack only - no normal melee dmg dealt
if (attType == BASE_ATTACK && m_currentSpells[CURRENT_MELEE_SPELL] && !extra)
m_currentSpells[CURRENT_MELEE_SPELL]->cast();
@@ -6361,54 +6387,6 @@ bool Unit::HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggere
}
case SPELLFAMILY_PALADIN:
{
- // Light's Beacon - Beacon of Light
- if (dummySpell->Id == 53651)
- {
- if (!victim || !procSpell)
- return false;
- triggered_spell_id = 0;
- Unit* beaconTarget = NULL;
- if (GetTypeId() != TYPEID_PLAYER)
- {
- beaconTarget = triggeredByAura->GetBase()->GetCaster();
- if (!beaconTarget || beaconTarget == this || !(beaconTarget->GetAura(53563, victim->GetGUID())))
- return false;
- basepoints0 = int32(damage);
- triggered_spell_id = procSpell->IsRankOf(sSpellMgr->GetSpellInfo(635)) ? 53652 : 53654;
- }
- else
- { // Check Party/Raid Group
- if (Group* group = ToPlayer()->GetGroup())
- {
- for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next())
- {
- if (Player* member = itr->GetSource())
- {
- // check if it was heal by paladin which cast this beacon of light
- if (member->GetAura(53563, victim->GetGUID()))
- {
- // do not proc when target of beacon of light is healed
- if (member == this)
- return false;
-
- beaconTarget = member;
- basepoints0 = int32(damage);
- triggered_spell_id = procSpell->IsRankOf(sSpellMgr->GetSpellInfo(635)) ? 53652 : 53654;
- break;
- }
- }
- }
- }
- }
-
- if (triggered_spell_id && beaconTarget)
- {
- victim->CastCustomSpell(beaconTarget, triggered_spell_id, &basepoints0, NULL, NULL, true);
- return true;
- }
-
- return false;
- }
// Judgements of the Wise
if (dummySpell->SpellIconID == 3017)
{
@@ -8956,6 +8934,12 @@ bool Unit::Attack(Unit* victim, bool meleeAttack)
if (HasAuraType(SPELL_AURA_MOD_UNATTACKABLE))
RemoveAurasByType(SPELL_AURA_MOD_UNATTACKABLE);
+ if (m_shouldReacquireTarget)
+ {
+ SetTarget(victim->GetGUID());
+ m_shouldReacquireTarget = false;
+ }
+
if (m_attacking)
{
if (m_attacking == victim)
@@ -9041,7 +9025,7 @@ bool Unit::AttackStop()
Unit* victim = m_attacking;
m_attacking->_removeAttacker(this);
- m_attacking = NULL;
+ m_attacking = nullptr;
// Clear our target
SetTarget(ObjectGuid::Empty);
@@ -12705,7 +12689,7 @@ Unit* Creature::SelectVictim()
// next-victim-selection algorithm and evade mode are called
// threat list sorting etc.
- Unit* target = NULL;
+ Unit* target = nullptr;
// First checking if we have some taunt on us
AuraEffectList const& tauntAuras = GetAuraEffectsByType(SPELL_AURA_MOD_TAUNT);
if (!tauntAuras.empty())
@@ -12773,7 +12757,8 @@ Unit* Creature::SelectVictim()
if (target && _IsTargetAcceptable(target) && CanCreatureAttack(target))
{
- SetInFront(target);
+ if(!IsFocusing())
+ SetInFront(target);
return target;
}
@@ -14176,8 +14161,8 @@ void Unit::ProcDamageAndSpellFor(bool isVictim, Unit* target, uint32 procFlag, u
Unit* actionTarget = !isVictim ? target : this;
DamageInfo damageInfo = DamageInfo(actor, actionTarget, damage, procSpell, procSpell ? SpellSchoolMask(procSpell->SchoolMask) : SPELL_SCHOOL_MASK_NORMAL, SPELL_DIRECT_DAMAGE);
- HealInfo healInfo = HealInfo(damage);
- ProcEventInfo eventInfo = ProcEventInfo(actor, actionTarget, target, procFlag, 0, 0, procExtra, NULL, &damageInfo, &healInfo);
+ HealInfo healInfo = HealInfo(actor, actionTarget, damage, procSpell, procSpell ? SpellSchoolMask(procSpell->SchoolMask) : SPELL_SCHOOL_MASK_NORMAL);
+ ProcEventInfo eventInfo = ProcEventInfo(actor, actionTarget, target, procFlag, 0, 0, procExtra, nullptr, &damageInfo, &healInfo);
ProcTriggeredList procTriggered;
// Fill procTriggered list
@@ -17561,7 +17546,7 @@ void Unit::SetFacingTo(float ori)
init.Launch();
}
-void Unit::SetFacingToObject(WorldObject* object)
+void Unit::SetFacingToObject(WorldObject const* object)
{
// never face when already moving
if (!IsStopped())
diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h
index 90d312f422a..8572c791d67 100644
--- a/src/server/game/Entities/Unit/Unit.h
+++ b/src/server/game/Entities/Unit/Unit.h
@@ -863,22 +863,30 @@ public:
class HealInfo
{
private:
- uint32 m_heal;
- uint32 m_absorb;
+ Unit* const _healer;
+ Unit* const _target;
+ uint32 _heal;
+ uint32 _absorb;
+ SpellInfo const* const _spellInfo;
+ SpellSchoolMask const _schoolMask;
+
public:
- explicit HealInfo(uint32 heal)
- : m_heal(heal)
- {
- m_absorb = 0;
- }
+ explicit HealInfo(Unit* healer, Unit* target, uint32 heal, SpellInfo const* spellInfo, SpellSchoolMask schoolMask)
+ : _healer(healer), _target(target), _heal(heal), _absorb(0), _spellInfo(spellInfo), _schoolMask(schoolMask) { }
+
void AbsorbHeal(uint32 amount)
{
amount = std::min(amount, GetHeal());
- m_absorb += amount;
- m_heal -= amount;
+ _absorb += amount;
+ _heal -= amount;
}
- uint32 GetHeal() const { return m_heal; }
+ Unit* GetHealer() const { return _healer; }
+ Unit* GetTarget() const { return _target; }
+ uint32 GetHeal() const { return _heal; }
+ uint32 GetAbsorb() const { return _absorb; }
+ SpellInfo const* GetSpellInfo() const { return _spellInfo; };
+ SpellSchoolMask GetSchoolMask() const { return _schoolMask; };
};
class ProcEventInfo
@@ -897,14 +905,8 @@ public:
uint32 GetSpellPhaseMask() const { return _spellPhaseMask; }
uint32 GetHitMask() const { return _hitMask; }
- SpellInfo const* GetSpellInfo() const { return NULL; }
- SpellInfo const* EnsureSpellInfo() const
- {
- SpellInfo const* spellInfo = GetSpellInfo();
- ASSERT(spellInfo);
- return spellInfo;
- }
- SpellSchoolMask GetSchoolMask() const { return SPELL_SCHOOL_MASK_NONE; }
+ SpellInfo const* GetSpellInfo() const;
+ SpellSchoolMask GetSchoolMask() const;
DamageInfo* GetDamageInfo() const { return _damageInfo; }
HealInfo* GetHealInfo() const { return _healInfo; }
@@ -1274,6 +1276,7 @@ class Unit : public WorldObject
void _removeAttacker(Unit* pAttacker); // must be called only from Unit::AttackStop()
Unit* getAttackerForHelper() const; // If someone wants to help, who to give them
bool Attack(Unit* victim, bool meleeAttack);
+ void MustReacquireTarget() { m_shouldReacquireTarget = true; } // flags the Unit for forced target reacquisition in the next ::Attack call
void CastStop(uint32 except_spellid = 0);
bool AttackStop();
void RemoveAllAttackers();
@@ -1584,7 +1587,7 @@ class Unit : public WorldObject
void SetInFront(WorldObject const* target);
void SetFacingTo(float ori);
- void SetFacingToObject(WorldObject* object);
+ void SetFacingToObject(WorldObject const* object);
void SendChangeCurrentVictimOpcode(HostileReference* pHostileReference);
void SendClearThreatListOpcode();
@@ -2153,6 +2156,7 @@ class Unit : public WorldObject
AttackerSet m_attackers;
Unit* m_attacking;
+ bool m_shouldReacquireTarget;
DeathState m_deathState;
diff --git a/src/server/game/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp
index 20f513323c1..1575b50098f 100644
--- a/src/server/game/Instances/InstanceScript.cpp
+++ b/src/server/game/Instances/InstanceScript.cpp
@@ -30,6 +30,12 @@
#include "WorldSession.h"
#include "Opcodes.h"
+BossBoundaryData::~BossBoundaryData()
+{
+ for (const_iterator it = begin(); it != end(); ++it)
+ delete it->Boundary;
+}
+
void InstanceScript::SaveToDB()
{
std::string data = GetSaveData();
@@ -98,9 +104,9 @@ void InstanceScript::SetHeaders(std::string const& dataHeaders)
void InstanceScript::LoadBossBoundaries(const BossBoundaryData& data)
{
- for (BossBoundaryEntry entry : data)
- if (entry.bossId < bosses.size())
- bosses[entry.bossId].boundary.insert(entry.boundary);
+ for (BossBoundaryEntry const& entry : data)
+ if (entry.BossId < bosses.size())
+ bosses[entry.BossId].boundary.insert(entry.Boundary);
}
void InstanceScript::LoadMinionData(const MinionData* data)
diff --git a/src/server/game/Instances/InstanceScript.h b/src/server/game/Instances/InstanceScript.h
index 8285eaa7346..ce83061e162 100644
--- a/src/server/game/Instances/InstanceScript.h
+++ b/src/server/game/Instances/InstanceScript.h
@@ -75,10 +75,23 @@ struct DoorData
struct BossBoundaryEntry
{
- uint32 const bossId;
- AreaBoundary const* const boundary;
+ uint32 BossId;
+ AreaBoundary const* Boundary;
+};
+
+struct BossBoundaryData
+{
+ typedef std::vector<BossBoundaryEntry> StorageType;
+ typedef StorageType::const_iterator const_iterator;
+
+ BossBoundaryData(std::initializer_list<BossBoundaryEntry> data) : _data(data) { }
+ ~BossBoundaryData();
+ const_iterator begin() const { return _data.begin(); }
+ const_iterator end() const { return _data.end(); }
+
+ private:
+ StorageType _data;
};
-typedef std::list<BossBoundaryEntry> BossBoundaryData;
struct MinionData
{
diff --git a/src/server/game/Maps/AreaBoundary.cpp b/src/server/game/Maps/AreaBoundary.cpp
index 837a9959041..e09b252179e 100644
--- a/src/server/game/Maps/AreaBoundary.cpp
+++ b/src/server/game/Maps/AreaBoundary.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015-2016 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
*
* 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
diff --git a/src/server/game/Maps/AreaBoundary.h b/src/server/game/Maps/AreaBoundary.h
index 24a00962359..a134b783ca6 100644
--- a/src/server/game/Maps/AreaBoundary.h
+++ b/src/server/game/Maps/AreaBoundary.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015-2016 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
*
* 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
@@ -32,15 +32,16 @@ class AreaBoundary
BOUNDARY_PARALLELOGRAM,
BOUNDARY_Z_RANGE,
};
+ virtual ~AreaBoundary() { }
BoundaryType GetBoundaryType() const { return m_boundaryType; }
bool IsWithinBoundary(const Position* pos) const { return (IsWithinBoundaryArea(pos) != m_isInvertedBoundary); }
struct DoublePosition : Position
{
double d_positionX, d_positionY, d_positionZ;
- DoublePosition(double x = 0, double y = 0, double z = 0, float o = 0)
- : Position((float)x, (float)y, (float)z, o), d_positionX(x), d_positionY(y), d_positionZ(z) { }
- DoublePosition(float x = 0, float y = 0, float z = 0, float o = 0)
+ DoublePosition(double x = 0.0, double y = 0.0, double z = 0.0, float o = 0.0f)
+ : Position(x, y, z, o), d_positionX(x), d_positionY(y), d_positionZ(z) { }
+ DoublePosition(float x, float y = 0.0f, float z = 0.0f, float o = 0.0f)
: Position(x, y, z, o), d_positionX(x), d_positionY(y), d_positionZ(z) { }
DoublePosition(const Position& pos)
: DoublePosition(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation()) { }
diff --git a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp
index d0340a4df5f..b595059557a 100644
--- a/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp
+++ b/src/server/game/Movement/MovementGenerators/TargetedMovementGenerator.cpp
@@ -39,6 +39,9 @@ void TargetedMovementGeneratorMedium<T, D>::_setTargetLocation(T* owner, bool up
if (owner->GetTypeId() == TYPEID_UNIT && !i_target->isInAccessiblePlaceFor(owner->ToCreature()))
return;
+ if (owner->GetTypeId() == TYPEID_UNIT && owner->ToCreature()->IsFocusing(nullptr, true))
+ return;
+
float x, y, z;
if (updateDestination || !i_path)
diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp
index aed7c0db043..84bff4a69d6 100644
--- a/src/server/game/Spells/Spell.cpp
+++ b/src/server/game/Spells/Spell.cpp
@@ -602,6 +602,8 @@ m_caster((info->HasAttribute(SPELL_ATTR6_CAST_BY_CHARMER) && caster->GetCharmerO
//Auto Shot & Shoot (wand)
m_autoRepeat = m_spellInfo->IsAutoRepeatRangedSpell();
+
+ m_isDelayedInstantCast = false;
m_runesState = 0;
m_powerCost = 0; // setup to correct value in Spell::prepare, must not be used before.
@@ -2936,6 +2938,27 @@ void Spell::prepare(SpellCastTargets const* targets, AuraEffect const* triggered
else
m_casttime = m_spellInfo->CalcCastTime(this);
+ if (m_caster->GetTypeId() == TYPEID_UNIT && !m_caster->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) // _UNIT actually means creature. for some reason.
+ if (!(IsNextMeleeSwingSpell() || IsAutoRepeat() || _triggeredCastFlags & TRIGGERED_IGNORE_SET_FACING))
+ {
+ if (m_targets.GetObjectTarget() && m_caster != m_targets.GetObjectTarget())
+ {
+ if (m_caster->ToCreature()->FocusTarget(this, m_targets.GetObjectTarget()))
+ {
+ m_isDelayedInstantCast = true;
+ m_timer = 100; // 100ms delay ensures client has updated creature orientation when cast goes off
+ }
+ }
+ else if (m_spellInfo->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST))
+ {
+ if (m_caster->ToCreature()->FocusTarget(this, nullptr))
+ {
+ m_isDelayedInstantCast = true;
+ m_timer = 100;
+ }
+ }
+ }
+
// don't allow channeled spells / spells with cast time to be cast while moving
// (even if they are interrupted on moving, spells with almost immediate effect get to have their effect processed before movement interrupter kicks in)
if ((m_spellInfo->IsChanneled() || m_casttime) && m_caster->GetTypeId() == TYPEID_PLAYER && m_caster->isMoving() && m_spellInfo->InterruptFlags & SPELL_INTERRUPT_FLAG_MOVEMENT)
@@ -2971,18 +2994,14 @@ void Spell::prepare(SpellCastTargets const* targets, AuraEffect const* triggered
}
m_caster->SetCurrentCastSpell(this);
- SendSpellStart();
-
- // set target for proper facing
- if ((m_casttime || m_spellInfo->IsChanneled()) && !(_triggeredCastFlags & TRIGGERED_IGNORE_SET_FACING))
- if (m_caster->GetTypeId() == TYPEID_UNIT && m_targets.GetObjectTarget() && m_caster != m_targets.GetObjectTarget())
- m_caster->ToCreature()->FocusTarget(this, m_targets.GetObjectTarget());
+ if (!m_isDelayedInstantCast)
+ SendSpellStart();
if (!(_triggeredCastFlags & TRIGGERED_IGNORE_GCD))
TriggerGlobalCooldown();
//item: first cast may destroy item and second cast causes crash
- if (!m_casttime && !m_spellInfo->StartRecoveryTime && !m_castItemGUID && GetCurrentContainer() == CURRENT_GENERIC_SPELL)
+ if (!m_casttime && !m_isDelayedInstantCast && !m_spellInfo->StartRecoveryTime && !m_castItemGUID && GetCurrentContainer() == CURRENT_GENERIC_SPELL)
cast(true);
}
}
@@ -2991,6 +3010,9 @@ void Spell::cancel()
{
if (m_spellState == SPELL_STATE_FINISHED)
return;
+ // delayed instant casts are used for client-side visual orientation; they are treated as instant for all intents and purposes server-side, and thus cannot be interrupted by another cast
+ if (m_isDelayedInstantCast)
+ return;
uint32 oldState = m_spellState;
m_spellState = SPELL_STATE_FINISHED;
@@ -3060,6 +3082,9 @@ void Spell::cast(bool skipCheck)
return;
}
+ if (m_isDelayedInstantCast)
+ SendSpellStart();
+
if (Player* playerCaster = m_caster->ToPlayer())
{
// now that we've done the basic check, now run the scripts
@@ -3139,6 +3164,16 @@ void Spell::cast(bool skipCheck)
}
}
+ // if the spell allows the creature to turn while casting, then adjust server-side orientation to face the target now
+ // client-side orientation is handled by the client itself, as the cast target is targeted due to Creature::FocusTarget
+ if (m_caster->GetTypeId() == TYPEID_UNIT && !m_caster->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED))
+ if (!m_spellInfo->HasAttribute(SPELL_ATTR5_DONT_TURN_DURING_CAST))
+ if (WorldObject* objTarget = m_targets.GetObjectTarget())
+ {
+ m_caster->SetInFront(objTarget);
+ m_caster->SetFacingToObject(objTarget);
+ }
+
SelectSpellTargets();
// Spell may be finished after target map check
@@ -3242,6 +3277,9 @@ void Spell::cast(bool skipCheck)
}
SetExecutedCurrently(false);
+
+ if (Creature* creatureCaster = m_caster->ToCreature())
+ creatureCaster->ReleaseFocus(this);
}
void Spell::handle_immediate()
diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h
index e9fde234889..696d2801645 100644
--- a/src/server/game/Spells/Spell.h
+++ b/src/server/game/Spells/Spell.h
@@ -524,6 +524,7 @@ class Spell
int32 m_channeledDuration; // Calculated channeled spell duration in order to calculate correct pushback.
bool m_canReflect; // can reflect this spell?
bool m_autoRepeat;
+ bool m_isDelayedInstantCast; // whether this is a creature's delayed instant cast
uint8 m_runesState;
uint8 m_delayAtDamageCount;
diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp
index e96ed8c6799..e0b9bf53b63 100644
--- a/src/server/game/Spells/SpellMgr.cpp
+++ b/src/server/game/Spells/SpellMgr.cpp
@@ -969,10 +969,12 @@ bool SpellMgr::CanSpellTriggerProcOnEvent(SpellProcEntry const& procEntry, ProcE
// check spell family name/flags (if set) for spells
if (eventInfo.GetTypeMask() & (PERIODIC_PROC_FLAG_MASK | SPELL_PROC_FLAG_MASK | PROC_FLAG_DONE_TRAP_ACTIVATION))
{
- if (procEntry.spellFamilyName && eventInfo.GetSpellInfo() && (procEntry.spellFamilyName != eventInfo.EnsureSpellInfo()->SpellFamilyName))
+ SpellInfo const* eventSpellInfo = eventInfo.GetSpellInfo();
+
+ if (procEntry.spellFamilyName && eventSpellInfo && (procEntry.spellFamilyName != eventSpellInfo->SpellFamilyName))
return false;
- if (procEntry.spellFamilyMask && eventInfo.GetSpellInfo() && !(procEntry.spellFamilyMask & eventInfo.EnsureSpellInfo()->SpellFamilyFlags))
+ if (procEntry.spellFamilyMask && eventSpellInfo && !(procEntry.spellFamilyMask & eventSpellInfo->SpellFamilyFlags))
return false;
}
@@ -3332,6 +3334,8 @@ void SpellMgr::LoadSpellInfoCorrections()
spellInfo->Attributes |= SPELL_ATTR0_PASSIVE;
break;
case 17364: // Stormstrike
+ case 48278: // Paralyze
+ case 53651: // Light's Beacon
spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS;
break;
case 51798: // Brewfest - Relay Race - Intro - Quest Complete
@@ -3565,7 +3569,6 @@ void SpellMgr::LoadSpellInfoCorrections()
spellInfo->AreaGroupId = 0; // originally, these require area 4522, which is... outside of Icecrown Citadel
break;
case 70602: // Corruption
- case 48278: // Paralyze
spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS;
break;
case 70715: // Column of Frost (visual marker)
diff --git a/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp b/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp
index 269e115189a..d5ed2421efc 100644
--- a/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp
+++ b/src/server/scripts/EasternKingdoms/AlteracValley/boss_drekthar.cpp
@@ -77,7 +77,7 @@ public:
Talk(SAY_RESPAWN);
}
- bool CheckInRoom()
+ bool CheckInRoom() override
{
if (me->GetDistance2d(me->GetHomePosition().GetPositionX(), me->GetHomePosition().GetPositionY()) > 50)
{
diff --git a/src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp b/src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp
index 93583364ef6..238942c15f6 100644
--- a/src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp
+++ b/src/server/scripts/EasternKingdoms/AlteracValley/boss_galvangar.cpp
@@ -78,7 +78,7 @@ public:
Talk(SAY_BUFF);
}
- bool CheckInRoom()
+ bool CheckInRoom() override
{
if (me->GetDistance2d(me->GetHomePosition().GetPositionX(), me->GetHomePosition().GetPositionY()) > 50)
{
diff --git a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp
index e5ac2676ad0..b1a0f0217c4 100644
--- a/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp
+++ b/src/server/scripts/Northrend/CrusadersColiseum/TrialOfTheCrusader/instance_trial_of_the_crusader.cpp
@@ -22,12 +22,11 @@
#include "Player.h"
#include "TemporarySummon.h"
-AreaBoundary const* const mainBoundary = new CircleBoundary(Position(563.26f, 139.6f), 75.0);
BossBoundaryData const boundaries = {
- { BOSS_BEASTS, mainBoundary },
- { BOSS_JARAXXUS, mainBoundary },
- { BOSS_CRUSADERS, mainBoundary },
- { BOSS_VALKIRIES, mainBoundary },
+ { BOSS_BEASTS, new CircleBoundary(Position(563.26f, 139.6f), 75.0) },
+ { BOSS_JARAXXUS, new CircleBoundary(Position(563.26f, 139.6f), 75.0) },
+ { BOSS_CRUSADERS, new CircleBoundary(Position(563.26f, 139.6f), 75.0) },
+ { BOSS_VALKIRIES, new CircleBoundary(Position(563.26f, 139.6f), 75.0) },
{ BOSS_ANUBARAK, new EllipseBoundary(Position(746.0f, 135.0f), 100.0, 75.0) }
};
diff --git a/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp b/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp
index 9bdbce81dbf..ac094588d35 100644
--- a/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp
+++ b/src/server/scripts/Northrend/IcecrownCitadel/boss_sindragosa.cpp
@@ -265,7 +265,7 @@ class boss_sindragosa : public CreatureScript
{
if (!instance->CheckRequiredBosses(DATA_SINDRAGOSA, victim->ToPlayer()))
{
- EnterEvadeMode();
+ EnterEvadeMode(EVADE_REASON_SEQUENCE_BREAK);
instance->DoCastSpellOnPlayers(LIGHT_S_HAMMER_TELEPORT);
return;
}
@@ -276,6 +276,13 @@ class boss_sindragosa : public CreatureScript
Talk(SAY_AGGRO);
}
+ void EnterEvadeMode(EvadeReason why) override
+ {
+ if (_isInAirPhase && why == EVADE_REASON_BOUNDARY)
+ return;
+ BossAI::EnterEvadeMode(why);
+ }
+
void JustReachedHome() override
{
BossAI::JustReachedHome();
diff --git a/src/server/scripts/Outland/zone_zangarmarsh.cpp b/src/server/scripts/Outland/zone_zangarmarsh.cpp
index 48bbb7bad4a..6f38cce0e5b 100644
--- a/src/server/scripts/Outland/zone_zangarmarsh.cpp
+++ b/src/server/scripts/Outland/zone_zangarmarsh.cpp
@@ -48,8 +48,6 @@ EndContentData */
enum AshyenAndKeleth
{
- GOSSIP_REWARD_BLESS = 0,
-
NPC_ASHYEN = 17900,
NPC_KELETH = 17901,
@@ -117,7 +115,6 @@ public:
if (spell)
{
creature->CastSpell(player, spell, true);
- creature->AI()->Talk(GOSSIP_REWARD_BLESS);
}
}
@@ -145,7 +142,6 @@ public:
if (spell)
{
creature->CastSpell(player, spell, true);
- creature->AI()->Talk(GOSSIP_REWARD_BLESS);
}
}
player->CLOSE_GOSSIP_MENU();
diff --git a/src/server/scripts/Spells/spell_paladin.cpp b/src/server/scripts/Spells/spell_paladin.cpp
index 9f6b4947328..13e7697910b 100644
--- a/src/server/scripts/Spells/spell_paladin.cpp
+++ b/src/server/scripts/Spells/spell_paladin.cpp
@@ -43,6 +43,12 @@ enum PaladinSpells
SPELL_PALADIN_BLESSING_OF_LOWER_CITY_PRIEST = 37880,
SPELL_PALADIN_BLESSING_OF_LOWER_CITY_SHAMAN = 37881,
+ SPELL_PALADIN_BEACON_OF_LIGHT = 53563,
+ SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_1 = 53652,
+ SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_2 = 53653,
+ SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_3 = 53654,
+ SPELL_PALADIN_HOLY_LIGHT = 635,
+
SPELL_PALADIN_DIVINE_STORM = 53385,
SPELL_PALADIN_DIVINE_STORM_DUMMY = 54171,
SPELL_PALADIN_DIVINE_STORM_HEAL = 54172,
@@ -1153,6 +1159,68 @@ class spell_pal_lay_on_hands : public SpellScriptLoader
}
};
+// 53651 - Light's Beacon - Beacon of Light
+class spell_pal_light_s_beacon : public SpellScriptLoader
+{
+ public:
+ spell_pal_light_s_beacon() : SpellScriptLoader("spell_pal_light_s_beacon") { }
+
+ class spell_pal_light_s_beacon_AuraScript : public AuraScript
+ {
+ PrepareAuraScript(spell_pal_light_s_beacon_AuraScript);
+
+ bool Validate(SpellInfo const* /*spellInfo*/) override
+ {
+ if (!sSpellMgr->GetSpellInfo(SPELL_PALADIN_BEACON_OF_LIGHT)
+ || !sSpellMgr->GetSpellInfo(SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_1)
+ || !sSpellMgr->GetSpellInfo(SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_2)
+ || !sSpellMgr->GetSpellInfo(SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_3)
+ || !sSpellMgr->GetSpellInfo(SPELL_PALADIN_HOLY_LIGHT))
+ return false;
+ return true;
+ }
+
+ bool CheckProc(ProcEventInfo& eventInfo)
+ {
+ if (GetTarget()->HasAura(SPELL_PALADIN_BEACON_OF_LIGHT, eventInfo.GetActor()->GetGUID()))
+ return false;
+ return true;
+ }
+
+ void HandleProc(AuraEffect const* aurEff, ProcEventInfo& eventInfo)
+ {
+ PreventDefaultAction();
+
+ SpellInfo const* procSpell = eventInfo.GetSpellInfo();
+ if (!procSpell)
+ return;
+
+ uint32 healSpellId = procSpell->IsRankOf(sSpellMgr->GetSpellInfo(SPELL_PALADIN_HOLY_LIGHT)) ? SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_1 : SPELL_PALADIN_BEACON_OF_LIGHT_HEAL_3;
+ uint32 heal = CalculatePct(eventInfo.GetHealInfo()->GetHeal(), aurEff->GetAmount());
+
+ Unit* beaconTarget = GetCaster();
+ if (!beaconTarget || !beaconTarget->HasAura(SPELL_PALADIN_BEACON_OF_LIGHT, eventInfo.GetActor()->GetGUID()))
+ return;
+
+ /// @todo: caster must be the healed unit to perform distance checks correctly
+ /// but that will break animation on clientside
+ /// caster in spell packets must be the healing unit
+ eventInfo.GetActor()->CastCustomSpell(healSpellId, SPELLVALUE_BASE_POINT0, heal, beaconTarget, true);
+ }
+
+ void Register() override
+ {
+ DoCheckProc += AuraCheckProcFn(spell_pal_light_s_beacon_AuraScript::CheckProc);
+ OnEffectProc += AuraEffectProcFn(spell_pal_light_s_beacon_AuraScript::HandleProc, EFFECT_0, SPELL_AURA_DUMMY);
+ }
+ };
+
+ AuraScript* GetAuraScript() const override
+ {
+ return new spell_pal_light_s_beacon_AuraScript();
+ }
+};
+
// 31789 - Righteous Defense
class spell_pal_righteous_defense : public SpellScriptLoader
{
@@ -1338,6 +1406,7 @@ void AddSC_paladin_spell_scripts()
new spell_pal_judgement("spell_pal_judgement_of_wisdom", SPELL_PALADIN_JUDGEMENT_OF_WISDOM);
new spell_pal_judgement_of_command();
new spell_pal_lay_on_hands();
+ new spell_pal_light_s_beacon();
new spell_pal_righteous_defense();
new spell_pal_sacred_shield();
new spell_pal_seal_of_righteousness();