Skip to content
This repository has been archived by the owner on Jan 18, 2024. It is now read-only.

Commit

Permalink
Core/Creatures: re-implement temporary summons (WIP)
Browse files Browse the repository at this point in the history
- replaced all temporary summon classes with new ones written from scratch to support all recently leaked informations in a clean and proper manner
- controlled summons will no longer use the m_controlled container for that one is meant to be used by charmed units only
- re-implemented pets from the ground up and handle their creation, updating and saving in a fast and efficient way
- allow guardians to have passive auras and stats as well
- defined and implemented PLAYER_FIELD_BYTE_HIDE_PET_BAR
- converted most pet packets to packet class
- made summon accessing type and moron safe
- dropped a bazillion of hacks and tempfixes
- fixed showing totem icons for all summons that go into a totem slot so they can be Unsummoned via UI actions
- cleaned up and optimized TotemAI
- implemented functionality to disable pet actions while mounted
- implemented a distance check for guardians and following summons to make sure that they despawn when too far away
  • Loading branch information
Ovahlord committed Nov 12, 2023
1 parent 4d27b31 commit ff481bc
Show file tree
Hide file tree
Showing 91 changed files with 5,929 additions and 4,725 deletions.
51 changes: 22 additions & 29 deletions src/server/database/Database/Implementation/CharacterDatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ void CharacterDatabaseConnection::DoPrepareStatements()
"subject, deliver_time, expire_time, money, has_items FROM mail WHERE receiver = ? ", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_MAIL_LIST_ITEMS, "SELECT itemEntry,count FROM item_instance WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_ENUM, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.level, c.zone, c.map, c.position_x, c.position_y, c.position_z, "
"gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, cb.guid, c.slot "
"FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.active = 1 LEFT JOIN guild_member AS gm ON c.guid = gm.guid "
"gm.guildid, c.playerFlags, c.at_login, cp.CreatureId, cp.TamedCreatureId, cp.DisplayId, c.equipmentCache, cb.guid, c.slot "
"FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.Guid AND cp.IsActive = 1 LEFT JOIN guild_member AS gm ON c.guid = gm.guid "
"LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 WHERE c.account = ? AND c.deleteInfos_Name IS NULL", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_ENUM_DECLINED_NAME, "SELECT c.guid, c.name, c.race, c.class, c.gender, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle, c.level, c.zone, c.map, "
"c.position_x, c.position_y, c.position_z, gm.guildid, c.playerFlags, c.at_login, cp.entry, cp.modelid, cp.level, c.equipmentCache, "
"cb.guid, c.slot, cd.genitive FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.owner AND cp.active = 1 "
"c.position_x, c.position_y, c.position_z, gm.guildid, c.playerFlags, c.at_login, cp.CreatureId, cp.TamedCreatureId, cp.DisplayId, c.equipmentCache, "
"cb.guid, c.slot, cd.genitive FROM characters AS c LEFT JOIN character_pet AS cp ON c.guid = cp.Guid AND cp.IsActive = 1 "
"LEFT JOIN character_declinedname AS cd ON c.guid = cd.guid LEFT JOIN guild_member AS gm ON c.guid = gm.guid "
"LEFT JOIN character_banned AS cb ON c.guid = cb.guid AND cb.active = 1 WHERE c.account = ? AND c.deleteInfos_Name IS NULL", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_FREE_NAME, "SELECT guid, name, at_login FROM characters WHERE guid = ? AND account = ? AND NOT EXISTS (SELECT NULL FROM characters WHERE name = ?)", CONNECTION_ASYNC);
Expand Down Expand Up @@ -611,35 +611,28 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_DEL_CALENDAR_INVITE, "DELETE FROM calendar_invites WHERE id = ?", CONNECTION_ASYNC);

