mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-02-02 02:23:49 +00:00
First Commit
For Azeroth!
This commit is contained in:
323
src/server/game/AI/CoreAI/CombatAI.cpp
Normal file
323
src/server/game/AI/CoreAI/CombatAI.cpp
Normal file
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "CombatAI.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "Vehicle.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Player.h"
|
||||
|
||||
/////////////////
|
||||
// AggressorAI
|
||||
/////////////////
|
||||
|
||||
int AggressorAI::Permissible(const Creature* creature)
|
||||
{
|
||||
// have some hostile factions, it will be selected by IsHostileTo check at MoveInLineOfSight
|
||||
if (!creature->IsCivilian() && !creature->IsNeutralToAll())
|
||||
return PERMIT_BASE_PROACTIVE;
|
||||
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
void AggressorAI::UpdateAI(uint32 /*diff*/)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// CombatAI
|
||||
/////////////////
|
||||
|
||||
void CombatAI::InitializeAI()
|
||||
{
|
||||
for (uint32 i = 0; i < CREATURE_MAX_SPELLS; ++i)
|
||||
if (me->m_spells[i] && sSpellMgr->GetSpellInfo(me->m_spells[i]))
|
||||
spells.push_back(me->m_spells[i]);
|
||||
|
||||
CreatureAI::InitializeAI();
|
||||
}
|
||||
|
||||
void CombatAI::Reset()
|
||||
{
|
||||
events.Reset();
|
||||
}
|
||||
|
||||
void CombatAI::JustDied(Unit* killer)
|
||||
{
|
||||
for (SpellVct::iterator i = spells.begin(); i != spells.end(); ++i)
|
||||
if (AISpellInfo[*i].condition == AICOND_DIE)
|
||||
me->CastSpell(killer, *i, true);
|
||||
}
|
||||
|
||||
void CombatAI::EnterCombat(Unit* who)
|
||||
{
|
||||
for (SpellVct::iterator i = spells.begin(); i != spells.end(); ++i)
|
||||
{
|
||||
if (AISpellInfo[*i].condition == AICOND_AGGRO)
|
||||
me->CastSpell(who, *i, false);
|
||||
else if (AISpellInfo[*i].condition == AICOND_COMBAT)
|
||||
events.ScheduleEvent(*i, AISpellInfo[*i].cooldown + rand()%AISpellInfo[*i].cooldown);
|
||||
}
|
||||
}
|
||||
|
||||
void CombatAI::UpdateAI(uint32 diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
events.Update(diff);
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
if (uint32 spellId = events.ExecuteEvent())
|
||||
{
|
||||
DoCast(spellId);
|
||||
events.ScheduleEvent(spellId, AISpellInfo[spellId].cooldown + rand()%AISpellInfo[spellId].cooldown);
|
||||
}
|
||||
else
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// CasterAI
|
||||
/////////////////
|
||||
|
||||
void CasterAI::InitializeAI()
|
||||
{
|
||||
CombatAI::InitializeAI();
|
||||
|
||||
m_attackDist = 30.0f;
|
||||
for (SpellVct::iterator itr = spells.begin(); itr != spells.end(); ++itr)
|
||||
if (AISpellInfo[*itr].condition == AICOND_COMBAT && m_attackDist > GetAISpellInfo(*itr)->maxRange)
|
||||
m_attackDist = GetAISpellInfo(*itr)->maxRange;
|
||||
if (m_attackDist == 30.0f)
|
||||
m_attackDist = MELEE_RANGE;
|
||||
}
|
||||
|
||||
void CasterAI::EnterCombat(Unit* who)
|
||||
{
|
||||
if (spells.empty())
|
||||
return;
|
||||
|
||||
uint32 spell = rand()%spells.size();
|
||||
uint32 count = 0;
|
||||
for (SpellVct::iterator itr = spells.begin(); itr != spells.end(); ++itr, ++count)
|
||||
{
|
||||
if (AISpellInfo[*itr].condition == AICOND_AGGRO)
|
||||
me->CastSpell(who, *itr, false);
|
||||
else if (AISpellInfo[*itr].condition == AICOND_COMBAT)
|
||||
{
|
||||
uint32 cooldown = GetAISpellInfo(*itr)->realCooldown;
|
||||
if (count == spell)
|
||||
{
|
||||
DoCast(spells[spell]);
|
||||
cooldown += me->GetCurrentSpellCastTime(*itr);
|
||||
}
|
||||
events.ScheduleEvent(*itr, cooldown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CasterAI::UpdateAI(uint32 diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
events.Update(diff);
|
||||
|
||||
if (me->GetVictim()->HasBreakableByDamageCrowdControlAura(me))
|
||||
{
|
||||
me->InterruptNonMeleeSpells(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
if (uint32 spellId = events.ExecuteEvent())
|
||||
{
|
||||
DoCast(spellId);
|
||||
uint32 casttime = me->GetCurrentSpellCastTime(spellId);
|
||||
events.ScheduleEvent(spellId, (casttime ? casttime : 500) + GetAISpellInfo(spellId)->realCooldown);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////
|
||||
// ArcherAI
|
||||
//////////////
|
||||
|
||||
ArcherAI::ArcherAI(Creature* c) : CreatureAI(c)
|
||||
{
|
||||
if (!me->m_spells[0])
|
||||
sLog->outError("ArcherAI set for creature (entry = %u) with spell1=0. AI will do nothing", me->GetEntry());
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(me->m_spells[0]);
|
||||
m_minRange = spellInfo ? spellInfo->GetMinRange(false) : 0;
|
||||
|
||||
if (!m_minRange)
|
||||
m_minRange = MELEE_RANGE;
|
||||
me->m_CombatDistance = spellInfo ? spellInfo->GetMaxRange(false) : 0;
|
||||
me->m_SightDistance = me->m_CombatDistance;
|
||||
}
|
||||
|
||||
void ArcherAI::AttackStart(Unit* who)
|
||||
{
|
||||
if (!who)
|
||||
return;
|
||||
|
||||
if (me->IsWithinCombatRange(who, m_minRange))
|
||||
{
|
||||
if (me->Attack(who, true) && !who->IsFlying())
|
||||
me->GetMotionMaster()->MoveChase(who);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (me->Attack(who, false) && !who->IsFlying())
|
||||
me->GetMotionMaster()->MoveChase(who, me->m_CombatDistance);
|
||||
}
|
||||
|
||||
if (who->IsFlying())
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
}
|
||||
|
||||
void ArcherAI::UpdateAI(uint32 /*diff*/)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
if (!me->IsWithinCombatRange(me->GetVictim(), m_minRange))
|
||||
DoSpellAttackIfReady(me->m_spells[0]);
|
||||
else
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
//////////////
|
||||
// TurretAI
|
||||
//////////////
|
||||
|
||||
TurretAI::TurretAI(Creature* c) : CreatureAI(c)
|
||||
{
|
||||
if (!me->m_spells[0])
|
||||
sLog->outError("TurretAI set for creature (entry = %u) with spell1=0. AI will do nothing", me->GetEntry());
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(me->m_spells[0]);
|
||||
m_minRange = spellInfo ? spellInfo->GetMinRange(false) : 0;
|
||||
me->m_CombatDistance = spellInfo ? spellInfo->GetMaxRange(false) : 0;
|
||||
me->m_SightDistance = me->m_CombatDistance;
|
||||
}
|
||||
|
||||
bool TurretAI::CanAIAttack(const Unit* /*who*/) const
|
||||
{
|
||||
// TODO: use one function to replace it
|
||||
if (!me->IsWithinCombatRange(me->GetVictim(), me->m_CombatDistance)
|
||||
|| (m_minRange && me->IsWithinCombatRange(me->GetVictim(), m_minRange)))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TurretAI::AttackStart(Unit* who)
|
||||
{
|
||||
if (who)
|
||||
me->Attack(who, false);
|
||||
}
|
||||
|
||||
void TurretAI::UpdateAI(uint32 /*diff*/)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
if( me->m_spells[0] )
|
||||
DoSpellAttackIfReady(me->m_spells[0]);
|
||||
}
|
||||
|
||||
//////////////
|
||||
// VehicleAI
|
||||
//////////////
|
||||
|
||||
VehicleAI::VehicleAI(Creature* c) : CreatureAI(c), m_ConditionsTimer(VEHICLE_CONDITION_CHECK_TIME)
|
||||
{
|
||||
LoadConditions();
|
||||
m_DoDismiss = false;
|
||||
m_DismissTimer = VEHICLE_DISMISS_TIME;
|
||||
}
|
||||
|
||||
//NOTE: VehicleAI::UpdateAI runs even while the vehicle is mounted
|
||||
void VehicleAI::UpdateAI(uint32 diff)
|
||||
{
|
||||
CheckConditions(diff);
|
||||
|
||||
if (m_DoDismiss)
|
||||
{
|
||||
if (m_DismissTimer < diff)
|
||||
{
|
||||
m_DoDismiss = false;
|
||||
me->DespawnOrUnsummon();
|
||||
}
|
||||
else
|
||||
m_DismissTimer -= diff;
|
||||
}
|
||||
}
|
||||
|
||||
void VehicleAI::OnCharmed(bool apply)
|
||||
{
|
||||
if (!me->GetVehicleKit()->IsVehicleInUse() && !apply && !conditions.empty()) // was used and has conditions
|
||||
m_DoDismiss = true; // needs reset
|
||||
else if (apply)
|
||||
m_DoDismiss = false; // in use again
|
||||
|
||||
m_DismissTimer = VEHICLE_DISMISS_TIME;//reset timer
|
||||
}
|
||||
|
||||
void VehicleAI::LoadConditions()
|
||||
{
|
||||
conditions = sConditionMgr->GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, me->GetEntry());
|
||||
//if (!conditions.empty())
|
||||
;//sLog->outDebug(LOG_FILTER_CONDITIONSYS, "VehicleAI::LoadConditions: loaded %u conditions", uint32(conditions.size()));
|
||||
}
|
||||
|
||||
void VehicleAI::CheckConditions(uint32 diff)
|
||||
{
|
||||
if (m_ConditionsTimer < diff)
|
||||
{
|
||||
if (!conditions.empty())
|
||||
{
|
||||
if (Vehicle* vehicleKit = me->GetVehicleKit())
|
||||
for (SeatMap::iterator itr = vehicleKit->Seats.begin(); itr != vehicleKit->Seats.end(); ++itr)
|
||||
if (Unit* passenger = ObjectAccessor::GetUnit(*me, itr->second.Passenger.Guid))
|
||||
{
|
||||
if (Player* player = passenger->ToPlayer())
|
||||
{
|
||||
if (!sConditionMgr->IsObjectMeetToConditions(player, me, conditions))
|
||||
{
|
||||
player->ExitVehicle();
|
||||
return; // check other pessanger in next tick
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_ConditionsTimer = VEHICLE_CONDITION_CHECK_TIME;
|
||||
}
|
||||
else
|
||||
m_ConditionsTimer -= diff;
|
||||
}
|
||||
120
src/server/game/AI/CoreAI/CombatAI.h
Normal file
120
src/server/game/AI/CoreAI/CombatAI.h
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITY_COMBATAI_H
|
||||
#define TRINITY_COMBATAI_H
|
||||
|
||||
#include "CreatureAI.h"
|
||||
#include "CreatureAIImpl.h"
|
||||
#include "ConditionMgr.h"
|
||||
|
||||
class Creature;
|
||||
|
||||
class AggressorAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit AggressorAI(Creature* c) : CreatureAI(c) {}
|
||||
|
||||
void UpdateAI(uint32);
|
||||
static int Permissible(const Creature*);
|
||||
};
|
||||
|
||||
typedef std::vector<uint32> SpellVct;
|
||||
|
||||
class CombatAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit CombatAI(Creature* c) : CreatureAI(c) {}
|
||||
|
||||
void InitializeAI();
|
||||
void Reset();
|
||||
void EnterCombat(Unit* who);
|
||||
void JustDied(Unit* killer);
|
||||
void UpdateAI(uint32 diff);
|
||||
|
||||
static int Permissible(Creature const* /*creature*/) { return PERMIT_BASE_NO; }
|
||||
|
||||
protected:
|
||||
EventMap events;
|
||||
SpellVct spells;
|
||||
};
|
||||
|
||||
class CasterAI : public CombatAI
|
||||
{
|
||||
public:
|
||||
explicit CasterAI(Creature* c) : CombatAI(c) { m_attackDist = MELEE_RANGE; }
|
||||
void InitializeAI();
|
||||
void AttackStart(Unit* victim) { AttackStartCaster(victim, m_attackDist); }
|
||||
void UpdateAI(uint32 diff);
|
||||
void EnterCombat(Unit* /*who*/);
|
||||
private:
|
||||
float m_attackDist;
|
||||
};
|
||||
|
||||
struct ArcherAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit ArcherAI(Creature* c);
|
||||
void AttackStart(Unit* who);
|
||||
void UpdateAI(uint32 diff);
|
||||
|
||||
|
||||
static int Permissible(Creature const* /*creature*/) { return PERMIT_BASE_NO; }
|
||||
|
||||
protected:
|
||||
float m_minRange;
|
||||
};
|
||||
|
||||
struct TurretAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit TurretAI(Creature* c);
|
||||
bool CanAIAttack(const Unit* who) const;
|
||||
void AttackStart(Unit* who);
|
||||
void UpdateAI(uint32 diff);
|
||||
|
||||
static int Permissible(Creature const* /*creature*/) { return PERMIT_BASE_NO; }
|
||||
|
||||
protected:
|
||||
float m_minRange;
|
||||
};
|
||||
|
||||
#define VEHICLE_CONDITION_CHECK_TIME 1000
|
||||
#define VEHICLE_DISMISS_TIME 5000
|
||||
struct VehicleAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit VehicleAI(Creature* creature);
|
||||
|
||||
void UpdateAI(uint32 diff);
|
||||
void MoveInLineOfSight(Unit*) {}
|
||||
void AttackStart(Unit*) {}
|
||||
void OnCharmed(bool apply);
|
||||
|
||||
static int Permissible(Creature const* /*creature*/) { return PERMIT_BASE_NO; }
|
||||
|
||||
private:
|
||||
void LoadConditions();
|
||||
void CheckConditions(uint32 diff);
|
||||
ConditionList conditions;
|
||||
uint32 m_ConditionsTimer;
|
||||
bool m_DoDismiss;
|
||||
uint32 m_DismissTimer;
|
||||
};
|
||||
|
||||
#endif
|
||||
29
src/server/game/AI/CoreAI/GameObjectAI.cpp
Normal file
29
src/server/game/AI/CoreAI/GameObjectAI.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "GameObjectAI.h"
|
||||
|
||||
//GameObjectAI::GameObjectAI(GameObject* g) : go(g) {}
|
||||
int GameObjectAI::Permissible(const GameObject* go)
|
||||
{
|
||||
if (go->GetAIName() == "GameObjectAI")
|
||||
return PERMIT_BASE_SPECIAL;
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
NullGameObjectAI::NullGameObjectAI(GameObject* g) : GameObjectAI(g) {}
|
||||
76
src/server/game/AI/CoreAI/GameObjectAI.h
Normal file
76
src/server/game/AI/CoreAI/GameObjectAI.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITY_GAMEOBJECTAI_H
|
||||
#define TRINITY_GAMEOBJECTAI_H
|
||||
|
||||
#include "Define.h"
|
||||
#include <list>
|
||||
#include "Object.h"
|
||||
#include "QuestDef.h"
|
||||
#include "GameObject.h"
|
||||
#include "CreatureAI.h"
|
||||
|
||||
class GameObjectAI
|
||||
{
|
||||
protected:
|
||||
GameObject* const go;
|
||||
public:
|
||||
explicit GameObjectAI(GameObject* g) : go(g) {}
|
||||
virtual ~GameObjectAI() {}
|
||||
|
||||
virtual void UpdateAI(uint32 /*diff*/) {}
|
||||
|
||||
virtual void InitializeAI() { Reset(); }
|
||||
|
||||
virtual void Reset() { }
|
||||
|
||||
// Pass parameters between AI
|
||||
virtual void DoAction(int32 /*param = 0 */) {}
|
||||
virtual void SetGUID(uint64 /*guid*/, int32 /*id = 0 */) {}
|
||||
virtual uint64 GetGUID(int32 /*id = 0 */) const { return 0; }
|
||||
|
||||
static int Permissible(GameObject const* go);
|
||||
|
||||
virtual bool GossipHello(Player* /*player*/, bool reportUse) { return false; }
|
||||
virtual bool GossipSelect(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/) { return false; }
|
||||
virtual bool GossipSelectCode(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/, char const* /*code*/) { return false; }
|
||||
virtual bool QuestAccept(Player* /*player*/, Quest const* /*quest*/) { return false; }
|
||||
virtual bool QuestReward(Player* /*player*/, Quest const* /*quest*/, uint32 /*opt*/) { return false; }
|
||||
virtual uint32 GetDialogStatus(Player* /*player*/) { return DIALOG_STATUS_SCRIPTED_NO_STATUS; }
|
||||
virtual void Destroyed(Player* /*player*/, uint32 /*eventId*/) {}
|
||||
virtual uint32 GetData(uint32 /*id*/) const { return 0; }
|
||||
virtual void SetData64(uint32 /*id*/, uint64 /*value*/) {}
|
||||
virtual uint64 GetData64(uint32 /*id*/) const { return 0; }
|
||||
virtual void SetData(uint32 /*id*/, uint32 /*value*/) {}
|
||||
virtual void OnGameEvent(bool /*start*/, uint16 /*eventId*/) {}
|
||||
virtual void OnStateChanged(uint32 /*state*/, Unit* /*unit*/) {}
|
||||
virtual void EventInform(uint32 /*eventId*/) {}
|
||||
virtual void SpellHit(Unit* unit, const SpellInfo* spellInfo) {}
|
||||
};
|
||||
|
||||
class NullGameObjectAI : public GameObjectAI
|
||||
{
|
||||
public:
|
||||
explicit NullGameObjectAI(GameObject* g);
|
||||
|
||||
void UpdateAI(uint32 /*diff*/) {}
|
||||
|
||||
static int Permissible(GameObject const* /*go*/) { return PERMIT_BASE_IDLE; }
|
||||
};
|
||||
#endif
|
||||
69
src/server/game/AI/CoreAI/GuardAI.cpp
Normal file
69
src/server/game/AI/CoreAI/GuardAI.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "GuardAI.h"
|
||||
#include "Errors.h"
|
||||
#include "Player.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "World.h"
|
||||
#include "CreatureAIImpl.h"
|
||||
|
||||
int GuardAI::Permissible(Creature const* creature)
|
||||
{
|
||||
if (creature->IsGuard())
|
||||
return PERMIT_BASE_SPECIAL;
|
||||
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
GuardAI::GuardAI(Creature* creature) : ScriptedAI(creature)
|
||||
{
|
||||
}
|
||||
|
||||
void GuardAI::Reset()
|
||||
{
|
||||
ScriptedAI::Reset();
|
||||
me->CastSpell(me, 18950 /*SPELL_INVISIBILITY_AND_STEALTH_DETECTION*/, true);
|
||||
}
|
||||
|
||||
void GuardAI::EnterEvadeMode()
|
||||
{
|
||||
if (!me->IsAlive())
|
||||
{
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
me->CombatStop(true);
|
||||
me->DeleteThreatList();
|
||||
return;
|
||||
}
|
||||
|
||||
;//sLog->outDebug(LOG_FILTER_UNITS, "Guard entry: %u enters evade mode.", me->GetEntry());
|
||||
|
||||
me->RemoveAllAuras();
|
||||
me->DeleteThreatList();
|
||||
me->CombatStop(true);
|
||||
|
||||
// Remove ChaseMovementGenerator from MotionMaster stack list, and add HomeMovementGenerator instead
|
||||
if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE)
|
||||
me->GetMotionMaster()->MoveTargetedHome();
|
||||
}
|
||||
|
||||
void GuardAI::JustDied(Unit* killer)
|
||||
{
|
||||
if (Player* player = killer->GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
me->SendZoneUnderAttackMessage(player);
|
||||
}
|
||||
37
src/server/game/AI/CoreAI/GuardAI.h
Normal file
37
src/server/game/AI/CoreAI/GuardAI.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITY_GUARDAI_H
|
||||
#define TRINITY_GUARDAI_H
|
||||
|
||||
#include "ScriptedCreature.h"
|
||||
|
||||
class Creature;
|
||||
|
||||
class GuardAI : public ScriptedAI
|
||||
{
|
||||
public:
|
||||
explicit GuardAI(Creature* creature);
|
||||
|
||||
static int Permissible(Creature const* creature);
|
||||
|
||||
void Reset();
|
||||
void EnterEvadeMode();
|
||||
void JustDied(Unit* killer);
|
||||
};
|
||||
#endif
|
||||
92
src/server/game/AI/CoreAI/PassiveAI.cpp
Normal file
92
src/server/game/AI/CoreAI/PassiveAI.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "PassiveAI.h"
|
||||
#include "Creature.h"
|
||||
#include "TemporarySummon.h"
|
||||
|
||||
PassiveAI::PassiveAI(Creature* c) : CreatureAI(c) { me->SetReactState(REACT_PASSIVE); }
|
||||
PossessedAI::PossessedAI(Creature* c) : CreatureAI(c) { me->SetReactState(REACT_PASSIVE); }
|
||||
NullCreatureAI::NullCreatureAI(Creature* c) : CreatureAI(c) { me->SetReactState(REACT_PASSIVE); }
|
||||
|
||||
void PassiveAI::UpdateAI(uint32)
|
||||
{
|
||||
if (me->IsInCombat() && me->getAttackers().empty())
|
||||
EnterEvadeMode();
|
||||
}
|
||||
|
||||
void PossessedAI::AttackStart(Unit* target)
|
||||
{
|
||||
me->Attack(target, true);
|
||||
}
|
||||
|
||||
void PossessedAI::UpdateAI(uint32 /*diff*/)
|
||||
{
|
||||
if (me->GetVictim())
|
||||
{
|
||||
if (!me->IsValidAttackTarget(me->GetVictim()))
|
||||
me->AttackStop();
|
||||
else
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
}
|
||||
|
||||
void PossessedAI::JustDied(Unit* /*u*/)
|
||||
{
|
||||
// We died while possessed, disable our loot
|
||||
me->RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE);
|
||||
}
|
||||
|
||||
void PossessedAI::KilledUnit(Unit* victim)
|
||||
{
|
||||
// We killed a creature, disable victim's loot
|
||||
//if (victim->GetTypeId() == TYPEID_UNIT)
|
||||
// victim->RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE);
|
||||
}
|
||||
|
||||
void CritterAI::DamageTaken(Unit*, uint32&, DamageEffectType, SpellSchoolMask)
|
||||
{
|
||||
if (!me->HasUnitState(UNIT_STATE_FLEEING))
|
||||
me->SetControlled(true, UNIT_STATE_FLEEING);
|
||||
|
||||
_combatTimer = 1;
|
||||
}
|
||||
|
||||
void CritterAI::EnterEvadeMode()
|
||||
{
|
||||
if (me->HasUnitState(UNIT_STATE_FLEEING))
|
||||
me->SetControlled(false, UNIT_STATE_FLEEING);
|
||||
CreatureAI::EnterEvadeMode();
|
||||
_combatTimer = 0;
|
||||
}
|
||||
|
||||
void CritterAI::UpdateAI(uint32 diff)
|
||||
{
|
||||
if (me->IsInCombat())
|
||||
{
|
||||
_combatTimer += diff;
|
||||
if (_combatTimer >= 5000)
|
||||
EnterEvadeMode();
|
||||
}
|
||||
}
|
||||
|
||||
void TriggerAI::IsSummonedBy(Unit* summoner)
|
||||
{
|
||||
if (me->m_spells[0])
|
||||
me->CastSpell(me, me->m_spells[0], false, 0, 0, summoner ? summoner->GetGUID() : 0);
|
||||
}
|
||||
89
src/server/game/AI/CoreAI/PassiveAI.h
Normal file
89
src/server/game/AI/CoreAI/PassiveAI.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITY_PASSIVEAI_H
|
||||
#define TRINITY_PASSIVEAI_H
|
||||
|
||||
#include "CreatureAI.h"
|
||||
//#include "CreatureAIImpl.h"
|
||||
|
||||
class PassiveAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit PassiveAI(Creature* c);
|
||||
|
||||
void MoveInLineOfSight(Unit*) {}
|
||||
void AttackStart(Unit*) {}
|
||||
void UpdateAI(uint32);
|
||||
|
||||
static int Permissible(const Creature*) { return PERMIT_BASE_IDLE; }
|
||||
};
|
||||
|
||||
class PossessedAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit PossessedAI(Creature* c);
|
||||
|
||||
void MoveInLineOfSight(Unit*) {}
|
||||
void AttackStart(Unit* target);
|
||||
void UpdateAI(uint32);
|
||||
void EnterEvadeMode() {}
|
||||
|
||||
void JustDied(Unit*);
|
||||
void KilledUnit(Unit* victim);
|
||||
|
||||
static int Permissible(const Creature*) { return PERMIT_BASE_IDLE; }
|
||||
};
|
||||
|
||||
class NullCreatureAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit NullCreatureAI(Creature* c);
|
||||
|
||||
void MoveInLineOfSight(Unit*) {}
|
||||
void AttackStart(Unit*) {}
|
||||
void UpdateAI(uint32) {}
|
||||
void EnterEvadeMode() {}
|
||||
void OnCharmed(bool /*apply*/) {}
|
||||
|
||||
static int Permissible(const Creature*) { return PERMIT_BASE_IDLE; }
|
||||
};
|
||||
|
||||
class CritterAI : public PassiveAI
|
||||
{
|
||||
public:
|
||||
explicit CritterAI(Creature* c) : PassiveAI(c) { _combatTimer = 0; }
|
||||
|
||||
void DamageTaken(Unit* /*done_by*/, uint32& /*damage*/, DamageEffectType damagetype, SpellSchoolMask damageSchoolMask);
|
||||
void EnterEvadeMode();
|
||||
void UpdateAI(uint32);
|
||||
|
||||
// Xinef: Added
|
||||
private:
|
||||
uint32 _combatTimer;
|
||||
};
|
||||
|
||||
class TriggerAI : public NullCreatureAI
|
||||
{
|
||||
public:
|
||||
explicit TriggerAI(Creature* c) : NullCreatureAI(c) {}
|
||||
void IsSummonedBy(Unit* summoner);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
742
src/server/game/AI/CoreAI/PetAI.cpp
Normal file
742
src/server/game/AI/CoreAI/PetAI.cpp
Normal file
@@ -0,0 +1,742 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "PetAI.h"
|
||||
#include "Errors.h"
|
||||
#include "Pet.h"
|
||||
#include "Player.h"
|
||||
#include "DBCStores.h"
|
||||
#include "Spell.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "Creature.h"
|
||||
#include "World.h"
|
||||
#include "Util.h"
|
||||
#include "Group.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "SpellAuraEffects.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
int PetAI::Permissible(const Creature* creature)
|
||||
{
|
||||
if (creature->IsPet())
|
||||
return PERMIT_BASE_SPECIAL;
|
||||
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
PetAI::PetAI(Creature* c) : CreatureAI(c), i_tracker(TIME_INTERVAL_LOOK)
|
||||
{
|
||||
UpdateAllies();
|
||||
}
|
||||
|
||||
bool PetAI::_needToStop()
|
||||
{
|
||||
// This is needed for charmed creatures, as once their target was reset other effects can trigger threat
|
||||
if (me->IsCharmed() && me->GetVictim() == me->GetCharmer())
|
||||
return true;
|
||||
|
||||
// xinef: dont allow to follow targets out of visibility range
|
||||
if (me->GetExactDist(me->GetVictim()) > me->GetVisibilityRange()-5.0f)
|
||||
return true;
|
||||
|
||||
// dont allow pets to follow targets far away from owner
|
||||
if (Unit* owner = me->GetCharmerOrOwner())
|
||||
if (owner->GetExactDist(me) >= (owner->GetVisibilityRange()-10.0f))
|
||||
return true;
|
||||
|
||||
if (!me->_CanDetectFeignDeathOf(me->GetVictim()))
|
||||
return true;
|
||||
|
||||
if (me->isTargetNotAcceptableByMMaps(me->GetVictim()->GetGUID(), sWorld->GetGameTime(), me->GetVictim()))
|
||||
return true;
|
||||
|
||||
return !me->CanCreatureAttack(me->GetVictim());
|
||||
}
|
||||
|
||||
void PetAI::_stopAttack()
|
||||
{
|
||||
if (!me->IsAlive())
|
||||
{
|
||||
;//sLog->outStaticDebug("Creature stoped attacking cuz his dead [guid=%u]", me->GetGUIDLow());
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
me->CombatStop();
|
||||
me->getHostileRefManager().deleteReferences();
|
||||
return;
|
||||
}
|
||||
|
||||
me->AttackStop();
|
||||
me->InterruptNonMeleeSpells(false);
|
||||
me->GetCharmInfo()->SetIsCommandAttack(false);
|
||||
ClearCharmInfoFlags();
|
||||
HandleReturnMovement();
|
||||
}
|
||||
|
||||
void PetAI::_doMeleeAttack()
|
||||
{
|
||||
// Xinef: Imps cannot attack with melee
|
||||
if (!_canMeleeAttack())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
bool PetAI::_canMeleeAttack() const
|
||||
{
|
||||
return me->GetEntry() != 416 /*ENTRY_IMP*/ &&
|
||||
me->GetEntry() != 510 /*ENTRY_WATER_ELEMENTAL*/ &&
|
||||
me->GetEntry() != 37994 /*ENTRY_WATER_ELEMENTAL_PERM*/;
|
||||
}
|
||||
|
||||
void PetAI::UpdateAI(uint32 diff)
|
||||
{
|
||||
if (!me->IsAlive() || !me->GetCharmInfo())
|
||||
return;
|
||||
|
||||
Unit* owner = me->GetCharmerOrOwner();
|
||||
|
||||
if (m_updateAlliesTimer <= diff)
|
||||
// UpdateAllies self set update timer
|
||||
UpdateAllies();
|
||||
else
|
||||
m_updateAlliesTimer -= diff;
|
||||
|
||||
if (me->GetVictim() && me->GetVictim()->IsAlive())
|
||||
{
|
||||
// is only necessary to stop casting, the pet must not exit combat
|
||||
if (me->GetVictim()->HasBreakableByDamageCrowdControlAura(me))
|
||||
{
|
||||
me->InterruptNonMeleeSpells(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_needToStop())
|
||||
{
|
||||
;//sLog->outStaticDebug("Pet AI stopped attacking [guid=%u]", me->GetGUIDLow());
|
||||
_stopAttack();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check before attacking to prevent pets from leaving stay position
|
||||
if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY))
|
||||
{
|
||||
if (me->GetCharmInfo()->IsCommandAttack() || (me->GetCharmInfo()->IsAtStay() && me->IsWithinMeleeRange(me->GetVictim())))
|
||||
_doMeleeAttack();
|
||||
}
|
||||
else
|
||||
_doMeleeAttack();
|
||||
}
|
||||
else if (!me->GetCharmInfo() || (!me->GetCharmInfo()->GetForcedSpell() && !me->HasUnitState(UNIT_STATE_CASTING)))
|
||||
{
|
||||
if (me->HasReactState(REACT_AGGRESSIVE) || me->GetCharmInfo()->IsAtStay())
|
||||
{
|
||||
// Every update we need to check targets only in certain cases
|
||||
// Aggressive - Allow auto select if owner or pet don't have a target
|
||||
// Stay - Only pick from pet or owner targets / attackers so targets won't run by
|
||||
// while chasing our owner. Don't do auto select.
|
||||
// All other cases (ie: defensive) - Targets are assigned by AttackedBy(), OwnerAttackedBy(), OwnerAttacked(), etc.
|
||||
Unit* nextTarget = SelectNextTarget(me->HasReactState(REACT_AGGRESSIVE));
|
||||
|
||||
if (nextTarget)
|
||||
AttackStart(nextTarget);
|
||||
else
|
||||
HandleReturnMovement();
|
||||
}
|
||||
else
|
||||
HandleReturnMovement();
|
||||
}
|
||||
|
||||
// xinef: charm info must be always available
|
||||
if (!me->GetCharmInfo())
|
||||
return;
|
||||
|
||||
// Autocast (casted only in combat or persistent spells in any state)
|
||||
if (!me->HasUnitState(UNIT_STATE_CASTING))
|
||||
{
|
||||
if (owner && owner->GetTypeId() == TYPEID_PLAYER && me->GetCharmInfo()->GetForcedSpell() && me->GetCharmInfo()->GetForcedTarget())
|
||||
{
|
||||
owner->ToPlayer()->GetSession()->HandlePetActionHelper(me, me->GetGUID(), abs(me->GetCharmInfo()->GetForcedSpell()), ACT_ENABLED, me->GetCharmInfo()->GetForcedTarget());
|
||||
|
||||
// xinef: if spell was casted properly and we are in passive mode, handle return
|
||||
if (!me->GetCharmInfo()->GetForcedSpell() && me->HasReactState(REACT_PASSIVE))
|
||||
{
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
{
|
||||
me->GetMotionMaster()->Clear(false);
|
||||
me->StopMoving();
|
||||
}
|
||||
else
|
||||
_stopAttack();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// xinef: dont allow ghouls to cast spells below 75 energy
|
||||
if (me->IsPet() && me->ToPet()->IsPetGhoul() && me->GetPower(POWER_ENERGY) < 75)
|
||||
return;
|
||||
|
||||
typedef std::vector<std::pair<Unit*, Spell*> > TargetSpellList;
|
||||
TargetSpellList targetSpellStore;
|
||||
|
||||
for (uint8 i = 0; i < me->GetPetAutoSpellSize(); ++i)
|
||||
{
|
||||
uint32 spellID = me->GetPetAutoSpellOnPos(i);
|
||||
if (!spellID)
|
||||
continue;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellID);
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
if (me->GetCharmInfo()->GetGlobalCooldownMgr().HasGlobalCooldown(spellInfo))
|
||||
continue;
|
||||
|
||||
// check spell cooldown, this should be checked in CheckCast...
|
||||
if (me->HasSpellCooldown(spellInfo->Id))
|
||||
continue;
|
||||
|
||||
if (spellInfo->IsPositive())
|
||||
{
|
||||
if (spellInfo->CanBeUsedInCombat())
|
||||
{
|
||||
// Check if we're in combat or commanded to attack
|
||||
if (!me->IsInCombat() && !me->GetCharmInfo()->IsCommandAttack())
|
||||
continue;
|
||||
}
|
||||
|
||||
Spell* spell = new Spell(me, spellInfo, TRIGGERED_NONE, 0);
|
||||
spell->LoadScripts(); // xinef: load for CanAutoCast (calling CheckPetCast)
|
||||
bool spellUsed = false;
|
||||
|
||||
// Some spells can target enemy or friendly (DK Ghoul's Leap)
|
||||
// Check for enemy first (pet then owner)
|
||||
Unit* target = me->getAttackerForHelper();
|
||||
if (!target && owner)
|
||||
target = owner->getAttackerForHelper();
|
||||
|
||||
if (target)
|
||||
{
|
||||
if (CanAttack(target) && spell->CanAutoCast(target))
|
||||
{
|
||||
targetSpellStore.push_back(std::make_pair(target, spell));
|
||||
spellUsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// No enemy, check friendly
|
||||
if (!spellUsed)
|
||||
{
|
||||
for (std::set<uint64>::const_iterator tar = m_AllySet.begin(); tar != m_AllySet.end(); ++tar)
|
||||
{
|
||||
Unit* ally = ObjectAccessor::GetUnit(*me, *tar);
|
||||
|
||||
//only buff targets that are in combat, unless the spell can only be cast while out of combat
|
||||
if (!ally)
|
||||
continue;
|
||||
|
||||
if (spell->CanAutoCast(ally))
|
||||
{
|
||||
targetSpellStore.push_back(std::make_pair(ally, spell));
|
||||
spellUsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No valid targets at all
|
||||
if (!spellUsed)
|
||||
delete spell;
|
||||
}
|
||||
else if (me->GetVictim() && CanAttack(me->GetVictim(), spellInfo) && spellInfo->CanBeUsedInCombat())
|
||||
{
|
||||
Spell* spell = new Spell(me, spellInfo, TRIGGERED_NONE, 0);
|
||||
if (spell->CanAutoCast(me->GetVictim()))
|
||||
targetSpellStore.push_back(std::make_pair(me->GetVictim(), spell));
|
||||
else
|
||||
delete spell;
|
||||
}
|
||||
}
|
||||
|
||||
//found units to cast on to
|
||||
if (!targetSpellStore.empty())
|
||||
{
|
||||
uint32 index = urand(0, targetSpellStore.size() - 1);
|
||||
|
||||
Spell* spell = targetSpellStore[index].second;
|
||||
Unit* target = targetSpellStore[index].first;
|
||||
|
||||
targetSpellStore.erase(targetSpellStore.begin() + index);
|
||||
|
||||
SpellCastTargets targets;
|
||||
targets.SetUnitTarget(target);
|
||||
|
||||
if (!me->HasInArc(M_PI, target))
|
||||
{
|
||||
me->SetInFront(target);
|
||||
if (target && target->GetTypeId() == TYPEID_PLAYER)
|
||||
me->SendUpdateToPlayer(target->ToPlayer());
|
||||
|
||||
if (owner && owner->GetTypeId() == TYPEID_PLAYER)
|
||||
me->SendUpdateToPlayer(owner->ToPlayer());
|
||||
}
|
||||
|
||||
me->AddSpellCooldown(spell->m_spellInfo->Id, 0, 0);
|
||||
|
||||
spell->prepare(&targets);
|
||||
}
|
||||
|
||||
// deleted cached Spell objects
|
||||
for (TargetSpellList::const_iterator itr = targetSpellStore.begin(); itr != targetSpellStore.end(); ++itr)
|
||||
delete itr->second;
|
||||
}
|
||||
}
|
||||
|
||||
void PetAI::UpdateAllies()
|
||||
{
|
||||
Unit* owner = me->GetCharmerOrOwner();
|
||||
Group* group = NULL;
|
||||
|
||||
m_updateAlliesTimer = 10*IN_MILLISECONDS; //update friendly targets every 10 seconds, lesser checks increase performance
|
||||
|
||||
if (!owner)
|
||||
return;
|
||||
else if (owner->GetTypeId() == TYPEID_PLAYER)
|
||||
group = owner->ToPlayer()->GetGroup();
|
||||
|
||||
//only pet and owner/not in group->ok
|
||||
if (m_AllySet.size() == 2 && !group)
|
||||
return;
|
||||
//owner is in group; group members filled in already (no raid -> subgroupcount = whole count)
|
||||
if (group && !group->isRaidGroup() && m_AllySet.size() == (group->GetMembersCount() + 2))
|
||||
return;
|
||||
|
||||
m_AllySet.clear();
|
||||
m_AllySet.insert(me->GetGUID());
|
||||
if (group) //add group
|
||||
{
|
||||
for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next())
|
||||
{
|
||||
Player* Target = itr->GetSource();
|
||||
if (!Target || !Target->IsInMap(owner) || !group->SameSubGroup(owner->ToPlayer(), Target))
|
||||
continue;
|
||||
|
||||
if (Target->GetGUID() == owner->GetGUID())
|
||||
continue;
|
||||
|
||||
m_AllySet.insert(Target->GetGUID());
|
||||
}
|
||||
}
|
||||
else //remove group
|
||||
m_AllySet.insert(owner->GetGUID());
|
||||
}
|
||||
|
||||
void PetAI::KilledUnit(Unit* victim)
|
||||
{
|
||||
// Called from Unit::Kill() in case where pet or owner kills something
|
||||
// if owner killed this victim, pet may still be attacking something else
|
||||
if (me->GetVictim() && me->GetVictim() != victim)
|
||||
return;
|
||||
|
||||
// Xinef: if pet is channeling a spell and owner killed something different, dont interrupt it
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING) && me->GetUInt64Value(UNIT_FIELD_CHANNEL_OBJECT) && me->GetUInt64Value(UNIT_FIELD_CHANNEL_OBJECT) != victim->GetGUID())
|
||||
return;
|
||||
|
||||
// Clear target just in case. May help problem where health / focus / mana
|
||||
// regen gets stuck. Also resets attack command.
|
||||
// Can't use _stopAttack() because that activates movement handlers and ignores
|
||||
// next target selection
|
||||
me->AttackStop();
|
||||
me->InterruptNonMeleeSpells(false);
|
||||
|
||||
// Before returning to owner, see if there are more things to attack
|
||||
if (Unit* nextTarget = SelectNextTarget(false))
|
||||
AttackStart(nextTarget);
|
||||
else
|
||||
HandleReturnMovement(); // Return
|
||||
}
|
||||
|
||||
void PetAI::AttackStart(Unit* target)
|
||||
{
|
||||
// Overrides Unit::AttackStart to correctly evaluate Pet states
|
||||
|
||||
// Check all pet states to decide if we can attack this target
|
||||
if (!CanAttack(target))
|
||||
return;
|
||||
|
||||
// Only chase if not commanded to stay or if stay but commanded to attack
|
||||
DoAttack(target, (!me->GetCharmInfo()->HasCommandState(COMMAND_STAY) || me->GetCharmInfo()->IsCommandAttack()));
|
||||
}
|
||||
|
||||
void PetAI::OwnerAttackedBy(Unit* attacker)
|
||||
{
|
||||
// Called when owner takes damage. This function helps keep pets from running off
|
||||
// simply due to owner gaining aggro.
|
||||
|
||||
if (!attacker)
|
||||
return;
|
||||
|
||||
// Passive pets don't do anything
|
||||
if (me->HasReactState(REACT_PASSIVE))
|
||||
return;
|
||||
|
||||
// Prevent pet from disengaging from current target
|
||||
if (me->GetVictim() && me->GetVictim()->IsAlive())
|
||||
return;
|
||||
|
||||
// Continue to evaluate and attack if necessary
|
||||
AttackStart(attacker);
|
||||
}
|
||||
|
||||
void PetAI::OwnerAttacked(Unit* target)
|
||||
{
|
||||
// Called when owner attacks something. Allows defensive pets to know
|
||||
// that they need to assist
|
||||
|
||||
// Target might be NULL if called from spell with invalid cast targets
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
// Passive pets don't do anything
|
||||
if (me->HasReactState(REACT_PASSIVE))
|
||||
return;
|
||||
|
||||
// Prevent pet from disengaging from current target
|
||||
if (me->GetVictim() && me->GetVictim()->IsAlive())
|
||||
return;
|
||||
|
||||
// Continue to evaluate and attack if necessary
|
||||
AttackStart(target);
|
||||
}
|
||||
|
||||
Unit* PetAI::SelectNextTarget(bool allowAutoSelect) const
|
||||
{
|
||||
// Provides next target selection after current target death.
|
||||
// This function should only be called internally by the AI
|
||||
// Targets are not evaluated here for being valid targets, that is done in _CanAttack()
|
||||
// The parameter: allowAutoSelect lets us disable aggressive pet auto targeting for certain situations
|
||||
|
||||
// Passive pets don't do next target selection
|
||||
if (me->HasReactState(REACT_PASSIVE))
|
||||
return NULL;
|
||||
|
||||
// Check pet attackers first so we don't drag a bunch of targets to the owner
|
||||
if (Unit* myAttacker = me->getAttackerForHelper())
|
||||
if (!myAttacker->HasBreakableByDamageCrowdControlAura() && me->_CanDetectFeignDeathOf(myAttacker) && me->CanCreatureAttack(myAttacker) && !me->isTargetNotAcceptableByMMaps(myAttacker->GetGUID(), sWorld->GetGameTime(), myAttacker))
|
||||
return myAttacker;
|
||||
|
||||
// Check pet's attackers first to prevent dragging mobs back to owner
|
||||
if (me->HasAuraType(SPELL_AURA_MOD_TAUNT))
|
||||
{
|
||||
const Unit::AuraEffectList& tauntAuras = me->GetAuraEffectsByType(SPELL_AURA_MOD_TAUNT);
|
||||
if (!tauntAuras.empty())
|
||||
for (Unit::AuraEffectList::const_reverse_iterator itr = tauntAuras.rbegin(); itr != tauntAuras.rend(); ++itr)
|
||||
if (Unit* caster = (*itr)->GetCaster())
|
||||
if (me->_CanDetectFeignDeathOf(caster) && me->CanCreatureAttack(caster) && !caster->HasAuraTypeWithCaster(SPELL_AURA_IGNORED, me->GetGUID()))
|
||||
return caster;
|
||||
}
|
||||
|
||||
// Not sure why we wouldn't have an owner but just in case...
|
||||
Unit* owner = me->GetCharmerOrOwner();
|
||||
if (!owner)
|
||||
return NULL;
|
||||
|
||||
// Check owner attackers
|
||||
if (Unit* ownerAttacker = owner->getAttackerForHelper())
|
||||
if (!ownerAttacker->HasBreakableByDamageCrowdControlAura() && me->_CanDetectFeignDeathOf(ownerAttacker) && me->CanCreatureAttack(ownerAttacker) && !me->isTargetNotAcceptableByMMaps(ownerAttacker->GetGUID(), sWorld->GetGameTime(), ownerAttacker))
|
||||
return ownerAttacker;
|
||||
|
||||
// Check owner victim
|
||||
// 3.0.2 - Pets now start attacking their owners victim in defensive mode as soon as the hunter does
|
||||
if (Unit* ownerVictim = owner->GetVictim())
|
||||
if (me->_CanDetectFeignDeathOf(ownerVictim) && me->CanCreatureAttack(ownerVictim) && !me->isTargetNotAcceptableByMMaps(ownerVictim->GetGUID(), sWorld->GetGameTime(), ownerVictim))
|
||||
return ownerVictim;
|
||||
|
||||
// Neither pet or owner had a target and aggressive pets can pick any target
|
||||
// To prevent aggressive pets from chain selecting targets and running off, we
|
||||
// only select a random target if certain conditions are met.
|
||||
if (allowAutoSelect)
|
||||
if (!me->GetCharmInfo()->IsReturning() || me->GetCharmInfo()->IsFollowing() || me->GetCharmInfo()->IsAtStay())
|
||||
if (Unit* nearTarget = me->ToCreature()->SelectNearestTargetInAttackDistance(MAX_AGGRO_RADIUS))
|
||||
return nearTarget;
|
||||
|
||||
// Default - no valid targets
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void PetAI::HandleReturnMovement()
|
||||
{
|
||||
// Handles moving the pet back to stay or owner
|
||||
|
||||
// Prevent activating movement when under control of spells
|
||||
// such as "Eyes of the Beast"
|
||||
if (me->isPossessed())
|
||||
return;
|
||||
|
||||
if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY))
|
||||
{
|
||||
if (!me->GetCharmInfo()->IsAtStay() && !me->GetCharmInfo()->IsReturning())
|
||||
{
|
||||
// Return to previous position where stay was clicked
|
||||
if (me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) == NULL_MOTION_TYPE)
|
||||
{
|
||||
float x, y, z;
|
||||
|
||||
me->GetCharmInfo()->GetStayPosition(x, y, z);
|
||||
ClearCharmInfoFlags();
|
||||
me->GetCharmInfo()->SetIsReturning(true);
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MovePoint(me->GetGUIDLow(), x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
else // COMMAND_FOLLOW
|
||||
{
|
||||
if (!me->GetCharmInfo()->IsFollowing() && !me->GetCharmInfo()->IsReturning())
|
||||
{
|
||||
if (me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) == NULL_MOTION_TYPE)
|
||||
{
|
||||
ClearCharmInfoFlags();
|
||||
me->GetCharmInfo()->SetIsReturning(true);
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveFollow(me->GetCharmerOrOwner(), PET_FOLLOW_DIST, me->GetFollowAngle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
me->GetCharmInfo()->SetForcedSpell(0);
|
||||
me->GetCharmInfo()->SetForcedTargetGUID(0);
|
||||
|
||||
// xinef: remember that npcs summoned by npcs can also be pets
|
||||
me->DeleteThreatList();
|
||||
me->ClearInPetCombat();
|
||||
}
|
||||
|
||||
void PetAI::SpellHit(Unit* caster, const SpellInfo* spellInfo)
|
||||
{
|
||||
// Xinef: taunt behavior code
|
||||
if (spellInfo->HasAura(SPELL_AURA_MOD_TAUNT) && !me->HasReactState(REACT_PASSIVE))
|
||||
{
|
||||
me->GetCharmInfo()->SetForcedSpell(0);
|
||||
me->GetCharmInfo()->SetForcedTargetGUID(0);
|
||||
AttackStart(caster);
|
||||
}
|
||||
}
|
||||
|
||||
void PetAI::DoAttack(Unit* target, bool chase)
|
||||
{
|
||||
// Handles attack with or without chase and also resets flags
|
||||
// for next update / creature kill
|
||||
|
||||
if (me->Attack(target, true))
|
||||
{
|
||||
// xinef: properly fix fake combat after pet is sent to attack
|
||||
if (Unit* owner = me->GetOwner())
|
||||
owner->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT);
|
||||
|
||||
me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PET_IN_COMBAT);
|
||||
|
||||
// Play sound to let the player know the pet is attacking something it picked on its own
|
||||
if (me->HasReactState(REACT_AGGRESSIVE) && !me->GetCharmInfo()->IsCommandAttack())
|
||||
me->SendPetAIReaction(me->GetGUID());
|
||||
|
||||
if (CharmInfo* ci = me->GetCharmInfo())
|
||||
{
|
||||
ci->SetIsAtStay(false);
|
||||
ci->SetIsCommandFollow(false);
|
||||
ci->SetIsFollowing(false);
|
||||
ci->SetIsReturning(false);
|
||||
}
|
||||
|
||||
if (chase)
|
||||
{
|
||||
me->GetMotionMaster()->MoveChase(target, !_canMeleeAttack() ? 20.0f: 0.0f, me->GetAngle(target));
|
||||
}
|
||||
else // (Stay && ((Aggressive || Defensive) && In Melee Range)))
|
||||
{
|
||||
me->GetCharmInfo()->SetIsAtStay(true);
|
||||
me->GetMotionMaster()->MovementExpiredOnSlot(MOTION_SLOT_ACTIVE, false);
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PetAI::MovementInform(uint32 moveType, uint32 data)
|
||||
{
|
||||
// Receives notification when pet reaches stay or follow owner
|
||||
switch (moveType)
|
||||
{
|
||||
case POINT_MOTION_TYPE:
|
||||
{
|
||||
// Pet is returning to where stay was clicked. data should be
|
||||
// pet's GUIDLow since we set that as the waypoint ID
|
||||
if (data == me->GetGUIDLow() && me->GetCharmInfo()->IsReturning())
|
||||
{
|
||||
ClearCharmInfoFlags();
|
||||
me->GetCharmInfo()->SetIsAtStay(true);
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FOLLOW_MOTION_TYPE:
|
||||
{
|
||||
// If data is owner's GUIDLow then we've reached follow point,
|
||||
// otherwise we're probably chasing a creature
|
||||
if (me->GetCharmerOrOwner() && me->GetCharmInfo() && data == me->GetCharmerOrOwner()->GetGUIDLow() && me->GetCharmInfo()->IsReturning())
|
||||
{
|
||||
ClearCharmInfoFlags();
|
||||
me->GetCharmInfo()->SetIsFollowing(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool PetAI::CanAttack(Unit* target, const SpellInfo* spellInfo)
|
||||
{
|
||||
// Evaluates wether a pet can attack a specific target based on CommandState, ReactState and other flags
|
||||
// IMPORTANT: The order in which things are checked is important, be careful if you add or remove checks
|
||||
|
||||
// Hmmm...
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
if (!target->IsAlive())
|
||||
{
|
||||
// xinef: if target is invalid, pet should evade automaticly
|
||||
// Clear target to prevent getting stuck on dead targets
|
||||
//me->AttackStop();
|
||||
//me->InterruptNonMeleeSpells(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// xinef: check unit states of pet
|
||||
if (me->HasUnitState(UNIT_STATE_LOST_CONTROL))
|
||||
return false;
|
||||
|
||||
// xinef: pets of mounted players have stunned flag only, check this also
|
||||
if (me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_STUNNED))
|
||||
return false;
|
||||
|
||||
// pussywizard: ZOMG! TEMP!
|
||||
if (!me->GetCharmInfo())
|
||||
{
|
||||
sLog->outMisc("PetAI::CanAttack (A1) - %u, %u", me->GetEntry(), GUID_LOPART(me->GetOwnerGUID()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Passive - passive pets can attack if told to
|
||||
if (me->HasReactState(REACT_PASSIVE))
|
||||
return me->GetCharmInfo()->IsCommandAttack();
|
||||
|
||||
// CC - mobs under crowd control can be attacked if owner commanded
|
||||
if (target->HasBreakableByDamageCrowdControlAura() && (!spellInfo || !spellInfo->HasAttribute(SPELL_ATTR4_DAMAGE_DOESNT_BREAK_AURAS)))
|
||||
return me->GetCharmInfo()->IsCommandAttack();
|
||||
|
||||
// Returning - pets ignore attacks only if owner clicked follow
|
||||
if (me->GetCharmInfo()->IsReturning())
|
||||
return !me->GetCharmInfo()->IsCommandFollow();
|
||||
|
||||
// Stay - can attack if target is within range or commanded to
|
||||
if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY))
|
||||
return (me->IsWithinMeleeRange(target) || me->GetCharmInfo()->IsCommandAttack());
|
||||
|
||||
// Pets attacking something (or chasing) should only switch targets if owner tells them to
|
||||
if (me->GetVictim() && me->GetVictim() != target)
|
||||
{
|
||||
// Check if our owner selected this target and clicked "attack"
|
||||
Unit* ownerTarget = NULL;
|
||||
if (Player* owner = me->GetCharmerOrOwner()->ToPlayer())
|
||||
ownerTarget = owner->GetSelectedUnit();
|
||||
else
|
||||
ownerTarget = me->GetCharmerOrOwner()->GetVictim();
|
||||
|
||||
if (ownerTarget && me->GetCharmInfo()->IsCommandAttack())
|
||||
return (target->GetGUID() == ownerTarget->GetGUID());
|
||||
}
|
||||
|
||||
// Follow
|
||||
if (me->GetCharmInfo()->HasCommandState(COMMAND_FOLLOW))
|
||||
return !me->GetCharmInfo()->IsReturning();
|
||||
|
||||
// default, though we shouldn't ever get here
|
||||
return false;
|
||||
}
|
||||
|
||||
void PetAI::ReceiveEmote(Player* player, uint32 emote)
|
||||
{
|
||||
if (me->GetOwnerGUID() && me->GetOwnerGUID() == player->GetGUID())
|
||||
switch (emote)
|
||||
{
|
||||
case TEXT_EMOTE_COWER:
|
||||
if (me->IsPet() && me->ToPet()->IsPetGhoul())
|
||||
me->HandleEmoteCommand(/*EMOTE_ONESHOT_ROAR*/EMOTE_ONESHOT_OMNICAST_GHOUL);
|
||||
break;
|
||||
case TEXT_EMOTE_ANGRY:
|
||||
if (me->IsPet() && me->ToPet()->IsPetGhoul())
|
||||
me->HandleEmoteCommand(/*EMOTE_ONESHOT_COWER*/EMOTE_STATE_STUN);
|
||||
break;
|
||||
case TEXT_EMOTE_GLARE:
|
||||
if (me->IsPet() && me->ToPet()->IsPetGhoul())
|
||||
me->HandleEmoteCommand(EMOTE_STATE_STUN);
|
||||
break;
|
||||
case TEXT_EMOTE_SOOTHE:
|
||||
if (me->IsPet() && me->ToPet()->IsPetGhoul())
|
||||
me->HandleEmoteCommand(EMOTE_ONESHOT_OMNICAST_GHOUL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PetAI::ClearCharmInfoFlags()
|
||||
{
|
||||
// Quick access to set all flags to FALSE
|
||||
|
||||
CharmInfo* ci = me->GetCharmInfo();
|
||||
|
||||
if (ci)
|
||||
{
|
||||
ci->SetIsAtStay(false);
|
||||
ci->SetIsCommandAttack(false);
|
||||
ci->SetIsCommandFollow(false);
|
||||
ci->SetIsFollowing(false);
|
||||
ci->SetIsReturning(false);
|
||||
}
|
||||
}
|
||||
|
||||
void PetAI::AttackedBy(Unit* attacker)
|
||||
{
|
||||
// Called when pet takes damage. This function helps keep pets from running off
|
||||
// simply due to gaining aggro.
|
||||
|
||||
if (!attacker)
|
||||
return;
|
||||
|
||||
// Passive pets don't do anything
|
||||
if (me->HasReactState(REACT_PASSIVE))
|
||||
return;
|
||||
|
||||
// Prevent pet from disengaging from current target
|
||||
if (me->GetVictim() && me->GetVictim()->IsAlive())
|
||||
return;
|
||||
|
||||
// Continue to evaluate and attack if necessary
|
||||
AttackStart(attacker);
|
||||
}
|
||||
72
src/server/game/AI/CoreAI/PetAI.h
Normal file
72
src/server/game/AI/CoreAI/PetAI.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITY_PETAI_H
|
||||
#define TRINITY_PETAI_H
|
||||
|
||||
#include "CreatureAI.h"
|
||||
#include "Timer.h"
|
||||
|
||||
class Creature;
|
||||
class Spell;
|
||||
|
||||
class PetAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
|
||||
explicit PetAI(Creature* c);
|
||||
|
||||
void UpdateAI(uint32);
|
||||
static int Permissible(const Creature*);
|
||||
|
||||
void KilledUnit(Unit* /*victim*/);
|
||||
void AttackStart(Unit* target);
|
||||
void MovementInform(uint32 moveType, uint32 data);
|
||||
void OwnerAttackedBy(Unit* attacker);
|
||||
void OwnerAttacked(Unit* target);
|
||||
void AttackedBy(Unit* attacker);
|
||||
void ReceiveEmote(Player* player, uint32 textEmote);
|
||||
|
||||
// The following aren't used by the PetAI but need to be defined to override
|
||||
// default CreatureAI functions which interfere with the PetAI
|
||||
//
|
||||
void MoveInLineOfSight(Unit* /*who*/) {} // CreatureAI interferes with returning pets
|
||||
void MoveInLineOfSight_Safe(Unit* /*who*/) {} // CreatureAI interferes with returning pets
|
||||
void EnterEvadeMode() {} // For fleeing, pets don't use this type of Evade mechanic
|
||||
void SpellHit(Unit* caster, const SpellInfo* spellInfo);
|
||||
|
||||
private:
|
||||
bool _isVisible(Unit*) const;
|
||||
bool _needToStop(void);
|
||||
void _stopAttack(void);
|
||||
void _doMeleeAttack();
|
||||
bool _canMeleeAttack() const;
|
||||
|
||||
void UpdateAllies();
|
||||
|
||||
TimeTracker i_tracker;
|
||||
std::set<uint64> m_AllySet;
|
||||
uint32 m_updateAlliesTimer;
|
||||
|
||||
Unit* SelectNextTarget(bool allowAutoSelect) const;
|
||||
void HandleReturnMovement();
|
||||
void DoAttack(Unit* target, bool chase);
|
||||
bool CanAttack(Unit* target, const SpellInfo* spellInfo = NULL);
|
||||
void ClearCharmInfoFlags();
|
||||
};
|
||||
#endif
|
||||
40
src/server/game/AI/CoreAI/ReactorAI.cpp
Normal file
40
src/server/game/AI/CoreAI/ReactorAI.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ByteBuffer.h"
|
||||
#include "ReactorAI.h"
|
||||
#include "Errors.h"
|
||||
#include "Log.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "CreatureAIImpl.h"
|
||||
|
||||
int ReactorAI::Permissible(const Creature* creature)
|
||||
{
|
||||
if (creature->IsCivilian() || creature->IsNeutralToAll())
|
||||
return PERMIT_BASE_REACTIVE;
|
||||
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
void ReactorAI::UpdateAI(uint32 /*diff*/)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
37
src/server/game/AI/CoreAI/ReactorAI.h
Normal file
37
src/server/game/AI/CoreAI/ReactorAI.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITY_REACTORAI_H
|
||||
#define TRINITY_REACTORAI_H
|
||||
|
||||
#include "CreatureAI.h"
|
||||
|
||||
class Unit;
|
||||
|
||||
class ReactorAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
|
||||
explicit ReactorAI(Creature* c) : CreatureAI(c) {}
|
||||
|
||||
void MoveInLineOfSight(Unit*) {}
|
||||
void UpdateAI(uint32 diff);
|
||||
|
||||
static int Permissible(const Creature*);
|
||||
};
|
||||
#endif
|
||||
117
src/server/game/AI/CoreAI/TotemAI.cpp
Normal file
117
src/server/game/AI/CoreAI/TotemAI.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "TotemAI.h"
|
||||
#include "Totem.h"
|
||||
#include "Creature.h"
|
||||
#include "DBCStores.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "SpellMgr.h"
|
||||
|
||||
#include "GridNotifiers.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "CellImpl.h"
|
||||
|
||||
int TotemAI::Permissible(Creature const* creature)
|
||||
{
|
||||
if (creature->IsTotem())
|
||||
return PERMIT_BASE_PROACTIVE;
|
||||
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
TotemAI::TotemAI(Creature* c) : CreatureAI(c), i_victimGuid(0)
|
||||
{
|
||||
ASSERT(c->IsTotem());
|
||||
}
|
||||
|
||||
void TotemAI::SpellHit(Unit* /*caster*/, const SpellInfo* spellInfo)
|
||||
{
|
||||
}
|
||||
|
||||
void TotemAI::DoAction(int32 param)
|
||||
{
|
||||
}
|
||||
|
||||
void TotemAI::MoveInLineOfSight(Unit* /*who*/)
|
||||
{
|
||||
}
|
||||
|
||||
void TotemAI::EnterEvadeMode()
|
||||
{
|
||||
me->CombatStop(true);
|
||||
}
|
||||
|
||||
void TotemAI::UpdateAI(uint32 /*diff*/)
|
||||
{
|
||||
if (me->ToTotem()->GetTotemType() != TOTEM_ACTIVE)
|
||||
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);
|
||||
|
||||
// SPELLMOD_RANGE not applied in this place just because not existence range mods for attacking totems
|
||||
|
||||
// pointer to appropriate target if found any
|
||||
Unit* victim = i_victimGuid ? ObjectAccessor::GetUnit(*me, i_victimGuid) : NULL;
|
||||
|
||||
// Search victim if no, not attackable, or out of range, or friendly (possible in case duel end)
|
||||
if (!victim ||
|
||||
!victim->isTargetableForAttack(true, me) || !me->IsWithinDistInMap(victim, max_range) ||
|
||||
me->IsFriendlyTo(victim) || !me->CanSeeOrDetect(victim))
|
||||
{
|
||||
victim = NULL;
|
||||
Trinity::NearestAttackableUnitInObjectRangeCheck u_check(me, me, max_range);
|
||||
Trinity::UnitLastSearcher<Trinity::NearestAttackableUnitInObjectRangeCheck> checker(me, victim, u_check);
|
||||
me->VisitNearbyObject(max_range, checker);
|
||||
}
|
||||
|
||||
// If have target
|
||||
if (victim)
|
||||
{
|
||||
// remember
|
||||
i_victimGuid = victim->GetGUID();
|
||||
|
||||
// attack
|
||||
me->SetInFront(victim); // client change orientation by self
|
||||
me->CastSpell(victim, me->ToTotem()->GetSpell(), false);
|
||||
}
|
||||
else
|
||||
i_victimGuid = 0;
|
||||
}
|
||||
|
||||
void TotemAI::AttackStart(Unit* /*victim*/)
|
||||
{
|
||||
// Sentry totem sends ping on attack
|
||||
if (me->GetEntry() == SENTRY_TOTEM_ENTRY && me->GetOwner()->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
WorldPacket data(MSG_MINIMAP_PING, (8+4+4));
|
||||
data << me->GetGUID();
|
||||
data << me->GetPositionX();
|
||||
data << me->GetPositionY();
|
||||
me->GetOwner()->ToPlayer()->GetSession()->SendPacket(&data);
|
||||
}
|
||||
}
|
||||
62
src/server/game/AI/CoreAI/TotemAI.h
Normal file
62
src/server/game/AI/CoreAI/TotemAI.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITY_TOTEMAI_H
|
||||
#define TRINITY_TOTEMAI_H
|
||||
|
||||
#include "CreatureAI.h"
|
||||
#include "Timer.h"
|
||||
|
||||
class Creature;
|
||||
class Totem;
|
||||
|
||||
class TotemAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
|
||||
explicit TotemAI(Creature* c);
|
||||
|
||||
void MoveInLineOfSight(Unit* who);
|
||||
void AttackStart(Unit* victim);
|
||||
void EnterEvadeMode();
|
||||
void SpellHit(Unit* /*caster*/, const SpellInfo* /*spellInfo*/);
|
||||
void DoAction(int32 param);
|
||||
|
||||
void UpdateAI(uint32 diff);
|
||||
static int Permissible(Creature const* creature);
|
||||
|
||||
private:
|
||||
uint64 i_victimGuid;
|
||||
};
|
||||
|
||||
class KillMagnetEvent : public BasicEvent
|
||||
{
|
||||
public:
|
||||
KillMagnetEvent(Unit& self) : _self(self) { }
|
||||
bool Execute(uint64 e_time, uint32 p_time)
|
||||
{
|
||||
_self.setDeathState(JUST_DIED);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
Unit& _self;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
336
src/server/game/AI/CoreAI/UnitAI.cpp
Normal file
336
src/server/game/AI/CoreAI/UnitAI.cpp
Normal file
@@ -0,0 +1,336 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "UnitAI.h"
|
||||
#include "Player.h"
|
||||
#include "Creature.h"
|
||||
#include "SpellAuras.h"
|
||||
#include "SpellAuraEffects.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "Spell.h"
|
||||
#include "CreatureAIImpl.h"
|
||||
|
||||
void UnitAI::AttackStart(Unit* victim)
|
||||
{
|
||||
if (victim && me->Attack(victim, true))
|
||||
me->GetMotionMaster()->MoveChase(victim);
|
||||
}
|
||||
|
||||
void UnitAI::AttackStartCaster(Unit* victim, float dist)
|
||||
{
|
||||
if (victim && me->Attack(victim, false))
|
||||
me->GetMotionMaster()->MoveChase(victim, dist);
|
||||
}
|
||||
|
||||
void UnitAI::DoMeleeAttackIfReady()
|
||||
{
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
Unit *victim = me->GetVictim();
|
||||
if (!victim || !victim->IsInWorld())
|
||||
return;
|
||||
|
||||
if (!me->IsWithinMeleeRange(victim))
|
||||
return;
|
||||
|
||||
//Make sure our attack is ready and we aren't currently casting before checking distance
|
||||
if (me->isAttackReady())
|
||||
{
|
||||
// xinef: prevent base and off attack in same time, delay attack at 0.2 sec
|
||||
if (me->haveOffhandWeapon())
|
||||
if (me->getAttackTimer(OFF_ATTACK) < ATTACK_DISPLAY_DELAY)
|
||||
me->setAttackTimer(OFF_ATTACK, ATTACK_DISPLAY_DELAY);
|
||||
|
||||
me->AttackerStateUpdate(victim);
|
||||
me->resetAttackTimer();
|
||||
}
|
||||
|
||||
if (me->haveOffhandWeapon() && me->isAttackReady(OFF_ATTACK))
|
||||
{
|
||||
// xinef: delay main hand attack if both will hit at the same time (players code)
|
||||
if (me->getAttackTimer(BASE_ATTACK) < ATTACK_DISPLAY_DELAY)
|
||||
me->setAttackTimer(BASE_ATTACK, ATTACK_DISPLAY_DELAY);
|
||||
|
||||
me->AttackerStateUpdate(victim, OFF_ATTACK);
|
||||
me->resetAttackTimer(OFF_ATTACK);
|
||||
}
|
||||
}
|
||||
|
||||
bool UnitAI::DoSpellAttackIfReady(uint32 spell)
|
||||
{
|
||||
if (me->HasUnitState(UNIT_STATE_CASTING) || !me->isAttackReady())
|
||||
return true;
|
||||
|
||||
if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell))
|
||||
{
|
||||
if (me->IsWithinCombatRange(me->GetVictim(), spellInfo->GetMaxRange(false)))
|
||||
{
|
||||
me->CastSpell(me->GetVictim(), spell, false);
|
||||
me->resetAttackTimer();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Unit* UnitAI::SelectTarget(SelectAggroTarget targetType, uint32 position, float dist, bool playerOnly, int32 aura)
|
||||
{
|
||||
return SelectTarget(targetType, position, DefaultTargetSelector(me, dist, playerOnly, aura));
|
||||
}
|
||||
|
||||
void UnitAI::SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectAggroTarget targetType, float dist, bool playerOnly, int32 aura)
|
||||
{
|
||||
SelectTargetList(targetList, DefaultTargetSelector(me, dist, playerOnly, aura), num, targetType);
|
||||
}
|
||||
|
||||
float UnitAI::DoGetSpellMaxRange(uint32 spellId, bool positive)
|
||||
{
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
return spellInfo ? spellInfo->GetMaxRange(positive) : 0;
|
||||
}
|
||||
|
||||
void UnitAI::DoAddAuraToAllHostilePlayers(uint32 spellid)
|
||||
{
|
||||
if (me->IsInCombat())
|
||||
{
|
||||
ThreatContainer::StorageType threatlist = me->getThreatManager().getThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
{
|
||||
if (Unit* unit = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid()))
|
||||
if (unit->GetTypeId() == TYPEID_PLAYER)
|
||||
me->AddAura(spellid, unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnitAI::DoCastToAllHostilePlayers(uint32 spellid, bool triggered)
|
||||
{
|
||||
if (me->IsInCombat())
|
||||
{
|
||||
ThreatContainer::StorageType threatlist = me->getThreatManager().getThreatList();
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
{
|
||||
if (Unit* unit = ObjectAccessor::GetUnit(*me, (*itr)->getUnitGuid()))
|
||||
if (unit->GetTypeId() == TYPEID_PLAYER)
|
||||
me->CastSpell(unit, spellid, triggered);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnitAI::DoCast(uint32 spellId)
|
||||
{
|
||||
Unit* target = NULL;
|
||||
//sLog->outError("aggre %u %u", spellId, (uint32)AISpellInfo[spellId].target);
|
||||
switch (AISpellInfo[spellId].target)
|
||||
{
|
||||
default:
|
||||
case AITARGET_SELF: target = me; break;
|
||||
case AITARGET_VICTIM: target = me->GetVictim(); break;
|
||||
case AITARGET_ENEMY:
|
||||
{
|
||||
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
bool playerOnly = spellInfo->HasAttribute(SPELL_ATTR3_ONLY_TARGET_PLAYERS);
|
||||
//float range = GetSpellMaxRange(spellInfo, false);
|
||||
target = SelectTarget(SELECT_TARGET_RANDOM, 0, spellInfo->GetMaxRange(false), playerOnly);
|
||||
break;
|
||||
}
|
||||
case AITARGET_ALLY: target = me; break;
|
||||
case AITARGET_BUFF: target = me; break;
|
||||
case AITARGET_DEBUFF:
|
||||
{
|
||||
const SpellInfo* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
bool playerOnly = spellInfo->HasAttribute(SPELL_ATTR3_ONLY_TARGET_PLAYERS);
|
||||
float range = spellInfo->GetMaxRange(false);
|
||||
|
||||
DefaultTargetSelector targetSelector(me, range, playerOnly, -(int32)spellId);
|
||||
if (!(spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_VICTIM)
|
||||
&& targetSelector(me->GetVictim()))
|
||||
target = me->GetVictim();
|
||||
else
|
||||
target = SelectTarget(SELECT_TARGET_RANDOM, 0, targetSelector);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (target)
|
||||
me->CastSpell(target, spellId, false);
|
||||
}
|
||||
|
||||
void UnitAI::DoCast(Unit* victim, uint32 spellId, bool triggered)
|
||||
{
|
||||
if (!victim || (me->HasUnitState(UNIT_STATE_CASTING) && !triggered))
|
||||
return;
|
||||
|
||||
me->CastSpell(victim, spellId, triggered);
|
||||
}
|
||||
|
||||
void UnitAI::DoCastVictim(uint32 spellId, bool triggered)
|
||||
{
|
||||
if (!me->GetVictim() || (me->HasUnitState(UNIT_STATE_CASTING) && !triggered))
|
||||
return;
|
||||
|
||||
me->CastSpell(me->GetVictim(), spellId, triggered);
|
||||
}
|
||||
|
||||
void UnitAI::DoCastAOE(uint32 spellId, bool triggered)
|
||||
{
|
||||
if (!triggered && me->HasUnitState(UNIT_STATE_CASTING))
|
||||
return;
|
||||
|
||||
me->CastSpell((Unit*)NULL, spellId, triggered);
|
||||
}
|
||||
|
||||
#define UPDATE_TARGET(a) {if (AIInfo->target<a) AIInfo->target=a;}
|
||||
|
||||
void UnitAI::FillAISpellInfo()
|
||||
{
|
||||
AISpellInfo = new AISpellInfoType[sSpellMgr->GetSpellInfoStoreSize()];
|
||||
|
||||
AISpellInfoType* AIInfo = AISpellInfo;
|
||||
const SpellInfo* spellInfo;
|
||||
|
||||
for (uint32 i = 0; i < sSpellMgr->GetSpellInfoStoreSize(); ++i, ++AIInfo)
|
||||
{
|
||||
spellInfo = sSpellMgr->GetSpellInfo(i);
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
if (spellInfo->HasAttribute(SPELL_ATTR0_CASTABLE_WHILE_DEAD))
|
||||
AIInfo->condition = AICOND_DIE;
|
||||
else if (spellInfo->IsPassive() || spellInfo->GetDuration() == -1)
|
||||
AIInfo->condition = AICOND_AGGRO;
|
||||
else
|
||||
AIInfo->condition = AICOND_COMBAT;
|
||||
|
||||
if (AIInfo->cooldown < spellInfo->RecoveryTime)
|
||||
AIInfo->cooldown = spellInfo->RecoveryTime;
|
||||
|
||||
if (!spellInfo->GetMaxRange(false))
|
||||
UPDATE_TARGET(AITARGET_SELF)
|
||||
else
|
||||
{
|
||||
for (uint32 j = 0; j < MAX_SPELL_EFFECTS; ++j)
|
||||
{
|
||||
uint32 targetType = spellInfo->Effects[j].TargetA.GetTarget();
|
||||
|
||||
if (targetType == TARGET_UNIT_TARGET_ENEMY
|
||||
|| targetType == TARGET_DEST_TARGET_ENEMY)
|
||||
UPDATE_TARGET(AITARGET_VICTIM)
|
||||
else if (targetType == TARGET_UNIT_DEST_AREA_ENEMY)
|
||||
UPDATE_TARGET(AITARGET_ENEMY)
|
||||
|
||||
if (spellInfo->Effects[j].Effect == SPELL_EFFECT_APPLY_AURA)
|
||||
{
|
||||
if (targetType == TARGET_UNIT_TARGET_ENEMY)
|
||||
UPDATE_TARGET(AITARGET_DEBUFF)
|
||||
else if (spellInfo->IsPositive())
|
||||
UPDATE_TARGET(AITARGET_BUFF)
|
||||
}
|
||||
}
|
||||
}
|
||||
AIInfo->realCooldown = spellInfo->RecoveryTime + spellInfo->StartRecoveryTime;
|
||||
AIInfo->maxRange = spellInfo->GetMaxRange(false) * 3 / 4;
|
||||
}
|
||||
}
|
||||
|
||||
//Enable PlayerAI when charmed
|
||||
void PlayerAI::OnCharmed(bool apply)
|
||||
{
|
||||
me->IsAIEnabled = apply;
|
||||
}
|
||||
|
||||
void SimpleCharmedAI::UpdateAI(uint32 /*diff*/)
|
||||
{
|
||||
Creature* charmer = me->GetCharmer()->ToCreature();
|
||||
|
||||
//kill self if charm aura has infinite duration
|
||||
if (charmer->IsInEvadeMode())
|
||||
{
|
||||
Unit::AuraEffectList const& auras = me->GetAuraEffectsByType(SPELL_AURA_MOD_CHARM);
|
||||
for (Unit::AuraEffectList::const_iterator iter = auras.begin(); iter != auras.end(); ++iter)
|
||||
if ((*iter)->GetCasterGUID() == charmer->GetGUID() && (*iter)->GetBase()->IsPermanent())
|
||||
{
|
||||
Unit::Kill(charmer, me);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!charmer->IsInCombat())
|
||||
me->GetMotionMaster()->MoveFollow(charmer, PET_FOLLOW_DIST, me->GetFollowAngle());
|
||||
|
||||
Unit* target = me->GetVictim();
|
||||
if (!target || !charmer->IsValidAttackTarget(target))
|
||||
AttackStart(charmer->SelectNearestTargetInAttackDistance(ATTACK_DISTANCE));
|
||||
}
|
||||
|
||||
SpellTargetSelector::SpellTargetSelector(Unit* caster, uint32 spellId) :
|
||||
_caster(caster), _spellInfo(sSpellMgr->GetSpellForDifficultyFromSpell(sSpellMgr->GetSpellInfo(spellId), caster))
|
||||
{
|
||||
ASSERT(_spellInfo);
|
||||
}
|
||||
|
||||
bool SpellTargetSelector::operator()(Unit const* target) const
|
||||
{
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
if (_spellInfo->CheckTarget(_caster, target) != SPELL_CAST_OK)
|
||||
return false;
|
||||
|
||||
// copypasta from Spell::CheckRange
|
||||
uint32 range_type = _spellInfo->RangeEntry ? _spellInfo->RangeEntry->type : 0;
|
||||
float max_range = _caster->GetSpellMaxRangeForTarget(target, _spellInfo);
|
||||
float min_range = _caster->GetSpellMinRangeForTarget(target, _spellInfo);
|
||||
|
||||
|
||||
if (target && target != _caster)
|
||||
{
|
||||
if (range_type == SPELL_RANGE_MELEE)
|
||||
{
|
||||
// Because of lag, we can not check too strictly here.
|
||||
if (!_caster->IsWithinMeleeRange(target, max_range))
|
||||
return false;
|
||||
}
|
||||
else if (!_caster->IsWithinCombatRange(target, max_range))
|
||||
return false;
|
||||
|
||||
if (range_type == SPELL_RANGE_RANGED)
|
||||
{
|
||||
if (_caster->IsWithinMeleeRange(target))
|
||||
return false;
|
||||
}
|
||||
else if (min_range && _caster->IsWithinCombatRange(target, min_range)) // skip this check if min_range = 0
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NonTankTargetSelector::operator()(Unit const* target) const
|
||||
{
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
if (_playerOnly && target->GetTypeId() != TYPEID_PLAYER)
|
||||
return false;
|
||||
|
||||
return target != _source->GetVictim();
|
||||
}
|
||||
340
src/server/game/AI/CoreAI/UnitAI.h
Normal file
340
src/server/game/AI/CoreAI/UnitAI.h
Normal file
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
* Copyright (C)
|
||||
* Copyright (C)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITY_UNITAI_H
|
||||
#define TRINITY_UNITAI_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "Unit.h"
|
||||
#include "Containers.h"
|
||||
#include <list>
|
||||
|
||||
class Player;
|
||||
class Quest;
|
||||
class Unit;
|
||||
struct AISpellInfoType;
|
||||
|
||||
//Selection method used by SelectTarget
|
||||
enum SelectAggroTarget
|
||||
{
|
||||
SELECT_TARGET_RANDOM = 0, //Just selects a random target
|
||||
SELECT_TARGET_TOPAGGRO, //Selects targes from top aggro to bottom
|
||||
SELECT_TARGET_BOTTOMAGGRO, //Selects targets from bottom aggro to top
|
||||
SELECT_TARGET_NEAREST,
|
||||
SELECT_TARGET_FARTHEST,
|
||||
};
|
||||
|
||||
// default predicate function to select target based on distance, player and/or aura criteria
|
||||
struct DefaultTargetSelector : public std::unary_function<Unit*, bool>
|
||||
{
|
||||
const Unit* me;
|
||||
float m_dist;
|
||||
bool m_playerOnly;
|
||||
int32 m_aura;
|
||||
|
||||
// unit: the reference unit
|
||||
// dist: if 0: ignored, if > 0: maximum distance to the reference unit, if < 0: minimum distance to the reference unit
|
||||
// playerOnly: self explaining
|
||||
// aura: if 0: ignored, if > 0: the target shall have the aura, if < 0, the target shall NOT have the aura
|
||||
DefaultTargetSelector(Unit const* unit, float dist, bool playerOnly, int32 aura) : me(unit), m_dist(dist), m_playerOnly(playerOnly), m_aura(aura) {}
|
||||
|
||||
bool operator()(Unit const* target) const
|
||||
{
|
||||
if (!me)
|
||||
return false;
|
||||
|
||||
if (!target)
|
||||
return false;
|
||||
|
||||
if (m_playerOnly && (target->GetTypeId() != TYPEID_PLAYER))
|
||||
return false;
|
||||
|
||||
if (m_dist > 0.0f && !me->IsWithinCombatRange(target, m_dist))
|
||||
return false;
|
||||
|
||||
if (m_dist < 0.0f && me->IsWithinCombatRange(target, -m_dist))
|
||||
return false;
|
||||
|
||||
if (m_aura)
|
||||
{
|
||||
if (m_aura > 0)
|
||||
{
|
||||
if (!target->HasAura(m_aura))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (target->HasAura(-m_aura))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Target selector for spell casts checking range, auras and attributes
|
||||
// TODO: Add more checks from Spell::CheckCast
|
||||
struct SpellTargetSelector : public std::unary_function<Unit*, bool>
|
||||
{
|
||||
public:
|
||||
SpellTargetSelector(Unit* caster, uint32 spellId);
|
||||
bool operator()(Unit const* target) const;
|
||||
|
||||
private:
|
||||
Unit const* _caster;
|
||||
SpellInfo const* _spellInfo;
|
||||
};
|
||||
|
||||
// Very simple target selector, will just skip main target
|
||||
// NOTE: When passing to UnitAI::SelectTarget remember to use 0 as position for random selection
|
||||
// because tank will not be in the temporary list
|
||||
struct NonTankTargetSelector : public std::unary_function<Unit*, bool>
|
||||
{
|
||||
public:
|
||||
NonTankTargetSelector(Creature* source, bool playerOnly = true) : _source(source), _playerOnly(playerOnly) { }
|
||||
bool operator()(Unit const* target) const;
|
||||
|
||||
private:
|
||||
Creature const* _source;
|
||||
bool _playerOnly;
|
||||
};
|
||||
|
||||
// Simple selector for units using mana
|
||||
struct PowerUsersSelector : public std::unary_function<Unit*, bool>
|
||||
{
|
||||
Unit const* _me;
|
||||
float const _dist;
|
||||
bool const _playerOnly;
|
||||
Powers const _power;
|
||||
|
||||
|
||||
PowerUsersSelector(Unit const* unit, Powers power, float dist, bool playerOnly) : _me(unit), _power(power), _dist(dist), _playerOnly(playerOnly) { }
|
||||
|
||||
bool operator()(Unit const* target) const
|
||||
{
|
||||
if (!_me || !target)
|
||||
return false;
|
||||
|
||||
if (target->getPowerType() != _power)
|
||||
return false;
|
||||
|
||||
if (_playerOnly && target->GetTypeId() != TYPEID_PLAYER)
|
||||
return false;
|
||||
|
||||
if (_dist > 0.0f && !_me->IsWithinCombatRange(target, _dist))
|
||||
return false;
|
||||
|
||||
if (_dist < 0.0f && _me->IsWithinCombatRange(target, -_dist))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct FarthestTargetSelector : public std::unary_function<Unit*, bool>
|
||||
{
|
||||
FarthestTargetSelector(Unit const* unit, float dist, bool playerOnly, bool inLos) : _me(unit), _dist(dist), _playerOnly(playerOnly), _inLos(inLos) {}
|
||||
|
||||
bool operator()(Unit const* target) const
|
||||
{
|
||||
if (!_me || !target)
|
||||
return false;
|
||||
|
||||
if (_playerOnly && target->GetTypeId() != TYPEID_PLAYER)
|
||||
return false;
|
||||
|
||||
if (_dist > 0.0f && !_me->IsWithinCombatRange(target, _dist))
|
||||
return false;
|
||||
|
||||
if (_inLos && !_me->IsWithinLOSInMap(target))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
const Unit* _me;
|
||||
float _dist;
|
||||
bool _playerOnly;
|
||||
bool _inLos;
|
||||
};
|
||||
|
||||
class UnitAI
|
||||
{
|
||||
protected:
|
||||
Unit* const me;
|
||||
public:
|
||||
explicit UnitAI(Unit* unit) : me(unit) {}
|
||||
virtual ~UnitAI() {}
|
||||
|
||||
virtual bool CanAIAttack(Unit const* /*target*/) const { return true; }
|
||||
virtual void AttackStart(Unit* /*target*/);
|
||||
virtual void UpdateAI(uint32 diff) = 0;
|
||||
|
||||
virtual void InitializeAI() { if (!me->isDead()) Reset(); }
|
||||
|
||||
virtual void Reset() {};
|
||||
|
||||
// Called when unit is charmed
|
||||
virtual void OnCharmed(bool apply) = 0;
|
||||
|
||||
// Pass parameters between AI
|
||||
virtual void DoAction(int32 /*param*/) {}
|
||||
virtual uint32 GetData(uint32 /*id = 0*/) const { return 0; }
|
||||
virtual void SetData(uint32 /*id*/, uint32 /*value*/) {}
|
||||
virtual void SetGUID(uint64 /*guid*/, int32 /*id*/ = 0) {}
|
||||
virtual uint64 GetGUID(int32 /*id*/ = 0) const { return 0; }
|
||||
|
||||
Unit* SelectTarget(SelectAggroTarget targetType, uint32 position = 0, float dist = 0.0f, bool playerOnly = false, int32 aura = 0);
|
||||
// Select the targets satifying the predicate.
|
||||
// predicate shall extend std::unary_function<Unit*, bool>
|
||||
template <class PREDICATE> Unit* SelectTarget(SelectAggroTarget targetType, uint32 position, PREDICATE const& predicate)
|
||||
{
|
||||
ThreatContainer::StorageType const& threatlist = me->getThreatManager().getThreatList();
|
||||
if (position >= threatlist.size())
|
||||
return NULL;
|
||||
|
||||
std::list<Unit*> targetList;
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
if (predicate((*itr)->getTarget()))
|
||||
targetList.push_back((*itr)->getTarget());
|
||||
|
||||
if (position >= targetList.size())
|
||||
return NULL;
|
||||
|
||||
if (targetType == SELECT_TARGET_NEAREST || targetType == SELECT_TARGET_FARTHEST)
|
||||
targetList.sort(Trinity::ObjectDistanceOrderPred(me));
|
||||
|
||||
switch (targetType)
|
||||
{
|
||||
case SELECT_TARGET_NEAREST:
|
||||
case SELECT_TARGET_TOPAGGRO:
|
||||
{
|
||||
std::list<Unit*>::iterator itr = targetList.begin();
|
||||
std::advance(itr, position);
|
||||
return *itr;
|
||||
}
|
||||
case SELECT_TARGET_FARTHEST:
|
||||
case SELECT_TARGET_BOTTOMAGGRO:
|
||||
{
|
||||
std::list<Unit*>::reverse_iterator ritr = targetList.rbegin();
|
||||
std::advance(ritr, position);
|
||||
return *ritr;
|
||||
}
|
||||
case SELECT_TARGET_RANDOM:
|
||||
{
|
||||
std::list<Unit*>::iterator itr = targetList.begin();
|
||||
std::advance(itr, urand(position, targetList.size() - 1));
|
||||
return *itr;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void SelectTargetList(std::list<Unit*>& targetList, uint32 num, SelectAggroTarget targetType, float dist = 0.0f, bool playerOnly = false, int32 aura = 0);
|
||||
|
||||
// Select the targets satifying the predicate.
|
||||
// predicate shall extend std::unary_function<Unit*, bool>
|
||||
template <class PREDICATE> void SelectTargetList(std::list<Unit*>& targetList, PREDICATE const& predicate, uint32 maxTargets, SelectAggroTarget targetType)
|
||||
{
|
||||
ThreatContainer::StorageType const& threatlist = me->getThreatManager().getThreatList();
|
||||
if (threatlist.empty())
|
||||
return;
|
||||
|
||||
for (ThreatContainer::StorageType::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
if (predicate((*itr)->getTarget()))
|
||||
targetList.push_back((*itr)->getTarget());
|
||||
|
||||
if (targetList.size() < maxTargets)
|
||||
return;
|
||||
|
||||
if (targetType == SELECT_TARGET_NEAREST || targetType == SELECT_TARGET_FARTHEST)
|
||||
targetList.sort(Trinity::ObjectDistanceOrderPred(me));
|
||||
|
||||
if (targetType == SELECT_TARGET_FARTHEST || targetType == SELECT_TARGET_BOTTOMAGGRO)
|
||||
targetList.reverse();
|
||||
|
||||
if (targetType == SELECT_TARGET_RANDOM)
|
||||
Trinity::Containers::RandomResizeList(targetList, maxTargets);
|
||||
else
|
||||
targetList.resize(maxTargets);
|
||||
}
|
||||
|
||||
// Called at any Damage to any victim (before damage apply)
|
||||
virtual void DamageDealt(Unit* /*victim*/, uint32& /*damage*/, DamageEffectType /*damageType*/) { }
|
||||
|
||||
// Called at any Damage from any attacker (before damage apply)
|
||||
// Note: it for recalculation damage or special reaction at damage
|
||||
// for attack reaction use AttackedBy called for not DOT damage in Unit::DealDamage also
|
||||
virtual void DamageTaken(Unit* /*attacker*/, uint32& /*damage*/, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/ ) {}
|
||||
|
||||
// Called when the creature receives heal
|
||||
virtual void HealReceived(Unit* /*done_by*/, uint32& /*addhealth*/) {}
|
||||
|
||||
// Called when the unit heals
|
||||
virtual void HealDone(Unit* /*done_to*/, uint32& /*addhealth*/) {}
|
||||
|
||||
void AttackStartCaster(Unit* victim, float dist);
|
||||
|
||||
void DoAddAuraToAllHostilePlayers(uint32 spellid);
|
||||
void DoCast(uint32 spellId);
|
||||
void DoCast(Unit* victim, uint32 spellId, bool triggered = false);
|
||||
void DoCastToAllHostilePlayers(uint32 spellid, bool triggered = false);
|
||||
void DoCastVictim(uint32 spellId, bool triggered = false);
|
||||
void DoCastAOE(uint32 spellId, bool triggered = false);
|
||||
|
||||
float DoGetSpellMaxRange(uint32 spellId, bool positive = false);
|
||||
|
||||
void DoMeleeAttackIfReady();
|
||||
bool DoSpellAttackIfReady(uint32 spell);
|
||||
|
||||
static AISpellInfoType* AISpellInfo;
|
||||
static void FillAISpellInfo();
|
||||
|
||||
virtual void sGossipHello(Player* /*player*/) {}
|
||||
virtual void sGossipSelect(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/) {}
|
||||
virtual void sGossipSelectCode(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/, char const* /*code*/) {}
|
||||
virtual void sQuestAccept(Player* /*player*/, Quest const* /*quest*/) {}
|
||||
virtual void sQuestSelect(Player* /*player*/, Quest const* /*quest*/) {}
|
||||
virtual void sQuestComplete(Player* /*player*/, Quest const* /*quest*/) {}
|
||||
virtual void sQuestReward(Player* /*player*/, Quest const* /*quest*/, uint32 /*opt*/) {}
|
||||
virtual void sOnGameEvent(bool /*start*/, uint16 /*eventId*/) {}
|
||||
};
|
||||
|
||||
class PlayerAI : public UnitAI
|
||||
{
|
||||
protected:
|
||||
Player* const me;
|
||||
public:
|
||||
explicit PlayerAI(Player* player) : UnitAI((Unit*)player), me(player) {}
|
||||
|
||||
void OnCharmed(bool apply);
|
||||
};
|
||||
|
||||
class SimpleCharmedAI : public PlayerAI
|
||||
{
|
||||
public:
|
||||
void UpdateAI(uint32 diff);
|
||||
SimpleCharmedAI(Player* player): PlayerAI(player) {}
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user