Spline movement controls movements of server-side controlled units (monster movement, taxi movement, etc). Proper implementation of effects such as charge, jump, cyclic movement will rely on it. However, need improve our states system before. Technical changes: * Added linear, catmullrom and bezier3 splines which based on client's algorthims. They can be reused for proper transport position interpolation. * Precission increased. There are no more position desync issues since client's position calculation formulas used. * Now possible to move by paths with multiple points, send whole path to client. -- Original author of research and implementation: SilverIce. Massive kudos. Original port for Trinity (ref #4629) Chaplain and Venugh With the following incremental fixes during my review: - Restore flightmaster end grid pre-loading - Fix uninitialized Creature::m_path_id - Add missing trinity_string entries for .movegens command - Fix a bug in WaypointMovementGenerator that would trigger unexpected pausing at waypoints for various amounts of time Known issues: - Errors like WaypointMovementGenerator::LoadPath creature XXX (Entry: YYYYY GUID: ZZZZZZ) doesn't have waypoint path id: 0. This is caused by bad DB data. This commit didn't "break" it. Do not forget to re-run CMake before compiling.
686 lines
19 KiB
C++
686 lines
19 KiB
C++
/* Copyright (C) 2008-2012 TrinityCore <http://www.trinitycore.org/>
|
|
*
|
|
* Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
|
*
|
|
* This program is free software licensed under GPL version 2
|
|
* Please see the included DOCS/LICENSE.TXT for more information */
|
|
|
|
#include "ScriptPCH.h"
|
|
#include "Item.h"
|
|
#include "Spell.h"
|
|
#include "ObjectMgr.h"
|
|
#include "TemporarySummon.h"
|
|
|
|
// Spell summary for ScriptedAI::SelectSpell
|
|
struct TSpellSummary
|
|
{
|
|
uint8 Targets; // set of enum SelectTarget
|
|
uint8 Effects; // set of enum SelectEffect
|
|
} extern* SpellSummary;
|
|
|
|
void SummonList::DoZoneInCombat(uint32 entry)
|
|
{
|
|
for (iterator i = begin(); i != end();)
|
|
{
|
|
Creature* summon = Unit::GetCreature(*me, *i);
|
|
++i;
|
|
if (summon && summon->IsAIEnabled
|
|
&& (!entry || summon->GetEntry() == entry))
|
|
summon->AI()->DoZoneInCombat();
|
|
}
|
|
}
|
|
|
|
void SummonList::DoAction(uint32 entry, int32 info)
|
|
{
|
|
for (iterator i = begin(); i != end();)
|
|
{
|
|
Creature* summon = Unit::GetCreature(*me, *i);
|
|
++i;
|
|
if (summon && summon->IsAIEnabled
|
|
&& (!entry || summon->GetEntry() == entry))
|
|
summon->AI()->DoAction(info);
|
|
}
|
|
}
|
|
|
|
void SummonList::DespawnEntry(uint32 entry)
|
|
{
|
|
for (iterator i = begin(); i != end();)
|
|
{
|
|
Creature* summon = Unit::GetCreature(*me, *i);
|
|
if (!summon)
|
|
erase(i++);
|
|
else if (summon->GetEntry() == entry)
|
|
{
|
|
erase(i++);
|
|
summon->DespawnOrUnsummon();
|
|
}
|
|
else
|
|
++i;
|
|
}
|
|
}
|
|
|
|
void SummonList::DespawnAll()
|
|
{
|
|
while (!empty())
|
|
{
|
|
Creature* summon = Unit::GetCreature(*me, *begin());
|
|
if (!summon)
|
|
erase(begin());
|
|
else
|
|
{
|
|
erase(begin());
|
|
summon->DespawnOrUnsummon();
|
|
}
|
|
}
|
|
}
|
|
|
|
void SummonList::RemoveNotExisting()
|
|
{
|
|
for (iterator i = begin(); i != end();)
|
|
{
|
|
if (Unit::GetCreature(*me, *i))
|
|
++i;
|
|
else
|
|
erase(i++);
|
|
}
|
|
}
|
|
|
|
bool SummonList::HasEntry(uint32 entry)
|
|
{
|
|
for (iterator i = begin(); i != end();)
|
|
{
|
|
Creature* summon = Unit::GetCreature(*me, *i);
|
|
if (!summon)
|
|
erase(i++);
|
|
else if (summon->GetEntry() == entry)
|
|
return true;
|
|
else
|
|
++i;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
ScriptedAI::ScriptedAI(Creature* creature) : CreatureAI(creature),
|
|
me(creature),
|
|
IsFleeing(false),
|
|
_evadeCheckCooldown(2500),
|
|
_isCombatMovementAllowed(true)
|
|
{
|
|
_isHeroic = me->GetMap()->IsHeroic();
|
|
_difficulty = Difficulty(me->GetMap()->GetSpawnMode());
|
|
}
|
|
|
|
void ScriptedAI::AttackStartNoMove(Unit* who)
|
|
{
|
|
if (!who)
|
|
return;
|
|
|
|
if (me->Attack(who, false))
|
|
DoStartNoMovement(who);
|
|
}
|
|
|
|
void ScriptedAI::UpdateAI(uint32 const /*diff*/)
|
|
{
|
|
//Check if we have a current target
|
|
if (!UpdateVictim())
|
|
return;
|
|
|
|
if (me->isAttackReady())
|
|
{
|
|
//If we are within range melee the target
|
|
if (me->IsWithinMeleeRange(me->getVictim()))
|
|
{
|
|
me->AttackerStateUpdate(me->getVictim());
|
|
me->resetAttackTimer();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScriptedAI::DoStartMovement(Unit* victim, float distance, float angle)
|
|
{
|
|
if (victim)
|
|
me->GetMotionMaster()->MoveChase(victim, distance, angle);
|
|
}
|
|
|
|
void ScriptedAI::DoStartNoMovement(Unit* victim)
|
|
{
|
|
if (!victim)
|
|
return;
|
|
|
|
me->GetMotionMaster()->MoveIdle();
|
|
}
|
|
|
|
void ScriptedAI::DoStopAttack()
|
|
{
|
|
if (me->getVictim())
|
|
me->AttackStop();
|
|
}
|
|
|
|
void ScriptedAI::DoCastSpell(Unit* target, SpellInfo const* spellInfo, bool triggered)
|
|
{
|
|
if (!target || me->IsNonMeleeSpellCasted(false))
|
|
return;
|
|
|
|
me->StopMoving();
|
|
me->CastSpell(target, spellInfo, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE);
|
|
}
|
|
|
|
void ScriptedAI::DoPlaySoundToSet(WorldObject* source, uint32 soundId)
|
|
{
|
|
if (!source)
|
|
return;
|
|
|
|
if (!GetSoundEntriesStore()->LookupEntry(soundId))
|
|
{
|
|
sLog->outError("TSCR: Invalid soundId %u used in DoPlaySoundToSet (Source: TypeId %u, GUID %u)", soundId, source->GetTypeId(), source->GetGUIDLow());
|
|
return;
|
|
}
|
|
|
|
source->PlayDirectSound(soundId);
|
|
}
|
|
|
|
Creature* ScriptedAI::DoSpawnCreature(uint32 entry, float offsetX, float offsetY, float offsetZ, float angle, uint32 type, uint32 despawntime)
|
|
{
|
|
return me->SummonCreature(entry, me->GetPositionX() + offsetX, me->GetPositionY() + offsetY, me->GetPositionZ() + offsetZ, angle, TempSummonType(type), despawntime);
|
|
}
|
|
|
|
SpellInfo const* ScriptedAI::SelectSpell(Unit* target, uint32 school, uint32 mechanic, SelectTargetType targets, uint32 powerCostMin, uint32 powerCostMax, float rangeMin, float rangeMax, SelectEffect effects)
|
|
{
|
|
//No target so we can't cast
|
|
if (!target)
|
|
return false;
|
|
|
|
//Silenced so we can't cast
|
|
if (me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED))
|
|
return false;
|
|
|
|
//Using the extended script system we first create a list of viable spells
|
|
SpellInfo const* apSpell[CREATURE_MAX_SPELLS];
|
|
memset(apSpell, 0, CREATURE_MAX_SPELLS * sizeof(SpellInfo*));
|
|
|
|
uint32 spellCount = 0;
|
|
|
|
SpellInfo const* tempSpell = NULL;
|
|
|
|
//Check if each spell is viable(set it to null if not)
|
|
for (uint32 i = 0; i < CREATURE_MAX_SPELLS; i++)
|
|
{
|
|
tempSpell = sSpellMgr->GetSpellInfo(me->m_spells[i]);
|
|
|
|
//This spell doesn't exist
|
|
if (!tempSpell)
|
|
continue;
|
|
|
|
// Targets and Effects checked first as most used restrictions
|
|
//Check the spell targets if specified
|
|
if (targets && !(SpellSummary[me->m_spells[i]].Targets & (1 << (targets-1))))
|
|
continue;
|
|
|
|
//Check the type of spell if we are looking for a specific spell type
|
|
if (effects && !(SpellSummary[me->m_spells[i]].Effects & (1 << (effects-1))))
|
|
continue;
|
|
|
|
//Check for school if specified
|
|
if (school && (tempSpell->SchoolMask & school) == 0)
|
|
continue;
|
|
|
|
//Check for spell mechanic if specified
|
|
if (mechanic && tempSpell->Mechanic != mechanic)
|
|
continue;
|
|
|
|
//Make sure that the spell uses the requested amount of power
|
|
if (powerCostMin && tempSpell->ManaCost < powerCostMin)
|
|
continue;
|
|
|
|
if (powerCostMax && tempSpell->ManaCost > powerCostMax)
|
|
continue;
|
|
|
|
//Continue if we don't have the mana to actually cast this spell
|
|
if (tempSpell->ManaCost > me->GetPower(Powers(tempSpell->PowerType)))
|
|
continue;
|
|
|
|
//Check if the spell meets our range requirements
|
|
if (rangeMin && me->GetSpellMinRangeForTarget(target, tempSpell) < rangeMin)
|
|
continue;
|
|
if (rangeMax && me->GetSpellMaxRangeForTarget(target, tempSpell) > rangeMax)
|
|
continue;
|
|
|
|
//Check if our target is in range
|
|
if (me->IsWithinDistInMap(target, float(me->GetSpellMinRangeForTarget(target, tempSpell))) || !me->IsWithinDistInMap(target, float(me->GetSpellMaxRangeForTarget(target, tempSpell))))
|
|
continue;
|
|
|
|
//All good so lets add it to the spell list
|
|
apSpell[spellCount] = tempSpell;
|
|
++spellCount;
|
|
}
|
|
|
|
//We got our usable spells so now lets randomly pick one
|
|
if (!spellCount)
|
|
return NULL;
|
|
|
|
return apSpell[urand(0, spellCount - 1)];
|
|
}
|
|
|
|
void ScriptedAI::DoResetThreat()
|
|
{
|
|
if (!me->CanHaveThreatList() || me->getThreatManager().isThreatListEmpty())
|
|
{
|
|
sLog->outError("TSCR: DoResetThreat called for creature that either cannot have threat list or has empty threat list (me entry = %d)", me->GetEntry());
|
|
return;
|
|
}
|
|
|
|
std::list<HostileReference*>& threatlist = me->getThreatManager().getThreatList();
|
|
|
|
for (std::list<HostileReference*>::iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
|
{
|
|
Unit* unit = Unit::GetUnit((*me), (*itr)->getUnitGuid());
|
|
|
|
if (unit && DoGetThreat(unit))
|
|
DoModifyThreatPercent(unit, -100);
|
|
}
|
|
}
|
|
|
|
float ScriptedAI::DoGetThreat(Unit* unit)
|
|
{
|
|
if (!unit)
|
|
return 0.0f;
|
|
return me->getThreatManager().getThreat(unit);
|
|
}
|
|
|
|
void ScriptedAI::DoModifyThreatPercent(Unit* unit, int32 pct)
|
|
{
|
|
if (!unit)
|
|
return;
|
|
me->getThreatManager().modifyThreatPercent(unit, pct);
|
|
}
|
|
|
|
void ScriptedAI::DoTeleportTo(float x, float y, float z, uint32 time)
|
|
{
|
|
me->Relocate(x, y, z);
|
|
float speed = me->GetDistance(x, y, z) / ((float)time * 0.001f);
|
|
me->MonsterMoveWithSpeed(x, y, z, speed);
|
|
}
|
|
|
|
void ScriptedAI::DoTeleportTo(const float position[4])
|
|
{
|
|
me->NearTeleportTo(position[0], position[1], position[2], position[3]);
|
|
}
|
|
|
|
void ScriptedAI::DoTeleportPlayer(Unit* unit, float x, float y, float z, float o)
|
|
{
|
|
if (!unit || unit->GetTypeId() != TYPEID_PLAYER)
|
|
{
|
|
if (unit)
|
|
sLog->outError("TSCR: Creature " UI64FMTD " (Entry: %u) Tried to teleport non-player unit (Type: %u GUID: " UI64FMTD ") to x: %f y:%f z: %f o: %f. Aborted.", me->GetGUID(), me->GetEntry(), unit->GetTypeId(), unit->GetGUID(), x, y, z, o);
|
|
return;
|
|
}
|
|
|
|
CAST_PLR(unit)->TeleportTo(unit->GetMapId(), x, y, z, o, TELE_TO_NOT_LEAVE_COMBAT);
|
|
}
|
|
|
|
void ScriptedAI::DoTeleportAll(float x, float y, float z, float o)
|
|
{
|
|
Map* map = me->GetMap();
|
|
if (!map->IsDungeon())
|
|
return;
|
|
|
|
Map::PlayerList const& PlayerList = map->GetPlayers();
|
|
for (Map::PlayerList::const_iterator itr = PlayerList.begin(); itr != PlayerList.end(); ++itr)
|
|
if (Player* player = itr->getSource())
|
|
if (player->isAlive())
|
|
player->TeleportTo(me->GetMapId(), x, y, z, o, TELE_TO_NOT_LEAVE_COMBAT);
|
|
}
|
|
|
|
Unit* ScriptedAI::DoSelectLowestHpFriendly(float range, uint32 minHPDiff)
|
|
{
|
|
Unit* unit = NULL;
|
|
Trinity::MostHPMissingInRange u_check(me, range, minHPDiff);
|
|
Trinity::UnitLastSearcher<Trinity::MostHPMissingInRange> searcher(me, unit, u_check);
|
|
me->VisitNearbyObject(range, searcher);
|
|
|
|
return unit;
|
|
}
|
|
|
|
std::list<Creature*> ScriptedAI::DoFindFriendlyCC(float range)
|
|
{
|
|
std::list<Creature*> list;
|
|
Trinity::FriendlyCCedInRange u_check(me, range);
|
|
Trinity::CreatureListSearcher<Trinity::FriendlyCCedInRange> searcher(me, list, u_check);
|
|
me->VisitNearbyObject(range, searcher);
|
|
return list;
|
|
}
|
|
|
|
std::list<Creature*> ScriptedAI::DoFindFriendlyMissingBuff(float range, uint32 uiSpellid)
|
|
{
|
|
std::list<Creature*> list;
|
|
Trinity::FriendlyMissingBuffInRange u_check(me, range, uiSpellid);
|
|
Trinity::CreatureListSearcher<Trinity::FriendlyMissingBuffInRange> searcher(me, list, u_check);
|
|
me->VisitNearbyObject(range, searcher);
|
|
return list;
|
|
}
|
|
|
|
Player* ScriptedAI::GetPlayerAtMinimumRange(float minimumRange)
|
|
{
|
|
Player* player = NULL;
|
|
|
|
CellCoord pair(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY()));
|
|
Cell cell(pair);
|
|
cell.SetNoCreate();
|
|
|
|
Trinity::PlayerAtMinimumRangeAway check(me, minimumRange);
|
|
Trinity::PlayerSearcher<Trinity::PlayerAtMinimumRangeAway> searcher(me, player, check);
|
|
TypeContainerVisitor<Trinity::PlayerSearcher<Trinity::PlayerAtMinimumRangeAway>, GridTypeMapContainer> visitor(searcher);
|
|
|
|
cell.Visit(pair, visitor, *me->GetMap(), *me, minimumRange);
|
|
|
|
return player;
|
|
}
|
|
|
|
void ScriptedAI::SetEquipmentSlots(bool loadDefault, int32 mainHand /*= EQUIP_NO_CHANGE*/, int32 offHand /*= EQUIP_NO_CHANGE*/, int32 ranged /*= EQUIP_NO_CHANGE*/)
|
|
{
|
|
if (loadDefault)
|
|
{
|
|
if (CreatureTemplate const* creatureInfo = sObjectMgr->GetCreatureTemplate(me->GetEntry()))
|
|
me->LoadEquipment(creatureInfo->equipmentId, true);
|
|
|
|
return;
|
|
}
|
|
|
|
if (mainHand >= 0)
|
|
me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0, uint32(mainHand));
|
|
|
|
if (offHand >= 0)
|
|
me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, uint32(offHand));
|
|
|
|
if (ranged >= 0)
|
|
me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 2, uint32(ranged));
|
|
}
|
|
|
|
void ScriptedAI::SetCombatMovement(bool allowMovement)
|
|
{
|
|
_isCombatMovementAllowed = allowMovement;
|
|
}
|
|
|
|
enum eNPCs
|
|
{
|
|
NPC_BROODLORD = 12017,
|
|
NPC_VOID_REAVER = 19516,
|
|
NPC_JAN_ALAI = 23578,
|
|
NPC_SARTHARION = 28860
|
|
};
|
|
|
|
// Hacklike storage used for misc creatures that are expected to evade of outside of a certain area.
|
|
// It is assumed the information is found elswehere and can be handled by the core. So far no luck finding such information/way to extract it.
|
|
bool ScriptedAI::EnterEvadeIfOutOfCombatArea(uint32 const diff)
|
|
{
|
|
if (_evadeCheckCooldown <= diff)
|
|
_evadeCheckCooldown = 2500;
|
|
else
|
|
{
|
|
_evadeCheckCooldown -= diff;
|
|
return false;
|
|
}
|
|
|
|
if (me->IsInEvadeMode() || !me->getVictim())
|
|
return false;
|
|
|
|
float x = me->GetPositionX();
|
|
float y = me->GetPositionY();
|
|
float z = me->GetPositionZ();
|
|
|
|
switch (me->GetEntry())
|
|
{
|
|
case NPC_BROODLORD: // broodlord (not move down stairs)
|
|
if (z > 448.60f)
|
|
return false;
|
|
break;
|
|
case NPC_VOID_REAVER: // void reaver (calculate from center of room)
|
|
if (me->GetDistance2d(432.59f, 371.93f) < 105.0f)
|
|
return false;
|
|
break;
|
|
case NPC_JAN_ALAI: // jan'alai (calculate by Z)
|
|
if (z > 12.0f)
|
|
return false;
|
|
break;
|
|
case NPC_SARTHARION: // sartharion (calculate box)
|
|
if (x > 3218.86f && x < 3275.69f && y < 572.40f && y > 484.68f)
|
|
return false;
|
|
break;
|
|
default: // For most of creatures that certain area is their home area.
|
|
sLog->outDetail("TSCR: EnterEvadeIfOutOfCombatArea used for creature entry %u, but does not have any definition. Using the default one.", me->GetEntry());
|
|
uint32 homeAreaId = me->GetMap()->GetAreaId(me->GetHomePosition().GetPositionX(), me->GetHomePosition().GetPositionY(), me->GetHomePosition().GetPositionZ());
|
|
if (me->GetAreaId() == homeAreaId)
|
|
return false;
|
|
}
|
|
|
|
EnterEvadeMode();
|
|
return true;
|
|
}
|
|
|
|
void Scripted_NoMovementAI::AttackStart(Unit* target)
|
|
{
|
|
if (!target)
|
|
return;
|
|
|
|
if (me->Attack(target, true))
|
|
DoStartNoMovement(target);
|
|
}
|
|
|
|
// BossAI - for instanced bosses
|
|
|
|
BossAI::BossAI(Creature* creature, uint32 bossId) : ScriptedAI(creature),
|
|
instance(creature->GetInstanceScript()),
|
|
summons(creature),
|
|
_boundary(instance ? instance->GetBossBoundary(bossId) : NULL),
|
|
_bossId(bossId)
|
|
{
|
|
}
|
|
|
|
void BossAI::_Reset()
|
|
{
|
|
if (!me->isAlive())
|
|
return;
|
|
|
|
me->ResetLootMode();
|
|
events.Reset();
|
|
summons.DespawnAll();
|
|
if (instance)
|
|
instance->SetBossState(_bossId, NOT_STARTED);
|
|
}
|
|
|
|
void BossAI::_JustDied()
|
|
{
|
|
events.Reset();
|
|
summons.DespawnAll();
|
|
if (instance)
|
|
{
|
|
instance->SetBossState(_bossId, DONE);
|
|
instance->SaveToDB();
|
|
}
|
|
}
|
|
|
|
void BossAI::_EnterCombat()
|
|
{
|
|
me->setActive(true);
|
|
DoZoneInCombat();
|
|
if (instance)
|
|
{
|
|
// bosses do not respawn, check only on enter combat
|
|
if (!instance->CheckRequiredBosses(_bossId))
|
|
{
|
|
EnterEvadeMode();
|
|
return;
|
|
}
|
|
instance->SetBossState(_bossId, IN_PROGRESS);
|
|
}
|
|
}
|
|
|
|
void BossAI::TeleportCheaters()
|
|
{
|
|
float x, y, z;
|
|
me->GetPosition(x, y, z);
|
|
std::list<HostileReference*>& threatList = me->getThreatManager().getThreatList();
|
|
for (std::list<HostileReference*>::iterator itr = threatList.begin(); itr != threatList.end(); ++itr)
|
|
if (Unit* target = (*itr)->getTarget())
|
|
if (target->GetTypeId() == TYPEID_PLAYER && !CheckBoundary(target))
|
|
target->NearTeleportTo(x, y, z, 0);
|
|
}
|
|
|
|
bool BossAI::CheckBoundary(Unit* who)
|
|
{
|
|
if (!GetBoundary() || !who)
|
|
return true;
|
|
|
|
for (BossBoundaryMap::const_iterator itr = GetBoundary()->begin(); itr != GetBoundary()->end(); ++itr)
|
|
{
|
|
switch (itr->first)
|
|
{
|
|
case BOUNDARY_N:
|
|
if (me->GetPositionX() > itr->second)
|
|
return false;
|
|
break;
|
|
case BOUNDARY_S:
|
|
if (me->GetPositionX() < itr->second)
|
|
return false;
|
|
break;
|
|
case BOUNDARY_E:
|
|
if (me->GetPositionY() < itr->second)
|
|
return false;
|
|
break;
|
|
case BOUNDARY_W:
|
|
if (me->GetPositionY() > itr->second)
|
|
return false;
|
|
break;
|
|
case BOUNDARY_NW:
|
|
if (me->GetPositionX() + me->GetPositionY() > itr->second)
|
|
return false;
|
|
break;
|
|
case BOUNDARY_SE:
|
|
if (me->GetPositionX() + me->GetPositionY() < itr->second)
|
|
return false;
|
|
break;
|
|
case BOUNDARY_NE:
|
|
if (me->GetPositionX() - me->GetPositionY() > itr->second)
|
|
return false;
|
|
break;
|
|
case BOUNDARY_SW:
|
|
if (me->GetPositionX() - me->GetPositionY() < itr->second)
|
|
return false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void BossAI::JustSummoned(Creature* summon)
|
|
{
|
|
summons.Summon(summon);
|
|
if (me->isInCombat())
|
|
DoZoneInCombat(summon);
|
|
}
|
|
|
|
void BossAI::SummonedCreatureDespawn(Creature* summon)
|
|
{
|
|
summons.Despawn(summon);
|
|
}
|
|
|
|
void BossAI::UpdateAI(uint32 const diff)
|
|
{
|
|
if (!UpdateVictim())
|
|
return;
|
|
|
|
events.Update(diff);
|
|
|
|
if (me->HasUnitState(UNIT_STAT_CASTING))
|
|
return;
|
|
|
|
while (uint32 eventId = events.ExecuteEvent())
|
|
ExecuteEvent(eventId);
|
|
|
|
DoMeleeAttackIfReady();
|
|
}
|
|
|
|
// WorldBossAI - for non-instanced bosses
|
|
|
|
WorldBossAI::WorldBossAI(Creature* creature) :
|
|
ScriptedAI(creature),
|
|
summons(creature)
|
|
{
|
|
}
|
|
|
|
void WorldBossAI::_Reset()
|
|
{
|
|
if (!me->isAlive())
|
|
return;
|
|
|
|
events.Reset();
|
|
summons.DespawnAll();
|
|
}
|
|
|
|
void WorldBossAI::_JustDied()
|
|
{
|
|
events.Reset();
|
|
summons.DespawnAll();
|
|
}
|
|
|
|
void WorldBossAI::_EnterCombat()
|
|
{
|
|
Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 0.0f, true);
|
|
if (target)
|
|
AttackStart(target);
|
|
}
|
|
|
|
void WorldBossAI::JustSummoned(Creature* summon)
|
|
{
|
|
summons.Summon(summon);
|
|
Unit* target = SelectTarget(SELECT_TARGET_RANDOM, 0, 0.0f, true);
|
|
if (target)
|
|
summon->AI()->AttackStart(target);
|
|
}
|
|
|
|
void WorldBossAI::SummonedCreatureDespawn(Creature* summon)
|
|
{
|
|
summons.Despawn(summon);
|
|
}
|
|
|
|
void WorldBossAI::UpdateAI(uint32 const diff)
|
|
{
|
|
if (!UpdateVictim())
|
|
return;
|
|
|
|
events.Update(diff);
|
|
|
|
if (me->HasUnitState(UNIT_STAT_CASTING))
|
|
return;
|
|
|
|
while (uint32 eventId = events.ExecuteEvent())
|
|
ExecuteEvent(eventId);
|
|
|
|
DoMeleeAttackIfReady();
|
|
}
|
|
|
|
// SD2 grid searchers.
|
|
Creature* GetClosestCreatureWithEntry(WorldObject* source, uint32 entry, float maxSearchRange, bool alive /*= true*/)
|
|
{
|
|
return source->FindNearestCreature(entry, maxSearchRange, alive);
|
|
}
|
|
|
|
GameObject* GetClosestGameObjectWithEntry(WorldObject* source, uint32 entry, float maxSearchRange)
|
|
{
|
|
return source->FindNearestGameObject(entry, maxSearchRange);
|
|
}
|
|
|
|
void GetCreatureListWithEntryInGrid(std::list<Creature*>& list, WorldObject* source, uint32 entry, float maxSearchRange)
|
|
{
|
|
source->GetCreatureListWithEntryInGrid(list, entry, maxSearchRange);
|
|
}
|
|
|
|
void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& list, WorldObject* source, uint32 entry, float maxSearchRange)
|
|
{
|
|
source->GetGameObjectListWithEntryInGrid(list, entry, maxSearchRange);
|
|
}
|