// Pet
PrepareStatement(CHAR_SEL_PET_SLOTS, "SELECT owner, slot FROM character_pet WHERE owner = ? AND slot >= ? AND slot <= ? ORDER BY slot", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_PET_SLOTS_DETAIL, "SELECT slot, id, entry, modelid, level, name FROM character_pet WHERE owner = ? AND slot >= ? AND slot <= ? ORDER BY slot", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_PET_ENTRY, "SELECT entry FROM character_pet WHERE owner = ? AND id = ? AND slot >= ? AND slot <= ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_PET_SLOT_BY_ID, "SELECT slot, id, entry FROM character_pet WHERE owner = ? AND id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_PET_SPELL_LIST, "SELECT DISTINCT pet_spell.spell FROM pet_spell, character_pet WHERE character_pet.owner = ? AND character_pet.id = pet_spell.guid AND character_pet.id <> ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_CHAR_PETS, "SELECT id FROM character_pet WHERE owner = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER, "DELETE FROM character_pet_declinedname WHERE owner = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME, "DELETE FROM character_pet_declinedname WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_PET_DECLINEDNAME, "INSERT INTO character_pet_declinedname (id, owner, genitive, dative, accusative, instrumental, prepositional) VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_PET_AURA, "SELECT casterGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges, critChance, applyResilience FROM pet_aura WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_PET_SPELL, "SELECT spell, active FROM pet_spell WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_PET_SPELL_COOLDOWN, "SELECT spell, time, categoryId, categoryEnd FROM pet_spell_cooldown WHERE guid = ? AND time > UNIX_TIMESTAMP()", CONNECTION_SYNCH);
PrepareStatement(CHAR_SEL_PET_DECLINED_NAME, "SELECT genitive, dative, accusative, instrumental, prepositional FROM character_pet_declinedname WHERE owner = ? AND id = ?", CONNECTION_SYNCH);
PrepareStatement(CHAR_INS_CHAR_PET_DECLINEDNAME, "INSERT INTO character_pet_declinedname (PetNumber, Guid, genitive, dative, accusative, instrumental, prepositional) VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME, "DELETE FROM character_pet_declinedname WHERE PetNumber = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_PET_DECLINED_NAMES, "SELECT PetNumber, genitive, dative, accusative, instrumental, prepositional FROM character_pet_declinedname WHERE PetNumber IN (SELECT PetNumber FROM character_pet WHERE Guid = ?)", CONNECTION_ASYNC);

PrepareStatement(CHAR_SEL_PET_AURAS, "SELECT guid, casterGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges, critChance, applyResilience FROM pet_aura WHERE guid IN (SELECT PetNumber FROM character_pet WHERE Guid = ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PET_AURAS, "DELETE FROM pet_aura WHERE guid = ?", CONNECTION_BOTH);
PrepareStatement(CHAR_INS_PET_AURA, "INSERT INTO pet_aura (guid, casterGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, "
"base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges, critChance, applyResilience) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_BOTH);

PrepareStatement(CHAR_SEL_PET_SPELLS, "SELECT guid, spell, active FROM pet_spell WHERE guid IN (SELECT PetNumber FROM character_pet WHERE Guid = ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PET_SPELLS, "DELETE FROM pet_spell WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_PET_SPELL, "INSERT INTO pet_spell (guid, spell, active) VALUES (?, ?, ?)", CONNECTION_BOTH);

PrepareStatement(CHAR_DEL_CHAR_PETS, "DELETE FROM character_pet WHERE Guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_PET, "DELETE FROM character_pet WHERE PetNumber = ?", CONNECTION_BOTH);
PrepareStatement(CHAR_SEL_CHAR_PET, "SELECT PetNumber, CreatureId, TamedCreatureId, DisplayId, SavedHealth, SavedPower, CreatedBySpellId, LastSaveTime, ReactState, Slot, HasBeenRenamed, IsActive, `Name`, ActionBar, Talents FROM character_pet WHERE Guid = ? ORDER BY Slot", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_PET, "UPDATE character_pet SET SavedHealth = ?, SavedPower = ?, LastSaveTime = ?, ReactState = ?, Slot = ?, HasBeenRenamed = ?, IsActive = ?, `Name` = ?, ActionBar = ?, Talents = ? WHERE PetNumber = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHAR_PET, "INSERT INTO character_pet (Guid, PetNumber, CreatureId, TamedCreatureId, DisplayId, SavedHealth, SavedPower, CreatedBySpellId, LastSaveTime, ReactState, Slot, HasBeenRenamed, IsActive, `Name`, ActionBar, Talents) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_BOTH);

PrepareStatement(CHAR_SEL_PET_SPELL_COOLDOWNS, "SELECT guid, spell, time, categoryId, categoryEnd FROM pet_spell_cooldown WHERE guid IN (SELECT PetNumber FROM character_pet WHERE Guid = ?) AND time > UNIX_TIMESTAMP()", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_PET_SPELL_COOLDOWNS, "DELETE FROM pet_spell_cooldown WHERE guid = ?", CONNECTION_BOTH);
PrepareStatement(CHAR_INS_PET_SPELL_COOLDOWN, "INSERT INTO pet_spell_cooldown (guid, spell, time, categoryId, categoryEnd) VALUES (?, ?, ?, ?, ?)", CONNECTION_BOTH);
PrepareStatement(CHAR_DEL_PET_SPELL_BY_SPELL, "DELETE FROM pet_spell WHERE guid = ? and spell = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_PET_SPELL, "INSERT INTO pet_spell (guid, spell, active) VALUES (?, ?, ?)", CONNECTION_BOTH);
PrepareStatement(CHAR_INS_PET_AURA, "INSERT INTO pet_aura (guid, casterGuid, spell, effectMask, recalculateMask, stackCount, amount0, amount1, amount2, "
"base_amount0, base_amount1, base_amount2, maxDuration, remainTime, remainCharges, critChance, applyResilience) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_BOTH);
PrepareStatement(CHAR_DEL_CHAR_PET_BY_OWNER, "DELETE FROM character_pet WHERE owner = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_PET_NAME, "UPDATE character_pet SET name = ?, renamed = 1 WHERE owner = ? AND id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_PET_SLOT_BY_SLOT, "UPDATE character_pet SET slot = ? WHERE owner = ? AND slot = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHAR_PET_SLOT_BY_ID, "UPDATE character_pet SET slot = ? WHERE owner = ? AND id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_DEL_CHAR_PET_BY_ID, "DELETE FROM character_pet WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_PET, "INSERT INTO character_pet (id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, active, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_CHAR_ALL_PETS_DETAIL, "SELECT id, entry, owner, modelid, level, exp, Reactstate, slot, name, renamed, active, curhealth, curmana, abdata, savetime, CreatedBySpell, PetType FROM character_pet WHERE owner = ? ORDER BY slot", CONNECTION_ASYNC);

// PvPstats
PrepareStatement(CHAR_SEL_PVPSTATS_MAXID, "SELECT MAX(id) FROM pvpstats_battlegrounds", CONNECTION_SYNCH);
Expand Down
39 changes: 16 additions & 23 deletions src/server/database/Database/Implementation/CharacterDatabase.h
Original file line number Diff line number Diff line change
Expand Up @@ -519,34 +519,27 @@ enum CharacterDatabaseStatements : uint32
CHAR_REP_CALENDAR_INVITE,
CHAR_DEL_CALENDAR_INVITE,

CHAR_SEL_PET_AURA,
CHAR_SEL_PET_SPELL,
CHAR_SEL_PET_SPELL_COOLDOWN,
CHAR_SEL_PET_DECLINED_NAME,
CHAR_SEL_PET_AURAS,
CHAR_DEL_PET_AURAS,
CHAR_DEL_PET_SPELL_COOLDOWNS,
CHAR_INS_PET_SPELL_COOLDOWN,
CHAR_DEL_PET_SPELL_BY_SPELL,
CHAR_INS_PET_SPELL,
CHAR_INS_PET_AURA,

CHAR_SEL_PET_SPELLS,
CHAR_DEL_PET_SPELLS,
CHAR_DEL_CHAR_PET_BY_OWNER,
CHAR_DEL_CHAR_PET_DECLINEDNAME_BY_OWNER,
CHAR_SEL_PET_SLOTS,
CHAR_SEL_PET_SLOTS_DETAIL,
CHAR_SEL_PET_ENTRY,
CHAR_SEL_PET_SLOT_BY_ID,
CHAR_SEL_PET_SPELL_LIST,
CHAR_SEL_CHAR_PETS,
CHAR_DEL_CHAR_PET_DECLINEDNAME,
CHAR_INS_PET_SPELL,

CHAR_SEL_PET_SPELL_COOLDOWNS,
CHAR_DEL_PET_SPELL_COOLDOWNS,
CHAR_INS_PET_SPELL_COOLDOWN,

CHAR_INS_CHAR_PET_DECLINEDNAME,
CHAR_UPD_CHAR_PET_NAME,
CHAR_UPD_CHAR_PET_SLOT_BY_SLOT,
CHAR_UPD_CHAR_PET_SLOT_BY_ID,
CHAR_DEL_CHAR_PET_BY_ID,
CHAR_INS_PET,
CHAR_SEL_CHAR_ALL_PETS_DETAIL,
CHAR_DEL_CHAR_PET_DECLINEDNAME,
CHAR_SEL_PET_DECLINED_NAMES,

CHAR_INS_CHAR_PET,
CHAR_DEL_CHAR_PET,
CHAR_DEL_CHAR_PETS,
CHAR_UPD_CHAR_PET,
CHAR_SEL_CHAR_PET,

CHAR_SEL_ITEMCONTAINER_ITEMS,
CHAR_DEL_ITEMCONTAINER_ITEMS,
Expand Down
15 changes: 6 additions & 9 deletions src/server/game/AI/CoreAI/PetAI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "PetAI.h"
#include "AIException.h"
#include "Creature.h"
#include "CharmInfo.h"
#include "Errors.h"
#include "Group.h"
#include "Log.h"
Expand All @@ -33,12 +34,8 @@

int32 PetAI::Permissible(Creature const* creature)
{
if (creature->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN))
{
if (reinterpret_cast<Guardian const*>(creature)->GetOwner()->GetTypeId() == TYPEID_PLAYER)
return PERMIT_BASE_PROACTIVE;
return PERMIT_BASE_REACTIVE;
}
if (creature->IsPet())
return PERMIT_BASE_PROACTIVE;

return PERMIT_BASE_NO;
}
Expand Down Expand Up @@ -440,13 +437,13 @@ void PetAI::HandleReturnMovement()
if (!me->GetCharmInfo()->IsAtStay() && !me->GetCharmInfo()->IsReturning())
{
// Return to previous position where stay was clicked
float x, y, z;
Position stayPosition;

me->GetCharmInfo()->GetStayPosition(x, y, z);
me->GetCharmInfo()->GetStayPosition(stayPosition);
ClearCharmInfoFlags();
me->GetCharmInfo()->SetIsReturning(true);
me->GetMotionMaster()->Clear();
me->GetMotionMaster()->MovePoint(me->GetGUID().GetCounter(), x, y, z);
me->GetMotionMaster()->MovePoint(me->GetGUID().GetCounter(), stayPosition);
}
}
if (me->GetCharmInfo()->HasCommandState(COMMAND_FOLLOW))
Expand Down
87 changes: 54 additions & 33 deletions src/server/game/AI/CoreAI/TotemAI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,62 +24,83 @@
#include "GridNotifiersImpl.h"
#include "CellImpl.h"

static constexpr uint32 TargetSearchInterval = 400; // let's mimic one retail AI tick

int32 TotemAI::Permissible(Creature const* creature)
{
if (creature->IsTotem())
if (creature->IsSummon() && creature->IsTotem())
return PERMIT_BASE_PROACTIVE;

return PERMIT_BASE_NO;
}

TotemAI::TotemAI(Creature* creature) : NullCreatureAI(creature), _victimGUID()
TotemAI::TotemAI(Creature* creature) : NullCreatureAI(creature), _spellInfo(nullptr), _spellRange(0.f), _targetSearchTimer(0) { }

void TotemAI::JustAppeared()
{
ASSERT(creature->IsTotem(), "TotemAI: AI assigned to a non-totem creature (%s)!", creature->GetGUID().ToString().c_str());
NullCreatureAI::JustAppeared();

_spellInfo = sSpellMgr->GetSpellInfo(me->m_spells[0]);
if (!_spellInfo)
return;

if (_spellInfo->IsPassive())
DoCastSelf(_spellInfo->Id);
else
_spellRange = _spellInfo->GetMaxRange(false);
}

void TotemAI::UpdateAI(uint32 /*diff*/)
void TotemAI::UpdateAI(uint32 diff)
{
if (me->ToTotem()->GetTotemType() != TOTEM_ACTIVE)
// Passive aura totems do not need any further casting
if (!_spellInfo || _spellInfo->IsPassive())
return;

if (!me->IsAlive() || me->IsNonMeleeSpellCast(false))
return;

// Search spell
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(me->ToTotem()->GetSpell());
if (!spellInfo)
return;

// Get spell range
float max_range = spellInfo->GetMaxRange(false);
if (_targetSearchTimer <= diff)
{
Unit* target = SelectTotemTarget();

// SpellModOp::Range not applied in this place just because not existence range mods for attacking totems
if ((!target && !_spellInfo->NeedsExplicitUnitTarget()) || target)
{
me->CastSpell(target, _spellInfo->Id);
if (target)
_victimGUID = target->GetGUID();
}
else
_targetSearchTimer = TargetSearchInterval;
}
else
_targetSearchTimer -= diff;
}

// pointer to appropriate target if found any
Unit* victim = _victimGUID ? ObjectAccessor::GetUnit(*me, _victimGUID) : nullptr;
Unit* TotemAI::SelectTotemTarget()
{
// Some totems do cast AoE spells so we don't do any target selection
if (!_spellInfo->NeedsExplicitUnitTarget())
return nullptr;

// Search victim if no, not attackable, or out of range, or friendly (possible in case duel end)
if (!victim || !victim->isTargetableForAttack() || !me->IsWithinDistInMap(victim, max_range) || me->IsFriendlyTo(victim) || !me->CanSeeOrDetect(victim))
Unit* target = nullptr;
if (!_victimGUID.IsEmpty())
{
victim = nullptr;
Trinity::NearestAttackableUnitInObjectRangeCheck u_check(me, me->GetCharmerOrOwnerOrSelf(), max_range);
Trinity::UnitLastSearcher<Trinity::NearestAttackableUnitInObjectRangeCheck> checker(me, victim, u_check);
Cell::VisitAllObjects(me, checker, max_range);
// We have an active target. Check if we can still attack it
target = ObjectAccessor::GetUnit(*me, _victimGUID);
if (!target || !target->IsAlive() || target->isTargetableForAttack() || me->IsFriendlyTo(target) || !me->CanSeeOrDetect(target) || !me->IsWithinDistInMap(target, _spellRange))
{
_victimGUID = ObjectGuid::Empty;
target = nullptr;
}
}

// If have target
if (victim)
if (!target)
{
// remember
_victimGUID = victim->GetGUID();

// attack
me->CastSpell(victim, me->ToTotem()->GetSpell());
// No valid target has been found so far. Let's try to find a new one
Trinity::NearestAttackableUnitInObjectRangeCheck u_check(me, Coalesce<Unit const>(me->GetCreator(), me), _spellRange);
Trinity::UnitLastSearcher<Trinity::NearestAttackableUnitInObjectRangeCheck> checker(me, target, u_check);
Cell::VisitAllObjects(me, checker, _spellRange);
}
else
_victimGUID.Clear();
}

void TotemAI::AttackStart(Unit* /*victim*/)
{
return target;
}
13 changes: 10 additions & 3 deletions src/server/game/AI/CoreAI/TotemAI.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,27 @@
#include "Timer.h"

class Creature;
class SpellInfo;
class Totem;

class TC_GAME_API TotemAI : public NullCreatureAI
{
public:
explicit TotemAI(Creature* creature);

explicit TotemAI(Creature* c);
void AttackStart(Unit* /*victim*/) override {}
void JustAppeared() override;
void UpdateAI(uint32 diff) override;

void AttackStart(Unit* victim) override;
virtual Unit* SelectTotemTarget();

void UpdateAI(uint32 diff) override;
static int32 Permissible(Creature const* creature);

protected:
SpellInfo const* _spellInfo;
float _spellRange;
private:
ObjectGuid _victimGUID;
uint32 _targetSearchTimer;
};
#endif
Loading

0 comments on commit ff481bc

Please sign in to comment.