rearranged core files
--HG-- branch : trunk
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "CombatAI.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "Vehicle.h"
|
||||
|
||||
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(const uint32 /*diff*/)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
// some day we will delete these useless things
|
||||
int CombatAI::Permissible(const Creature * /*creature*/)
|
||||
{
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
int ArchorAI::Permissible(const Creature * /*creature*/)
|
||||
{
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
int TurretAI::Permissible(const Creature * /*creature*/)
|
||||
{
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
int AOEAI::Permissible(const Creature * /*creature*/)
|
||||
{
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
int VehicleAI::Permissible(const Creature * /*creature*/)
|
||||
{
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
void CombatAI::InitializeAI()
|
||||
{
|
||||
for (uint32 i = 0; i < CREATURE_MAX_SPELLS; ++i)
|
||||
if (me->m_spells[i] && GetSpellStore()->LookupEntry(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(const uint32 diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
events.Update(diff);
|
||||
|
||||
if (me->hasUnitState(UNIT_STAT_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();
|
||||
|
||||
float 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(const uint32 diff)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
events.Update(diff);
|
||||
|
||||
if (me->hasUnitState(UNIT_STAT_CASTING))
|
||||
return;
|
||||
|
||||
if (uint32 spellId = events.ExecuteEvent())
|
||||
{
|
||||
DoCast(spellId);
|
||||
uint32 casttime = me->GetCurrentSpellCastTime(spellId);
|
||||
events.ScheduleEvent(spellId, (casttime ? casttime : 500) + GetAISpellInfo(spellId)->realCooldown);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////
|
||||
//ArchorAI
|
||||
//////////////
|
||||
|
||||
ArchorAI::ArchorAI(Creature *c) : CreatureAI(c)
|
||||
{
|
||||
if (!me->m_spells[0])
|
||||
sLog.outError("ArchorAI set for creature (entry = %u) with spell1=0. AI will do nothing", me->GetEntry());
|
||||
|
||||
m_minRange = GetSpellMinRange(me->m_spells[0], false);
|
||||
if (!m_minRange)
|
||||
m_minRange = MELEE_RANGE;
|
||||
me->m_CombatDistance = GetSpellMaxRange(me->m_spells[0], false);
|
||||
me->m_SightDistance = me->m_CombatDistance;
|
||||
}
|
||||
|
||||
void ArchorAI::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 ArchorAI::UpdateAI(const 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());
|
||||
|
||||
m_minRange = GetSpellMinRange(me->m_spells[0], false);
|
||||
me->m_CombatDistance = GetSpellMaxRange(me->m_spells[0], false);
|
||||
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(const uint32 /*diff*/)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoSpellAttackIfReady(me->m_spells[0]);
|
||||
}
|
||||
|
||||
//////////////
|
||||
//AOEAI
|
||||
//////////////
|
||||
|
||||
AOEAI::AOEAI(Creature *c) : CreatureAI(c)
|
||||
{
|
||||
if (!me->m_spells[0])
|
||||
sLog.outError("AOEAI set for creature (entry = %u) with spell1=0. AI will do nothing", me->GetEntry());
|
||||
|
||||
me->SetVisibility(VISIBILITY_ON);//visible to see all spell anims
|
||||
me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);//can't be targeted
|
||||
me->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_ATTACKABLE_1);//can't be damaged
|
||||
me->SetDisplayId(11686);//invisible model,around a size of a player
|
||||
}
|
||||
|
||||
bool AOEAI::CanAIAttack(const Unit * /*who*/) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void AOEAI::AttackStart(Unit * /*who*/)
|
||||
{
|
||||
}
|
||||
|
||||
void AOEAI::UpdateAI(const uint32 /*diff*/)
|
||||
{
|
||||
if (!me->HasAura(me->m_spells[0]))
|
||||
me->CastSpell(me, me->m_spells[0],false);
|
||||
}
|
||||
|
||||
//////////////
|
||||
//VehicleAI
|
||||
//////////////
|
||||
|
||||
VehicleAI::VehicleAI(Creature *c) : CreatureAI(c), m_vehicle(c->GetVehicleKit()), m_IsVehicleInUse(false), 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(const uint32 diff)
|
||||
{
|
||||
CheckConditions(diff);
|
||||
|
||||
if (m_DoDismiss)
|
||||
{
|
||||
if (m_DismissTimer < diff)
|
||||
{
|
||||
m_DoDismiss = false;
|
||||
me->SetVisibility(VISIBILITY_OFF);
|
||||
me->ForcedDespawn();
|
||||
}else m_DismissTimer -= diff;
|
||||
}
|
||||
}
|
||||
|
||||
void VehicleAI::Reset()
|
||||
{
|
||||
me->SetVisibility(VISIBILITY_ON);
|
||||
|
||||
m_vehicle->Reset();
|
||||
}
|
||||
|
||||
void VehicleAI::OnCharmed(bool apply)
|
||||
{
|
||||
if (m_IsVehicleInUse && !apply && !conditions.empty())//was used and has conditions
|
||||
{
|
||||
m_DoDismiss = true;//needs reset
|
||||
me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_PLAYER_VEHICLE);
|
||||
me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK);
|
||||
}
|
||||
else if (apply)
|
||||
m_DoDismiss = false;//in use again
|
||||
m_DismissTimer = VEHICLE_DISMISS_TIME;//reset timer
|
||||
m_IsVehicleInUse = apply;
|
||||
}
|
||||
|
||||
void VehicleAI::LoadConditions()
|
||||
{
|
||||
conditions = sConditionMgr.GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, me->GetEntry());
|
||||
if (!conditions.empty())
|
||||
{
|
||||
sLog.outDebug("VehicleAI::LoadConditions: loaded %u conditions", uint32(conditions.size()));
|
||||
}
|
||||
}
|
||||
|
||||
void VehicleAI::CheckConditions(const uint32 diff)
|
||||
{
|
||||
if(m_ConditionsTimer < diff)
|
||||
{
|
||||
if (!conditions.empty())
|
||||
{
|
||||
for (SeatMap::iterator itr = m_vehicle->m_Seats.begin(); itr != m_vehicle->m_Seats.end(); ++itr)
|
||||
if (Unit *passenger = itr->second.passenger)
|
||||
{
|
||||
if (Player* plr = passenger->ToPlayer())
|
||||
{
|
||||
if (!sConditionMgr.IsPlayerMeetToConditions(plr, conditions))
|
||||
{
|
||||
plr->ExitVehicle();
|
||||
return;//check other pessanger in next tick
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_ConditionsTimer = VEHICLE_CONDITION_CHECK_TIME;
|
||||
} else m_ConditionsTimer -= diff;
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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(const 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(const uint32 diff);
|
||||
static int Permissible(const Creature *);
|
||||
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(const uint32 diff);
|
||||
void EnterCombat(Unit * /*who*/);
|
||||
private:
|
||||
float m_attackDist;
|
||||
};
|
||||
|
||||
struct ArchorAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit ArchorAI(Creature *c);
|
||||
void AttackStart(Unit *who);
|
||||
void UpdateAI(const uint32 diff);
|
||||
|
||||
static int Permissible(const Creature *);
|
||||
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(const uint32 diff);
|
||||
|
||||
static int Permissible(const Creature *);
|
||||
protected:
|
||||
float m_minRange;
|
||||
};
|
||||
|
||||
struct AOEAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit AOEAI(Creature *c);
|
||||
bool CanAIAttack(const Unit *who) const;
|
||||
void AttackStart(Unit *who);
|
||||
void UpdateAI(const uint32 diff);
|
||||
|
||||
static int Permissible(const Creature *);
|
||||
};
|
||||
#define VEHICLE_CONDITION_CHECK_TIME 1000
|
||||
#define VEHICLE_DISMISS_TIME 5000
|
||||
struct VehicleAI : public CreatureAI
|
||||
{
|
||||
public:
|
||||
explicit VehicleAI(Creature *c);
|
||||
|
||||
void UpdateAI(const uint32 diff);
|
||||
static int Permissible(const Creature *);
|
||||
void Reset();
|
||||
void MoveInLineOfSight(Unit *) {}
|
||||
void AttackStart(Unit *) {}
|
||||
void OnCharmed(bool apply);
|
||||
|
||||
private:
|
||||
Vehicle* m_vehicle;
|
||||
bool m_IsVehicleInUse;
|
||||
void LoadConditions();
|
||||
void CheckConditions(const uint32 diff);
|
||||
ConditionList conditions;
|
||||
uint32 m_ConditionsTimer;
|
||||
bool m_DoDismiss;
|
||||
uint32 m_DismissTimer;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "CreatureAI.h"
|
||||
#include "CreatureAIImpl.h"
|
||||
#include "Creature.h"
|
||||
#include "World.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "Vehicle.h"
|
||||
|
||||
//Disable CreatureAI when charmed
|
||||
void CreatureAI::OnCharmed(bool /*apply*/)
|
||||
{
|
||||
//me->IsAIEnabled = !apply;*/
|
||||
me->NeedChangeAI = true;
|
||||
me->IsAIEnabled = false;
|
||||
}
|
||||
|
||||
AISpellInfoType * UnitAI::AISpellInfo;
|
||||
AISpellInfoType * GetAISpellInfo(uint32 i) { return &CreatureAI::AISpellInfo[i]; }
|
||||
|
||||
void CreatureAI::DoZoneInCombat(Creature* creature)
|
||||
{
|
||||
if (!creature)
|
||||
creature = me;
|
||||
|
||||
if (!creature->CanHaveThreatList())
|
||||
return;
|
||||
|
||||
Map *map = creature->GetMap();
|
||||
if (!map->IsDungeon()) //use IsDungeon instead of Instanceable, in case battlegrounds will be instantiated
|
||||
{
|
||||
sLog.outError("DoZoneInCombat call for map that isn't an instance (creature entry = %d)", creature->GetTypeId() == TYPEID_UNIT ? creature->ToCreature()->GetEntry() : 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!creature->HasReactState(REACT_PASSIVE) && !creature->getVictim())
|
||||
{
|
||||
if (Unit *target = creature->SelectNearestTarget(50))
|
||||
creature->AI()->AttackStart(target);
|
||||
else if (creature->isSummon())
|
||||
{
|
||||
if (Unit *summoner = creature->ToTempSummon()->GetSummoner())
|
||||
{
|
||||
Unit *target = summoner->getAttackerForHelper();
|
||||
if (!target && summoner->CanHaveThreatList() && !summoner->getThreatManager().isThreatListEmpty())
|
||||
target = summoner->getThreatManager().getHostilTarget();
|
||||
if (target && (creature->IsFriendlyTo(summoner) || creature->IsHostileTo(target)))
|
||||
creature->AI()->AttackStart(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!creature->HasReactState(REACT_PASSIVE) && !creature->getVictim())
|
||||
{
|
||||
sLog.outError("DoZoneInCombat called for creature that has empty threat list (creature entry = %u)", creature->GetEntry());
|
||||
return;
|
||||
}
|
||||
|
||||
Map::PlayerList const &PlList = map->GetPlayers();
|
||||
|
||||
if (PlList.isEmpty())
|
||||
return;
|
||||
|
||||
for (Map::PlayerList::const_iterator i = PlList.begin(); i != PlList.end(); ++i)
|
||||
{
|
||||
if (Player* pPlayer = i->getSource())
|
||||
{
|
||||
if (pPlayer->isGameMaster())
|
||||
continue;
|
||||
|
||||
if (pPlayer->isAlive())
|
||||
{
|
||||
creature->SetInCombatWith(pPlayer);
|
||||
pPlayer->SetInCombatWith(creature);
|
||||
creature->AddThreat(pPlayer, 0.0f);
|
||||
}
|
||||
|
||||
/* Causes certain things to never leave the threat list (Priest Lightwell, etc):
|
||||
for (Unit::ControlList::const_iterator itr = pPlayer->m_Controlled.begin(); itr != pPlayer->m_Controlled.end(); ++itr)
|
||||
{
|
||||
creature->SetInCombatWith(*itr);
|
||||
(*itr)->SetInCombatWith(creature);
|
||||
creature->AddThreat(*itr, 0.0f);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scripts does not take care about MoveInLineOfSight loops
|
||||
// MoveInLineOfSight can be called inside another MoveInLineOfSight and cause stack overflow
|
||||
void CreatureAI::MoveInLineOfSight_Safe(Unit *who)
|
||||
{
|
||||
if (m_MoveInLineOfSight_locked == true)
|
||||
return;
|
||||
m_MoveInLineOfSight_locked = true;
|
||||
MoveInLineOfSight(who);
|
||||
m_MoveInLineOfSight_locked = false;
|
||||
}
|
||||
|
||||
void CreatureAI::MoveInLineOfSight(Unit *who)
|
||||
{
|
||||
if (me->getVictim())
|
||||
return;
|
||||
|
||||
if (me->GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET) // non-combat pets should just stand there and look good;)
|
||||
return;
|
||||
|
||||
if (me->canStartAttack(who, false))
|
||||
AttackStart(who);
|
||||
//else if (who->getVictim() && me->IsFriendlyTo(who)
|
||||
// && me->IsWithinDistInMap(who, sWorld.getConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_RADIUS))
|
||||
// && me->canStartAttack(who->getVictim(), true)) // TODO: if we use true, it will not attack it when it arrives
|
||||
// me->GetMotionMaster()->MoveChase(who->getVictim());
|
||||
}
|
||||
|
||||
void CreatureAI::EnterEvadeMode()
|
||||
{
|
||||
if (!_EnterEvadeMode())
|
||||
return;
|
||||
|
||||
sLog.outDebug("Creature %u enters evade mode.", me->GetEntry());
|
||||
|
||||
if (!me->GetVehicle()) // otherwise me will be in evade mode forever
|
||||
{
|
||||
if (Unit *owner = me->GetCharmerOrOwner())
|
||||
{
|
||||
me->GetMotionMaster()->Clear(false);
|
||||
me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle(), MOTION_SLOT_ACTIVE);
|
||||
}
|
||||
else
|
||||
me->GetMotionMaster()->MoveTargetedHome();
|
||||
}
|
||||
|
||||
Reset();
|
||||
|
||||
if (me->IsVehicle()) // use the same sequence of addtoworld, aireset may remove all summons!
|
||||
me->GetVehicleKit()->Reset();
|
||||
}
|
||||
|
||||
/*void CreatureAI::AttackedBy(Unit* attacker)
|
||||
{
|
||||
if (!me->getVictim())
|
||||
AttackStart(attacker);
|
||||
}*/
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef TRINITY_CREATUREAI_H
|
||||
#define TRINITY_CREATUREAI_H
|
||||
|
||||
#include "UnitAI.h"
|
||||
#include "Common.h"
|
||||
|
||||
class WorldObject;
|
||||
class Unit;
|
||||
class Creature;
|
||||
class Player;
|
||||
struct SpellEntry;
|
||||
|
||||
#define TIME_INTERVAL_LOOK 5000
|
||||
#define VISIBILITY_RANGE 10000
|
||||
|
||||
//Spell targets used by SelectSpell
|
||||
enum SelectTargetType
|
||||
{
|
||||
SELECT_TARGET_DONTCARE = 0, //All target types allowed
|
||||
|
||||
SELECT_TARGET_SELF, //Only Self casting
|
||||
|
||||
SELECT_TARGET_SINGLE_ENEMY, //Only Single Enemy
|
||||
SELECT_TARGET_AOE_ENEMY, //Only AoE Enemy
|
||||
SELECT_TARGET_ANY_ENEMY, //AoE or Single Enemy
|
||||
|
||||
SELECT_TARGET_SINGLE_FRIEND, //Only Single Friend
|
||||
SELECT_TARGET_AOE_FRIEND, //Only AoE Friend
|
||||
SELECT_TARGET_ANY_FRIEND, //AoE or Single Friend
|
||||
};
|
||||
|
||||
//Spell Effects used by SelectSpell
|
||||
enum SelectEffect
|
||||
{
|
||||
SELECT_EFFECT_DONTCARE = 0, //All spell effects allowed
|
||||
SELECT_EFFECT_DAMAGE, //Spell does damage
|
||||
SELECT_EFFECT_HEALING, //Spell does healing
|
||||
SELECT_EFFECT_AURA, //Spell applies an aura
|
||||
};
|
||||
|
||||
enum SCEquip
|
||||
{
|
||||
EQUIP_NO_CHANGE = -1,
|
||||
EQUIP_UNEQUIP = 0
|
||||
};
|
||||
|
||||
class CreatureAI : public UnitAI
|
||||
{
|
||||
protected:
|
||||
Creature * const me;
|
||||
|
||||
bool UpdateVictim();
|
||||
bool UpdateVictimWithGaze();
|
||||
bool UpdateCombatState();
|
||||
|
||||
void SetGazeOn(Unit *target);
|
||||
|
||||
Creature *DoSummon(uint32 uiEntry, const Position &pos, uint32 uiDespawntime = 30000, TempSummonType uiType = TEMPSUMMON_CORPSE_TIMED_DESPAWN);
|
||||
Creature *DoSummon(uint32 uiEntry, WorldObject *obj, float fRadius = 5.0f, uint32 uiDespawntime = 30000, TempSummonType uiType = TEMPSUMMON_CORPSE_TIMED_DESPAWN);
|
||||
Creature *DoSummonFlyer(uint32 uiEntry, WorldObject *obj, float fZ, float fRadius = 5.0f, uint32 uiDespawntime = 30000, TempSummonType uiType = TEMPSUMMON_CORPSE_TIMED_DESPAWN);
|
||||
|
||||
public:
|
||||
explicit CreatureAI(Creature *c) : UnitAI((Unit*)c), me(c), m_MoveInLineOfSight_locked(false) {}
|
||||
|
||||
virtual ~CreatureAI() {}
|
||||
|
||||
/// == Reactions At =================================
|
||||
|
||||
// Called if IsVisible(Unit *who) is true at each *who move, reaction at visibility zone enter
|
||||
void MoveInLineOfSight_Safe(Unit *who);
|
||||
|
||||
// Called for reaction at stopping attack at no attackers or targets
|
||||
virtual void EnterEvadeMode();
|
||||
|
||||
// Called for reaction at enter to combat if not in combat yet (enemy can be NULL)
|
||||
virtual void EnterCombat(Unit* /*enemy*/) {}
|
||||
|
||||
// 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 * /*done_by*/, uint32 & /*damage*/) {}
|
||||
|
||||
// Called when the creature is killed
|
||||
virtual void JustDied(Unit *) {}
|
||||
|
||||
// Called when the creature kills a unit
|
||||
virtual void KilledUnit(Unit *) {}
|
||||
|
||||
// Called when the creature summon successfully other creature
|
||||
virtual void JustSummoned(Creature*) {}
|
||||
virtual void IsSummonedBy(Unit * /*summoner*/) {}
|
||||
|
||||
virtual void SummonedCreatureDespawn(Creature* /*unit*/) {}
|
||||
|
||||
// Called when hit by a spell
|
||||
virtual void SpellHit(Unit*, const SpellEntry*) {}
|
||||
|
||||
// Called when spell hits a target
|
||||
virtual void SpellHitTarget(Unit* /*target*/, const SpellEntry*) {}
|
||||
|
||||
// Called to get trigger target for aura effect
|
||||
virtual Unit * GetAuraEffectTriggerTarget(uint32 /*spellId*/, uint8 /*effIndex*/) {return NULL;}
|
||||
|
||||
// Called when the creature is target of hostile action: swing, hostile spell landed, fear/etc)
|
||||
//virtual void AttackedBy(Unit* attacker);
|
||||
virtual bool IsEscorted() { return false; }
|
||||
|
||||
// Called when creature is spawned or respawned (for reseting variables)
|
||||
virtual void JustRespawned() { Reset(); }
|
||||
|
||||
// Called at waypoint reached or point movement finished
|
||||
virtual void MovementInform(uint32 /*MovementType*/, uint32 /*Data*/) {}
|
||||
|
||||
void OnCharmed(bool apply);
|
||||
|
||||
//virtual void SpellClick(Player *player) {}
|
||||
|
||||
// Called at reaching home after evade
|
||||
virtual void JustReachedHome() {}
|
||||
|
||||
void DoZoneInCombat(Creature* pUnit = NULL);
|
||||
|
||||
// Called at text emote receive from player
|
||||
virtual void ReceiveEmote(Player* /*pPlayer*/, uint32 /*text_emote*/) {}
|
||||
|
||||
/// == Triggered Actions Requested ==================
|
||||
|
||||
// Called when creature attack expected (if creature can and no have current victim)
|
||||
// Note: for reaction at hostile action must be called AttackedBy function.
|
||||
//virtual void AttackStart(Unit *) {}
|
||||
|
||||
// Called at World update tick
|
||||
//virtual void UpdateAI(const uint32 /*diff*/) {}
|
||||
|
||||
/// == State checks =================================
|
||||
|
||||
// Is unit visible for MoveInLineOfSight
|
||||
//virtual bool IsVisible(Unit *) const { return false; }
|
||||
|
||||
// called when the corpse of this creature gets removed
|
||||
virtual void CorpseRemoved(uint32 & /*respawnDelay*/) {}
|
||||
|
||||
// Called when victim entered water and creature can not enter water
|
||||
//virtual bool canReachByRangeAttack(Unit*) { return false; }
|
||||
|
||||
/// == Fields =======================================
|
||||
|
||||
// Pointer to controlled by AI creature
|
||||
//Creature* const me;
|
||||
|
||||
virtual void PassengerBoarded(Unit * /*who*/, int8 /*seatId*/, bool /*apply*/) {}
|
||||
|
||||
protected:
|
||||
virtual void MoveInLineOfSight(Unit *);
|
||||
|
||||
bool _EnterEvadeMode();
|
||||
|
||||
private:
|
||||
bool m_MoveInLineOfSight_locked;
|
||||
};
|
||||
|
||||
enum Permitions
|
||||
{
|
||||
PERMIT_BASE_NO = -1,
|
||||
PERMIT_BASE_IDLE = 1,
|
||||
PERMIT_BASE_REACTIVE = 100,
|
||||
PERMIT_BASE_PROACTIVE = 200,
|
||||
PERMIT_BASE_FACTION_SPECIFIC = 400,
|
||||
PERMIT_BASE_SPECIAL = 800
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#ifndef TRINITY_CREATUREAIFACTORY_H
|
||||
#define TRINITY_CREATUREAIFACTORY_H
|
||||
|
||||
//#include "Policies/Singleton.h"
|
||||
#include "Dynamic/ObjectRegistry.h"
|
||||
#include "Dynamic/FactoryHolder.h"
|
||||
|
||||
struct SelectableAI : public FactoryHolder<CreatureAI>, public Permissible<Creature>
|
||||
{
|
||||
SelectableAI(const char *id) : FactoryHolder<CreatureAI>(id) {}
|
||||
};
|
||||
|
||||
template<class REAL_AI>
|
||||
struct CreatureAIFactory : public SelectableAI
|
||||
{
|
||||
CreatureAIFactory(const char *name) : SelectableAI(name) {}
|
||||
|
||||
CreatureAI* Create(void *) const;
|
||||
|
||||
int Permit(const Creature *c) const { return REAL_AI::Permissible(c); }
|
||||
};
|
||||
|
||||
template<class REAL_AI>
|
||||
inline CreatureAI*
|
||||
CreatureAIFactory<REAL_AI>::Create(void *data) const
|
||||
{
|
||||
Creature* creature = reinterpret_cast<Creature *>(data);
|
||||
return (new REAL_AI(creature));
|
||||
}
|
||||
|
||||
typedef FactoryHolder<CreatureAI> CreatureAICreator;
|
||||
typedef FactoryHolder<CreatureAI>::FactoryHolderRegistry CreatureAIRegistry;
|
||||
typedef FactoryHolder<CreatureAI>::FactoryHolderRepository CreatureAIRepository;
|
||||
#endif
|
||||
@@ -0,0 +1,638 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
#ifndef CREATUREAIIMPL_H
|
||||
#define CREATUREAIIMPL_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "Platform/Define.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "SpellMgr.h"
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2)
|
||||
{
|
||||
return (urand(0,1)) ? v1 : v2;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3)
|
||||
{
|
||||
switch (urand(0,2))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4)
|
||||
{
|
||||
switch (urand(0,3))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5)
|
||||
{
|
||||
switch (urand(0,4))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6)
|
||||
{
|
||||
switch (urand(0,5))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7)
|
||||
{
|
||||
switch (urand(0,6))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8)
|
||||
{
|
||||
switch (urand(0,7))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
|
||||
const T& v9)
|
||||
{
|
||||
switch (urand(0,8))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
case 8: return v9;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
|
||||
const T& v9, const T& v10)
|
||||
{
|
||||
switch (urand(0,9))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
case 8: return v9;
|
||||
case 9: return v10;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
|
||||
const T& v9, const T& v10, const T& v11)
|
||||
{
|
||||
switch (urand(0,10))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
case 8: return v9;
|
||||
case 9: return v10;
|
||||
case 10: return v11;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
|
||||
const T& v9, const T& v10, const T& v11, const T& v12)
|
||||
{
|
||||
switch (urand(0,11))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
case 8: return v9;
|
||||
case 9: return v10;
|
||||
case 10: return v11;
|
||||
case 11: return v12;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
|
||||
const T& v9, const T& v10, const T& v11, const T& v12, const T& v13)
|
||||
{
|
||||
switch (urand(0,12))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
case 8: return v9;
|
||||
case 9: return v10;
|
||||
case 10: return v11;
|
||||
case 11: return v12;
|
||||
case 12: return v13;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
|
||||
const T& v9, const T& v10, const T& v11, const T& v12, const T& v13, const T& v14)
|
||||
{
|
||||
switch (urand(0,13))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
case 8: return v9;
|
||||
case 9: return v10;
|
||||
case 10: return v11;
|
||||
case 11: return v12;
|
||||
case 12: return v13;
|
||||
case 13: return v14;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
|
||||
const T& v9, const T& v10, const T& v11, const T& v12, const T& v13, const T& v14, const T& v15)
|
||||
{
|
||||
switch (urand(0,14))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
case 8: return v9;
|
||||
case 9: return v10;
|
||||
case 10: return v11;
|
||||
case 11: return v12;
|
||||
case 12: return v13;
|
||||
case 13: return v14;
|
||||
case 14: return v15;
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline
|
||||
const T& RAND(const T& v1, const T& v2, const T& v3, const T& v4, const T& v5, const T& v6, const T& v7, const T& v8,
|
||||
const T& v9, const T& v10, const T& v11, const T& v12, const T& v13, const T& v14, const T& v15, const T& v16)
|
||||
{
|
||||
switch (urand(0,15))
|
||||
{
|
||||
default:
|
||||
case 0: return v1;
|
||||
case 1: return v2;
|
||||
case 2: return v3;
|
||||
case 3: return v4;
|
||||
case 4: return v5;
|
||||
case 5: return v6;
|
||||
case 6: return v7;
|
||||
case 7: return v8;
|
||||
case 8: return v9;
|
||||
case 9: return v10;
|
||||
case 10: return v11;
|
||||
case 11: return v12;
|
||||
case 12: return v13;
|
||||
case 13: return v14;
|
||||
case 14: return v15;
|
||||
case 15: return v16;
|
||||
}
|
||||
}
|
||||
|
||||
class EventMap : private std::map<uint32, uint32>
|
||||
{
|
||||
private:
|
||||
uint32 m_time, m_phase;
|
||||
public:
|
||||
explicit EventMap() : m_phase(0), m_time(0) {}
|
||||
|
||||
uint32 GetTimer() const { return m_time; }
|
||||
|
||||
void Reset() { clear(); m_time = 0; m_phase = 0; }
|
||||
|
||||
void Update(uint32 time) { m_time += time; }
|
||||
|
||||
void SetPhase(uint32 phase)
|
||||
{
|
||||
if (phase && phase < 9)
|
||||
m_phase = (1 << (phase + 24));
|
||||
}
|
||||
|
||||
void ScheduleEvent(uint32 eventId, uint32 time, uint32 gcd = 0, uint32 phase = 0)
|
||||
{
|
||||
time += m_time;
|
||||
if (gcd && gcd < 9)
|
||||
eventId |= (1 << (gcd + 16));
|
||||
if (phase && phase < 9)
|
||||
eventId |= (1 << (phase + 24));
|
||||
iterator itr = find(time);
|
||||
while (itr != end())
|
||||
{
|
||||
++time;
|
||||
itr = find(time);
|
||||
}
|
||||
insert(std::make_pair(time, eventId));
|
||||
}
|
||||
|
||||
void RescheduleEvent(uint32 eventId, uint32 time, uint32 gcd = 0, uint32 phase = 0)
|
||||
{
|
||||
CancelEvent(eventId);
|
||||
ScheduleEvent(eventId, time, gcd, phase);
|
||||
}
|
||||
|
||||
void RepeatEvent(uint32 time)
|
||||
{
|
||||
if (empty())
|
||||
return;
|
||||
uint32 eventId = begin()->second;
|
||||
erase(begin());
|
||||
time += m_time;
|
||||
iterator itr = find(time);
|
||||
while (itr != end())
|
||||
{
|
||||
++time;
|
||||
itr = find(time);
|
||||
}
|
||||
insert(std::make_pair(time, eventId));
|
||||
}
|
||||
|
||||
void PopEvent()
|
||||
{
|
||||
erase(begin());
|
||||
}
|
||||
|
||||
uint32 ExecuteEvent()
|
||||
{
|
||||
while (!empty())
|
||||
{
|
||||
if (begin()->first > m_time)
|
||||
return 0;
|
||||
else if (m_phase && (begin()->second & 0xFF000000) && !(begin()->second & m_phase))
|
||||
erase(begin());
|
||||
else
|
||||
{
|
||||
uint32 eventId = (begin()->second & 0x0000FFFF);
|
||||
erase(begin());
|
||||
return eventId;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 GetEvent()
|
||||
{
|
||||
while (!empty())
|
||||
{
|
||||
if (begin()->first > m_time)
|
||||
return 0;
|
||||
else if (m_phase && (begin()->second & 0xFF000000) && !(begin()->second & m_phase))
|
||||
erase(begin());
|
||||
else
|
||||
return (begin()->second & 0x0000FFFF);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Delay all events
|
||||
void DelayEvents(uint32 delay)
|
||||
{
|
||||
if (delay < m_time)
|
||||
m_time -= delay;
|
||||
else
|
||||
m_time = 0;
|
||||
}
|
||||
|
||||
// Delay all events having the specified Global Cooldown.
|
||||
void DelayEvents(uint32 delay, uint32 gcd)
|
||||
{
|
||||
uint32 nextTime = m_time + delay;
|
||||
gcd = (1 << (gcd + 16));
|
||||
for (iterator itr = begin(); itr != end() && itr->first < nextTime;)
|
||||
{
|
||||
if (itr->second & gcd)
|
||||
{
|
||||
ScheduleEvent(itr->second, itr->first-m_time+delay);
|
||||
erase(itr);
|
||||
itr = begin();
|
||||
}
|
||||
else
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
|
||||
void CancelEvent(uint32 eventId)
|
||||
{
|
||||
for (iterator itr = begin(); itr != end();)
|
||||
{
|
||||
if (eventId == (itr->second & 0x0000FFFF))
|
||||
{
|
||||
erase(itr);
|
||||
itr = begin();
|
||||
}
|
||||
else
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
|
||||
void CancelEventsByGCD(uint32 gcd)
|
||||
{
|
||||
gcd = (1 << (gcd + 16));
|
||||
|
||||
for (iterator itr = begin(); itr != end();)
|
||||
{
|
||||
if (itr->second & gcd)
|
||||
{
|
||||
erase(itr);
|
||||
itr = begin();
|
||||
}
|
||||
else
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
enum AITarget
|
||||
{
|
||||
AITARGET_SELF,
|
||||
AITARGET_VICTIM,
|
||||
AITARGET_ENEMY,
|
||||
AITARGET_ALLY,
|
||||
AITARGET_BUFF,
|
||||
AITARGET_DEBUFF,
|
||||
};
|
||||
|
||||
enum AICondition
|
||||
{
|
||||
AICOND_AGGRO,
|
||||
AICOND_COMBAT,
|
||||
AICOND_DIE,
|
||||
};
|
||||
|
||||
#define AI_DEFAULT_COOLDOWN 5000
|
||||
|
||||
struct AISpellInfoType
|
||||
{
|
||||
AISpellInfoType() : target(AITARGET_SELF), condition(AICOND_COMBAT)
|
||||
, cooldown(AI_DEFAULT_COOLDOWN), realCooldown(0), maxRange(0.0f){}
|
||||
AITarget target;
|
||||
AICondition condition;
|
||||
uint32 cooldown;
|
||||
uint32 realCooldown;
|
||||
float maxRange;
|
||||
};
|
||||
|
||||
AISpellInfoType * GetAISpellInfo(uint32 i);
|
||||
|
||||
inline void CreatureAI::SetGazeOn(Unit *target)
|
||||
{
|
||||
if (me->canAttack(target))
|
||||
{
|
||||
AttackStart(target);
|
||||
me->SetReactState(REACT_PASSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool CreatureAI::UpdateVictimWithGaze()
|
||||
{
|
||||
if (!me->isInCombat())
|
||||
return false;
|
||||
|
||||
if (me->HasReactState(REACT_PASSIVE))
|
||||
{
|
||||
if (me->getVictim())
|
||||
return true;
|
||||
else
|
||||
me->SetReactState(REACT_AGGRESSIVE);
|
||||
}
|
||||
|
||||
if (Unit *victim = me->SelectVictim())
|
||||
AttackStart(victim);
|
||||
return me->getVictim();
|
||||
}
|
||||
|
||||
inline bool CreatureAI::UpdateCombatState()
|
||||
{
|
||||
if (!me->isInCombat())
|
||||
return false;
|
||||
|
||||
if (!me->HasReactState(REACT_PASSIVE))
|
||||
{
|
||||
if (Unit *victim = me->SelectVictim())
|
||||
AttackStart(victim);
|
||||
return me->getVictim();
|
||||
}
|
||||
else if (me->getThreatManager().isThreatListEmpty())
|
||||
{
|
||||
EnterEvadeMode();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool CreatureAI::UpdateVictim()
|
||||
{
|
||||
if (!me->isInCombat())
|
||||
return false;
|
||||
|
||||
if (!me->HasReactState(REACT_PASSIVE))
|
||||
{
|
||||
if (Unit *victim = me->SelectVictim())
|
||||
AttackStart(victim);
|
||||
return me->getVictim();
|
||||
}
|
||||
else if (me->getThreatManager().isThreatListEmpty())
|
||||
{
|
||||
EnterEvadeMode();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
inline bool CreatureAI::UpdateVictim()
|
||||
{
|
||||
if (!me->isInCombat())
|
||||
return false;
|
||||
if (Unit *victim = me->SelectVictim())
|
||||
AttackStart(victim);
|
||||
return me->getVictim();
|
||||
}
|
||||
*/
|
||||
|
||||
inline bool CreatureAI::_EnterEvadeMode()
|
||||
{
|
||||
if (!me->isAlive())
|
||||
return false;
|
||||
|
||||
// sometimes bosses stuck in combat?
|
||||
me->RemoveAllAuras();
|
||||
me->DeleteThreatList();
|
||||
me->CombatStop(true);
|
||||
me->LoadCreaturesAddon();
|
||||
me->SetLootRecipient(NULL);
|
||||
me->ResetPlayerDamageReq();
|
||||
|
||||
if (me->IsInEvadeMode())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void UnitAI::DoCast(Unit* victim, uint32 spellId, bool triggered)
|
||||
{
|
||||
if (!victim || (me->hasUnitState(UNIT_STAT_CASTING) && !triggered))
|
||||
return;
|
||||
|
||||
me->CastSpell(victim, spellId, triggered);
|
||||
}
|
||||
|
||||
inline void UnitAI::DoCastVictim(uint32 spellId, bool triggered)
|
||||
{
|
||||
me->CastSpell(me->getVictim(), spellId, triggered);
|
||||
}
|
||||
|
||||
inline void UnitAI::DoCastAOE(uint32 spellId, bool triggered)
|
||||
{
|
||||
if (!triggered && me->hasUnitState(UNIT_STAT_CASTING))
|
||||
return;
|
||||
|
||||
me->CastSpell((Unit*)NULL, spellId, triggered);
|
||||
}
|
||||
|
||||
inline Creature *CreatureAI::DoSummon(uint32 uiEntry, const Position &pos, uint32 uiDespawntime, TempSummonType uiType)
|
||||
{
|
||||
return me->SummonCreature(uiEntry, pos, uiType, uiDespawntime);
|
||||
}
|
||||
|
||||
inline Creature *CreatureAI::DoSummon(uint32 uiEntry, WorldObject* obj, float fRadius, uint32 uiDespawntime, TempSummonType uiType)
|
||||
{
|
||||
Position pos;
|
||||
obj->GetRandomNearPosition(pos, fRadius);
|
||||
return me->SummonCreature(uiEntry, pos, uiType, uiDespawntime);
|
||||
}
|
||||
|
||||
inline Creature *CreatureAI::DoSummonFlyer(uint32 uiEntry, WorldObject *obj, float _fZ, float fRadius, uint32 uiDespawntime, TempSummonType uiType)
|
||||
{
|
||||
Position pos;
|
||||
obj->GetRandomNearPosition(pos, fRadius);
|
||||
pos.m_positionZ += _fZ;
|
||||
return me->SummonCreature(uiEntry, pos, uiType, uiDespawntime);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "PassiveAI.h"
|
||||
#include "ReactorAI.h"
|
||||
#include "CombatAI.h"
|
||||
#include "GuardAI.h"
|
||||
#include "PetAI.h"
|
||||
#include "TotemAI.h"
|
||||
#include "CreatureEventAI.h"
|
||||
#include "RandomMovementGenerator.h"
|
||||
#include "MovementGeneratorImpl.h"
|
||||
#include "CreatureAIRegistry.h"
|
||||
#include "WaypointMovementGenerator.h"
|
||||
#include "CreatureAIFactory.h"
|
||||
|
||||
//#include "CreatureAIImpl.h"
|
||||
namespace AIRegistry
|
||||
{
|
||||
void Initialize()
|
||||
{
|
||||
(new CreatureAIFactory<NullCreatureAI>("NullCreatureAI"))->RegisterSelf();
|
||||
(new CreatureAIFactory<TriggerAI>("TriggerAI"))->RegisterSelf();
|
||||
(new CreatureAIFactory<AggressorAI>("AggressorAI"))->RegisterSelf();
|
||||
(new CreatureAIFactory<ReactorAI>("ReactorAI"))->RegisterSelf();
|
||||
(new CreatureAIFactory<PassiveAI>("PassiveAI"))->RegisterSelf();
|
||||
(new CreatureAIFactory<CritterAI>("CritterAI"))->RegisterSelf();
|
||||
(new CreatureAIFactory<GuardAI>("GuardAI"))->RegisterSelf();
|
||||
(new CreatureAIFactory<PetAI>("PetAI"))->RegisterSelf();
|
||||
(new CreatureAIFactory<TotemAI>("TotemAI"))->RegisterSelf();
|
||||
(new CreatureAIFactory<CombatAI>("CombatAI"))->RegisterSelf();
|
||||
(new CreatureAIFactory<ArchorAI>("ArchorAI"))->RegisterSelf();
|
||||
(new CreatureAIFactory<TurretAI>("TurretAI"))->RegisterSelf();
|
||||
(new CreatureAIFactory<CreatureEventAI>("EventAI"))->RegisterSelf();
|
||||
(new CreatureAIFactory<AOEAI>("AOEAI"))->RegisterSelf();
|
||||
(new CreatureAIFactory<VehicleAI>("VehicleAI"))->RegisterSelf();
|
||||
|
||||
(new MovementGeneratorFactory<RandomMovementGenerator<Creature> >(RANDOM_MOTION_TYPE))->RegisterSelf();
|
||||
(new MovementGeneratorFactory<WaypointMovementGenerator<Creature> >(WAYPOINT_MOTION_TYPE))->RegisterSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef TRINITY_CREATUREAIREGISTRY_H
|
||||
#define TRINITY_CREATUREAIREGISTRY_H
|
||||
|
||||
namespace AIRegistry
|
||||
{
|
||||
void Initialize(void);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "Creature.h"
|
||||
#include "CreatureAISelector.h"
|
||||
#include "PassiveAI.h"
|
||||
#include "Policies/SingletonImp.h"
|
||||
#include "MovementGenerator.h"
|
||||
#include "Pet.h"
|
||||
#include "TemporarySummon.h"
|
||||
#include "CreatureAIFactory.h"
|
||||
#include "ScriptMgr.h"
|
||||
|
||||
INSTANTIATE_SINGLETON_1(CreatureAIRegistry);
|
||||
INSTANTIATE_SINGLETON_1(MovementGeneratorRegistry);
|
||||
|
||||
namespace FactorySelector
|
||||
{
|
||||
CreatureAI* selectAI(Creature *creature)
|
||||
{
|
||||
const CreatureAICreator *ai_factory = NULL;
|
||||
CreatureAIRegistry &ai_registry(CreatureAIRepository::Instance());
|
||||
|
||||
if (creature->isPet())
|
||||
ai_factory = ai_registry.GetRegistryItem("PetAI");
|
||||
|
||||
//scriptname in db
|
||||
if (!ai_factory)
|
||||
if (CreatureAI* scriptedAI = sScriptMgr.GetAI(creature))
|
||||
return scriptedAI;
|
||||
|
||||
// AIname in db
|
||||
std::string ainame=creature->GetAIName();
|
||||
if (!ai_factory && !ainame.empty())
|
||||
ai_factory = ai_registry.GetRegistryItem(ainame.c_str());
|
||||
|
||||
// select by NPC flags
|
||||
if (!ai_factory)
|
||||
{
|
||||
if (creature->IsVehicle())
|
||||
ai_factory = ai_registry.GetRegistryItem("VehicleAI");
|
||||
else if (creature->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN) && ((Guardian*)creature)->GetOwner()->GetTypeId() == TYPEID_PLAYER)
|
||||
ai_factory = ai_registry.GetRegistryItem("PetAI");
|
||||
else if (creature->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK))
|
||||
ai_factory = ai_registry.GetRegistryItem("NullCreatureAI");
|
||||
else if (creature->isGuard())
|
||||
ai_factory = ai_registry.GetRegistryItem("GuardAI");
|
||||
else if (creature->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN))
|
||||
ai_factory = ai_registry.GetRegistryItem("PetAI");
|
||||
else if (creature->isTotem())
|
||||
ai_factory = ai_registry.GetRegistryItem("TotemAI");
|
||||
else if (creature->isTrigger())
|
||||
{
|
||||
if (creature->m_spells[0])
|
||||
ai_factory = ai_registry.GetRegistryItem("TriggerAI");
|
||||
else
|
||||
ai_factory = ai_registry.GetRegistryItem("NullCreatureAI");
|
||||
}
|
||||
else if (creature->GetCreatureType() == CREATURE_TYPE_CRITTER && !creature->HasUnitTypeMask(UNIT_MASK_GUARDIAN))
|
||||
ai_factory = ai_registry.GetRegistryItem("CritterAI");
|
||||
}
|
||||
|
||||
// select by permit check
|
||||
if (!ai_factory)
|
||||
{
|
||||
int best_val = -1;
|
||||
typedef CreatureAIRegistry::RegistryMapType RMT;
|
||||
RMT const &l = ai_registry.GetRegisteredItems();
|
||||
for (RMT::const_iterator iter = l.begin(); iter != l.end(); ++iter)
|
||||
{
|
||||
const CreatureAICreator *factory = iter->second;
|
||||
const SelectableAI *p = dynamic_cast<const SelectableAI *>(factory);
|
||||
assert(p != NULL);
|
||||
int val = p->Permit(creature);
|
||||
if (val > best_val)
|
||||
{
|
||||
best_val = val;
|
||||
ai_factory = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// select NullCreatureAI if not another cases
|
||||
ainame = (ai_factory == NULL) ? "NullCreatureAI" : ai_factory->key();
|
||||
|
||||
DEBUG_LOG("Creature %u used AI is %s.", creature->GetGUIDLow(), ainame.c_str());
|
||||
return (ai_factory == NULL ? new NullCreatureAI(creature) : ai_factory->Create(creature));
|
||||
}
|
||||
|
||||
MovementGenerator* selectMovementGenerator(Creature *creature)
|
||||
{
|
||||
MovementGeneratorRegistry &mv_registry(MovementGeneratorRepository::Instance());
|
||||
assert(creature->GetCreatureInfo() != NULL);
|
||||
const MovementGeneratorCreator *mv_factory = mv_registry.GetRegistryItem(creature->GetDefaultMovementType());
|
||||
|
||||
/* if (mv_factory == NULL)
|
||||
{
|
||||
int best_val = -1;
|
||||
std::vector<std::string> l;
|
||||
mv_registry.GetRegisteredItems(l);
|
||||
for (std::vector<std::string>::iterator iter = l.begin(); iter != l.end(); ++iter)
|
||||
{
|
||||
const MovementGeneratorCreator *factory = mv_registry.GetRegistryItem((*iter).c_str());
|
||||
const SelectableMovement *p = dynamic_cast<const SelectableMovement *>(factory);
|
||||
assert(p != NULL);
|
||||
int val = p->Permit(creature);
|
||||
if (val > best_val)
|
||||
{
|
||||
best_val = val;
|
||||
mv_factory = p;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
return (mv_factory == NULL ? NULL : mv_factory->Create(creature));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef TRINITY_CREATUREAISELECTOR_H
|
||||
#define TRINITY_CREATUREAISELECTOR_H
|
||||
|
||||
class CreatureAI;
|
||||
class Creature;
|
||||
class MovementGenerator;
|
||||
|
||||
namespace FactorySelector
|
||||
{
|
||||
CreatureAI* selectAI(Creature *);
|
||||
MovementGenerator* selectMovementGenerator(Creature *);
|
||||
}
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,637 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef TRINITY_CREATURE_EAI_H
|
||||
#define TRINITY_CREATURE_EAI_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "Creature.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "Unit.h"
|
||||
|
||||
class Player;
|
||||
class WorldObject;
|
||||
|
||||
#define EVENT_UPDATE_TIME 500
|
||||
#define MAX_ACTIONS 3
|
||||
#define MAX_PHASE 32
|
||||
|
||||
enum EventAI_Type
|
||||
{
|
||||
EVENT_T_TIMER = 0, // InitialMin, InitialMax, RepeatMin, RepeatMax
|
||||
EVENT_T_TIMER_OOC = 1, // InitialMin, InitialMax, RepeatMin, RepeatMax
|
||||
EVENT_T_HP = 2, // HPMax%, HPMin%, RepeatMin, RepeatMax
|
||||
EVENT_T_MANA = 3, // ManaMax%,ManaMin% RepeatMin, RepeatMax
|
||||
EVENT_T_AGGRO = 4, // NONE
|
||||
EVENT_T_KILL = 5, // RepeatMin, RepeatMax
|
||||
EVENT_T_DEATH = 6, // NONE
|
||||
EVENT_T_EVADE = 7, // NONE
|
||||
EVENT_T_SPELLHIT = 8, // SpellID, School, RepeatMin, RepeatMax
|
||||
EVENT_T_RANGE = 9, // MinDist, MaxDist, RepeatMin, RepeatMax
|
||||
EVENT_T_OOC_LOS = 10, // NoHostile, MaxRnage, RepeatMin, RepeatMax
|
||||
EVENT_T_SPAWNED = 11, // Condition, CondValue1
|
||||
EVENT_T_TARGET_HP = 12, // HPMax%, HPMin%, RepeatMin, RepeatMax
|
||||
EVENT_T_TARGET_CASTING = 13, // RepeatMin, RepeatMax
|
||||
EVENT_T_FRIENDLY_HP = 14, // HPDeficit, Radius, RepeatMin, RepeatMax
|
||||
EVENT_T_FRIENDLY_IS_CC = 15, // DispelType, Radius, RepeatMin, RepeatMax
|
||||
EVENT_T_FRIENDLY_MISSING_BUFF = 16, // SpellId, Radius, RepeatMin, RepeatMax
|
||||
EVENT_T_SUMMONED_UNIT = 17, // CreatureId, RepeatMin, RepeatMax
|
||||
EVENT_T_TARGET_MANA = 18, // ManaMax%, ManaMin%, RepeatMin, RepeatMax
|
||||
EVENT_T_QUEST_ACCEPT = 19, // QuestID
|
||||
EVENT_T_QUEST_COMPLETE = 20, //
|
||||
EVENT_T_REACHED_HOME = 21, // NONE
|
||||
EVENT_T_RECEIVE_EMOTE = 22, // EmoteId, Condition, CondValue1, CondValue2
|
||||
EVENT_T_BUFFED = 23, // Param1 = SpellID, Param2 = Number of Time STacked, Param3/4 Repeat Min/Max
|
||||
EVENT_T_TARGET_BUFFED = 24, // Param1 = SpellID, Param2 = Number of Time STacked, Param3/4 Repeat Min/Max
|
||||
EVENT_T_RESET = 35, // Is it called after combat, when the creature respawn and spawn. -- TRINITY ONLY
|
||||
|
||||
EVENT_T_END,
|
||||
};
|
||||
|
||||
enum EventAI_ActionType
|
||||
{
|
||||
ACTION_T_NONE = 0, // No action
|
||||
ACTION_T_TEXT = 1, // TextId1, optionally -TextId2, optionally -TextId3(if -TextId2 exist). If more than just -TextId1 is defined, randomize. Negative values.
|
||||
ACTION_T_SET_FACTION = 2, // FactionId (or 0 for default)
|
||||
ACTION_T_MORPH_TO_ENTRY_OR_MODEL = 3, // Creature_template entry(param1) OR ModelId (param2) (or 0 for both to demorph)
|
||||
ACTION_T_SOUND = 4, // SoundId
|
||||
ACTION_T_EMOTE = 5, // EmoteId
|
||||
ACTION_T_RANDOM_SAY = 6, // UNUSED
|
||||
ACTION_T_RANDOM_YELL = 7, // UNUSED
|
||||
ACTION_T_RANDOM_TEXTEMOTE = 8, // UNUSED
|
||||
ACTION_T_RANDOM_SOUND = 9, // SoundId1, SoundId2, SoundId3 (-1 in any field means no output if randomed that field)
|
||||
ACTION_T_RANDOM_EMOTE = 10, // EmoteId1, EmoteId2, EmoteId3 (-1 in any field means no output if randomed that field)
|
||||
ACTION_T_CAST = 11, // SpellId, Target, CastFlags
|
||||
ACTION_T_SUMMON = 12, // CreatureID, Target, Duration in ms
|
||||
ACTION_T_THREAT_SINGLE_PCT = 13, // Threat%, Target
|
||||
ACTION_T_THREAT_ALL_PCT = 14, // Threat%
|
||||
ACTION_T_QUEST_EVENT = 15, // QuestID, Target
|
||||
ACTION_T_CAST_EVENT = 16, // QuestID, SpellId, Target - must be removed as hack?
|
||||
ACTION_T_SET_UNIT_FIELD = 17, // Field_Number, Value, Target
|
||||
ACTION_T_SET_UNIT_FLAG = 18, // Flags (may be more than one field OR'd together), Target
|
||||
ACTION_T_REMOVE_UNIT_FLAG = 19, // Flags (may be more than one field OR'd together), Target
|
||||
ACTION_T_AUTO_ATTACK = 20, // AllowAttackState (0 = stop attack, anything else means continue attacking)
|
||||
ACTION_T_COMBAT_MOVEMENT = 21, // AllowCombatMovement (0 = stop combat based movement, anything else continue attacking)
|
||||
ACTION_T_SET_PHASE = 22, // Phase
|
||||
ACTION_T_INC_PHASE = 23, // Value (may be negative to decrement phase, should not be 0)
|
||||
ACTION_T_EVADE = 24, // No Params
|
||||
ACTION_T_FLEE_FOR_ASSIST = 25, // No Params
|
||||
ACTION_T_QUEST_EVENT_ALL = 26, // QuestID
|
||||
ACTION_T_CAST_EVENT_ALL = 27, // CreatureId, SpellId
|
||||
ACTION_T_REMOVEAURASFROMSPELL = 28, // Target, Spellid
|
||||
ACTION_T_RANGED_MOVEMENT = 29, // Distance, Angle
|
||||
ACTION_T_RANDOM_PHASE = 30, // PhaseId1, PhaseId2, PhaseId3
|
||||
ACTION_T_RANDOM_PHASE_RANGE = 31, // PhaseMin, PhaseMax
|
||||
ACTION_T_SUMMON_ID = 32, // CreatureId, Target, SpawnId
|
||||
ACTION_T_KILLED_MONSTER = 33, // CreatureId, Target
|
||||
ACTION_T_SET_INST_DATA = 34, // Field, Data
|
||||
ACTION_T_SET_INST_DATA64 = 35, // Field, Target
|
||||
ACTION_T_UPDATE_TEMPLATE = 36, // Entry, Team
|
||||
ACTION_T_DIE = 37, // No Params
|
||||
ACTION_T_ZONE_COMBAT_PULSE = 38, // No Params
|
||||
ACTION_T_CALL_FOR_HELP = 39, // Radius
|
||||
ACTION_T_SET_SHEATH = 40, // Sheath (0-passive,1-melee,2-ranged)
|
||||
ACTION_T_FORCE_DESPAWN = 41, // No Params
|
||||
ACTION_T_SET_INVINCIBILITY_HP_LEVEL = 42, // MinHpValue, format(0-flat,1-percent from max health)
|
||||
|
||||
ACTION_T_SET_PHASE_MASK = 97,
|
||||
ACTION_T_SET_STAND_STATE = 98,
|
||||
ACTION_T_MOVE_RANDOM_POINT = 99,
|
||||
ACTION_T_SET_VISIBILITY = 100,
|
||||
ACTION_T_SET_ACTIVE = 101, //Apply
|
||||
ACTION_T_SET_AGGRESSIVE = 102, //Apply
|
||||
ACTION_T_ATTACK_START_PULSE = 103, //Distance
|
||||
ACTION_T_SUMMON_GO = 104, //GameObjectID, DespawnTime in ms
|
||||
|
||||
ACTION_T_END = 105,
|
||||
};
|
||||
|
||||
enum Target
|
||||
{
|
||||
//Self (me)
|
||||
TARGET_T_SELF = 0, //Self cast
|
||||
|
||||
//Hostile targets (if pet then returns pet owner)
|
||||
TARGET_T_HOSTILE, //Our current target (ie: highest aggro)
|
||||
TARGET_T_HOSTILE_SECOND_AGGRO, //Second highest aggro (generaly used for cleaves and some special attacks)
|
||||
TARGET_T_HOSTILE_LAST_AGGRO, //Dead last on aggro (no idea what this could be used for)
|
||||
TARGET_T_HOSTILE_RANDOM, //Just any random target on our threat list
|
||||
TARGET_T_HOSTILE_RANDOM_NOT_TOP, //Any random target except top threat
|
||||
|
||||
//Invoker targets (if pet then returns pet owner)
|
||||
TARGET_T_ACTION_INVOKER, //Unit who caused this Event to occur (only works for EVENT_T_AGGRO, EVENT_T_KILL, EVENT_T_DEATH, EVENT_T_SPELLHIT, EVENT_T_OOC_LOS, EVENT_T_FRIENDLY_HP, EVENT_T_FRIENDLY_IS_CC, EVENT_T_FRIENDLY_MISSING_BUFF)
|
||||
|
||||
//Hostile targets (including pets)
|
||||
TARGET_T_HOSTILE_WPET, //Current target (can be a pet)
|
||||
TARGET_T_HOSTILE_WPET_SECOND_AGGRO, //Second highest aggro (generaly used for cleaves and some special attacks)
|
||||
TARGET_T_HOSTILE_WPET_LAST_AGGRO, //Dead last on aggro (no idea what this could be used for)
|
||||
TARGET_T_HOSTILE_WPET_RANDOM, //Just any random target on our threat list
|
||||
TARGET_T_HOSTILE_WPET_RANDOM_NOT_TOP, //Any random target except top threat
|
||||
|
||||
TARGET_T_ACTION_INVOKER_WPET,
|
||||
|
||||
TARGET_T_END
|
||||
};
|
||||
|
||||
enum CastFlags
|
||||
{
|
||||
CAST_INTURRUPT_PREVIOUS = 0x01, //Interrupt any spell casting
|
||||
CAST_TRIGGERED = 0x02, //Triggered (this makes spell cost zero mana and have no cast time)
|
||||
CAST_FORCE_CAST = 0x04, //Forces cast even if creature is out of mana or out of range
|
||||
CAST_NO_MELEE_IF_OOM = 0x08, //Prevents creature from entering melee if out of mana or out of range
|
||||
CAST_FORCE_TARGET_SELF = 0x10, //Forces the target to cast this spell on itself
|
||||
CAST_AURA_NOT_PRESENT = 0x20, //Only casts the spell if the target does not have an aura from the spell
|
||||
};
|
||||
|
||||
enum EventFlags
|
||||
{
|
||||
EFLAG_REPEATABLE = 0x01, //Event repeats
|
||||
EFLAG_DIFFICULTY_0 = 0x02, //Event only occurs in instance difficulty 0
|
||||
EFLAG_DIFFICULTY_1 = 0x04, //Event only occurs in instance difficulty 1
|
||||
EFLAG_DIFFICULTY_2 = 0x08, //Event only occurs in instance difficulty 2
|
||||
EFLAG_DIFFICULTY_3 = 0x10, //Event only occurs in instance difficulty 3
|
||||
EFLAG_RESERVED_5 = 0x20,
|
||||
EFLAG_RESERVED_6 = 0x40,
|
||||
EFLAG_DEBUG_ONLY = 0x80, //Event only occurs in debug build
|
||||
|
||||
EFLAG_DIFFICULTY_ALL = (EFLAG_DIFFICULTY_0|EFLAG_DIFFICULTY_1|EFLAG_DIFFICULTY_2|EFLAG_DIFFICULTY_3)
|
||||
};
|
||||
|
||||
enum SpawnedEventMode
|
||||
{
|
||||
SPAWNED_EVENT_ALWAY = 0,
|
||||
SPAWNED_EVENT_MAP = 1,
|
||||
SPAWNED_EVENT_ZONE = 2
|
||||
};
|
||||
|
||||
// String text additional data, used in (CreatureEventAI)
|
||||
struct StringTextData
|
||||
{
|
||||
uint32 SoundId;
|
||||
uint8 Type;
|
||||
uint32 Language;
|
||||
uint32 Emote;
|
||||
};
|
||||
// Text Maps
|
||||
typedef UNORDERED_MAP<int32, StringTextData> CreatureEventAI_TextMap;
|
||||
|
||||
struct CreatureEventAI_Action
|
||||
{
|
||||
EventAI_ActionType type: 16;
|
||||
union
|
||||
{
|
||||
// ACTION_T_TEXT = 1
|
||||
struct
|
||||
{
|
||||
int32 TextId1;
|
||||
int32 TextId2;
|
||||
int32 TextId3;
|
||||
} text;
|
||||
// ACTION_T_SET_FACTION = 2
|
||||
struct
|
||||
{
|
||||
uint32 factionId; // faction or 0 for default)
|
||||
} set_faction;
|
||||
// ACTION_T_MORPH_TO_ENTRY_OR_MODEL = 3
|
||||
struct
|
||||
{
|
||||
uint32 creatureId; // set one from fields (or 0 for both to demorph)
|
||||
uint32 modelId;
|
||||
} morph;
|
||||
// ACTION_T_SOUND = 4
|
||||
struct
|
||||
{
|
||||
uint32 soundId;
|
||||
} sound;
|
||||
// ACTION_T_EMOTE = 5
|
||||
struct
|
||||
{
|
||||
uint32 emoteId;
|
||||
} emote;
|
||||
// ACTION_T_RANDOM_SOUND = 9
|
||||
struct
|
||||
{
|
||||
int32 soundId1; // (-1 in any field means no output if randomed that field)
|
||||
int32 soundId2;
|
||||
int32 soundId3;
|
||||
} random_sound;
|
||||
// ACTION_T_RANDOM_EMOTE = 10
|
||||
struct
|
||||
{
|
||||
int32 emoteId1; // (-1 in any field means no output if randomed that field)
|
||||
int32 emoteId2;
|
||||
int32 emoteId3;
|
||||
} random_emote;
|
||||
// ACTION_T_CAST = 11
|
||||
struct
|
||||
{
|
||||
uint32 spellId;
|
||||
uint32 target;
|
||||
uint32 castFlags;
|
||||
} cast;
|
||||
// ACTION_T_SUMMON = 12
|
||||
struct
|
||||
{
|
||||
uint32 creatureId;
|
||||
uint32 target;
|
||||
uint32 duration;
|
||||
} summon;
|
||||
// ACTION_T_THREAT_SINGLE_PCT = 13
|
||||
struct
|
||||
{
|
||||
int32 percent;
|
||||
uint32 target;
|
||||
} threat_single_pct;
|
||||
// ACTION_T_THREAT_ALL_PCT = 14
|
||||
struct
|
||||
{
|
||||
int32 percent;
|
||||
} threat_all_pct;
|
||||
// ACTION_T_QUEST_EVENT = 15
|
||||
struct
|
||||
{
|
||||
uint32 questId;
|
||||
uint32 target;
|
||||
} quest_event;
|
||||
// ACTION_T_CAST_EVENT = 16
|
||||
struct
|
||||
{
|
||||
uint32 creatureId;
|
||||
uint32 spellId;
|
||||
uint32 target;
|
||||
} cast_event;
|
||||
// ACTION_T_SET_UNIT_FIELD = 17
|
||||
struct
|
||||
{
|
||||
uint32 field;
|
||||
uint32 value;
|
||||
uint32 target;
|
||||
} set_unit_field;
|
||||
// ACTION_T_SET_UNIT_FLAG = 18, // value provided mask bits that will be set
|
||||
// ACTION_T_REMOVE_UNIT_FLAG = 19, // value provided mask bits that will be clear
|
||||
struct
|
||||
{
|
||||
uint32 value;
|
||||
uint32 target;
|
||||
} unit_flag;
|
||||
// ACTION_T_AUTO_ATTACK = 20
|
||||
struct
|
||||
{
|
||||
uint32 state; // 0 = stop attack, anything else means continue attacking
|
||||
} auto_attack;
|
||||
// ACTION_T_COMBAT_MOVEMENT = 21
|
||||
struct
|
||||
{
|
||||
uint32 state; // 0 = stop combat based movement, anything else continue attacking
|
||||
uint32 melee; // if set: at stop send melee combat stop if in combat, use for terminate melee fighting state for switch to ranged
|
||||
} combat_movement;
|
||||
// ACTION_T_SET_PHASE = 22
|
||||
struct
|
||||
{
|
||||
uint32 phase;
|
||||
} set_phase;
|
||||
// ACTION_T_INC_PHASE = 23
|
||||
struct
|
||||
{
|
||||
int32 step;
|
||||
} set_inc_phase;
|
||||
// ACTION_T_QUEST_EVENT_ALL = 26
|
||||
struct
|
||||
{
|
||||
uint32 questId;
|
||||
} quest_event_all;
|
||||
// ACTION_T_CAST_EVENT_ALL = 27
|
||||
struct
|
||||
{
|
||||
uint32 creatureId;
|
||||
uint32 spellId;
|
||||
} cast_event_all;
|
||||
// ACTION_T_REMOVEAURASFROMSPELL = 28
|
||||
struct
|
||||
{
|
||||
uint32 target;
|
||||
uint32 spellId;
|
||||
} remove_aura;
|
||||
// ACTION_T_RANGED_MOVEMENT = 29
|
||||
struct
|
||||
{
|
||||
uint32 distance;
|
||||
int32 angle;
|
||||
} ranged_movement;
|
||||
// ACTION_T_RANDOM_PHASE = 30
|
||||
struct
|
||||
{
|
||||
uint32 phase1;
|
||||
uint32 phase2;
|
||||
uint32 phase3;
|
||||
} random_phase;
|
||||
// ACTION_T_RANDOM_PHASE_RANGE = 31
|
||||
struct
|
||||
{
|
||||
uint32 phaseMin;
|
||||
uint32 phaseMax;
|
||||
} random_phase_range;
|
||||
// ACTION_T_SUMMON_ID = 32
|
||||
struct
|
||||
{
|
||||
uint32 creatureId;
|
||||
uint32 target;
|
||||
uint32 spawnId;
|
||||
} summon_id;
|
||||
// ACTION_T_KILLED_MONSTER = 33
|
||||
struct
|
||||
{
|
||||
uint32 creatureId;
|
||||
uint32 target;
|
||||
} killed_monster;
|
||||
// ACTION_T_SET_INST_DATA = 34
|
||||
struct
|
||||
{
|
||||
uint32 field;
|
||||
uint32 value;
|
||||
} set_inst_data;
|
||||
// ACTION_T_SET_INST_DATA64 = 35
|
||||
struct
|
||||
{
|
||||
uint32 field;
|
||||
uint32 target;
|
||||
} set_inst_data64;
|
||||
// ACTION_T_UPDATE_TEMPLATE = 36
|
||||
struct
|
||||
{
|
||||
uint32 creatureId;
|
||||
uint32 team;
|
||||
} update_template;
|
||||
// ACTION_T_CALL_FOR_HELP = 39
|
||||
struct
|
||||
{
|
||||
uint32 radius;
|
||||
} call_for_help;
|
||||
// ACTION_T_SET_SHEATH = 40
|
||||
struct
|
||||
{
|
||||
uint32 sheath;
|
||||
} set_sheath;
|
||||
// ACTION_T_FORCE_DESPAWN = 41
|
||||
struct
|
||||
{
|
||||
uint32 msDelay;
|
||||
} forced_despawn;
|
||||
// ACTION_T_SET_INVINCIBILITY_HP_LEVEL = 42
|
||||
struct
|
||||
{
|
||||
uint32 hp_level;
|
||||
uint32 is_percent;
|
||||
} invincibility_hp_level;
|
||||
// RAW
|
||||
struct
|
||||
{
|
||||
uint32 param1;
|
||||
uint32 param2;
|
||||
uint32 param3;
|
||||
} raw;
|
||||
};
|
||||
};
|
||||
|
||||
struct CreatureEventAI_Event
|
||||
{
|
||||
uint32 event_id;
|
||||
|
||||
uint32 creature_id;
|
||||
|
||||
uint32 event_inverse_phase_mask;
|
||||
|
||||
EventAI_Type event_type : 16;
|
||||
uint8 event_chance : 8;
|
||||
uint8 event_flags : 8;
|
||||
|
||||
union
|
||||
{
|
||||
// EVENT_T_TIMER = 0
|
||||
// EVENT_T_TIMER_OOC = 1
|
||||
struct
|
||||
{
|
||||
uint32 initialMin;
|
||||
uint32 initialMax;
|
||||
uint32 repeatMin;
|
||||
uint32 repeatMax;
|
||||
} timer;
|
||||
// EVENT_T_HP = 2
|
||||
// EVENT_T_MANA = 3
|
||||
// EVENT_T_TARGET_HP = 12
|
||||
// EVENT_T_TARGET_MANA = 18
|
||||
struct
|
||||
{
|
||||
uint32 percentMax;
|
||||
uint32 percentMin;
|
||||
uint32 repeatMin;
|
||||
uint32 repeatMax;
|
||||
} percent_range;
|
||||
// EVENT_T_KILL = 5
|
||||
struct
|
||||
{
|
||||
uint32 repeatMin;
|
||||
uint32 repeatMax;
|
||||
} kill;
|
||||
// EVENT_T_SPELLHIT = 8
|
||||
struct
|
||||
{
|
||||
uint32 spellId;
|
||||
uint32 schoolMask; // -1 ( == 0xffffffff) is ok value for full mask, or must be more limited mask like (0 < 1) = 1 for normal/physical school
|
||||
uint32 repeatMin;
|
||||
uint32 repeatMax;
|
||||
} spell_hit;
|
||||
// EVENT_T_RANGE = 9
|
||||
struct
|
||||
{
|
||||
uint32 minDist;
|
||||
uint32 maxDist;
|
||||
uint32 repeatMin;
|
||||
uint32 repeatMax;
|
||||
} range;
|
||||
// EVENT_T_OOC_LOS = 10
|
||||
struct
|
||||
{
|
||||
uint32 noHostile;
|
||||
uint32 maxRange;
|
||||
uint32 repeatMin;
|
||||
uint32 repeatMax;
|
||||
} ooc_los;
|
||||
// EVENT_T_SPAWNED = 11
|
||||
struct
|
||||
{
|
||||
uint32 condition;
|
||||
uint32 conditionValue1;
|
||||
} spawned;
|
||||
// EVENT_T_TARGET_CASTING = 13
|
||||
struct
|
||||
{
|
||||
uint32 repeatMin;
|
||||
uint32 repeatMax;
|
||||
} target_casting;
|
||||
// EVENT_T_FRIENDLY_HP = 14
|
||||
struct
|
||||
{
|
||||
uint32 hpDeficit;
|
||||
uint32 radius;
|
||||
uint32 repeatMin;
|
||||
uint32 repeatMax;
|
||||
} friendly_hp;
|
||||
// EVENT_T_FRIENDLY_IS_CC = 15
|
||||
struct
|
||||
{
|
||||
uint32 dispelType; // unused ?
|
||||
uint32 radius;
|
||||
uint32 repeatMin;
|
||||
uint32 repeatMax;
|
||||
} friendly_is_cc;
|
||||
// EVENT_T_FRIENDLY_MISSING_BUFF = 16
|
||||
struct
|
||||
{
|
||||
uint32 spellId;
|
||||
uint32 radius;
|
||||
uint32 repeatMin;
|
||||
uint32 repeatMax;
|
||||
} friendly_buff;
|
||||
// EVENT_T_SUMMONED_UNIT = 17
|
||||
struct
|
||||
{
|
||||
uint32 creatureId;
|
||||
uint32 repeatMin;
|
||||
uint32 repeatMax;
|
||||
} summon_unit;
|
||||
// EVENT_T_QUEST_ACCEPT = 19
|
||||
// EVENT_T_QUEST_COMPLETE = 20
|
||||
struct
|
||||
{
|
||||
uint32 questId;
|
||||
} quest;
|
||||
// EVENT_T_RECEIVE_EMOTE = 22
|
||||
struct
|
||||
{
|
||||
uint32 emoteId;
|
||||
uint32 condition;
|
||||
uint32 conditionValue1;
|
||||
uint32 conditionValue2;
|
||||
} receive_emote;
|
||||
// EVENT_T_BUFFED = 23
|
||||
// EVENT_T_TARGET_BUFFED = 24
|
||||
struct
|
||||
{
|
||||
uint32 spellId;
|
||||
uint32 amount;
|
||||
uint32 repeatMin;
|
||||
uint32 repeatMax;
|
||||
} buffed;
|
||||
|
||||
// RAW
|
||||
struct
|
||||
{
|
||||
uint32 param1;
|
||||
uint32 param2;
|
||||
uint32 param3;
|
||||
uint32 param4;
|
||||
} raw;
|
||||
};
|
||||
|
||||
CreatureEventAI_Action action[MAX_ACTIONS];
|
||||
};
|
||||
//Event_Map
|
||||
typedef UNORDERED_MAP<uint32, std::vector<CreatureEventAI_Event> > CreatureEventAI_Event_Map;
|
||||
|
||||
struct CreatureEventAI_Summon
|
||||
{
|
||||
uint32 id;
|
||||
|
||||
float position_x;
|
||||
float position_y;
|
||||
float position_z;
|
||||
float orientation;
|
||||
uint32 SpawnTimeSecs;
|
||||
};
|
||||
|
||||
//EventSummon_Map
|
||||
typedef UNORDERED_MAP<uint32, CreatureEventAI_Summon> CreatureEventAI_Summon_Map;
|
||||
|
||||
struct CreatureEventAIHolder
|
||||
{
|
||||
CreatureEventAIHolder(CreatureEventAI_Event p) : Event(p), Time(0), Enabled(true){}
|
||||
|
||||
CreatureEventAI_Event Event;
|
||||
uint32 Time;
|
||||
bool Enabled;
|
||||
|
||||
// helper
|
||||
bool UpdateRepeatTimer(Creature* creature, uint32 repeatMin, uint32 repeatMax);
|
||||
};
|
||||
|
||||
class CreatureEventAI : public CreatureAI
|
||||
{
|
||||
|
||||
public:
|
||||
explicit CreatureEventAI(Creature *c);
|
||||
~CreatureEventAI()
|
||||
{
|
||||
CreatureEventAIList.clear();
|
||||
}
|
||||
void JustRespawned();
|
||||
void Reset();
|
||||
void JustReachedHome();
|
||||
void EnterCombat(Unit *enemy);
|
||||
void EnterEvadeMode();
|
||||
void JustDied(Unit* /*killer*/);
|
||||
void KilledUnit(Unit* victim);
|
||||
void JustSummoned(Creature* pUnit);
|
||||
void AttackStart(Unit *who);
|
||||
void MoveInLineOfSight(Unit *who);
|
||||
void SpellHit(Unit* pUnit, const SpellEntry* pSpell);
|
||||
void DamageTaken(Unit* done_by, uint32& damage);
|
||||
void UpdateAI(const uint32 diff);
|
||||
void ReceiveEmote(Player* pPlayer, uint32 text_emote);
|
||||
static int Permissible(const Creature *);
|
||||
|
||||
bool ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pActionInvoker = NULL);
|
||||
void ProcessAction(CreatureEventAI_Action const& action, uint32 rnd, uint32 EventId, Unit* pActionInvoker);
|
||||
inline uint32 GetRandActionParam(uint32 rnd, uint32 param1, uint32 param2, uint32 param3);
|
||||
inline int32 GetRandActionParam(uint32 rnd, int32 param1, int32 param2, int32 param3);
|
||||
inline Unit* GetTargetByType(uint32 Target, Unit* pActionInvoker);
|
||||
|
||||
void DoScriptText(int32 textEntry, WorldObject* pSource, Unit* target);
|
||||
bool CanCast(Unit* Target, SpellEntry const *Spell, bool Triggered);
|
||||
|
||||
bool SpawnedEventConditionsCheck(CreatureEventAI_Event const& event);
|
||||
|
||||
Unit* DoSelectLowestHpFriendly(float range, uint32 MinHPDiff);
|
||||
void DoFindFriendlyMissingBuff(std::list<Creature*>& _list, float range, uint32 spellid);
|
||||
void DoFindFriendlyCC(std::list<Creature*>& _list, float range);
|
||||
|
||||
//Holder for events (stores enabled, time, and eventid)
|
||||
std::list<CreatureEventAIHolder> CreatureEventAIList;
|
||||
uint32 EventUpdateTime; //Time between event updates
|
||||
uint32 EventDiff; //Time between the last event call
|
||||
bool bEmptyList;
|
||||
|
||||
//Variables used by Events themselves
|
||||
uint8 Phase; // Current phase, max 32 phases
|
||||
bool CombatMovementEnabled; // If we allow targeted movment gen (movement twoards top threat)
|
||||
bool MeleeEnabled; // If we allow melee auto attack
|
||||
float AttackDistance; // Distance to attack from
|
||||
float AttackAngle; // Angle of attack
|
||||
uint32 InvinceabilityHpLevel; // Minimal health level allowed at damage apply
|
||||
};
|
||||
#endif
|
||||
@@ -0,0 +1,745 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "Common.h"
|
||||
#include "Database/DatabaseEnv.h"
|
||||
#include "Database/SQLStorage.h"
|
||||
#include "CreatureEventAI.h"
|
||||
#include "CreatureEventAIMgr.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "ProgressBar.h"
|
||||
#include "Policies/SingletonImp.h"
|
||||
#include "ObjectDefines.h"
|
||||
#include "GridDefines.h"
|
||||
#include "ConditionMgr.h"
|
||||
|
||||
INSTANTIATE_SINGLETON_1(CreatureEventAIMgr);
|
||||
|
||||
// -------------------
|
||||
void CreatureEventAIMgr::LoadCreatureEventAI_Texts()
|
||||
{
|
||||
// Drop Existing Text Map, only done once and we are ready to add data from multiple sources.
|
||||
m_CreatureEventAI_TextMap.clear();
|
||||
|
||||
// Load EventAI Text
|
||||
objmgr.LoadTrinityStrings(WorldDatabase,"creature_ai_texts",MIN_CREATURE_AI_TEXT_STRING_ID,MAX_CREATURE_AI_TEXT_STRING_ID);
|
||||
|
||||
// Gather Additional data from EventAI Texts
|
||||
QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, sound, type, language, emote FROM creature_ai_texts");
|
||||
|
||||
sLog.outString("Loading EventAI Texts additional data...");
|
||||
if (result)
|
||||
{
|
||||
barGoLink bar(result->GetRowCount());
|
||||
uint32 count = 0;
|
||||
|
||||
do
|
||||
{
|
||||
bar.step();
|
||||
Field* fields = result->Fetch();
|
||||
StringTextData temp;
|
||||
|
||||
int32 i = fields[0].GetInt32();
|
||||
temp.SoundId = fields[1].GetInt32();
|
||||
temp.Type = fields[2].GetInt32();
|
||||
temp.Language = fields[3].GetInt32();
|
||||
temp.Emote = fields[4].GetInt32();
|
||||
|
||||
// range negative
|
||||
if (i > MIN_CREATURE_AI_TEXT_STRING_ID || i <= MAX_CREATURE_AI_TEXT_STRING_ID)
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` is not in valid range(%d-%d)",i,MIN_CREATURE_AI_TEXT_STRING_ID,MAX_CREATURE_AI_TEXT_STRING_ID);
|
||||
continue;
|
||||
}
|
||||
|
||||
// range negative (don't must be happen, loaded from same table)
|
||||
if (!objmgr.GetTrinityStringLocale(i))
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` not found",i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (temp.SoundId)
|
||||
{
|
||||
if (!sSoundEntriesStore.LookupEntry(temp.SoundId))
|
||||
sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` has Sound %u but sound does not exist.",i,temp.SoundId);
|
||||
}
|
||||
|
||||
if (!GetLanguageDescByID(temp.Language))
|
||||
sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` using Language %u but Language does not exist.",i,temp.Language);
|
||||
|
||||
if (temp.Type > CHAT_TYPE_ZONE_YELL)
|
||||
sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` has Type %u but this Chat Type does not exist.",i,temp.Type);
|
||||
|
||||
if (temp.Emote)
|
||||
{
|
||||
if (!sEmotesStore.LookupEntry(temp.Emote))
|
||||
sLog.outErrorDb("CreatureEventAI: Entry %i in table `creature_ai_texts` has Emote %u but emote does not exist.",i,temp.Emote);
|
||||
}
|
||||
|
||||
m_CreatureEventAI_TextMap[i] = temp;
|
||||
++count;
|
||||
} while (result->NextRow());
|
||||
|
||||
sLog.outString();
|
||||
sLog.outString(">> Loaded %u additional CreatureEventAI Texts data.", count);
|
||||
}
|
||||
else
|
||||
{
|
||||
barGoLink bar(1);
|
||||
bar.step();
|
||||
sLog.outString();
|
||||
sLog.outString(">> Loaded 0 additional CreatureEventAI Texts data. DB table `creature_ai_texts` is empty.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// -------------------
|
||||
void CreatureEventAIMgr::LoadCreatureEventAI_Summons()
|
||||
{
|
||||
|
||||
//Drop Existing EventSummon Map
|
||||
m_CreatureEventAI_Summon_Map.clear();
|
||||
|
||||
// Gather additional data for EventAI
|
||||
QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id, position_x, position_y, position_z, orientation, spawntimesecs FROM creature_ai_summons");
|
||||
if (result)
|
||||
{
|
||||
barGoLink bar(result->GetRowCount());
|
||||
uint32 Count = 0;
|
||||
|
||||
do
|
||||
{
|
||||
bar.step();
|
||||
Field *fields = result->Fetch();
|
||||
|
||||
CreatureEventAI_Summon temp;
|
||||
|
||||
uint32 i = fields[0].GetUInt32();
|
||||
temp.position_x = fields[1].GetFloat();
|
||||
temp.position_y = fields[2].GetFloat();
|
||||
temp.position_z = fields[3].GetFloat();
|
||||
temp.orientation = fields[4].GetFloat();
|
||||
temp.SpawnTimeSecs = fields[5].GetUInt32();
|
||||
|
||||
if (!Trinity::IsValidMapCoord(temp.position_x,temp.position_y,temp.position_z,temp.orientation))
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Summon id %u have wrong coordinates (%f,%f,%f,%f), skipping.", i,temp.position_x,temp.position_y,temp.position_z,temp.orientation);
|
||||
continue;
|
||||
}
|
||||
|
||||
//Add to map
|
||||
m_CreatureEventAI_Summon_Map[i] = temp;
|
||||
++Count;
|
||||
} while (result->NextRow());
|
||||
|
||||
sLog.outString();
|
||||
sLog.outString(">> Loaded %u CreatureEventAI summon definitions", Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
barGoLink bar(1);
|
||||
bar.step();
|
||||
sLog.outString();
|
||||
sLog.outString(">> Loaded 0 CreatureEventAI Summon definitions. DB table `creature_ai_summons` is empty.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// -------------------
|
||||
void CreatureEventAIMgr::LoadCreatureEventAI_Scripts()
|
||||
{
|
||||
//Drop Existing EventAI List
|
||||
m_CreatureEventAI_Event_Map.clear();
|
||||
|
||||
// Gather event data
|
||||
QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id, creature_id, event_type, event_inverse_phase_mask, event_chance, event_flags, "
|
||||
"event_param1, event_param2, event_param3, event_param4, "
|
||||
"action1_type, action1_param1, action1_param2, action1_param3, "
|
||||
"action2_type, action2_param1, action2_param2, action2_param3, "
|
||||
"action3_type, action3_param1, action3_param2, action3_param3 "
|
||||
"FROM creature_ai_scripts");
|
||||
if (result)
|
||||
{
|
||||
barGoLink bar(result->GetRowCount());
|
||||
uint32 Count = 0;
|
||||
|
||||
do
|
||||
{
|
||||
bar.step();
|
||||
Field *fields = result->Fetch();
|
||||
|
||||
CreatureEventAI_Event temp;
|
||||
temp.event_id = EventAI_Type(fields[0].GetUInt32());
|
||||
uint32 i = temp.event_id;
|
||||
|
||||
temp.creature_id = fields[1].GetUInt32();
|
||||
uint32 creature_id = temp.creature_id;
|
||||
|
||||
uint32 e_type = fields[2].GetUInt32();
|
||||
//Report any errors in event
|
||||
if (e_type >= EVENT_T_END)
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u have wrong type (%u), skipping.", i,e_type);
|
||||
continue;
|
||||
}
|
||||
temp.event_type = EventAI_Type(e_type);
|
||||
|
||||
temp.event_inverse_phase_mask = fields[3].GetUInt32();
|
||||
temp.event_chance = fields[4].GetUInt8();
|
||||
temp.event_flags = fields[5].GetUInt8();
|
||||
temp.raw.param1 = fields[6].GetUInt32();
|
||||
temp.raw.param2 = fields[7].GetUInt32();
|
||||
temp.raw.param3 = fields[8].GetUInt32();
|
||||
temp.raw.param4 = fields[9].GetUInt32();
|
||||
|
||||
//Creature does not exist in database
|
||||
if (!sCreatureStorage.LookupEntry<CreatureInfo>(temp.creature_id))
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u has script for non-existing creature entry (%u), skipping.", i, temp.creature_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
//No chance of this event occuring
|
||||
if (temp.event_chance == 0)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u has 0 percent chance. Event will never trigger!", i);
|
||||
//Chance above 100, force it to be 100
|
||||
else if (temp.event_chance > 100)
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using event %u with more than 100 percent chance. Adjusting to 100 percent.", temp.creature_id, i);
|
||||
temp.event_chance = 100;
|
||||
}
|
||||
|
||||
//Individual event checks
|
||||
switch (temp.event_type)
|
||||
{
|
||||
case EVENT_T_TIMER:
|
||||
case EVENT_T_TIMER_OOC:
|
||||
if (temp.timer.initialMax < temp.timer.initialMin)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using timed event(%u) with param2 < param1 (InitialMax < InitialMin). Event will never repeat.", temp.creature_id, i);
|
||||
if (temp.timer.repeatMax < temp.timer.repeatMin)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
|
||||
break;
|
||||
case EVENT_T_HP:
|
||||
case EVENT_T_MANA:
|
||||
case EVENT_T_TARGET_HP:
|
||||
case EVENT_T_TARGET_MANA:
|
||||
if (temp.percent_range.percentMax > 100)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using percentage event(%u) with param2 (MinPercent) > 100. Event will never trigger! ", temp.creature_id, i);
|
||||
|
||||
if (temp.percent_range.percentMax <= temp.percent_range.percentMin)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using percentage event(%u) with param1 <= param2 (MaxPercent <= MinPercent). Event will never trigger! ", temp.creature_id, i);
|
||||
|
||||
if (temp.event_flags & EFLAG_REPEATABLE && !temp.percent_range.repeatMin && !temp.percent_range.repeatMax)
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u has param3 and param4=0 (RepeatMin/RepeatMax) but cannot be repeatable without timers. Removing EFLAG_REPEATABLE for event %u.", temp.creature_id, i);
|
||||
temp.event_flags &= ~EFLAG_REPEATABLE;
|
||||
}
|
||||
break;
|
||||
case EVENT_T_SPELLHIT:
|
||||
if (temp.spell_hit.spellId)
|
||||
{
|
||||
SpellEntry const* pSpell = sSpellStore.LookupEntry(temp.spell_hit.spellId);
|
||||
if (!pSpell)
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u has non-existant SpellID(%u) defined in event %u.", temp.creature_id, temp.spell_hit.spellId, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((temp.spell_hit.schoolMask & pSpell->SchoolMask) != pSpell->SchoolMask)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u has param1(spellId %u) but param2 is not -1 and not equal to spell's school mask. Event %u can never trigger.", temp.creature_id, temp.spell_hit.schoolMask, i);
|
||||
}
|
||||
|
||||
if (!temp.spell_hit.schoolMask)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u is using invalid SpellSchoolMask(%u) defined in event %u.", temp.creature_id, temp.spell_hit.schoolMask, i);
|
||||
|
||||
if (temp.spell_hit.repeatMax < temp.spell_hit.repeatMin)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
|
||||
break;
|
||||
case EVENT_T_RANGE:
|
||||
if (temp.range.maxDist < temp.range.minDist)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (MaxDist < MinDist). Event will never repeat.", temp.creature_id, i);
|
||||
if (temp.range.repeatMax < temp.range.repeatMin)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
|
||||
break;
|
||||
case EVENT_T_OOC_LOS:
|
||||
if (temp.ooc_los.repeatMax < temp.ooc_los.repeatMin)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
|
||||
break;
|
||||
case EVENT_T_SPAWNED:
|
||||
switch(temp.spawned.condition)
|
||||
{
|
||||
case SPAWNED_EVENT_ALWAY:
|
||||
break;
|
||||
case SPAWNED_EVENT_MAP:
|
||||
if (!sMapStore.LookupEntry(temp.spawned.conditionValue1))
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using spawned event(%u) with param1 = %u 'map specific' but with not existed map (%u) in param2. Event will never repeat.", temp.creature_id, i, temp.spawned.condition, temp.spawned.conditionValue1);
|
||||
break;
|
||||
case SPAWNED_EVENT_ZONE:
|
||||
if (!GetAreaEntryByAreaID(temp.spawned.conditionValue1))
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using spawned event(%u) with param1 = %u 'area specific' but with not existed area (%u) in param2. Event will never repeat.", temp.creature_id, i, temp.spawned.condition, temp.spawned.conditionValue1);
|
||||
default:
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using invalid spawned event %u mode (%u) in param1", temp.creature_id, i, temp.spawned.condition);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case EVENT_T_FRIENDLY_HP:
|
||||
if (temp.friendly_hp.repeatMax < temp.friendly_hp.repeatMin)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
|
||||
break;
|
||||
case EVENT_T_FRIENDLY_IS_CC:
|
||||
if (temp.friendly_is_cc.repeatMax < temp.friendly_is_cc.repeatMin)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
|
||||
break;
|
||||
case EVENT_T_FRIENDLY_MISSING_BUFF:
|
||||
{
|
||||
SpellEntry const* pSpell = sSpellStore.LookupEntry(temp.spell_hit.spellId);
|
||||
if (!pSpell)
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u has non-existant SpellID(%u) defined in event %u.", temp.creature_id, temp.spell_hit.spellId, i);
|
||||
continue;
|
||||
}
|
||||
if (temp.friendly_buff.repeatMax < temp.friendly_buff.repeatMin)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
|
||||
break;
|
||||
}
|
||||
case EVENT_T_KILL:
|
||||
if (temp.kill.repeatMax < temp.kill.repeatMin)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
|
||||
break;
|
||||
case EVENT_T_TARGET_CASTING:
|
||||
if (temp.target_casting.repeatMax < temp.target_casting.repeatMin)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
|
||||
break;
|
||||
case EVENT_T_SUMMONED_UNIT:
|
||||
if (!sCreatureStorage.LookupEntry<CreatureInfo>(temp.summon_unit.creatureId))
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with not existed creature template id (%u) in param1, skipped.", temp.creature_id, i, temp.summon_unit.creatureId);
|
||||
if (temp.summon_unit.repeatMax < temp.summon_unit.repeatMin)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with param2 < param1 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
|
||||
break;
|
||||
case EVENT_T_QUEST_ACCEPT:
|
||||
case EVENT_T_QUEST_COMPLETE:
|
||||
if (!objmgr.GetQuestTemplate(temp.quest.questId))
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using event(%u) with not existed qyest id (%u) in param1, skipped.", temp.creature_id, i, temp.quest.questId);
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u using not implemented event (%u) in event %u.", temp.creature_id, temp.event_id, i);
|
||||
continue;
|
||||
|
||||
case EVENT_T_AGGRO:
|
||||
case EVENT_T_DEATH:
|
||||
case EVENT_T_EVADE:
|
||||
case EVENT_T_REACHED_HOME:
|
||||
{
|
||||
if (temp.event_flags & EFLAG_REPEATABLE)
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u has EFLAG_REPEATABLE set. Event can never be repeatable. Removing flag for event %u.", temp.creature_id, i);
|
||||
temp.event_flags &= ~EFLAG_REPEATABLE;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_T_RECEIVE_EMOTE:
|
||||
{
|
||||
if (!sEmotesTextStore.LookupEntry(temp.receive_emote.emoteId))
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u using event %u: param1 (EmoteTextId: %u) are not valid.",temp.creature_id, i, temp.receive_emote.emoteId);
|
||||
continue;
|
||||
}
|
||||
if (temp.receive_emote.condition)
|
||||
{
|
||||
Condition* cond = new Condition();
|
||||
cond->mConditionType = ConditionType(temp.receive_emote.condition);
|
||||
cond->mConditionValue1 = temp.receive_emote.conditionValue1;
|
||||
cond->mConditionValue2 = temp.receive_emote.conditionValue2;
|
||||
if (!sConditionMgr.isConditionTypeValid(cond))
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u using event %u: param2 (Condition: %u) are not valid.",temp.creature_id, i, temp.receive_emote.condition);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(temp.event_flags & EFLAG_REPEATABLE))
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u using event %u: EFLAG_REPEATABLE not set. Event must always be repeatable. Flag applied.", temp.creature_id, i);
|
||||
temp.event_flags |= EFLAG_REPEATABLE;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_T_BUFFED:
|
||||
case EVENT_T_TARGET_BUFFED:
|
||||
{
|
||||
SpellEntry const* pSpell = sSpellStore.LookupEntry(temp.buffed.spellId);
|
||||
if (!pSpell)
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u has non-existant SpellID(%u) defined in event %u.", temp.creature_id, temp.spell_hit.spellId, i);
|
||||
continue;
|
||||
}
|
||||
if (temp.buffed.repeatMax < temp.buffed.repeatMin)
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u are using repeatable event(%u) with param4 < param3 (RepeatMax < RepeatMin). Event will never repeat.", temp.creature_id, i);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
sLog.outErrorDb("CreatureEventAI: Creature %u using not checked at load event (%u) in event %u. Need check code update?", temp.creature_id, temp.event_id, i);
|
||||
break;
|
||||
}
|
||||
|
||||
for (uint32 j = 0; j < MAX_ACTIONS; j++)
|
||||
{
|
||||
uint16 action_type = fields[10+(j*4)].GetUInt16();
|
||||
if (action_type >= ACTION_T_END)
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u has incorrect action type (%u), replace by ACTION_T_NONE.", i, j+1, action_type);
|
||||
temp.action[j].type = ACTION_T_NONE;
|
||||
continue;
|
||||
}
|
||||
|
||||
CreatureEventAI_Action& action = temp.action[j];
|
||||
|
||||
action.type = EventAI_ActionType(action_type);
|
||||
action.raw.param1 = fields[11+(j*4)].GetUInt32();
|
||||
action.raw.param2 = fields[12+(j*4)].GetUInt32();
|
||||
action.raw.param3 = fields[13+(j*4)].GetUInt32();
|
||||
|
||||
//Report any errors in actions
|
||||
switch (action.type)
|
||||
{
|
||||
case ACTION_T_NONE:
|
||||
break;
|
||||
case ACTION_T_TEXT:
|
||||
{
|
||||
if (action.text.TextId1 < 0)
|
||||
{
|
||||
if (m_CreatureEventAI_TextMap.find(action.text.TextId1) == m_CreatureEventAI_TextMap.end())
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 refrences non-existing entry in texts table.", i, j+1);
|
||||
}
|
||||
if (action.text.TextId2 < 0)
|
||||
{
|
||||
if (m_CreatureEventAI_TextMap.find(action.text.TextId2) == m_CreatureEventAI_TextMap.end())
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u param2 refrences non-existing entry in texts table.", i, j+1);
|
||||
|
||||
if (!action.text.TextId1)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u has param2, but param1 is not set. Required for randomized text.", i, j+1);
|
||||
}
|
||||
if (action.text.TextId3 < 0)
|
||||
{
|
||||
if (m_CreatureEventAI_TextMap.find(action.text.TextId3) == m_CreatureEventAI_TextMap.end())
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u param3 refrences non-existing entry in texts table.", i, j+1);
|
||||
|
||||
if (!action.text.TextId1 || !action.text.TextId2)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u has param3, but param1 and/or param2 is not set. Required for randomized text.", i, j+1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ACTION_T_SET_FACTION:
|
||||
if (action.set_faction.factionId !=0 && !sFactionStore.LookupEntry(action.set_faction.factionId))
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent FactionId %u.", i, j+1, action.set_faction.factionId);
|
||||
action.set_faction.factionId = 0;
|
||||
}
|
||||
break;
|
||||
case ACTION_T_MORPH_TO_ENTRY_OR_MODEL:
|
||||
if (action.morph.creatureId !=0 || action.morph.modelId !=0)
|
||||
{
|
||||
if (action.morph.creatureId && !sCreatureStorage.LookupEntry<CreatureInfo>(action.morph.creatureId))
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant Creature entry %u.", i, j+1, action.morph.creatureId);
|
||||
action.morph.creatureId = 0;
|
||||
}
|
||||
|
||||
if (action.morph.modelId)
|
||||
{
|
||||
if (action.morph.creatureId)
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u have unused ModelId %u with also set creature id %u.", i, j+1, action.morph.modelId,action.morph.creatureId);
|
||||
action.morph.modelId = 0;
|
||||
}
|
||||
else if (!sCreatureDisplayInfoStore.LookupEntry(action.morph.modelId))
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant ModelId %u.", i, j+1, action.morph.modelId);
|
||||
action.morph.modelId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ACTION_T_SOUND:
|
||||
if (!sSoundEntriesStore.LookupEntry(action.sound.soundId))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant SoundID %u.", i, j+1, action.sound.soundId);
|
||||
break;
|
||||
case ACTION_T_EMOTE:
|
||||
if (!sEmotesStore.LookupEntry(action.emote.emoteId))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 (EmoteId: %u) are not valid.", i, j+1, action.emote.emoteId);
|
||||
break;
|
||||
case ACTION_T_RANDOM_SOUND:
|
||||
if (!sSoundEntriesStore.LookupEntry(action.random_sound.soundId1))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 uses non-existant SoundID %u.", i, j+1, action.random_sound.soundId1);
|
||||
if (action.random_sound.soundId2 >= 0 && !sSoundEntriesStore.LookupEntry(action.random_sound.soundId2))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u param2 uses non-existant SoundID %u.", i, j+1, action.random_sound.soundId2);
|
||||
if (action.random_sound.soundId3 >= 0 && !sSoundEntriesStore.LookupEntry(action.random_sound.soundId3))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u param3 uses non-existant SoundID %u.", i, j+1, action.random_sound.soundId3);
|
||||
break;
|
||||
case ACTION_T_RANDOM_EMOTE:
|
||||
if (!sEmotesStore.LookupEntry(action.random_emote.emoteId1))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 (EmoteId: %u) are not valid.", i, j+1, action.random_emote.emoteId1);
|
||||
if (action.random_emote.emoteId2 >= 0 && !sEmotesStore.LookupEntry(action.random_emote.emoteId2))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u param2 (EmoteId: %u) are not valid.", i, j+1, action.random_emote.emoteId2);
|
||||
if (action.random_emote.emoteId3 >= 0 && !sEmotesStore.LookupEntry(action.random_emote.emoteId3))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u param3 (EmoteId: %u) are not valid.", i, j+1, action.random_emote.emoteId3);
|
||||
break;
|
||||
case ACTION_T_CAST:
|
||||
{
|
||||
const SpellEntry *spell = sSpellStore.LookupEntry(action.cast.spellId);
|
||||
if (!spell)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i, j+1, action.cast.spellId);
|
||||
/* FIXME: temp.raw.param3 not have event tipes with recovery time in it....
|
||||
else
|
||||
{
|
||||
if (spell->RecoveryTime > 0 && temp.event_flags & EFLAG_REPEATABLE)
|
||||
{
|
||||
//output as debug for now, also because there's no general rule all spells have RecoveryTime
|
||||
if (temp.event_param3 < spell->RecoveryTime)
|
||||
sLog.outDebug("CreatureEventAI: Event %u Action %u uses SpellID %u but cooldown is longer(%u) than minumum defined in event param3(%u).", i, j+1,action.cast.spellId, spell->RecoveryTime, temp.event_param3);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
//Cast is always triggered if target is forced to cast on self
|
||||
if (action.cast.castFlags & CAST_FORCE_TARGET_SELF)
|
||||
action.cast.castFlags |= CAST_TRIGGERED;
|
||||
|
||||
if (action.cast.target >= TARGET_T_END)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
|
||||
break;
|
||||
}
|
||||
case ACTION_T_SUMMON:
|
||||
if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.summon.creatureId))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent creature entry %u.", i, j+1, action.summon.creatureId);
|
||||
|
||||
if (action.summon.target >= TARGET_T_END)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
|
||||
break;
|
||||
case ACTION_T_THREAT_SINGLE_PCT:
|
||||
if (std::abs(action.threat_single_pct.percent) > 100)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid percent value %u.", i, j+1, action.threat_single_pct.percent);
|
||||
if (action.threat_single_pct.target >= TARGET_T_END)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
|
||||
break;
|
||||
case ACTION_T_THREAT_ALL_PCT:
|
||||
if (std::abs(action.threat_all_pct.percent) > 100)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses invalid percent value %u.", i, j+1, action.threat_all_pct.percent);
|
||||
break;
|
||||
case ACTION_T_QUEST_EVENT:
|
||||
if (Quest const* qid = objmgr.GetQuestTemplate(action.quest_event.questId))
|
||||
{
|
||||
if (!qid->HasFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u. SpecialFlags for quest entry %u does not include |2, Action will not have any effect.", i, j+1, action.quest_event.questId);
|
||||
}
|
||||
else
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent Quest entry %u.", i, j+1, action.quest_event.questId);
|
||||
|
||||
if (action.quest_event.target >= TARGET_T_END)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
|
||||
|
||||
break;
|
||||
case ACTION_T_CAST_EVENT:
|
||||
if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.cast_event.creatureId))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent creature entry %u.", i, j+1, action.cast_event.creatureId);
|
||||
if (!sSpellStore.LookupEntry(action.cast_event.spellId))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i, j+1, action.cast_event.spellId);
|
||||
if (action.cast_event.target >= TARGET_T_END)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
|
||||
break;
|
||||
case ACTION_T_SET_UNIT_FIELD:
|
||||
if (action.set_unit_field.field < OBJECT_END || action.set_unit_field.field >= UNIT_END)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u param1 (UNIT_FIELD*). Index out of range for intended use.", i, j+1);
|
||||
if (action.set_unit_field.target >= TARGET_T_END)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
|
||||
break;
|
||||
case ACTION_T_SET_UNIT_FLAG:
|
||||
case ACTION_T_REMOVE_UNIT_FLAG:
|
||||
if (action.unit_flag.target >= TARGET_T_END)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
|
||||
break;
|
||||
case ACTION_T_SET_PHASE:
|
||||
if (action.set_phase.phase >= MAX_PHASE)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phase >= %u. Phase mask cannot be used past phase %u.", i, j+1, MAX_PHASE, MAX_PHASE-1);
|
||||
break;
|
||||
case ACTION_T_INC_PHASE:
|
||||
if (action.set_inc_phase.step == 0)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u is incrementing phase by 0. Was this intended?", i, j+1);
|
||||
else if (std::abs(action.set_inc_phase.step) > MAX_PHASE-1)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u is change phase by too large for any use %i.", i, j+1, action.set_inc_phase.step);
|
||||
break;
|
||||
case ACTION_T_QUEST_EVENT_ALL:
|
||||
if (Quest const* qid = objmgr.GetQuestTemplate(action.quest_event_all.questId))
|
||||
{
|
||||
if (!qid->HasFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u. SpecialFlags for quest entry %u does not include |2, Action will not have any effect.", i, j+1, action.quest_event_all.questId);
|
||||
}
|
||||
else
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent Quest entry %u.", i, j+1, action.quest_event_all.questId);
|
||||
break;
|
||||
case ACTION_T_CAST_EVENT_ALL:
|
||||
if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.cast_event_all.creatureId))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent creature entry %u.", i, j+1, action.cast_event_all.creatureId);
|
||||
if (!sSpellStore.LookupEntry(action.cast_event_all.spellId))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i, j+1, action.cast_event_all.spellId);
|
||||
break;
|
||||
case ACTION_T_REMOVEAURASFROMSPELL:
|
||||
if (!sSpellStore.LookupEntry(action.remove_aura.spellId))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existent SpellID %u.", i, j+1, action.remove_aura.spellId);
|
||||
if (action.remove_aura.target >= TARGET_T_END)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
|
||||
break;
|
||||
case ACTION_T_RANDOM_PHASE: //PhaseId1, PhaseId2, PhaseId3
|
||||
if (action.random_phase.phase1 >= MAX_PHASE)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phase1 >= %u. Phase mask cannot be used past phase %u.", i, j+1, MAX_PHASE, MAX_PHASE-1);
|
||||
if (action.random_phase.phase2 >= MAX_PHASE)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phase2 >= %u. Phase mask cannot be used past phase %u.", i, j+1, MAX_PHASE, MAX_PHASE-1);
|
||||
if (action.random_phase.phase3 >= MAX_PHASE)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phase3 >= %u. Phase mask cannot be used past phase %u.", i, j+1, MAX_PHASE, MAX_PHASE-1);
|
||||
break;
|
||||
case ACTION_T_RANDOM_PHASE_RANGE: //PhaseMin, PhaseMax
|
||||
if (action.random_phase_range.phaseMin >= MAX_PHASE)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phaseMin >= %u. Phase mask cannot be used past phase %u.", i, j+1, MAX_PHASE, MAX_PHASE-1);
|
||||
if (action.random_phase_range.phaseMin >= MAX_PHASE)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phaseMax >= %u. Phase mask cannot be used past phase %u.", i, j+1, MAX_PHASE, MAX_PHASE-1);
|
||||
if (action.random_phase_range.phaseMin >= action.random_phase_range.phaseMax)
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set phaseMax <= phaseMin.", i, j+1);
|
||||
std::swap(action.random_phase_range.phaseMin,action.random_phase_range.phaseMax);
|
||||
// equal case processed at call
|
||||
}
|
||||
break;
|
||||
case ACTION_T_SUMMON_ID:
|
||||
if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.summon_id.creatureId))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant creature entry %u.", i, j+1, action.summon_id.creatureId);
|
||||
if (action.summon_id.target >= TARGET_T_END)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
|
||||
if (m_CreatureEventAI_Summon_Map.find(action.summon_id.spawnId) == m_CreatureEventAI_Summon_Map.end())
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u summons missing CreatureEventAI_Summon %u", i, j+1, action.summon_id.spawnId);
|
||||
break;
|
||||
case ACTION_T_KILLED_MONSTER:
|
||||
if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.killed_monster.creatureId))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant creature entry %u.", i, j+1, action.killed_monster.creatureId);
|
||||
if (action.killed_monster.target >= TARGET_T_END)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
|
||||
break;
|
||||
case ACTION_T_SET_INST_DATA:
|
||||
if (!(temp.event_flags & EFLAG_DIFFICULTY_ALL))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u. Cannot set instance data without difficulty event flags.", i, j+1);
|
||||
if (action.set_inst_data.value > 4/*SPECIAL*/)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u attempts to set instance data above encounter state 4. Custom case?", i, j+1);
|
||||
break;
|
||||
case ACTION_T_SET_INST_DATA64:
|
||||
if (!(temp.event_flags & EFLAG_DIFFICULTY_ALL))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u. Cannot set instance data without difficulty event flags.", i, j+1);
|
||||
if (action.set_inst_data64.target >= TARGET_T_END)
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses incorrect Target type", i, j+1);
|
||||
break;
|
||||
case ACTION_T_UPDATE_TEMPLATE:
|
||||
if (!sCreatureStorage.LookupEntry<CreatureInfo>(action.update_template.creatureId))
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses non-existant creature entry %u.", i, j+1, action.update_template.creatureId);
|
||||
break;
|
||||
case ACTION_T_SET_SHEATH:
|
||||
if (action.set_sheath.sheath >= MAX_SHEATH_STATE)
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses wrong sheath state %u.", i, j+1, action.set_sheath.sheath);
|
||||
action.set_sheath.sheath = SHEATH_STATE_UNARMED;
|
||||
}
|
||||
break;
|
||||
case ACTION_T_SET_INVINCIBILITY_HP_LEVEL:
|
||||
if (action.invincibility_hp_level.is_percent)
|
||||
{
|
||||
if (action.invincibility_hp_level.hp_level > 100)
|
||||
{
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u uses wrong percent value %u.", i, j+1, action.invincibility_hp_level.hp_level);
|
||||
action.invincibility_hp_level.hp_level = 100;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ACTION_T_EVADE: //No Params
|
||||
case ACTION_T_FLEE_FOR_ASSIST: //No Params
|
||||
case ACTION_T_DIE: //No Params
|
||||
case ACTION_T_ZONE_COMBAT_PULSE: //No Params
|
||||
case ACTION_T_FORCE_DESPAWN: //No Params
|
||||
case ACTION_T_AUTO_ATTACK: //AllowAttackState (0 = stop attack, anything else means continue attacking)
|
||||
case ACTION_T_COMBAT_MOVEMENT: //AllowCombatMovement (0 = stop combat based movement, anything else continue attacking)
|
||||
case ACTION_T_RANGED_MOVEMENT: //Distance, Angle
|
||||
case ACTION_T_CALL_FOR_HELP: //Distance
|
||||
break;
|
||||
|
||||
case ACTION_T_RANDOM_SAY:
|
||||
case ACTION_T_RANDOM_YELL:
|
||||
case ACTION_T_RANDOM_TEXTEMOTE:
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u currently unused ACTION type. Did you forget to update database?", i, j+1);
|
||||
break;
|
||||
|
||||
case ACTION_T_MOVE_RANDOM_POINT:
|
||||
case ACTION_T_SET_STAND_STATE:
|
||||
case ACTION_T_SET_PHASE_MASK:
|
||||
case ACTION_T_SET_VISIBILITY:
|
||||
case ACTION_T_SET_ACTIVE:
|
||||
case ACTION_T_SET_AGGRESSIVE:
|
||||
case ACTION_T_ATTACK_START_PULSE:
|
||||
case ACTION_T_SUMMON_GO:
|
||||
break;
|
||||
|
||||
default:
|
||||
sLog.outErrorDb("CreatureEventAI: Event %u Action %u have currently not checked at load action type (%u). Need check code update?", i, j+1, temp.action[j].type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Add to list
|
||||
m_CreatureEventAI_Event_Map[creature_id].push_back(temp);
|
||||
++Count;
|
||||
|
||||
if (CreatureInfo const* cInfo = sCreatureStorage.LookupEntry<CreatureInfo>(temp.creature_id))
|
||||
{
|
||||
if (!cInfo->AIName || !cInfo->AIName[0])
|
||||
{
|
||||
//sLog.outErrorDb("CreatureEventAI: Creature Entry %u has EventAI script but its AIName is empty. Set to EventAI as default.", cInfo->Entry);
|
||||
size_t len = strlen("EventAI")+1;
|
||||
const_cast<CreatureInfo*>(cInfo)->AIName = new char[len];
|
||||
strncpy(const_cast<char*>(cInfo->AIName), "EventAI", len);
|
||||
}
|
||||
if (strcmp(cInfo->AIName, "EventAI"))
|
||||
{
|
||||
//sLog.outErrorDb("CreatureEventAI: Creature Entry %u has EventAI script but it has AIName %s. EventAI script will be overriden.", cInfo->Entry, cInfo->AIName);
|
||||
}
|
||||
if (cInfo->ScriptID)
|
||||
{
|
||||
//sLog.outErrorDb("CreatureEventAI: Creature Entry %u has EventAI script but it also has C++ script. EventAI script will be overriden.", cInfo->Entry);
|
||||
}
|
||||
}
|
||||
} while (result->NextRow());
|
||||
|
||||
sLog.outString();
|
||||
sLog.outString(">> Loaded %u CreatureEventAI scripts", Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
barGoLink bar(1);
|
||||
bar.step();
|
||||
sLog.outString();
|
||||
sLog.outString(">> Loaded 0 CreatureEventAI scripts. DB table `creature_ai_scripts` is empty.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef TRINITY_CREATURE_EAI_MGR_H
|
||||
#define TRINITY_CREATURE_EAI_MGR_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "CreatureEventAI.h"
|
||||
|
||||
class CreatureEventAIMgr
|
||||
{
|
||||
public:
|
||||
CreatureEventAIMgr(){};
|
||||
~CreatureEventAIMgr(){};
|
||||
|
||||
void LoadCreatureEventAI_Texts();
|
||||
void LoadCreatureEventAI_Summons();
|
||||
void LoadCreatureEventAI_Scripts();
|
||||
|
||||
CreatureEventAI_Event_Map const& GetCreatureEventAIMap() const { return m_CreatureEventAI_Event_Map; }
|
||||
CreatureEventAI_Summon_Map const& GetCreatureEventAISummonMap() const { return m_CreatureEventAI_Summon_Map; }
|
||||
CreatureEventAI_TextMap const& GetCreatureEventAITextMap() const { return m_CreatureEventAI_TextMap; }
|
||||
|
||||
private:
|
||||
CreatureEventAI_Event_Map m_CreatureEventAI_Event_Map;
|
||||
CreatureEventAI_Summon_Map m_CreatureEventAI_Summon_Map;
|
||||
CreatureEventAI_TextMap m_CreatureEventAI_TextMap;
|
||||
};
|
||||
|
||||
#define CreatureEAI_Mgr Trinity::Singleton<CreatureEventAIMgr>::Instance()
|
||||
#endif
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "GuardAI.h"
|
||||
#include "Errors.h"
|
||||
#include "Player.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "World.h"
|
||||
#include "CreatureAIImpl.h"
|
||||
|
||||
int GuardAI::Permissible(const Creature *creature)
|
||||
{
|
||||
if (creature->isGuard())
|
||||
return PERMIT_BASE_SPECIAL;
|
||||
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
GuardAI::GuardAI(Creature *c) : CreatureAI(c), i_victimGuid(0), i_state(STATE_NORMAL), i_tracker(TIME_INTERVAL_LOOK)
|
||||
{
|
||||
}
|
||||
|
||||
void GuardAI::MoveInLineOfSight(Unit *u)
|
||||
{
|
||||
// Ignore Z for flying creatures
|
||||
if (!me->canFly() && me->GetDistanceZ(u) > CREATURE_Z_ATTACK_RANGE)
|
||||
return;
|
||||
|
||||
if (!me->getVictim() && me->canAttack(u) &&
|
||||
(u->IsHostileToPlayers() || me->IsHostileTo(u) /*|| u->getVictim() && me->IsFriendlyTo(u->getVictim())*/) &&
|
||||
u->isInAccessiblePlaceFor(me))
|
||||
{
|
||||
float attackRadius = me->GetAttackDistance(u);
|
||||
if (me->IsWithinDistInMap(u,attackRadius))
|
||||
{
|
||||
//Need add code to let guard support player
|
||||
AttackStart(u);
|
||||
//u->RemoveAurasByType(SPELL_AURA_MOD_STEALTH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GuardAI::EnterEvadeMode()
|
||||
{
|
||||
if (!me->isAlive())
|
||||
{
|
||||
DEBUG_LOG("Creature stopped attacking because he is dead [guid=%u]", me->GetGUIDLow());
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
|
||||
i_state = STATE_NORMAL;
|
||||
|
||||
i_victimGuid = 0;
|
||||
me->CombatStop(true);
|
||||
me->DeleteThreatList();
|
||||
return;
|
||||
}
|
||||
|
||||
Unit* victim = ObjectAccessor::GetUnit(*me, i_victimGuid);
|
||||
|
||||
if (!victim)
|
||||
{
|
||||
DEBUG_LOG("Creature stopped attacking because victim does not exist [guid=%u]", me->GetGUIDLow());
|
||||
}
|
||||
else if (!victim ->isAlive())
|
||||
{
|
||||
DEBUG_LOG("Creature stopped attacking because victim is dead [guid=%u]", me->GetGUIDLow());
|
||||
}
|
||||
else if (victim ->HasStealthAura())
|
||||
{
|
||||
DEBUG_LOG("Creature stopped attacking because victim is using stealth [guid=%u]", me->GetGUIDLow());
|
||||
}
|
||||
else if (victim ->isInFlight())
|
||||
{
|
||||
DEBUG_LOG("Creature stopped attacking because victim is flying away [guid=%u]", me->GetGUIDLow());
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_LOG("Creature stopped attacking because victim outran him [guid=%u]", me->GetGUIDLow());
|
||||
}
|
||||
|
||||
me->RemoveAllAuras();
|
||||
me->DeleteThreatList();
|
||||
i_victimGuid = 0;
|
||||
me->CombatStop(true);
|
||||
i_state = STATE_NORMAL;
|
||||
|
||||
// Remove TargetedMovementGenerator from MotionMaster stack list, and add HomeMovementGenerator instead
|
||||
if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE)
|
||||
me->GetMotionMaster()->MoveTargetedHome();
|
||||
}
|
||||
|
||||
void GuardAI::UpdateAI(const uint32 /*diff*/)
|
||||
{
|
||||
// update i_victimGuid if me->getVictim() !=0 and changed
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
i_victimGuid = me->getVictim()->GetGUID();
|
||||
|
||||
if (me->isAttackReady())
|
||||
{
|
||||
if (me->IsWithinMeleeRange(me->getVictim()))
|
||||
{
|
||||
me->AttackerStateUpdate(me->getVictim());
|
||||
me->resetAttackTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool GuardAI::IsVisible(Unit *pl) const
|
||||
{
|
||||
return me->IsWithinDist(pl,sWorld.getConfig(CONFIG_SIGHT_GUARDER))
|
||||
&& pl->isVisibleForOrDetect(me,true);
|
||||
}
|
||||
|
||||
void GuardAI::JustDied(Unit *killer)
|
||||
{
|
||||
if (Player* pkiller = killer->GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
me->SendZoneUnderAttackMessage(pkiller);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef TRINITY_GUARDAI_H
|
||||
#define TRINITY_GUARDAI_H
|
||||
|
||||
#include "CreatureAI.h"
|
||||
#include "Timer.h"
|
||||
|
||||
class Creature;
|
||||
|
||||
class GuardAI : public CreatureAI
|
||||
{
|
||||
enum GuardState
|
||||
{
|
||||
STATE_NORMAL = 1,
|
||||
STATE_LOOK_AT_VICTIM = 2
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
explicit GuardAI(Creature *c);
|
||||
|
||||
void MoveInLineOfSight(Unit *);
|
||||
void EnterEvadeMode();
|
||||
void JustDied(Unit *);
|
||||
bool IsVisible(Unit *) const;
|
||||
|
||||
void UpdateAI(const uint32);
|
||||
static int Permissible(const Creature *);
|
||||
|
||||
private:
|
||||
uint64 i_victimGuid;
|
||||
GuardState i_state;
|
||||
TimeTracker i_tracker;
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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(const uint32)
|
||||
{
|
||||
if (me->isInCombat() && me->getAttackers().empty())
|
||||
EnterEvadeMode();
|
||||
}
|
||||
|
||||
void PossessedAI::AttackStart(Unit *target)
|
||||
{
|
||||
me->Attack(target, true);
|
||||
}
|
||||
|
||||
void PossessedAI::UpdateAI(const uint32 /*diff*/)
|
||||
{
|
||||
if (me->getVictim())
|
||||
{
|
||||
if (!me->canAttack(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 * /*done_by*/, uint32 &)
|
||||
{
|
||||
if (!me->hasUnitState(UNIT_STAT_FLEEING))
|
||||
me->SetControlled(true, UNIT_STAT_FLEEING);
|
||||
}
|
||||
|
||||
void CritterAI::EnterEvadeMode()
|
||||
{
|
||||
if (me->hasUnitState(UNIT_STAT_FLEEING))
|
||||
me->SetControlled(false, UNIT_STAT_FLEEING);
|
||||
CreatureAI::EnterEvadeMode();
|
||||
}
|
||||
|
||||
void TriggerAI::IsSummonedBy(Unit *summoner)
|
||||
{
|
||||
if (me->m_spells[0])
|
||||
me->CastSpell(me, me->m_spells[0], false, 0, 0, summoner->GetGUID());
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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(const 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(const 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(const 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) {}
|
||||
|
||||
void DamageTaken(Unit *done_by, uint32 & /*damage*/);
|
||||
void EnterEvadeMode();
|
||||
};
|
||||
|
||||
class TriggerAI : public NullCreatureAI
|
||||
{
|
||||
public:
|
||||
explicit TriggerAI(Creature *c) : NullCreatureAI(c) {}
|
||||
void IsSummonedBy(Unit *summoner);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,471 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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"
|
||||
|
||||
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)
|
||||
{
|
||||
m_AllySet.clear();
|
||||
UpdateAllies();
|
||||
}
|
||||
|
||||
void PetAI::EnterEvadeMode()
|
||||
{
|
||||
}
|
||||
|
||||
bool PetAI::_needToStop() const
|
||||
{
|
||||
// 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;
|
||||
|
||||
return !me->canAttack(me->getVictim());
|
||||
}
|
||||
|
||||
void PetAI::_stopAttack()
|
||||
{
|
||||
if (!me->isAlive())
|
||||
{
|
||||
DEBUG_LOG("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->GetCharmInfo()->SetIsCommandAttack(false);
|
||||
HandleReturnMovement();
|
||||
}
|
||||
|
||||
void PetAI::UpdateAI(const uint32 diff)
|
||||
{
|
||||
if (!me->isAlive())
|
||||
return;
|
||||
|
||||
Unit* owner = me->GetCharmerOrOwner();
|
||||
|
||||
if (m_updateAlliesTimer <= diff)
|
||||
// UpdateAllies self set update timer
|
||||
UpdateAllies();
|
||||
else
|
||||
m_updateAlliesTimer -= diff;
|
||||
|
||||
// me->getVictim() can't be used for check in case stop fighting, me->getVictim() clear at Unit death etc.
|
||||
if (me->getVictim())
|
||||
{
|
||||
if (_needToStop())
|
||||
{
|
||||
DEBUG_LOG("Pet AI stoped attacking [guid=%u]", me->GetGUIDLow());
|
||||
_stopAttack();
|
||||
return;
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
else if (owner && me->GetCharmInfo()) //no victim
|
||||
{
|
||||
Unit *nextTarget = SelectNextTarget();
|
||||
|
||||
if (nextTarget)
|
||||
AttackStart(nextTarget);
|
||||
else
|
||||
HandleReturnMovement();
|
||||
}
|
||||
else if (owner && !me->hasUnitState(UNIT_STAT_FOLLOW)) // no charm info and no victim
|
||||
me->GetMotionMaster()->MoveFollow(owner,PET_FOLLOW_DIST, me->GetFollowAngle());
|
||||
|
||||
if (!me->GetCharmInfo())
|
||||
return;
|
||||
|
||||
// Autocast (casted only in combat or persistent spells in any state)
|
||||
if (me->GetGlobalCooldown() == 0 && !me->hasUnitState(UNIT_STAT_CASTING))
|
||||
{
|
||||
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;
|
||||
|
||||
SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellID);
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
// ignore some combinations of combat state and combat/noncombat spells
|
||||
if (!me->getVictim())
|
||||
{
|
||||
// ignore attacking spells, and allow only self/around spells
|
||||
if (!IsPositiveSpell(spellInfo->Id))
|
||||
continue;
|
||||
|
||||
// non combat spells allowed
|
||||
// only pet spells have IsNonCombatSpell and not fit this reqs:
|
||||
// Consume Shadows, Lesser Invisibility, so ignore checks for its
|
||||
if (!IsNonCombatSpell(spellInfo))
|
||||
{
|
||||
// allow only spell without spell cost or with spell cost but not duration limit
|
||||
int32 duration = GetSpellDuration(spellInfo);
|
||||
if ((spellInfo->manaCost || spellInfo->ManaCostPercentage || spellInfo->manaPerSecond) && duration > 0)
|
||||
continue;
|
||||
|
||||
// allow only spell without cooldown > duration
|
||||
int32 cooldown = GetSpellRecoveryTime(spellInfo);
|
||||
if (cooldown >= 0 && duration >= 0 && cooldown > duration)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// just ignore non-combat spells
|
||||
if (IsNonCombatSpell(spellInfo))
|
||||
continue;
|
||||
}
|
||||
|
||||
Spell *spell = new Spell(me, spellInfo, false, 0);
|
||||
|
||||
// Fix to allow pets on STAY to autocast
|
||||
if (me->getVictim() && _CanAttack(me->getVictim()) && spell->CanAutoCast(me->getVictim()))
|
||||
{
|
||||
targetSpellStore.push_back(std::make_pair<Unit*, Spell*>(me->getVictim(), spell));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool spellUsed = false;
|
||||
for (std::set<uint64>::const_iterator tar = m_AllySet.begin(); tar != m_AllySet.end(); ++tar)
|
||||
{
|
||||
Unit* Target = ObjectAccessor::GetUnit(*me,*tar);
|
||||
|
||||
//only buff targets that are in combat, unless the spell can only be cast while out of combat
|
||||
if (!Target)
|
||||
continue;
|
||||
|
||||
if (spell->CanAutoCast(Target))
|
||||
{
|
||||
targetSpellStore.push_back(std::make_pair<Unit*, Spell*>(Target, spell));
|
||||
spellUsed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!spellUsed)
|
||||
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->AddCreatureSpellCooldown(spell->m_spellInfo->Id);
|
||||
|
||||
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 *pGroup = NULL;
|
||||
|
||||
m_updateAlliesTimer = 10*IN_MILISECONDS; //update friendly targets every 10 seconds, lesser checks increase performance
|
||||
|
||||
if (!owner)
|
||||
return;
|
||||
else if (owner->GetTypeId() == TYPEID_PLAYER)
|
||||
pGroup = owner->ToPlayer()->GetGroup();
|
||||
|
||||
//only pet and owner/not in group->ok
|
||||
if (m_AllySet.size() == 2 && !pGroup)
|
||||
return;
|
||||
//owner is in group; group members filled in already (no raid -> subgroupcount = whole count)
|
||||
if (pGroup && !pGroup->isRaidGroup() && m_AllySet.size() == (pGroup->GetMembersCount() + 2))
|
||||
return;
|
||||
|
||||
m_AllySet.clear();
|
||||
m_AllySet.insert(me->GetGUID());
|
||||
if (pGroup) //add group
|
||||
{
|
||||
for (GroupReference *itr = pGroup->GetFirstMember(); itr != NULL; itr = itr->next())
|
||||
{
|
||||
Player* Target = itr->getSource();
|
||||
if (!Target || !pGroup->SameSubGroup((Player*)owner, 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;
|
||||
|
||||
// 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->GetCharmInfo()->SetIsCommandAttack(false);
|
||||
|
||||
Unit *nextTarget = SelectNextTarget();
|
||||
|
||||
if (nextTarget)
|
||||
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;
|
||||
|
||||
// We can attack, should we chase or not?
|
||||
if (me->GetCharmInfo()->HasCommandState(COMMAND_FOLLOW))
|
||||
DoAttack(target,true); // FOLLOW, attack with chase
|
||||
else
|
||||
{
|
||||
if (me->GetCharmInfo()->IsCommandAttack())
|
||||
DoAttack(target,true); // STAY or FOLLOW, player clicked "attack" so attack with chase
|
||||
else
|
||||
DoAttack(target,false); // STAY, target in range, attack not clicked so attack without chase
|
||||
}
|
||||
}
|
||||
|
||||
Unit *PetAI::SelectNextTarget()
|
||||
{
|
||||
// Provides next target selection after current target death
|
||||
|
||||
// Passive pets don't do next target selection
|
||||
if (me->HasReactState(REACT_PASSIVE))
|
||||
return NULL;
|
||||
|
||||
// Check pet's attackers first to prevent dragging mobs back
|
||||
// to owner
|
||||
if (me->getAttackerForHelper())
|
||||
return me->getAttackerForHelper();
|
||||
|
||||
// Check owner's attackers if pet didn't have any
|
||||
if (me->GetCharmerOrOwner()->getAttackerForHelper())
|
||||
return me->GetCharmerOrOwner()->getAttackerForHelper();
|
||||
|
||||
// 3.0.2 - Pets now start attacking their owners target in defensive mode as soon as the hunter does
|
||||
if (me->GetCharmerOrOwner()->getVictim())
|
||||
return me->GetCharmerOrOwner()->getVictim();
|
||||
|
||||
// Default
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void PetAI::HandleReturnMovement()
|
||||
{
|
||||
// Handles moving the pet back to stay or owner
|
||||
|
||||
if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY))
|
||||
{
|
||||
if (!me->GetCharmInfo()->IsAtStay() && !me->GetCharmInfo()->IsReturning())
|
||||
{
|
||||
// Return to previous position where stay was clicked
|
||||
if (!me->GetCharmInfo()->IsCommandAttack())
|
||||
{
|
||||
float x,y,z;
|
||||
|
||||
me->GetCharmInfo()->GetStayPosition(x, y, z);
|
||||
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->GetCharmInfo()->IsCommandAttack())
|
||||
{
|
||||
me->GetCharmInfo()->SetIsReturning(true);
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveFollow(me->GetCharmerOrOwner(), PET_FOLLOW_DIST, me->GetFollowAngle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PetAI::DoAttack(Unit *target, bool chase)
|
||||
{
|
||||
// Handles attack with or without chase and also resets all
|
||||
// PetAI flags for next update / creature kill
|
||||
|
||||
// me->GetCharmInfo()->SetIsCommandAttack(false);
|
||||
|
||||
// The following conditions are true if chase == true
|
||||
// (Follow && (Aggressive || Defensive))
|
||||
// ((Stay || Follow) && (Passive && player clicked attack))
|
||||
|
||||
if (chase)
|
||||
{
|
||||
if (me->Attack(target,true))
|
||||
{
|
||||
me->GetCharmInfo()->SetIsAtStay(false);
|
||||
me->GetCharmInfo()->SetIsFollowing(false);
|
||||
me->GetCharmInfo()->SetIsReturning(false);
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveChase(target);
|
||||
}
|
||||
}
|
||||
else // (Stay && ((Aggressive || Defensive) && In Melee Range)))
|
||||
{
|
||||
me->GetCharmInfo()->SetIsAtStay(true);
|
||||
me->GetCharmInfo()->SetIsFollowing(false);
|
||||
me->GetCharmInfo()->SetIsReturning(false);
|
||||
me->Attack(target,true);
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
me->GetCharmInfo()->SetIsAtStay(true);
|
||||
me->GetCharmInfo()->SetIsReturning(false);
|
||||
me->GetCharmInfo()->SetIsFollowing(false);
|
||||
me->GetCharmInfo()->SetIsCommandAttack(false);
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TARGETED_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())
|
||||
{
|
||||
me->GetCharmInfo()->SetIsAtStay(false);
|
||||
me->GetCharmInfo()->SetIsReturning(false);
|
||||
me->GetCharmInfo()->SetIsFollowing(true);
|
||||
me->GetCharmInfo()->SetIsCommandAttack(false);
|
||||
me->addUnitState(UNIT_STAT_FOLLOW);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool PetAI::_CanAttack(Unit *target)
|
||||
{
|
||||
// Evaluates wether a pet can attack a specific
|
||||
// target based on CommandState, ReactState and other flags
|
||||
|
||||
// Returning - check first since pets returning ignore attacks
|
||||
if (me->GetCharmInfo()->IsReturning())
|
||||
return false;
|
||||
|
||||
// Passive - check now so we don't have to worry about passive in later checks
|
||||
if (me->HasReactState(REACT_PASSIVE))
|
||||
return me->GetCharmInfo()->IsCommandAttack();
|
||||
|
||||
// Pets commanded to attack should not stop their approach if attacked by another creature
|
||||
if (me->getVictim() && (me->getVictim() != target))
|
||||
return !me->GetCharmInfo()->IsCommandAttack();
|
||||
|
||||
// From this point on, pet will always be either aggressive or defensive
|
||||
|
||||
// Stay - can attack if target is within range or commanded to
|
||||
if (me->GetCharmInfo()->HasCommandState(COMMAND_STAY))
|
||||
return (me->IsWithinMeleeRange(target, MIN_MELEE_REACH) || me->GetCharmInfo()->IsCommandAttack());
|
||||
|
||||
// Follow
|
||||
if (me->GetCharmInfo()->HasCommandState(COMMAND_FOLLOW))
|
||||
return true;
|
||||
|
||||
// default, though we shouldn't ever get here
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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 EnterEvadeMode();
|
||||
void JustDied(Unit * /*who*/) { _stopAttack(); }
|
||||
|
||||
void UpdateAI(const uint32);
|
||||
static int Permissible(const Creature *);
|
||||
|
||||
void KilledUnit(Unit * /*victim*/);
|
||||
void AttackStart(Unit *target);
|
||||
void MovementInform(uint32 moveType, uint32 data);
|
||||
|
||||
private:
|
||||
bool _isVisible(Unit *) const;
|
||||
bool _needToStop(void) const;
|
||||
void _stopAttack(void);
|
||||
|
||||
void UpdateAllies();
|
||||
|
||||
TimeTracker i_tracker;
|
||||
bool inCombat;
|
||||
std::set<uint64> m_AllySet;
|
||||
uint32 m_updateAlliesTimer;
|
||||
|
||||
Unit *SelectNextTarget();
|
||||
void HandleReturnMovement();
|
||||
void DoAttack(Unit *target, bool chase);
|
||||
bool _CanAttack(Unit *target);
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "ByteBuffer.h"
|
||||
#include "ReactorAI.h"
|
||||
#include "Errors.h"
|
||||
#include "Log.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "CreatureAIImpl.h"
|
||||
|
||||
#define REACTOR_VISIBLE_RANGE (26.46f)
|
||||
|
||||
int
|
||||
ReactorAI::Permissible(const Creature *creature)
|
||||
{
|
||||
if (creature->isCivilian() || creature->IsNeutralToAll())
|
||||
return PERMIT_BASE_REACTIVE;
|
||||
|
||||
return PERMIT_BASE_NO;
|
||||
}
|
||||
|
||||
void
|
||||
ReactorAI::MoveInLineOfSight(Unit *)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ReactorAI::UpdateAI(const uint32 /*time_diff*/)
|
||||
{
|
||||
// update i_victimGuid if me->getVictim() !=0 and changed
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
if (me->isAttackReady())
|
||||
{
|
||||
if (me->IsWithinMeleeRange(me->getVictim()))
|
||||
{
|
||||
me->AttackerStateUpdate(me->getVictim());
|
||||
me->resetAttackTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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(const uint32);
|
||||
static int Permissible(const Creature *);
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,740 @@
|
||||
/* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
*
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
#include "ScriptedPch.h"
|
||||
#include "Item.h"
|
||||
#include "Spell.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "TemporarySummon.h"
|
||||
|
||||
// Spell summary for ScriptedAI::SelectSpell
|
||||
struct TSpellSummary
|
||||
{
|
||||
uint8 Targets; // set of enum SelectTarget
|
||||
uint8 Effects; // set of enum SelectEffect
|
||||
} *SpellSummary;
|
||||
|
||||
void SummonList::DoZoneInCombat(uint32 entry)
|
||||
{
|
||||
for (iterator i = begin(); i != end();)
|
||||
{
|
||||
Creature *summon = Unit::GetCreature(*me, *i);
|
||||
++i;
|
||||
if (summon && summon->IsAIEnabled
|
||||
&& (!entry || summon->GetEntry() == entry))
|
||||
summon->AI()->DoZoneInCombat();
|
||||
}
|
||||
}
|
||||
|
||||
void SummonList::DoAction(uint32 entry, uint32 info)
|
||||
{
|
||||
for (iterator i = begin(); i != end();)
|
||||
{
|
||||
Creature *summon = Unit::GetCreature(*me, *i);
|
||||
++i;
|
||||
if (summon && summon->IsAIEnabled
|
||||
&& (!entry || summon->GetEntry() == entry))
|
||||
summon->AI()->DoAction(info);
|
||||
}
|
||||
}
|
||||
|
||||
void SummonList::DespawnEntry(uint32 entry)
|
||||
{
|
||||
for (iterator i = begin(); i != end();)
|
||||
{
|
||||
Creature *summon = Unit::GetCreature(*me, *i);
|
||||
if (!summon)
|
||||
erase(i++);
|
||||
else if (summon->GetEntry() == entry)
|
||||
{
|
||||
erase(i++);
|
||||
summon->setDeathState(JUST_DIED);
|
||||
summon->RemoveCorpse();
|
||||
}
|
||||
else
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
void SummonList::DespawnAll()
|
||||
{
|
||||
while (!empty())
|
||||
{
|
||||
Creature *summon = Unit::GetCreature(*me, *begin());
|
||||
if (!summon)
|
||||
erase(begin());
|
||||
else
|
||||
{
|
||||
erase(begin());
|
||||
if (summon->isSummon())
|
||||
{
|
||||
summon->DestroyForNearbyPlayers();
|
||||
CAST_SUM(summon)->UnSummon();
|
||||
}
|
||||
else
|
||||
summon->DisappearAndDie();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScriptedAI::ScriptedAI(Creature* pCreature) : CreatureAI(pCreature),
|
||||
me(pCreature),
|
||||
IsFleeing(false),
|
||||
m_bCombatMovement(true),
|
||||
m_uiEvadeCheckCooldown(2500)
|
||||
{
|
||||
m_heroicMode = me->GetMap()->IsHeroic();
|
||||
m_difficulty = Difficulty(me->GetMap()->GetSpawnMode());
|
||||
}
|
||||
|
||||
void ScriptedAI::AttackStartNoMove(Unit* pWho)
|
||||
{
|
||||
if (!pWho)
|
||||
return;
|
||||
|
||||
if (me->Attack(pWho, false))
|
||||
DoStartNoMovement(pWho);
|
||||
}
|
||||
|
||||
void ScriptedAI::UpdateAI(const uint32 /*uiDiff*/)
|
||||
{
|
||||
//Check if we have a current target
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
if (me->isAttackReady())
|
||||
{
|
||||
//If we are within range melee the target
|
||||
if (me->IsWithinMeleeRange(me->getVictim()))
|
||||
{
|
||||
me->AttackerStateUpdate(me->getVictim());
|
||||
me->resetAttackTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptedAI::DoStartMovement(Unit* pVictim, float fDistance, float fAngle)
|
||||
{
|
||||
if (pVictim)
|
||||
me->GetMotionMaster()->MoveChase(pVictim, fDistance, fAngle);
|
||||
}
|
||||
|
||||
void ScriptedAI::DoStartNoMovement(Unit* pVictim)
|
||||
{
|
||||
if (!pVictim)
|
||||
return;
|
||||
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
}
|
||||
|
||||
void ScriptedAI::DoStopAttack()
|
||||
{
|
||||
if (me->getVictim())
|
||||
me->AttackStop();
|
||||
}
|
||||
|
||||
void ScriptedAI::DoCastSpell(Unit* pTarget, SpellEntry const* pSpellInfo, bool bTriggered)
|
||||
{
|
||||
if (!pTarget || me->IsNonMeleeSpellCasted(false))
|
||||
return;
|
||||
|
||||
me->StopMoving();
|
||||
me->CastSpell(pTarget, pSpellInfo, bTriggered);
|
||||
}
|
||||
|
||||
void ScriptedAI::DoPlaySoundToSet(WorldObject* pSource, uint32 uiSoundId)
|
||||
{
|
||||
if (!pSource)
|
||||
return;
|
||||
|
||||
if (!GetSoundEntriesStore()->LookupEntry(uiSoundId))
|
||||
{
|
||||
error_log("TSCR: Invalid soundId %u used in DoPlaySoundToSet (Source: TypeId %u, GUID %u)", uiSoundId, pSource->GetTypeId(), pSource->GetGUIDLow());
|
||||
return;
|
||||
}
|
||||
|
||||
pSource->PlayDirectSound(uiSoundId);
|
||||
}
|
||||
|
||||
Creature* ScriptedAI::DoSpawnCreature(uint32 uiId, float fX, float fY, float fZ, float fAngle, uint32 uiType, uint32 uiDespawntime)
|
||||
{
|
||||
return me->SummonCreature(uiId, me->GetPositionX()+fX, me->GetPositionY()+fY, me->GetPositionZ()+fZ, fAngle, (TempSummonType)uiType, uiDespawntime);
|
||||
}
|
||||
|
||||
Unit* ScriptedAI::SelectUnit(SelectAggroTarget pTarget, uint32 uiPosition)
|
||||
{
|
||||
//ThreatList m_threatlist;
|
||||
std::list<HostileReference*>& threatlist = me->getThreatManager().getThreatList();
|
||||
std::list<HostileReference*>::iterator itr = threatlist.begin();
|
||||
std::list<HostileReference*>::reverse_iterator ritr = threatlist.rbegin();
|
||||
|
||||
if (uiPosition >= threatlist.size() || !threatlist.size())
|
||||
return NULL;
|
||||
|
||||
switch (pTarget)
|
||||
{
|
||||
case SELECT_TARGET_RANDOM:
|
||||
advance (itr , uiPosition + (rand() % (threatlist.size() - uiPosition)));
|
||||
return Unit::GetUnit((*me),(*itr)->getUnitGuid());
|
||||
break;
|
||||
|
||||
case SELECT_TARGET_TOPAGGRO:
|
||||
advance (itr , uiPosition);
|
||||
return Unit::GetUnit((*me),(*itr)->getUnitGuid());
|
||||
break;
|
||||
|
||||
case SELECT_TARGET_BOTTOMAGGRO:
|
||||
advance (ritr , uiPosition);
|
||||
return Unit::GetUnit((*me),(*ritr)->getUnitGuid());
|
||||
break;
|
||||
|
||||
default:
|
||||
return UnitAI::SelectTarget(pTarget, uiPosition);
|
||||
}
|
||||
}
|
||||
|
||||
SpellEntry const* ScriptedAI::SelectSpell(Unit* pTarget, uint32 uiSchool, uint32 uiMechanic, SelectTargetType selectTargets, uint32 uiPowerCostMin, uint32 uiPowerCostMax, float fRangeMin, float fRangeMax, SelectEffect selectEffects)
|
||||
{
|
||||
//No target so we can't cast
|
||||
if (!pTarget)
|
||||
return false;
|
||||
|
||||
//Silenced so we can't cast
|
||||
if (me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED))
|
||||
return false;
|
||||
|
||||
//Using the extended script system we first create a list of viable spells
|
||||
SpellEntry const* apSpell[CREATURE_MAX_SPELLS];
|
||||
memset(apSpell, 0, sizeof(SpellEntry*)*CREATURE_MAX_SPELLS);
|
||||
|
||||
uint32 uiSpellCount = 0;
|
||||
|
||||
SpellEntry const* pTempSpell;
|
||||
SpellRangeEntry const* pTempRange;
|
||||
|
||||
//Check if each spell is viable(set it to null if not)
|
||||
for (uint32 i = 0; i < CREATURE_MAX_SPELLS; i++)
|
||||
{
|
||||
pTempSpell = GetSpellStore()->LookupEntry(me->m_spells[i]);
|
||||
|
||||
//This spell doesn't exist
|
||||
if (!pTempSpell)
|
||||
continue;
|
||||
|
||||
// Targets and Effects checked first as most used restrictions
|
||||
//Check the spell targets if specified
|
||||
if (selectTargets && !(SpellSummary[me->m_spells[i]].Targets & (1 << (selectTargets-1))))
|
||||
continue;
|
||||
|
||||
//Check the type of spell if we are looking for a specific spell type
|
||||
if (selectEffects && !(SpellSummary[me->m_spells[i]].Effects & (1 << (selectEffects-1))))
|
||||
continue;
|
||||
|
||||
//Check for school if specified
|
||||
if (uiSchool && (pTempSpell->SchoolMask & uiSchool) == 0)
|
||||
continue;
|
||||
|
||||
//Check for spell mechanic if specified
|
||||
if (uiMechanic && pTempSpell->Mechanic != uiMechanic)
|
||||
continue;
|
||||
|
||||
//Make sure that the spell uses the requested amount of power
|
||||
if (uiPowerCostMin && pTempSpell->manaCost < uiPowerCostMin)
|
||||
continue;
|
||||
|
||||
if (uiPowerCostMax && pTempSpell->manaCost > uiPowerCostMax)
|
||||
continue;
|
||||
|
||||
//Continue if we don't have the mana to actually cast this spell
|
||||
if (pTempSpell->manaCost > me->GetPower((Powers)pTempSpell->powerType))
|
||||
continue;
|
||||
|
||||
//Get the Range
|
||||
pTempRange = GetSpellRangeStore()->LookupEntry(pTempSpell->rangeIndex);
|
||||
|
||||
//Spell has invalid range store so we can't use it
|
||||
if (!pTempRange)
|
||||
continue;
|
||||
|
||||
//Check if the spell meets our range requirements
|
||||
if (fRangeMin && me->GetSpellMinRangeForTarget(pTarget, pTempRange) < fRangeMin)
|
||||
continue;
|
||||
if (fRangeMax && me->GetSpellMaxRangeForTarget(pTarget, pTempRange) > fRangeMax)
|
||||
continue;
|
||||
|
||||
//Check if our target is in range
|
||||
if (me->IsWithinDistInMap(pTarget, me->GetSpellMinRangeForTarget(pTarget, pTempRange)) || !me->IsWithinDistInMap(pTarget, me->GetSpellMaxRangeForTarget(pTarget, pTempRange)))
|
||||
continue;
|
||||
|
||||
//All good so lets add it to the spell list
|
||||
apSpell[uiSpellCount] = pTempSpell;
|
||||
++uiSpellCount;
|
||||
}
|
||||
|
||||
//We got our usable spells so now lets randomly pick one
|
||||
if (!uiSpellCount)
|
||||
return NULL;
|
||||
|
||||
return apSpell[rand()%uiSpellCount];
|
||||
}
|
||||
|
||||
bool ScriptedAI::CanCast(Unit* pTarget, SpellEntry const* pSpell, bool bTriggered)
|
||||
{
|
||||
//No target so we can't cast
|
||||
if (!pTarget || !pSpell)
|
||||
return false;
|
||||
|
||||
//Silenced so we can't cast
|
||||
if (!bTriggered && me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED))
|
||||
return false;
|
||||
|
||||
//Check for power
|
||||
if (!bTriggered && me->GetPower((Powers)pSpell->powerType) < pSpell->manaCost)
|
||||
return false;
|
||||
|
||||
SpellRangeEntry const* pTempRange = GetSpellRangeStore()->LookupEntry(pSpell->rangeIndex);
|
||||
|
||||
//Spell has invalid range store so we can't use it
|
||||
if (!pTempRange)
|
||||
return false;
|
||||
|
||||
//Unit is out of range of this spell
|
||||
if (me->IsInRange(pTarget, me->GetSpellMinRangeForTarget(pTarget, pTempRange), me->GetSpellMaxRangeForTarget(pTarget, pTempRange)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FillSpellSummary()
|
||||
{
|
||||
SpellSummary = new TSpellSummary[GetSpellStore()->GetNumRows()];
|
||||
|
||||
SpellEntry const* pTempSpell;
|
||||
|
||||
for (uint32 i = 0; i < GetSpellStore()->GetNumRows(); ++i)
|
||||
{
|
||||
SpellSummary[i].Effects = 0;
|
||||
SpellSummary[i].Targets = 0;
|
||||
|
||||
pTempSpell = GetSpellStore()->LookupEntry(i);
|
||||
//This spell doesn't exist
|
||||
if (!pTempSpell)
|
||||
continue;
|
||||
|
||||
for (uint32 j = 0; j < 3; ++j)
|
||||
{
|
||||
//Spell targets self
|
||||
if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_CASTER)
|
||||
SpellSummary[i].Targets |= 1 << (SELECT_TARGET_SELF-1);
|
||||
|
||||
//Spell targets a single enemy
|
||||
if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_ENEMY ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_DST_TARGET_ENEMY)
|
||||
SpellSummary[i].Targets |= 1 << (SELECT_TARGET_SINGLE_ENEMY-1);
|
||||
|
||||
//Spell targets AoE at enemy
|
||||
if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_AREA_ENEMY_SRC ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_AREA_ENEMY_DST ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_SRC_CASTER ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_DEST_DYNOBJ_ENEMY)
|
||||
SpellSummary[i].Targets |= 1 << (SELECT_TARGET_AOE_ENEMY-1);
|
||||
|
||||
//Spell targets an enemy
|
||||
if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_ENEMY ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_DST_TARGET_ENEMY ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_AREA_ENEMY_SRC ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_AREA_ENEMY_DST ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_SRC_CASTER ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_DEST_DYNOBJ_ENEMY)
|
||||
SpellSummary[i].Targets |= 1 << (SELECT_TARGET_ANY_ENEMY-1);
|
||||
|
||||
//Spell targets a single friend(or self)
|
||||
if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_CASTER ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_ALLY ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_PARTY)
|
||||
SpellSummary[i].Targets |= 1 << (SELECT_TARGET_SINGLE_FRIEND-1);
|
||||
|
||||
//Spell targets aoe friends
|
||||
if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_PARTY_CASTER ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_PARTY_TARGET ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_SRC_CASTER)
|
||||
SpellSummary[i].Targets |= 1 << (SELECT_TARGET_AOE_FRIEND-1);
|
||||
|
||||
//Spell targets any friend(or self)
|
||||
if (pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_CASTER ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_ALLY ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_TARGET_PARTY ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_PARTY_CASTER ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_UNIT_PARTY_TARGET ||
|
||||
pTempSpell->EffectImplicitTargetA[j] == TARGET_SRC_CASTER)
|
||||
SpellSummary[i].Targets |= 1 << (SELECT_TARGET_ANY_FRIEND-1);
|
||||
|
||||
//Make sure that this spell includes a damage effect
|
||||
if (pTempSpell->Effect[j] == SPELL_EFFECT_SCHOOL_DAMAGE ||
|
||||
pTempSpell->Effect[j] == SPELL_EFFECT_INSTAKILL ||
|
||||
pTempSpell->Effect[j] == SPELL_EFFECT_ENVIRONMENTAL_DAMAGE ||
|
||||
pTempSpell->Effect[j] == SPELL_EFFECT_HEALTH_LEECH)
|
||||
SpellSummary[i].Effects |= 1 << (SELECT_EFFECT_DAMAGE-1);
|
||||
|
||||
//Make sure that this spell includes a healing effect (or an apply aura with a periodic heal)
|
||||
if (pTempSpell->Effect[j] == SPELL_EFFECT_HEAL ||
|
||||
pTempSpell->Effect[j] == SPELL_EFFECT_HEAL_MAX_HEALTH ||
|
||||
pTempSpell->Effect[j] == SPELL_EFFECT_HEAL_MECHANICAL ||
|
||||
(pTempSpell->Effect[j] == SPELL_EFFECT_APPLY_AURA && pTempSpell->EffectApplyAuraName[j] == 8))
|
||||
SpellSummary[i].Effects |= 1 << (SELECT_EFFECT_HEALING-1);
|
||||
|
||||
//Make sure that this spell applies an aura
|
||||
if (pTempSpell->Effect[j] == SPELL_EFFECT_APPLY_AURA)
|
||||
SpellSummary[i].Effects |= 1 << (SELECT_EFFECT_AURA-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptedAI::DoResetThreat()
|
||||
{
|
||||
if (!me->CanHaveThreatList() || me->getThreatManager().isThreatListEmpty())
|
||||
{
|
||||
error_log("TSCR: DoResetThreat called for creature that either cannot have threat list or has empty threat list (me entry = %d)", me->GetEntry());
|
||||
return;
|
||||
}
|
||||
|
||||
std::list<HostileReference*>& threatlist = me->getThreatManager().getThreatList();
|
||||
|
||||
for (std::list<HostileReference*>::iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
{
|
||||
Unit* pUnit = Unit::GetUnit((*me), (*itr)->getUnitGuid());
|
||||
|
||||
if (pUnit && DoGetThreat(pUnit))
|
||||
DoModifyThreatPercent(pUnit, -100);
|
||||
}
|
||||
}
|
||||
|
||||
float ScriptedAI::DoGetThreat(Unit* pUnit)
|
||||
{
|
||||
if (!pUnit) return 0.0f;
|
||||
return me->getThreatManager().getThreat(pUnit);
|
||||
}
|
||||
|
||||
void ScriptedAI::DoModifyThreatPercent(Unit* pUnit, int32 pct)
|
||||
{
|
||||
if (!pUnit) return;
|
||||
me->getThreatManager().modifyThreatPercent(pUnit, pct);
|
||||
}
|
||||
|
||||
void ScriptedAI::DoTeleportTo(float fX, float fY, float fZ, uint32 uiTime)
|
||||
{
|
||||
me->Relocate(fX, fY, fZ);
|
||||
me->SendMonsterMove(fX, fY, fZ, uiTime);
|
||||
}
|
||||
|
||||
void ScriptedAI::DoTeleportTo(const float fPos[4])
|
||||
{
|
||||
me->NearTeleportTo(fPos[0], fPos[1], fPos[2], fPos[3]);
|
||||
}
|
||||
|
||||
void ScriptedAI::DoTeleportPlayer(Unit* pUnit, float fX, float fY, float fZ, float fO)
|
||||
{
|
||||
if (!pUnit || pUnit->GetTypeId() != TYPEID_PLAYER)
|
||||
{
|
||||
if (pUnit)
|
||||
error_log("TSCR: Creature %u (Entry: %u) Tried to teleport non-player unit (Type: %u GUID: %u) to x: %f y:%f z: %f o: %f. Aborted.", me->GetGUID(), me->GetEntry(), pUnit->GetTypeId(), pUnit->GetGUID(), fX, fY, fZ, fO);
|
||||
return;
|
||||
}
|
||||
|
||||
CAST_PLR(pUnit)->TeleportTo(pUnit->GetMapId(), fX, fY, fZ, fO, TELE_TO_NOT_LEAVE_COMBAT);
|
||||
}
|
||||
|
||||
void ScriptedAI::DoTeleportAll(float fX, float fY, float fZ, float fO)
|
||||
{
|
||||
Map *map = me->GetMap();
|
||||
if (!map->IsDungeon())
|
||||
return;
|
||||
|
||||
Map::PlayerList const &PlayerList = map->GetPlayers();
|
||||
for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
|
||||
if (Player* i_pl = i->getSource())
|
||||
if (i_pl->isAlive())
|
||||
i_pl->TeleportTo(me->GetMapId(), fX, fY, fZ, fO, TELE_TO_NOT_LEAVE_COMBAT);
|
||||
}
|
||||
|
||||
Unit* ScriptedAI::DoSelectLowestHpFriendly(float fRange, uint32 uiMinHPDiff)
|
||||
{
|
||||
Unit* pUnit = NULL;
|
||||
Trinity::MostHPMissingInRange u_check(me, fRange, uiMinHPDiff);
|
||||
Trinity::UnitLastSearcher<Trinity::MostHPMissingInRange> searcher(me, pUnit, u_check);
|
||||
me->VisitNearbyObject(fRange, searcher);
|
||||
|
||||
return pUnit;
|
||||
}
|
||||
|
||||
std::list<Creature*> ScriptedAI::DoFindFriendlyCC(float fRange)
|
||||
{
|
||||
std::list<Creature*> pList;
|
||||
Trinity::FriendlyCCedInRange u_check(me, fRange);
|
||||
Trinity::CreatureListSearcher<Trinity::FriendlyCCedInRange> searcher(me, pList, u_check);
|
||||
me->VisitNearbyObject(fRange, searcher);
|
||||
return pList;
|
||||
}
|
||||
|
||||
std::list<Creature*> ScriptedAI::DoFindFriendlyMissingBuff(float fRange, uint32 uiSpellid)
|
||||
{
|
||||
std::list<Creature*> pList;
|
||||
Trinity::FriendlyMissingBuffInRange u_check(me, fRange, uiSpellid);
|
||||
Trinity::CreatureListSearcher<Trinity::FriendlyMissingBuffInRange> searcher(me, pList, u_check);
|
||||
me->VisitNearbyObject(fRange, searcher);
|
||||
return pList;
|
||||
}
|
||||
|
||||
Player* ScriptedAI::GetPlayerAtMinimumRange(float fMinimumRange)
|
||||
{
|
||||
Player* pPlayer = NULL;
|
||||
|
||||
CellPair pair(Trinity::ComputeCellPair(me->GetPositionX(), me->GetPositionY()));
|
||||
Cell cell(pair);
|
||||
cell.data.Part.reserved = ALL_DISTRICT;
|
||||
cell.SetNoCreate();
|
||||
|
||||
Trinity::PlayerAtMinimumRangeAway check(me, fMinimumRange);
|
||||
Trinity::PlayerSearcher<Trinity::PlayerAtMinimumRangeAway> searcher(me, pPlayer, check);
|
||||
TypeContainerVisitor<Trinity::PlayerSearcher<Trinity::PlayerAtMinimumRangeAway>, GridTypeMapContainer> visitor(searcher);
|
||||
|
||||
cell.Visit(pair, visitor, *(me->GetMap()));
|
||||
|
||||
return pPlayer;
|
||||
}
|
||||
|
||||
void ScriptedAI::SetEquipmentSlots(bool bLoadDefault, int32 uiMainHand, int32 uiOffHand, int32 uiRanged)
|
||||
{
|
||||
if (bLoadDefault)
|
||||
{
|
||||
if (CreatureInfo const* pInfo = GetCreatureTemplateStore(me->GetEntry()))
|
||||
me->LoadEquipment(pInfo->equipmentId,true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (uiMainHand >= 0)
|
||||
me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0, uint32(uiMainHand));
|
||||
|
||||
if (uiOffHand >= 0)
|
||||
me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1, uint32(uiOffHand));
|
||||
|
||||
if (uiRanged >= 0)
|
||||
me->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 2, uint32(uiRanged));
|
||||
}
|
||||
|
||||
void ScriptedAI::SetCombatMovement(bool bCombatMove)
|
||||
{
|
||||
m_bCombatMovement = bCombatMove;
|
||||
}
|
||||
|
||||
enum eNPCs
|
||||
{
|
||||
NPC_BROODLORD = 12017,
|
||||
NPC_VOID_REAVER = 19516,
|
||||
NPC_JAN_ALAI = 23578,
|
||||
NPC_SARTHARION = 28860
|
||||
};
|
||||
|
||||
// Hacklike storage used for misc creatures that are expected to evade of outside of a certain area.
|
||||
// It is assumed the information is found elswehere and can be handled by the core. So far no luck finding such information/way to extract it.
|
||||
bool ScriptedAI::EnterEvadeIfOutOfCombatArea(const uint32 uiDiff)
|
||||
{
|
||||
if (m_uiEvadeCheckCooldown <= uiDiff)
|
||||
m_uiEvadeCheckCooldown = 2500;
|
||||
else
|
||||
{
|
||||
m_uiEvadeCheckCooldown -= uiDiff;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (me->IsInEvadeMode() || !me->getVictim())
|
||||
return false;
|
||||
|
||||
float fX = me->GetPositionX();
|
||||
float fY = me->GetPositionY();
|
||||
float fZ = me->GetPositionZ();
|
||||
|
||||
switch(me->GetEntry())
|
||||
{
|
||||
case NPC_BROODLORD: // broodlord (not move down stairs)
|
||||
if (fZ > 448.60f)
|
||||
return false;
|
||||
break;
|
||||
case NPC_VOID_REAVER: // void reaver (calculate from center of room)
|
||||
if (me->GetDistance2d(432.59f, 371.93f) < 105.0f)
|
||||
return false;
|
||||
break;
|
||||
case NPC_JAN_ALAI: // jan'alai (calculate by Z)
|
||||
if (fZ > 12.0f)
|
||||
return false;
|
||||
break;
|
||||
case NPC_SARTHARION: // sartharion (calculate box)
|
||||
if (fX > 3218.86f && fX < 3275.69f && fY < 572.40f && fY > 484.68f)
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
error_log("TSCR: EnterEvadeIfOutOfCombatArea used for creature entry %u, but does not have any definition.", me->GetEntry());
|
||||
return false;
|
||||
}
|
||||
|
||||
EnterEvadeMode();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Scripted_NoMovementAI::AttackStart(Unit* pWho)
|
||||
{
|
||||
if (!pWho)
|
||||
return;
|
||||
|
||||
if (me->Attack(pWho, true))
|
||||
{
|
||||
DoStartNoMovement(pWho);
|
||||
}
|
||||
}
|
||||
|
||||
BossAI::BossAI(Creature *c, uint32 id) : ScriptedAI(c)
|
||||
, bossId(id), summons(me), instance(c->GetInstanceData())
|
||||
, boundary(instance ? instance->GetBossBoundary(id) : NULL)
|
||||
{
|
||||
}
|
||||
|
||||
void BossAI::_Reset()
|
||||
{
|
||||
if (!me->isAlive())
|
||||
return;
|
||||
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
if (instance)
|
||||
instance->SetBossState(bossId, NOT_STARTED);
|
||||
}
|
||||
|
||||
void BossAI::_JustDied()
|
||||
{
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
if (instance)
|
||||
{
|
||||
instance->SetBossState(bossId, DONE);
|
||||
instance->SaveToDB();
|
||||
}
|
||||
}
|
||||
|
||||
void BossAI::_EnterCombat()
|
||||
{
|
||||
me->setActive(true);
|
||||
DoZoneInCombat();
|
||||
if (instance)
|
||||
instance->SetBossState(bossId, IN_PROGRESS);
|
||||
}
|
||||
|
||||
void BossAI::TeleportCheaters()
|
||||
{
|
||||
float x, y, z;
|
||||
me->GetPosition(x, y, z);
|
||||
std::list<HostileReference*> &m_threatlist = me->getThreatManager().getThreatList();
|
||||
for (std::list<HostileReference*>::iterator itr = m_threatlist.begin(); itr != m_threatlist.end(); ++itr)
|
||||
if ((*itr)->getTarget()->GetTypeId() == TYPEID_PLAYER && !CheckBoundary((*itr)->getTarget()))
|
||||
(*itr)->getTarget()->NearTeleportTo(x, y, z, 0);
|
||||
}
|
||||
|
||||
bool BossAI::CheckBoundary(Unit *who)
|
||||
{
|
||||
if (!boundary || !who)
|
||||
return true;
|
||||
|
||||
for (BossBoundaryMap::const_iterator itr = boundary->begin(); itr != boundary->end(); ++itr)
|
||||
{
|
||||
switch (itr->first)
|
||||
{
|
||||
case BOUNDARY_N:
|
||||
if (me->GetPositionX() > itr->second)
|
||||
return false;
|
||||
break;
|
||||
case BOUNDARY_S:
|
||||
if (me->GetPositionX() < itr->second)
|
||||
return false;
|
||||
break;
|
||||
case BOUNDARY_E:
|
||||
if (me->GetPositionY() < itr->second)
|
||||
return false;
|
||||
break;
|
||||
case BOUNDARY_W:
|
||||
if (me->GetPositionY() > itr->second)
|
||||
return false;
|
||||
break;
|
||||
case BOUNDARY_NW:
|
||||
if (me->GetPositionX() + me->GetPositionY() > itr->second)
|
||||
return false;
|
||||
break;
|
||||
case BOUNDARY_SE:
|
||||
if (me->GetPositionX() + me->GetPositionY() < itr->second)
|
||||
return false;
|
||||
break;
|
||||
case BOUNDARY_NE:
|
||||
if (me->GetPositionX() - me->GetPositionY() > itr->second)
|
||||
return false;
|
||||
break;
|
||||
case BOUNDARY_SW:
|
||||
if (me->GetPositionX() - me->GetPositionY() < itr->second)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BossAI::JustSummoned(Creature *summon)
|
||||
{
|
||||
summons.Summon(summon);
|
||||
if (me->isInCombat())
|
||||
DoZoneInCombat(summon);
|
||||
}
|
||||
|
||||
void BossAI::SummonedCreatureDespawn(Creature *summon)
|
||||
{
|
||||
summons.Despawn(summon);
|
||||
}
|
||||
|
||||
#define GOBJECT(x) (const_cast<GameObjectInfo*>(GetGameObjectInfo(x)))
|
||||
|
||||
void LoadOverridenSQLData()
|
||||
{
|
||||
GameObjectInfo *goInfo;
|
||||
|
||||
// Sunwell Plateau : Kalecgos : Spectral Rift
|
||||
goInfo = GOBJECT(187055);
|
||||
if (goInfo)
|
||||
if (goInfo->type == GAMEOBJECT_TYPE_GOOBER)
|
||||
goInfo->goober.lockId = 57; // need LOCKTYPE_QUICK_OPEN
|
||||
|
||||
// Naxxramas : Sapphiron Birth
|
||||
goInfo = GOBJECT(181356);
|
||||
if (goInfo)
|
||||
if (goInfo->type == GAMEOBJECT_TYPE_TRAP)
|
||||
goInfo->trap.radius = 50;
|
||||
}
|
||||
|
||||
// SD2 grid searchers.
|
||||
Creature *GetClosestCreatureWithEntry(WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange, bool bAlive)
|
||||
{
|
||||
return pSource->FindNearestCreature(uiEntry, fMaxSearchRange, bAlive);
|
||||
}
|
||||
GameObject *GetClosestGameObjectWithEntry(WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange)
|
||||
{
|
||||
return pSource->FindNearestGameObject(uiEntry, fMaxSearchRange);
|
||||
}
|
||||
void GetCreatureListWithEntryInGrid(std::list<Creature*>& lList, WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange)
|
||||
{
|
||||
return pSource->GetCreatureListWithEntryInGrid(lList, uiEntry, fMaxSearchRange);
|
||||
}
|
||||
void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& lList, WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange)
|
||||
{
|
||||
return pSource->GetGameObjectListWithEntryInGrid(lList, uiEntry, fMaxSearchRange);
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
*
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
#ifndef SC_CREATURE_H
|
||||
#define SC_CREATURE_H
|
||||
|
||||
#include "Creature.h"
|
||||
#include "CreatureAI.h"
|
||||
#include "CreatureAIImpl.h"
|
||||
#include "InstanceData.h"
|
||||
|
||||
#define SCRIPT_CAST_TYPE dynamic_cast
|
||||
//#define SCRIPT_CAST_TYPE static_cast
|
||||
|
||||
#define CAST_PLR(a) (SCRIPT_CAST_TYPE<Player*>(a))
|
||||
#define CAST_CRE(a) (SCRIPT_CAST_TYPE<Creature*>(a))
|
||||
#define CAST_SUM(a) (SCRIPT_CAST_TYPE<TempSummon*>(a))
|
||||
#define CAST_PET(a) (SCRIPT_CAST_TYPE<Pet*>(a))
|
||||
#define CAST_AI(a,b) (SCRIPT_CAST_TYPE<a*>(b))
|
||||
|
||||
#define GET_SPELL(a) (const_cast<SpellEntry*>(GetSpellStore()->LookupEntry(a)))
|
||||
|
||||
class ScriptedInstance;
|
||||
|
||||
class SummonList : public std::list<uint64>
|
||||
{
|
||||
public:
|
||||
explicit SummonList(Creature* creature) : me(creature) {}
|
||||
void Summon(Creature *summon) { push_back(summon->GetGUID()); }
|
||||
void Despawn(Creature *summon) { remove(summon->GetGUID()); }
|
||||
void DespawnEntry(uint32 entry);
|
||||
void DespawnAll();
|
||||
void DoAction(uint32 entry, uint32 info);
|
||||
void DoZoneInCombat(uint32 entry = 0);
|
||||
private:
|
||||
Creature *me;
|
||||
};
|
||||
|
||||
struct ScriptedAI : public CreatureAI
|
||||
{
|
||||
explicit ScriptedAI(Creature* pCreature);
|
||||
virtual ~ScriptedAI() {}
|
||||
|
||||
//*************
|
||||
//CreatureAI Functions
|
||||
//*************
|
||||
|
||||
void AttackStartNoMove(Unit *pTarget);
|
||||
|
||||
// Called at any Damage from any attacker (before damage apply)
|
||||
void DamageTaken(Unit* /*pDone_by*/, uint32& /*uiDamage*/) {}
|
||||
|
||||
//Called at World update tick
|
||||
void UpdateAI(const uint32);
|
||||
|
||||
//Called at creature death
|
||||
void JustDied(Unit* /*who*/){}
|
||||
|
||||
//Called at creature killing another unit
|
||||
void KilledUnit(Unit* /*who*/){}
|
||||
|
||||
// Called when the creature summon successfully other creature
|
||||
void JustSummoned(Creature*) {}
|
||||
|
||||
// Called when a summoned creature is despawned
|
||||
void SummonedCreatureDespawn(Creature*) {}
|
||||
|
||||
// Called when hit by a spell
|
||||
void SpellHit(Unit* /*caster*/, const SpellEntry * /*spell*/) {}
|
||||
|
||||
// Called when spell hits a target
|
||||
void SpellHitTarget(Unit * /*pTarget*/, const SpellEntry * /*spell*/) {}
|
||||
|
||||
//Called at waypoint reached or PointMovement end
|
||||
void MovementInform(uint32 /*type*/, uint32 /*id*/){}
|
||||
|
||||
// Called when AI is temporarily replaced or put back when possess is applied or removed
|
||||
void OnPossess(bool /*apply*/) {}
|
||||
|
||||
//*************
|
||||
// Variables
|
||||
//*************
|
||||
|
||||
//Pointer to creature we are manipulating
|
||||
Creature* me;
|
||||
|
||||
//For fleeing
|
||||
bool IsFleeing;
|
||||
|
||||
//*************
|
||||
//Pure virtual functions
|
||||
//*************
|
||||
|
||||
//Called at creature reset either by death or evade
|
||||
void Reset() {}
|
||||
|
||||
//Called at creature aggro either by MoveInLOS or Attack Start
|
||||
void EnterCombat(Unit* /*who*/) {}
|
||||
|
||||
//*************
|
||||
//AI Helper Functions
|
||||
//*************
|
||||
|
||||
//Start movement toward victim
|
||||
void DoStartMovement(Unit* pVictim, float fDistance = 0, float fAngle = 0);
|
||||
|
||||
//Start no movement on victim
|
||||
void DoStartNoMovement(Unit* pVictim);
|
||||
|
||||
//Stop attack of current victim
|
||||
void DoStopAttack();
|
||||
|
||||
//Cast spell by spell info
|
||||
void DoCastSpell(Unit* pTarget, SpellEntry const* pSpellInfo, bool bTriggered = false);
|
||||
|
||||
//Plays a sound to all nearby players
|
||||
void DoPlaySoundToSet(WorldObject* pSource, uint32 sound);
|
||||
|
||||
//Drops all threat to 0%. Does not remove players from the threat list
|
||||
void DoResetThreat();
|
||||
|
||||
float DoGetThreat(Unit* u);
|
||||
void DoModifyThreatPercent(Unit* pUnit, int32 pct);
|
||||
|
||||
void DoTeleportTo(float fX, float fY, float fZ, uint32 uiTime = 0);
|
||||
void DoTeleportTo(const float pos[4]);
|
||||
|
||||
void DoAction(const int32 /*param*/) {}
|
||||
|
||||
//Teleports a player without dropping threat (only teleports to same map)
|
||||
void DoTeleportPlayer(Unit* pUnit, float fX, float fY, float fZ, float fO);
|
||||
void DoTeleportAll(float fX, float fY, float fZ, float fO);
|
||||
|
||||
//Returns friendly unit with the most amount of hp missing from max hp
|
||||
Unit* DoSelectLowestHpFriendly(float fRange, uint32 uiMinHPDiff = 1);
|
||||
|
||||
//Returns a list of friendly CC'd units within range
|
||||
std::list<Creature*> DoFindFriendlyCC(float fRange);
|
||||
|
||||
//Returns a list of all friendly units missing a specific buff within range
|
||||
std::list<Creature*> DoFindFriendlyMissingBuff(float fRange, uint32 uiSpellId);
|
||||
|
||||
//Return a player with at least minimumRange from me
|
||||
Player* GetPlayerAtMinimumRange(float fMinimumRange);
|
||||
|
||||
//Spawns a creature relative to me
|
||||
Creature* DoSpawnCreature(uint32 uiId, float fX, float fY, float fZ, float fAngle, uint32 uiType, uint32 uiDespawntime);
|
||||
|
||||
//Selects a unit from the creature's current aggro list
|
||||
Unit* SelectUnit(SelectAggroTarget pTarget, uint32 uiPosition);
|
||||
|
||||
bool HealthBelowPct(uint32 pct) const { return me->GetHealth() * 100 < me->GetMaxHealth() * pct; }
|
||||
|
||||
//Returns spells that meet the specified criteria from the creatures spell list
|
||||
SpellEntry const* SelectSpell(Unit* Target, uint32 School, uint32 Mechanic, SelectTargetType Targets, uint32 PowerCostMin, uint32 PowerCostMax, float RangeMin, float RangeMax, SelectEffect Effect);
|
||||
|
||||
//Checks if you can cast the specified spell
|
||||
bool CanCast(Unit* pTarget, SpellEntry const* pSpell, bool bTriggered = false);
|
||||
|
||||
void SetEquipmentSlots(bool bLoadDefault, int32 uiMainHand = EQUIP_NO_CHANGE, int32 uiOffHand = EQUIP_NO_CHANGE, int32 uiRanged = EQUIP_NO_CHANGE);
|
||||
|
||||
//Generally used to control if MoveChase() is to be used or not in AttackStart(). Some creatures does not chase victims
|
||||
void SetCombatMovement(bool CombatMove);
|
||||
bool IsCombatMovement() { return m_bCombatMovement; }
|
||||
|
||||
bool EnterEvadeIfOutOfCombatArea(const uint32 uiDiff);
|
||||
|
||||
// return true for heroic mode. i.e.
|
||||
// - for dungeon in mode 10-heroic,
|
||||
// - for raid in mode 10-Heroic
|
||||
// - for raid in mode 25-heroic
|
||||
// DO NOT USE to check raid in mode 25-normal.
|
||||
bool IsHeroic() { return m_heroicMode; }
|
||||
|
||||
// return the dungeon or raid difficulty
|
||||
Difficulty getDifficulty() { return m_difficulty; }
|
||||
|
||||
template<class T> inline
|
||||
const T& DUNGEON_MODE(const T& normal5, const T& heroic10)
|
||||
{
|
||||
switch(m_difficulty)
|
||||
{
|
||||
case DUNGEON_DIFFICULTY_NORMAL:
|
||||
return normal5;
|
||||
case DUNGEON_DIFFICULTY_HEROIC:
|
||||
return heroic10;
|
||||
}
|
||||
|
||||
return heroic10;
|
||||
}
|
||||
|
||||
template<class T> inline
|
||||
const T& RAID_MODE(const T& normal10, const T& normal25)
|
||||
{
|
||||
switch(m_difficulty)
|
||||
{
|
||||
case RAID_DIFFICULTY_10MAN_NORMAL:
|
||||
return normal10;
|
||||
case RAID_DIFFICULTY_25MAN_NORMAL:
|
||||
return normal25;
|
||||
}
|
||||
|
||||
return normal25;
|
||||
}
|
||||
|
||||
template<class T> inline
|
||||
const T& RAID_MODE(const T& normal10, const T& normal25, const T& heroic10, const T& heroic25)
|
||||
{
|
||||
switch(m_difficulty)
|
||||
{
|
||||
case RAID_DIFFICULTY_10MAN_NORMAL:
|
||||
return normal10;
|
||||
case RAID_DIFFICULTY_25MAN_NORMAL:
|
||||
return normal25;
|
||||
case RAID_DIFFICULTY_10MAN_HEROIC:
|
||||
return heroic10;
|
||||
case RAID_DIFFICULTY_25MAN_HEROIC:
|
||||
return heroic25;
|
||||
}
|
||||
|
||||
return heroic25;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_bCombatMovement;
|
||||
uint32 m_uiEvadeCheckCooldown;
|
||||
|
||||
bool m_heroicMode;
|
||||
Difficulty m_difficulty;
|
||||
};
|
||||
|
||||
struct Scripted_NoMovementAI : public ScriptedAI
|
||||
{
|
||||
Scripted_NoMovementAI(Creature* creature) : ScriptedAI(creature) {}
|
||||
virtual ~Scripted_NoMovementAI() {}
|
||||
|
||||
//Called at each attack of me by any victim
|
||||
void AttackStart(Unit* who);
|
||||
};
|
||||
|
||||
struct BossAI : public ScriptedAI
|
||||
{
|
||||
BossAI(Creature *c, uint32 id);
|
||||
virtual ~BossAI() {}
|
||||
|
||||
const uint32 bossId;
|
||||
EventMap events;
|
||||
SummonList summons;
|
||||
InstanceData * const instance;
|
||||
const BossBoundaryMap * const boundary;
|
||||
|
||||
void JustSummoned(Creature *summon);
|
||||
void SummonedCreatureDespawn(Creature *summon);
|
||||
|
||||
void UpdateAI(const uint32 diff) = 0;
|
||||
|
||||
void Reset() { _Reset(); }
|
||||
void EnterCombat(Unit * /*who*/) { _EnterCombat(); }
|
||||
void JustDied(Unit * /*killer*/) { _JustDied(); }
|
||||
void JustReachedHome() { me->setActive(false); }
|
||||
|
||||
protected:
|
||||
void _Reset();
|
||||
void _EnterCombat();
|
||||
void _JustDied();
|
||||
void _JustReachedHome() { me->setActive(false); }
|
||||
|
||||
bool CheckInRoom()
|
||||
{
|
||||
if (CheckBoundary(me))
|
||||
return true;
|
||||
EnterEvadeMode();
|
||||
return false;
|
||||
}
|
||||
bool CheckBoundary(Unit *who);
|
||||
void TeleportCheaters();
|
||||
};
|
||||
|
||||
// SD2 grid searchers.
|
||||
Creature *GetClosestCreatureWithEntry(WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange, bool bAlive = true);
|
||||
GameObject *GetClosestGameObjectWithEntry(WorldObject *pSource, uint32 uiEntry, float fMaxSearchRange);
|
||||
void GetCreatureListWithEntryInGrid(std::list<Creature*>& lList, WorldObject* pSource, uint32 uiEntry, float fMaxSearchRange);
|
||||
void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& lList, WorldObject* pSource, uint32 uiEntry, float fMaxSearchRange);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,506 @@
|
||||
/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
/* ScriptData
|
||||
SDName: Npc_EscortAI
|
||||
SD%Complete: 100
|
||||
SDComment:
|
||||
SDCategory: Npc
|
||||
EndScriptData */
|
||||
|
||||
#include "ScriptedPch.h"
|
||||
#include "ScriptedEscortAI.h"
|
||||
|
||||
enum ePoints
|
||||
{
|
||||
POINT_LAST_POINT = 0xFFFFFF,
|
||||
POINT_HOME = 0xFFFFFE
|
||||
};
|
||||
|
||||
npc_escortAI::npc_escortAI(Creature* pCreature) : ScriptedAI(pCreature),
|
||||
m_uiPlayerGUID(0),
|
||||
MaxPlayerDistance(DEFAULT_MAX_PLAYER_DISTANCE),
|
||||
m_uiPlayerCheckTimer(1000),
|
||||
m_uiWPWaitTimer(2500),
|
||||
m_uiEscortState(STATE_ESCORT_NONE),
|
||||
m_bIsActiveAttacker(true),
|
||||
m_bIsRunning(false),
|
||||
DespawnAtEnd(true),
|
||||
DespawnAtFar(true),
|
||||
m_pQuestForEscort(NULL),
|
||||
m_bCanInstantRespawn(false),
|
||||
m_bCanReturnToStart(false),
|
||||
ScriptWP(false)
|
||||
{}
|
||||
|
||||
void npc_escortAI::AttackStart(Unit* pWho)
|
||||
{
|
||||
if (!pWho)
|
||||
return;
|
||||
|
||||
if (me->Attack(pWho, true))
|
||||
{
|
||||
if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == POINT_MOTION_TYPE)
|
||||
me->GetMotionMaster()->MovementExpired();
|
||||
|
||||
if (IsCombatMovement())
|
||||
me->GetMotionMaster()->MoveChase(pWho);
|
||||
}
|
||||
}
|
||||
|
||||
//see followerAI
|
||||
bool npc_escortAI::AssistPlayerInCombat(Unit* pWho)
|
||||
{
|
||||
if (!pWho || !pWho->getVictim())
|
||||
return false;
|
||||
|
||||
//experimental (unknown) flag not present
|
||||
if (!(me->GetCreatureInfo()->type_flags & CREATURE_TYPEFLAGS_UNK13))
|
||||
return false;
|
||||
|
||||
//not a player
|
||||
if (!pWho->getVictim()->GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
return false;
|
||||
|
||||
//never attack friendly
|
||||
if (me->IsFriendlyTo(pWho))
|
||||
return false;
|
||||
|
||||
//too far away and no free sight?
|
||||
if (me->IsWithinDistInMap(pWho, GetMaxPlayerDistance()) && me->IsWithinLOSInMap(pWho))
|
||||
{
|
||||
//already fighting someone?
|
||||
if (!me->getVictim())
|
||||
{
|
||||
AttackStart(pWho);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pWho->SetInCombatWith(me);
|
||||
me->AddThreat(pWho, 0.0f);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void npc_escortAI::MoveInLineOfSight(Unit* pWho)
|
||||
{
|
||||
if (!me->hasUnitState(UNIT_STAT_STUNNED) && pWho->isTargetableForAttack() && pWho->isInAccessiblePlaceFor(me))
|
||||
{
|
||||
if (HasEscortState(STATE_ESCORT_ESCORTING) && AssistPlayerInCombat(pWho))
|
||||
return;
|
||||
|
||||
if (!me->canFly() && me->GetDistanceZ(pWho) > CREATURE_Z_ATTACK_RANGE)
|
||||
return;
|
||||
|
||||
if (me->IsHostileTo(pWho))
|
||||
{
|
||||
float fAttackRadius = me->GetAttackDistance(pWho);
|
||||
if (me->IsWithinDistInMap(pWho, fAttackRadius) && me->IsWithinLOSInMap(pWho))
|
||||
{
|
||||
if (!me->getVictim())
|
||||
{
|
||||
pWho->RemoveAurasDueToSpell(SPELL_AURA_MOD_STEALTH);
|
||||
AttackStart(pWho);
|
||||
}
|
||||
else if (me->GetMap()->IsDungeon())
|
||||
{
|
||||
pWho->SetInCombatWith(me);
|
||||
me->AddThreat(pWho, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void npc_escortAI::JustDied(Unit* /*pKiller*/)
|
||||
{
|
||||
if (!HasEscortState(STATE_ESCORT_ESCORTING) || !m_uiPlayerGUID || !m_pQuestForEscort)
|
||||
return;
|
||||
|
||||
if (Player* pPlayer = GetPlayerForEscort())
|
||||
{
|
||||
if (Group* pGroup = pPlayer->GetGroup())
|
||||
{
|
||||
for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next())
|
||||
{
|
||||
if (Player* pMember = pRef->getSource())
|
||||
{
|
||||
if (pMember->GetQuestStatus(m_pQuestForEscort->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
|
||||
pMember->FailQuest(m_pQuestForEscort->GetQuestId());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pPlayer->GetQuestStatus(m_pQuestForEscort->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
|
||||
pPlayer->FailQuest(m_pQuestForEscort->GetQuestId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void npc_escortAI::JustRespawned()
|
||||
{
|
||||
m_uiEscortState = STATE_ESCORT_NONE;
|
||||
|
||||
if (!IsCombatMovement())
|
||||
SetCombatMovement(true);
|
||||
|
||||
//add a small delay before going to first waypoint, normal in near all cases
|
||||
m_uiWPWaitTimer = 2500;
|
||||
|
||||
if (me->getFaction() != me->GetCreatureInfo()->faction_A)
|
||||
me->RestoreFaction();
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
void npc_escortAI::ReturnToLastPoint()
|
||||
{
|
||||
float x, y, z, o;
|
||||
me->GetHomePosition(x, y, z, o);
|
||||
me->GetMotionMaster()->MovePoint(POINT_LAST_POINT, x, y, z);
|
||||
}
|
||||
|
||||
void npc_escortAI::EnterEvadeMode()
|
||||
{
|
||||
me->RemoveAllAuras();
|
||||
me->DeleteThreatList();
|
||||
me->CombatStop(true);
|
||||
me->SetLootRecipient(NULL);
|
||||
|
||||
if (HasEscortState(STATE_ESCORT_ESCORTING))
|
||||
{
|
||||
AddEscortState(STATE_ESCORT_RETURNING);
|
||||
ReturnToLastPoint();
|
||||
debug_log("TSCR: EscortAI has left combat and is now returning to last point");
|
||||
}
|
||||
else
|
||||
{
|
||||
me->GetMotionMaster()->MoveTargetedHome();
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool npc_escortAI::IsPlayerOrGroupInRange()
|
||||
{
|
||||
if (Player* pPlayer = GetPlayerForEscort())
|
||||
{
|
||||
if (Group* pGroup = pPlayer->GetGroup())
|
||||
{
|
||||
for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next())
|
||||
{
|
||||
Player* pMember = pRef->getSource();
|
||||
|
||||
if (pMember && me->IsWithinDistInMap(pMember, GetMaxPlayerDistance()))
|
||||
{
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (me->IsWithinDistInMap(pPlayer, GetMaxPlayerDistance()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void npc_escortAI::UpdateAI(const uint32 uiDiff)
|
||||
{
|
||||
//Waypoint Updating
|
||||
if (HasEscortState(STATE_ESCORT_ESCORTING) && !me->getVictim() && m_uiWPWaitTimer && !HasEscortState(STATE_ESCORT_RETURNING))
|
||||
{
|
||||
if (m_uiWPWaitTimer <= uiDiff)
|
||||
{
|
||||
//End of the line
|
||||
if (CurrentWP == WaypointList.end())
|
||||
{
|
||||
if (DespawnAtEnd)
|
||||
{
|
||||
debug_log("TSCR: EscortAI reached end of waypoints");
|
||||
|
||||
if (m_bCanReturnToStart)
|
||||
{
|
||||
float fRetX, fRetY, fRetZ;
|
||||
me->GetRespawnCoord(fRetX, fRetY, fRetZ);
|
||||
|
||||
me->GetMotionMaster()->MovePoint(POINT_HOME, fRetX, fRetY, fRetZ);
|
||||
|
||||
m_uiWPWaitTimer = 0;
|
||||
|
||||
debug_log("TSCR: EscortAI are returning home to spawn location: %u, %f, %f, %f", POINT_HOME, fRetX, fRetY, fRetZ);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_bCanInstantRespawn)
|
||||
{
|
||||
me->setDeathState(JUST_DIED);
|
||||
me->Respawn();
|
||||
}
|
||||
else
|
||||
me->ForcedDespawn();
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
debug_log("TSCR: EscortAI reached end of waypoints with Despawn off");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!HasEscortState(STATE_ESCORT_PAUSED))
|
||||
{
|
||||
me->GetMotionMaster()->MovePoint(CurrentWP->id, CurrentWP->x, CurrentWP->y, CurrentWP->z);
|
||||
debug_log("TSCR: EscortAI start waypoint %u (%f, %f, %f).", CurrentWP->id, CurrentWP->x, CurrentWP->y, CurrentWP->z);
|
||||
|
||||
WaypointStart(CurrentWP->id);
|
||||
|
||||
m_uiWPWaitTimer = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
m_uiWPWaitTimer -= uiDiff;
|
||||
}
|
||||
|
||||
//Check if player or any member of his group is within range
|
||||
if (HasEscortState(STATE_ESCORT_ESCORTING) && m_uiPlayerGUID && !me->getVictim() && !HasEscortState(STATE_ESCORT_RETURNING))
|
||||
{
|
||||
if (m_uiPlayerCheckTimer <= uiDiff)
|
||||
{
|
||||
if (DespawnAtFar && !IsPlayerOrGroupInRange())
|
||||
{
|
||||
debug_log("TSCR: EscortAI failed because player/group was to far away or not found");
|
||||
|
||||
if (m_bCanInstantRespawn)
|
||||
{
|
||||
me->setDeathState(JUST_DIED);
|
||||
me->Respawn();
|
||||
}
|
||||
else
|
||||
me->ForcedDespawn();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_uiPlayerCheckTimer = 1000;
|
||||
}
|
||||
else
|
||||
m_uiPlayerCheckTimer -= uiDiff;
|
||||
}
|
||||
|
||||
UpdateEscortAI(uiDiff);
|
||||
}
|
||||
|
||||
void npc_escortAI::UpdateEscortAI(const uint32 /*uiDiff*/)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
void npc_escortAI::MovementInform(uint32 uiMoveType, uint32 uiPointId)
|
||||
{
|
||||
if (uiMoveType != POINT_MOTION_TYPE || !HasEscortState(STATE_ESCORT_ESCORTING))
|
||||
return;
|
||||
|
||||
//Combat start position reached, continue waypoint movement
|
||||
if (uiPointId == POINT_LAST_POINT)
|
||||
{
|
||||
debug_log("TSCR: EscortAI has returned to original position before combat");
|
||||
|
||||
if (m_bIsRunning && me->HasUnitMovementFlag(MOVEMENTFLAG_WALK_MODE))
|
||||
me->RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE);
|
||||
else if (!m_bIsRunning && !me->HasUnitMovementFlag(MOVEMENTFLAG_WALK_MODE))
|
||||
me->AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE);
|
||||
|
||||
RemoveEscortState(STATE_ESCORT_RETURNING);
|
||||
|
||||
if (!m_uiWPWaitTimer)
|
||||
m_uiWPWaitTimer = 1;
|
||||
}
|
||||
else if (uiPointId == POINT_HOME)
|
||||
{
|
||||
debug_log("TSCR: EscortAI has returned to original home location and will continue from beginning of waypoint list.");
|
||||
|
||||
CurrentWP = WaypointList.begin();
|
||||
m_uiWPWaitTimer = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Make sure that we are still on the right waypoint
|
||||
if (CurrentWP->id != uiPointId)
|
||||
{
|
||||
error_log("TSCR ERROR: EscortAI reached waypoint out of order %u, expected %u, creature entry %u", uiPointId, CurrentWP->id, me->GetEntry());
|
||||
return;
|
||||
}
|
||||
|
||||
debug_log("TSCR: EscortAI Waypoint %u reached", CurrentWP->id);
|
||||
|
||||
//Call WP function
|
||||
WaypointReached(CurrentWP->id);
|
||||
|
||||
m_uiWPWaitTimer = CurrentWP->WaitTimeMs + 1;
|
||||
|
||||
++CurrentWP;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
void npc_escortAI::OnPossess(bool apply)
|
||||
{
|
||||
// We got possessed in the middle of being escorted, store the point
|
||||
// where we left off to come back to when possess is removed
|
||||
if (HasEscortState(STATE_ESCORT_ESCORTING))
|
||||
{
|
||||
if (apply)
|
||||
me->GetPosition(LastPos.x, LastPos.y, LastPos.z);
|
||||
else
|
||||
{
|
||||
Returning = true;
|
||||
me->GetMotionMaster()->MovementExpired();
|
||||
me->GetMotionMaster()->MovePoint(WP_LAST_POINT, LastPos.x, LastPos.y, LastPos.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
void npc_escortAI::AddWaypoint(uint32 id, float x, float y, float z, uint32 WaitTimeMs)
|
||||
{
|
||||
Escort_Waypoint t(id, x, y, z, WaitTimeMs);
|
||||
|
||||
WaypointList.push_back(t);
|
||||
|
||||
// i think SD2 no longer uses this function
|
||||
ScriptWP = true;
|
||||
/*PointMovement wp;
|
||||
wp.m_uiCreatureEntry = me->GetEntry();
|
||||
wp.m_uiPointId = id;
|
||||
wp.m_fX = x;
|
||||
wp.m_fY = y;
|
||||
wp.m_fZ = z;
|
||||
wp.m_uiWaitTime = WaitTimeMs;
|
||||
PointMovementMap[wp.m_uiCreatureEntry].push_back(wp);*/
|
||||
}
|
||||
|
||||
void npc_escortAI::FillPointMovementListForCreature()
|
||||
{
|
||||
std::vector<ScriptPointMove> const &pPointsEntries = pSystemMgr.GetPointMoveList(me->GetEntry());
|
||||
|
||||
if (pPointsEntries.empty())
|
||||
return;
|
||||
|
||||
std::vector<ScriptPointMove>::const_iterator itr;
|
||||
|
||||
for (itr = pPointsEntries.begin(); itr != pPointsEntries.end(); ++itr)
|
||||
{
|
||||
Escort_Waypoint pPoint(itr->uiPointId, itr->fX, itr->fY, itr->fZ, itr->uiWaitTime);
|
||||
WaypointList.push_back(pPoint);
|
||||
}
|
||||
}
|
||||
|
||||
void npc_escortAI::SetRun(bool bRun)
|
||||
{
|
||||
if (bRun)
|
||||
{
|
||||
if (!m_bIsRunning)
|
||||
me->RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE);
|
||||
else
|
||||
debug_log("TSCR: EscortAI attempt to set run mode, but is already running.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_bIsRunning)
|
||||
me->AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE);
|
||||
else
|
||||
debug_log("TSCR: EscortAI attempt to set walk mode, but is already walking.");
|
||||
}
|
||||
m_bIsRunning = bRun;
|
||||
}
|
||||
|
||||
//TODO: get rid of this many variables passed in function.
|
||||
void npc_escortAI::Start(bool bIsActiveAttacker, bool bRun, uint64 uiPlayerGUID, const Quest* pQuest, bool bInstantRespawn, bool bCanLoopPath)
|
||||
{
|
||||
if (me->getVictim())
|
||||
{
|
||||
error_log("TSCR ERROR: EscortAI attempt to Start while in combat. Scriptname: %s, creature entry: %u", me->GetScriptName().c_str(), me->GetEntry());
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasEscortState(STATE_ESCORT_ESCORTING))
|
||||
{
|
||||
error_log("TSCR: EscortAI attempt to Start while already escorting.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ScriptWP) // sd2 never adds wp in script, but tc does
|
||||
{
|
||||
|
||||
if (!WaypointList.empty())
|
||||
WaypointList.clear();
|
||||
|
||||
FillPointMovementListForCreature();
|
||||
|
||||
}
|
||||
|
||||
if (WaypointList.empty())
|
||||
{
|
||||
error_db_log("TSCR: EscortAI Start with 0 waypoints (possible missing entry in script_waypoint. Quest: %u).", pQuest ? pQuest->GetQuestId() : 0);
|
||||
return;
|
||||
}
|
||||
|
||||
//set variables
|
||||
m_bIsActiveAttacker = bIsActiveAttacker;
|
||||
m_bIsRunning = bRun;
|
||||
|
||||
m_uiPlayerGUID = uiPlayerGUID;
|
||||
m_pQuestForEscort = pQuest;
|
||||
|
||||
m_bCanInstantRespawn = bInstantRespawn;
|
||||
m_bCanReturnToStart = bCanLoopPath;
|
||||
|
||||
if (m_bCanReturnToStart && m_bCanInstantRespawn)
|
||||
debug_log("TSCR: EscortAI is set to return home after waypoint end and instant respawn at waypoint end. Creature will never despawn.");
|
||||
|
||||
if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE)
|
||||
{
|
||||
me->GetMotionMaster()->MovementExpired();
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
debug_log("TSCR: EscortAI start with WAYPOINT_MOTION_TYPE, changed to MoveIdle.");
|
||||
}
|
||||
|
||||
//disable npcflags
|
||||
me->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
|
||||
|
||||
debug_log("TSCR: EscortAI started with %u waypoints. ActiveAttacker = %d, Run = %d, PlayerGUID = %u", WaypointList.size(), m_bIsActiveAttacker, m_bIsRunning, m_uiPlayerGUID);
|
||||
|
||||
CurrentWP = WaypointList.begin();
|
||||
|
||||
//Set initial speed
|
||||
if (m_bIsRunning)
|
||||
me->RemoveUnitMovementFlag(MOVEMENTFLAG_WALK_MODE);
|
||||
else
|
||||
me->AddUnitMovementFlag(MOVEMENTFLAG_WALK_MODE);
|
||||
|
||||
AddEscortState(STATE_ESCORT_ESCORTING);
|
||||
}
|
||||
|
||||
void npc_escortAI::SetEscortPaused(bool bPaused)
|
||||
{
|
||||
if (!HasEscortState(STATE_ESCORT_ESCORTING))
|
||||
return;
|
||||
|
||||
if (bPaused)
|
||||
AddEscortState(STATE_ESCORT_PAUSED);
|
||||
else
|
||||
RemoveEscortState(STATE_ESCORT_PAUSED);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
#ifndef SC_ESCORTAI_H
|
||||
#define SC_ESCORTAI_H
|
||||
|
||||
#include "ScriptSystem.h"
|
||||
|
||||
#define DEFAULT_MAX_PLAYER_DISTANCE 50
|
||||
|
||||
struct Escort_Waypoint
|
||||
{
|
||||
Escort_Waypoint(uint32 _id, float _x, float _y, float _z, uint32 _w)
|
||||
{
|
||||
id = _id;
|
||||
x = _x;
|
||||
y = _y;
|
||||
z = _z;
|
||||
WaitTimeMs = _w;
|
||||
}
|
||||
|
||||
uint32 id;
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
uint32 WaitTimeMs;
|
||||
};
|
||||
|
||||
enum eEscortState
|
||||
{
|
||||
STATE_ESCORT_NONE = 0x000, //nothing in progress
|
||||
STATE_ESCORT_ESCORTING = 0x001, //escort are in progress
|
||||
STATE_ESCORT_RETURNING = 0x002, //escort is returning after being in combat
|
||||
STATE_ESCORT_PAUSED = 0x004 //will not proceed with waypoints before state is removed
|
||||
};
|
||||
|
||||
struct npc_escortAI : public ScriptedAI
|
||||
{
|
||||
public:
|
||||
explicit npc_escortAI(Creature* pCreature);
|
||||
~npc_escortAI() {}
|
||||
|
||||
// CreatureAI functions
|
||||
void AttackStart(Unit* who);
|
||||
|
||||
void MoveInLineOfSight(Unit* who);
|
||||
|
||||
void JustDied(Unit*);
|
||||
|
||||
void JustRespawned();
|
||||
|
||||
void ReturnToLastPoint();
|
||||
|
||||
void EnterEvadeMode();
|
||||
|
||||
void UpdateAI(const uint32); //the "internal" update, calls UpdateEscortAI()
|
||||
virtual void UpdateEscortAI(const uint32); //used when it's needed to add code in update (abilities, scripted events, etc)
|
||||
|
||||
void MovementInform(uint32, uint32);
|
||||
|
||||
// EscortAI functions
|
||||
void AddWaypoint(uint32 id, float x, float y, float z, uint32 WaitTimeMs = 0);
|
||||
|
||||
virtual void WaypointReached(uint32 uiPointId) = 0;
|
||||
virtual void WaypointStart(uint32 /*uiPointId*/) {}
|
||||
|
||||
void Start(bool bIsActiveAttacker = true, bool bRun = false, uint64 uiPlayerGUID = 0, const Quest* pQuest = NULL, bool bInstantRespawn = false, bool bCanLoopPath = false);
|
||||
|
||||
void SetRun(bool bRun = true);
|
||||
void SetEscortPaused(bool uPaused);
|
||||
|
||||
bool HasEscortState(uint32 uiEscortState) { return (m_uiEscortState & uiEscortState); }
|
||||
virtual bool IsEscorted() { return (m_uiEscortState & STATE_ESCORT_ESCORTING); }
|
||||
|
||||
void SetMaxPlayerDistance(float newMax) { MaxPlayerDistance = newMax; }
|
||||
float GetMaxPlayerDistance() { return MaxPlayerDistance; }
|
||||
|
||||
void SetDespawnAtEnd(bool despawn) { DespawnAtEnd = despawn; }
|
||||
void SetDespawnAtFar(bool despawn) { DespawnAtFar = despawn; }
|
||||
bool GetAttack() { return m_bIsActiveAttacker; }//used in EnterEvadeMode override
|
||||
void SetCanAttack(bool attack) { m_bIsActiveAttacker = attack; }
|
||||
uint64 GetEventStarterGUID() { return m_uiPlayerGUID; }
|
||||
|
||||
protected:
|
||||
Player* GetPlayerForEscort() { return (Player*)Unit::GetUnit(*me, m_uiPlayerGUID); }
|
||||
|
||||
private:
|
||||
bool AssistPlayerInCombat(Unit* pWho);
|
||||
bool IsPlayerOrGroupInRange();
|
||||
void FillPointMovementListForCreature();
|
||||
|
||||
void AddEscortState(uint32 uiEscortState) { m_uiEscortState |= uiEscortState; }
|
||||
void RemoveEscortState(uint32 uiEscortState) { m_uiEscortState &= ~uiEscortState; }
|
||||
|
||||
uint64 m_uiPlayerGUID;
|
||||
uint32 m_uiWPWaitTimer;
|
||||
uint32 m_uiPlayerCheckTimer;
|
||||
uint32 m_uiEscortState;
|
||||
float MaxPlayerDistance;
|
||||
|
||||
const Quest* m_pQuestForEscort; //generally passed in Start() when regular escort script.
|
||||
|
||||
std::list<Escort_Waypoint> WaypointList;
|
||||
std::list<Escort_Waypoint>::iterator CurrentWP;
|
||||
|
||||
bool m_bIsActiveAttacker; //obsolete, determined by faction.
|
||||
bool m_bIsRunning; //all creatures are walking by default (has flag MOVEMENTFLAG_WALK)
|
||||
bool m_bCanInstantRespawn; //if creature should respawn instantly after escort over (if not, database respawntime are used)
|
||||
bool m_bCanReturnToStart; //if creature can walk same path (loop) without despawn. Not for regular escort quests.
|
||||
bool DespawnAtEnd;
|
||||
bool DespawnAtFar;
|
||||
bool ScriptWP;
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,387 @@
|
||||
/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
/* ScriptData
|
||||
SDName: FollowerAI
|
||||
SD%Complete: 50
|
||||
SDComment: This AI is under development
|
||||
SDCategory: Npc
|
||||
EndScriptData */
|
||||
|
||||
#include "ScriptedPch.h"
|
||||
#include "ScriptedFollowerAI.h"
|
||||
|
||||
const float MAX_PLAYER_DISTANCE = 100.0f;
|
||||
|
||||
enum ePoints
|
||||
{
|
||||
POINT_COMBAT_START = 0xFFFFFF
|
||||
};
|
||||
|
||||
FollowerAI::FollowerAI(Creature* pCreature) : ScriptedAI(pCreature),
|
||||
m_uiLeaderGUID(0),
|
||||
m_pQuestForFollow(NULL),
|
||||
m_uiUpdateFollowTimer(2500),
|
||||
m_uiFollowState(STATE_FOLLOW_NONE)
|
||||
{}
|
||||
|
||||
void FollowerAI::AttackStart(Unit* pWho)
|
||||
{
|
||||
if (!pWho)
|
||||
return;
|
||||
|
||||
if (me->Attack(pWho, true))
|
||||
{
|
||||
me->AddThreat(pWho, 0.0f);
|
||||
me->SetInCombatWith(pWho);
|
||||
pWho->SetInCombatWith(me);
|
||||
|
||||
if (me->hasUnitState(UNIT_STAT_FOLLOW))
|
||||
me->clearUnitState(UNIT_STAT_FOLLOW);
|
||||
|
||||
if (IsCombatMovement())
|
||||
me->GetMotionMaster()->MoveChase(pWho);
|
||||
}
|
||||
}
|
||||
|
||||
//This part provides assistance to a player that are attacked by pWho, even if out of normal aggro range
|
||||
//It will cause me to attack pWho that are attacking _any_ player (which has been confirmed may happen also on offi)
|
||||
//The flag (type_flag) is unconfirmed, but used here for further research and is a good candidate.
|
||||
bool FollowerAI::AssistPlayerInCombat(Unit* pWho)
|
||||
{
|
||||
if (!pWho || !pWho->getVictim())
|
||||
return false;
|
||||
|
||||
//experimental (unknown) flag not present
|
||||
if (!(me->GetCreatureInfo()->type_flags & CREATURE_TYPEFLAGS_UNK13))
|
||||
return false;
|
||||
|
||||
//not a player
|
||||
if (!pWho->getVictim()->GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
return false;
|
||||
|
||||
//never attack friendly
|
||||
if (me->IsFriendlyTo(pWho))
|
||||
return false;
|
||||
|
||||
//too far away and no free sight?
|
||||
if (me->IsWithinDistInMap(pWho, MAX_PLAYER_DISTANCE) && me->IsWithinLOSInMap(pWho))
|
||||
{
|
||||
//already fighting someone?
|
||||
if (!me->getVictim())
|
||||
{
|
||||
AttackStart(pWho);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pWho->SetInCombatWith(me);
|
||||
me->AddThreat(pWho, 0.0f);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FollowerAI::MoveInLineOfSight(Unit* pWho)
|
||||
{
|
||||
if (!me->hasUnitState(UNIT_STAT_STUNNED) && pWho->isTargetableForAttack() && pWho->isInAccessiblePlaceFor(me))
|
||||
{
|
||||
if (HasFollowState(STATE_FOLLOW_INPROGRESS) && AssistPlayerInCombat(pWho))
|
||||
return;
|
||||
|
||||
if (!me->canFly() && me->GetDistanceZ(pWho) > CREATURE_Z_ATTACK_RANGE)
|
||||
return;
|
||||
|
||||
if (me->IsHostileTo(pWho))
|
||||
{
|
||||
float fAttackRadius = me->GetAttackDistance(pWho);
|
||||
if (me->IsWithinDistInMap(pWho, fAttackRadius) && me->IsWithinLOSInMap(pWho))
|
||||
{
|
||||
if (!me->getVictim())
|
||||
{
|
||||
pWho->RemoveAurasDueToSpell(SPELL_AURA_MOD_STEALTH);
|
||||
AttackStart(pWho);
|
||||
}
|
||||
else if (me->GetMap()->IsDungeon())
|
||||
{
|
||||
pWho->SetInCombatWith(me);
|
||||
me->AddThreat(pWho, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FollowerAI::JustDied(Unit* /*pKiller*/)
|
||||
{
|
||||
if (!HasFollowState(STATE_FOLLOW_INPROGRESS) || !m_uiLeaderGUID || !m_pQuestForFollow)
|
||||
return;
|
||||
|
||||
//TODO: need a better check for quests with time limit.
|
||||
if (Player* pPlayer = GetLeaderForFollower())
|
||||
{
|
||||
if (Group* pGroup = pPlayer->GetGroup())
|
||||
{
|
||||
for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next())
|
||||
{
|
||||
if (Player* pMember = pRef->getSource())
|
||||
{
|
||||
if (pMember->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
|
||||
pMember->FailQuest(m_pQuestForFollow->GetQuestId());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pPlayer->GetQuestStatus(m_pQuestForFollow->GetQuestId()) == QUEST_STATUS_INCOMPLETE)
|
||||
pPlayer->FailQuest(m_pQuestForFollow->GetQuestId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FollowerAI::JustRespawned()
|
||||
{
|
||||
m_uiFollowState = STATE_FOLLOW_NONE;
|
||||
|
||||
if (!IsCombatMovement())
|
||||
SetCombatMovement(true);
|
||||
|
||||
if (me->getFaction() != me->GetCreatureInfo()->faction_A)
|
||||
me->setFaction(me->GetCreatureInfo()->faction_A);
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
void FollowerAI::EnterEvadeMode()
|
||||
{
|
||||
me->RemoveAllAuras();
|
||||
me->DeleteThreatList();
|
||||
me->CombatStop(true);
|
||||
me->SetLootRecipient(NULL);
|
||||
|
||||
if (HasFollowState(STATE_FOLLOW_INPROGRESS))
|
||||
{
|
||||
debug_log("TSCR: FollowerAI left combat, returning to CombatStartPosition.");
|
||||
|
||||
if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE)
|
||||
{
|
||||
float fPosX, fPosY, fPosZ;
|
||||
me->GetPosition(fPosX, fPosY, fPosZ);
|
||||
me->GetMotionMaster()->MovePoint(POINT_COMBAT_START, fPosX, fPosY, fPosZ);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == TARGETED_MOTION_TYPE)
|
||||
me->GetMotionMaster()->MoveTargetedHome();
|
||||
}
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
void FollowerAI::UpdateAI(const uint32 uiDiff)
|
||||
{
|
||||
if (HasFollowState(STATE_FOLLOW_INPROGRESS) && !me->getVictim())
|
||||
{
|
||||
if (m_uiUpdateFollowTimer <= uiDiff)
|
||||
{
|
||||
if (HasFollowState(STATE_FOLLOW_COMPLETE) && !HasFollowState(STATE_FOLLOW_POSTEVENT))
|
||||
{
|
||||
debug_log("TSCR: FollowerAI is set completed, despawns.");
|
||||
me->ForcedDespawn();
|
||||
return;
|
||||
}
|
||||
|
||||
bool bIsMaxRangeExceeded = true;
|
||||
|
||||
if (Player* pPlayer = GetLeaderForFollower())
|
||||
{
|
||||
if (HasFollowState(STATE_FOLLOW_RETURNING))
|
||||
{
|
||||
debug_log("TSCR: FollowerAI is returning to leader.");
|
||||
|
||||
RemoveFollowState(STATE_FOLLOW_RETURNING);
|
||||
me->GetMotionMaster()->MoveFollow(pPlayer, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Group* pGroup = pPlayer->GetGroup())
|
||||
{
|
||||
for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next())
|
||||
{
|
||||
Player* pMember = pRef->getSource();
|
||||
|
||||
if (pMember && me->IsWithinDistInMap(pMember, MAX_PLAYER_DISTANCE))
|
||||
{
|
||||
bIsMaxRangeExceeded = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (me->IsWithinDistInMap(pPlayer, MAX_PLAYER_DISTANCE))
|
||||
bIsMaxRangeExceeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (bIsMaxRangeExceeded)
|
||||
{
|
||||
debug_log("TSCR: FollowerAI failed because player/group was to far away or not found");
|
||||
me->ForcedDespawn();
|
||||
return;
|
||||
}
|
||||
|
||||
m_uiUpdateFollowTimer = 1000;
|
||||
}
|
||||
else
|
||||
m_uiUpdateFollowTimer -= uiDiff;
|
||||
}
|
||||
|
||||
UpdateFollowerAI(uiDiff);
|
||||
}
|
||||
|
||||
void FollowerAI::UpdateFollowerAI(const uint32 /*uiDiff*/)
|
||||
{
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
void FollowerAI::MovementInform(uint32 uiMotionType, uint32 uiPointId)
|
||||
{
|
||||
if (uiMotionType != POINT_MOTION_TYPE || !HasFollowState(STATE_FOLLOW_INPROGRESS))
|
||||
return;
|
||||
|
||||
if (uiPointId == POINT_COMBAT_START)
|
||||
{
|
||||
if (GetLeaderForFollower())
|
||||
{
|
||||
if (!HasFollowState(STATE_FOLLOW_PAUSED))
|
||||
AddFollowState(STATE_FOLLOW_RETURNING);
|
||||
}
|
||||
else
|
||||
me->ForcedDespawn();
|
||||
}
|
||||
}
|
||||
|
||||
void FollowerAI::StartFollow(Player* pLeader, uint32 uiFactionForFollower, const Quest* pQuest)
|
||||
{
|
||||
if (me->getVictim())
|
||||
{
|
||||
debug_log("TSCR: FollowerAI attempt to StartFollow while in combat.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasFollowState(STATE_FOLLOW_INPROGRESS))
|
||||
{
|
||||
error_log("TSCR: FollowerAI attempt to StartFollow while already following.");
|
||||
return;
|
||||
}
|
||||
|
||||
//set variables
|
||||
m_uiLeaderGUID = pLeader->GetGUID();
|
||||
|
||||
if (uiFactionForFollower)
|
||||
me->setFaction(uiFactionForFollower);
|
||||
|
||||
m_pQuestForFollow = pQuest;
|
||||
|
||||
if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE)
|
||||
{
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
debug_log("TSCR: FollowerAI start with WAYPOINT_MOTION_TYPE, set to MoveIdle.");
|
||||
}
|
||||
|
||||
me->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
|
||||
|
||||
AddFollowState(STATE_FOLLOW_INPROGRESS);
|
||||
|
||||
me->GetMotionMaster()->MoveFollow(pLeader, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
|
||||
|
||||
debug_log("TSCR: FollowerAI start follow %s (GUID %u)", pLeader->GetName(), m_uiLeaderGUID);
|
||||
}
|
||||
|
||||
Player* FollowerAI::GetLeaderForFollower()
|
||||
{
|
||||
if (Player* pLeader = Unit::GetPlayer(m_uiLeaderGUID))
|
||||
{
|
||||
if (pLeader->isAlive())
|
||||
return pLeader;
|
||||
else
|
||||
{
|
||||
if (Group* pGroup = pLeader->GetGroup())
|
||||
{
|
||||
for (GroupReference* pRef = pGroup->GetFirstMember(); pRef != NULL; pRef = pRef->next())
|
||||
{
|
||||
Player* pMember = pRef->getSource();
|
||||
|
||||
if (pMember && pMember->isAlive() && me->IsWithinDistInMap(pMember, MAX_PLAYER_DISTANCE))
|
||||
{
|
||||
debug_log("TSCR: FollowerAI GetLeader changed and returned new leader.");
|
||||
m_uiLeaderGUID = pMember->GetGUID();
|
||||
return pMember;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug_log("TSCR: FollowerAI GetLeader can not find suitable leader.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void FollowerAI::SetFollowComplete(bool bWithEndEvent)
|
||||
{
|
||||
if (me->hasUnitState(UNIT_STAT_FOLLOW))
|
||||
{
|
||||
me->clearUnitState(UNIT_STAT_FOLLOW);
|
||||
|
||||
me->StopMoving();
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
}
|
||||
|
||||
if (bWithEndEvent)
|
||||
AddFollowState(STATE_FOLLOW_POSTEVENT);
|
||||
else
|
||||
{
|
||||
if (HasFollowState(STATE_FOLLOW_POSTEVENT))
|
||||
RemoveFollowState(STATE_FOLLOW_POSTEVENT);
|
||||
}
|
||||
|
||||
AddFollowState(STATE_FOLLOW_COMPLETE);
|
||||
}
|
||||
|
||||
void FollowerAI::SetFollowPaused(bool bPaused)
|
||||
{
|
||||
if (!HasFollowState(STATE_FOLLOW_INPROGRESS) || HasFollowState(STATE_FOLLOW_COMPLETE))
|
||||
return;
|
||||
|
||||
if (bPaused)
|
||||
{
|
||||
AddFollowState(STATE_FOLLOW_PAUSED);
|
||||
|
||||
if (me->hasUnitState(UNIT_STAT_FOLLOW))
|
||||
{
|
||||
me->clearUnitState(UNIT_STAT_FOLLOW);
|
||||
|
||||
me->StopMoving();
|
||||
me->GetMotionMaster()->Clear();
|
||||
me->GetMotionMaster()->MoveIdle();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveFollowState(STATE_FOLLOW_PAUSED);
|
||||
|
||||
if (Player* pLeader = GetLeaderForFollower())
|
||||
me->GetMotionMaster()->MoveFollow(pLeader, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
#ifndef SC_FOLLOWERAI_H
|
||||
#define SC_FOLLOWERAI_H
|
||||
|
||||
#include "ScriptSystem.h"
|
||||
|
||||
enum eFollowState
|
||||
{
|
||||
STATE_FOLLOW_NONE = 0x000,
|
||||
STATE_FOLLOW_INPROGRESS = 0x001, //must always have this state for any follow
|
||||
STATE_FOLLOW_RETURNING = 0x002, //when returning to combat start after being in combat
|
||||
STATE_FOLLOW_PAUSED = 0x004, //disables following
|
||||
STATE_FOLLOW_COMPLETE = 0x008, //follow is completed and may end
|
||||
STATE_FOLLOW_PREEVENT = 0x010, //not implemented (allow pre event to run, before follow is initiated)
|
||||
STATE_FOLLOW_POSTEVENT = 0x020 //can be set at complete and allow post event to run
|
||||
};
|
||||
|
||||
class FollowerAI : public ScriptedAI
|
||||
{
|
||||
public:
|
||||
explicit FollowerAI(Creature* pCreature);
|
||||
~FollowerAI() {}
|
||||
|
||||
//virtual void WaypointReached(uint32 uiPointId) = 0;
|
||||
|
||||
void MovementInform(uint32 uiMotionType, uint32 uiPointId);
|
||||
|
||||
void AttackStart(Unit*);
|
||||
|
||||
void MoveInLineOfSight(Unit*);
|
||||
|
||||
void EnterEvadeMode();
|
||||
|
||||
void JustDied(Unit*);
|
||||
|
||||
void JustRespawned();
|
||||
|
||||
void UpdateAI(const uint32); //the "internal" update, calls UpdateFollowerAI()
|
||||
virtual void UpdateFollowerAI(const uint32); //used when it's needed to add code in update (abilities, scripted events, etc)
|
||||
|
||||
void StartFollow(Player* pPlayer, uint32 uiFactionForFollower = 0, const Quest* pQuest = NULL);
|
||||
|
||||
void SetFollowPaused(bool bPaused); //if special event require follow mode to hold/resume during the follow
|
||||
void SetFollowComplete(bool bWithEndEvent = false);
|
||||
|
||||
bool HasFollowState(uint32 uiFollowState) { return (m_uiFollowState & uiFollowState); }
|
||||
|
||||
protected:
|
||||
Player* GetLeaderForFollower();
|
||||
|
||||
private:
|
||||
void AddFollowState(uint32 uiFollowState) { m_uiFollowState |= uiFollowState; }
|
||||
void RemoveFollowState(uint32 uiFollowState) { m_uiFollowState &= ~uiFollowState; }
|
||||
|
||||
bool AssistPlayerInCombat(Unit* pWho);
|
||||
|
||||
uint64 m_uiLeaderGUID;
|
||||
uint32 m_uiUpdateFollowTimer;
|
||||
uint32 m_uiFollowState;
|
||||
|
||||
const Quest* m_pQuestForFollow; //normally we have a quest
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,187 @@
|
||||
/* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
*
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
#ifndef SC_GOSSIP_H
|
||||
#define SC_GOSSIP_H
|
||||
|
||||
#include "Player.h"
|
||||
#include "GossipDef.h"
|
||||
#include "QuestDef.h"
|
||||
|
||||
// Gossip Item Text
|
||||
#define GOSSIP_TEXT_BROWSE_GOODS "I'd like to browse your goods."
|
||||
#define GOSSIP_TEXT_TRAIN "Train me!"
|
||||
|
||||
#define GOSSIP_TEXT_BANK "The bank"
|
||||
#define GOSSIP_TEXT_IRONFORGE_BANK "The bank of Ironforge"
|
||||
#define GOSSIP_TEXT_STORMWIND_BANK "The bank of Stormwind"
|
||||
#define GOSSIP_TEXT_WINDRIDER "The wind rider master"
|
||||
#define GOSSIP_TEXT_GRYPHON "The gryphon master"
|
||||
#define GOSSIP_TEXT_BATHANDLER "The bat handler"
|
||||
#define GOSSIP_TEXT_HIPPOGRYPH "The hippogryph master"
|
||||
#define GOSSIP_TEXT_ZEPPLINMASTER "The zeppelin master"
|
||||
#define GOSSIP_TEXT_DEEPRUNTRAM "The Deeprun Tram"
|
||||
#define GOSSIP_TEXT_FERRY "The Rut'theran Ferry"
|
||||
#define GOSSIP_TEXT_FLIGHTMASTER "The flight master"
|
||||
#define GOSSIP_TEXT_AUCTIONHOUSE "The auction house"
|
||||
#define GOSSIP_TEXT_GUILDMASTER "The guild master"
|
||||
#define GOSSIP_TEXT_INN "The inn"
|
||||
#define GOSSIP_TEXT_MAILBOX "The mailbox"
|
||||
#define GOSSIP_TEXT_STABLEMASTER "The stable master"
|
||||
#define GOSSIP_TEXT_WEAPONMASTER "The weapon master"
|
||||
#define GOSSIP_TEXT_OFFICERS "The officers' lounge"
|
||||
#define GOSSIP_TEXT_BATTLEMASTER "The battlemaster"
|
||||
#define GOSSIP_TEXT_BARBER "Barber"
|
||||
#define GOSSIP_TEXT_CLASSTRAINER "A class trainer"
|
||||
#define GOSSIP_TEXT_PROFTRAINER "A profession trainer"
|
||||
#define GOSSIP_TEXT_LEXICON "Lexicon of Power"
|
||||
|
||||
#define GOSSIP_TEXT_ALTERACVALLEY "Alterac Valley"
|
||||
#define GOSSIP_TEXT_ARATHIBASIN "Arathi Basin"
|
||||
#define GOSSIP_TEXT_WARSONGULCH "Warsong Gulch"
|
||||
#define GOSSIP_TEXT_ARENA "Arena"
|
||||
#define GOSSIP_TEXT_EYEOFTHESTORM "Eye of The Storm"
|
||||
#define GOSSIP_TEXT_STRANDOFANCIENT "Strand of the Ancients"
|
||||
|
||||
#define GOSSIP_TEXT_DEATH_KNIGHT "Death Knight"
|
||||
#define GOSSIP_TEXT_DRUID "Druid"
|
||||
#define GOSSIP_TEXT_HUNTER "Hunter"
|
||||
#define GOSSIP_TEXT_PRIEST "Priest"
|
||||
#define GOSSIP_TEXT_ROGUE "Rogue"
|
||||
#define GOSSIP_TEXT_WARRIOR "Warrior"
|
||||
#define GOSSIP_TEXT_PALADIN "Paladin"
|
||||
#define GOSSIP_TEXT_SHAMAN "Shaman"
|
||||
#define GOSSIP_TEXT_MAGE "Mage"
|
||||
#define GOSSIP_TEXT_WARLOCK "Warlock"
|
||||
|
||||
#define GOSSIP_TEXT_ALCHEMY "Alchemy"
|
||||
#define GOSSIP_TEXT_BLACKSMITHING "Blacksmithing"
|
||||
#define GOSSIP_TEXT_COOKING "Cooking"
|
||||
#define GOSSIP_TEXT_ENCHANTING "Enchanting"
|
||||
#define GOSSIP_TEXT_ENGINEERING "Engineering"
|
||||
#define GOSSIP_TEXT_FIRSTAID "First Aid"
|
||||
#define GOSSIP_TEXT_HERBALISM "Herbalism"
|
||||
#define GOSSIP_TEXT_INSCRIPTION "Inscription"
|
||||
#define GOSSIP_TEXT_JEWELCRAFTING "Jewelcrafting"
|
||||
#define GOSSIP_TEXT_LEATHERWORKING "Leatherworking"
|
||||
#define GOSSIP_TEXT_TAILORING "Tailoring"
|
||||
#define GOSSIP_TEXT_MINING "Mining"
|
||||
#define GOSSIP_TEXT_FISHING "Fishing"
|
||||
#define GOSSIP_TEXT_SKINNING "Skinning"
|
||||
|
||||
enum eTradeskill
|
||||
{
|
||||
// Skill defines
|
||||
TRADESKILL_ALCHEMY = 1,
|
||||
TRADESKILL_BLACKSMITHING = 2,
|
||||
TRADESKILL_COOKING = 3,
|
||||
TRADESKILL_ENCHANTING = 4,
|
||||
TRADESKILL_ENGINEERING = 5,
|
||||
TRADESKILL_FIRSTAID = 6,
|
||||
TRADESKILL_HERBALISM = 7,
|
||||
TRADESKILL_LEATHERWORKING = 8,
|
||||
TRADESKILL_POISONS = 9,
|
||||
TRADESKILL_TAILORING = 10,
|
||||
TRADESKILL_MINING = 11,
|
||||
TRADESKILL_FISHING = 12,
|
||||
TRADESKILL_SKINNING = 13,
|
||||
TRADESKILL_JEWLCRAFTING = 14,
|
||||
TRADESKILL_INSCRIPTION = 15,
|
||||
|
||||
TRADESKILL_LEVEL_NONE = 0,
|
||||
TRADESKILL_LEVEL_APPRENTICE = 1,
|
||||
TRADESKILL_LEVEL_JOURNEYMAN = 2,
|
||||
TRADESKILL_LEVEL_EXPERT = 3,
|
||||
TRADESKILL_LEVEL_ARTISAN = 4,
|
||||
TRADESKILL_LEVEL_MASTER = 5,
|
||||
TRADESKILL_LEVEL_GRAND_MASTER = 6,
|
||||
|
||||
// Gossip defines
|
||||
GOSSIP_ACTION_TRADE = 1,
|
||||
GOSSIP_ACTION_TRAIN = 2,
|
||||
GOSSIP_ACTION_TAXI = 3,
|
||||
GOSSIP_ACTION_GUILD = 4,
|
||||
GOSSIP_ACTION_BATTLE = 5,
|
||||
GOSSIP_ACTION_BANK = 6,
|
||||
GOSSIP_ACTION_INN = 7,
|
||||
GOSSIP_ACTION_HEAL = 8,
|
||||
GOSSIP_ACTION_TABARD = 9,
|
||||
GOSSIP_ACTION_AUCTION = 10,
|
||||
GOSSIP_ACTION_INN_INFO = 11,
|
||||
GOSSIP_ACTION_UNLEARN = 12,
|
||||
GOSSIP_ACTION_INFO_DEF = 1000,
|
||||
|
||||
GOSSIP_SENDER_MAIN = 1,
|
||||
GOSSIP_SENDER_INN_INFO = 2,
|
||||
GOSSIP_SENDER_INFO = 3,
|
||||
GOSSIP_SENDER_SEC_PROFTRAIN = 4,
|
||||
GOSSIP_SENDER_SEC_CLASSTRAIN = 5,
|
||||
GOSSIP_SENDER_SEC_BATTLEINFO = 6,
|
||||
GOSSIP_SENDER_SEC_BANK = 7,
|
||||
GOSSIP_SENDER_SEC_INN = 8,
|
||||
GOSSIP_SENDER_SEC_MAILBOX = 9,
|
||||
GOSSIP_SENDER_SEC_STABLEMASTER = 10
|
||||
};
|
||||
|
||||
extern uint32 GetSkillLevel(Player *player,uint32 skill);
|
||||
|
||||
// Defined fuctions to use with player.
|
||||
|
||||
// This fuction add's a menu item,
|
||||
// a - Icon Id
|
||||
// b - Text
|
||||
// c - Sender(this is to identify the current Menu with this item)
|
||||
// d - Action (identifys this Menu Item)
|
||||
// e - Text to be displayed in pop up box
|
||||
// f - Money value in pop up box
|
||||
#define ADD_GOSSIP_ITEM(a,b,c,d) PlayerTalkClass->GetGossipMenu().AddMenuItem(a,b,c,d,"",0)
|
||||
#define ADD_GOSSIP_ITEM_EXTENDED(a,b,c,d,e,f,g) PlayerTalkClass->GetGossipMenu().AddMenuItem(a,b,c,d,e,f,g)
|
||||
|
||||
// This fuction Sends the current menu to show to client, a - NPCTEXTID(uint32) , b - npc guid(uint64)
|
||||
#define SEND_GOSSIP_MENU(a,b) PlayerTalkClass->SendGossipMenu(a,b)
|
||||
|
||||
// This fuction shows POI(point of interest) to client.
|
||||
// a - position X
|
||||
// b - position Y
|
||||
// c - Icon Id
|
||||
// d - Flags
|
||||
// e - Data
|
||||
// f - Location Name
|
||||
#define SEND_POI(a,b,c,d,e,f) PlayerTalkClass->SendPointOfInterest(a,b,c,d,e,f)
|
||||
|
||||
// Closes the Menu
|
||||
#define CLOSE_GOSSIP_MENU() PlayerTalkClass->CloseGossip()
|
||||
|
||||
// Fuction to tell to client the details
|
||||
// a - quest object
|
||||
// b - npc guid(uint64)
|
||||
// c - Activate accept(bool)
|
||||
#define SEND_QUEST_DETAILS(a,b,c) PlayerTalkClass->SendQuestDetails(a,b,c)
|
||||
|
||||
// Fuction to tell to client the requested items to complete quest
|
||||
// a - quest object
|
||||
// b - npc guid(uint64)
|
||||
// c - Iscompletable(bool)
|
||||
// d - close at cancel(bool) - in case single incomplite ques
|
||||
#define SEND_REQUESTEDITEMS(a,b,c,d) PlayerTalkClass->SendRequestedItems(a,b,c,d)
|
||||
|
||||
// Fuctions to send NPC lists, a - is always the npc guid(uint64)
|
||||
#define SEND_VENDORLIST(a) GetSession()->SendListInventory(a)
|
||||
#define SEND_TRAINERLIST(a) GetSession()->SendTrainerList(a)
|
||||
|
||||
// Ressurect's the player if is dead.
|
||||
#define SEND_SPRESURRECT() GetSession()->SendSpiritResurrect()
|
||||
|
||||
// Get the player's honor rank.
|
||||
#define GET_HONORRANK() GetHonorRank()
|
||||
// -----------------------------------
|
||||
|
||||
// defined fuctions to use with Creature
|
||||
|
||||
#define QUEST_DIALOG_STATUS(a,b,c) GetSession()->getDialogStatus(a,b,c)
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>.sourceforge.net/>
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
/* ScriptData
|
||||
SDName: Guard_AI
|
||||
SD%Complete: 90
|
||||
SDComment:
|
||||
SDCategory: Guards
|
||||
EndScriptData */
|
||||
|
||||
#include "ScriptedPch.h"
|
||||
#include "ScriptedGuardAI.h"
|
||||
|
||||
// **** This script is for use within every single guard to save coding time ****
|
||||
|
||||
#define GENERIC_CREATURE_COOLDOWN 5000
|
||||
|
||||
#define SAY_GUARD_SIL_AGGRO1 -1070001
|
||||
#define SAY_GUARD_SIL_AGGRO2 -1070002
|
||||
#define SAY_GUARD_SIL_AGGRO3 -1070003
|
||||
|
||||
guardAI::guardAI(Creature* pCreature) : ScriptedAI(pCreature),
|
||||
GlobalCooldown(0),
|
||||
BuffTimer(0)
|
||||
{}
|
||||
|
||||
void guardAI::Reset()
|
||||
{
|
||||
GlobalCooldown = 0;
|
||||
BuffTimer = 0; //Rebuff as soon as we can
|
||||
}
|
||||
|
||||
void guardAI::EnterCombat(Unit *who)
|
||||
{
|
||||
if (me->GetEntry() == 15184)
|
||||
DoScriptText(RAND(SAY_GUARD_SIL_AGGRO1,SAY_GUARD_SIL_AGGRO2,SAY_GUARD_SIL_AGGRO3), me, who);
|
||||
|
||||
if (SpellEntry const *spell = me->reachWithSpellAttack(who))
|
||||
DoCastSpell(who, spell);
|
||||
}
|
||||
|
||||
void guardAI::JustDied(Unit *Killer)
|
||||
{
|
||||
//Send Zone Under Attack message to the LocalDefense and WorldDefense Channels
|
||||
if (Player* pKiller = Killer->GetCharmerOrOwnerPlayerOrPlayerItself())
|
||||
me->SendZoneUnderAttackMessage(pKiller);
|
||||
}
|
||||
|
||||
void guardAI::UpdateAI(const uint32 diff)
|
||||
{
|
||||
//Always decrease our global cooldown first
|
||||
if (GlobalCooldown > diff)
|
||||
GlobalCooldown -= diff;
|
||||
else GlobalCooldown = 0;
|
||||
|
||||
//Buff timer (only buff when we are alive and not in combat
|
||||
if (me->isAlive() && !me->isInCombat())
|
||||
if (BuffTimer <= diff)
|
||||
{
|
||||
//Find a spell that targets friendly and applies an aura (these are generally buffs)
|
||||
SpellEntry const *info = SelectSpell(me, 0, 0, SELECT_TARGET_ANY_FRIEND, 0, 0, 0, 0, SELECT_EFFECT_AURA);
|
||||
|
||||
if (info && !GlobalCooldown)
|
||||
{
|
||||
//Cast the buff spell
|
||||
DoCastSpell(me, info);
|
||||
|
||||
//Set our global cooldown
|
||||
GlobalCooldown = GENERIC_CREATURE_COOLDOWN;
|
||||
|
||||
//Set our timer to 10 minutes before rebuff
|
||||
BuffTimer = 600000;
|
||||
} //Try again in 30 seconds
|
||||
else BuffTimer = 30000;
|
||||
} else BuffTimer -= diff;
|
||||
|
||||
//Return since we have no target
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
// Make sure our attack is ready and we arn't currently casting
|
||||
if (me->isAttackReady() && !me->IsNonMeleeSpellCasted(false))
|
||||
{
|
||||
//If we are within range melee the target
|
||||
if (me->IsWithinMeleeRange(me->getVictim()))
|
||||
{
|
||||
bool Healing = false;
|
||||
SpellEntry const *info = NULL;
|
||||
|
||||
//Select a healing spell if less than 30% hp
|
||||
if (me->GetHealth()*100 / me->GetMaxHealth() < 30)
|
||||
info = SelectSpell(me, 0, 0, SELECT_TARGET_ANY_FRIEND, 0, 0, 0, 0, SELECT_EFFECT_HEALING);
|
||||
|
||||
//No healing spell available, select a hostile spell
|
||||
if (info) Healing = true;
|
||||
else info = SelectSpell(me->getVictim(), 0, 0, SELECT_TARGET_ANY_ENEMY, 0, 0, 0, 0, SELECT_EFFECT_DONTCARE);
|
||||
|
||||
//20% chance to replace our white hit with a spell
|
||||
if (info && rand() % 5 == 0 && !GlobalCooldown)
|
||||
{
|
||||
//Cast the spell
|
||||
if (Healing)DoCastSpell(me, info);
|
||||
else DoCastSpell(me->getVictim(), info);
|
||||
|
||||
//Set our global cooldown
|
||||
GlobalCooldown = GENERIC_CREATURE_COOLDOWN;
|
||||
}
|
||||
else me->AttackerStateUpdate(me->getVictim());
|
||||
|
||||
me->resetAttackTimer();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Only run this code if we arn't already casting
|
||||
if (!me->IsNonMeleeSpellCasted(false))
|
||||
{
|
||||
bool Healing = false;
|
||||
SpellEntry const *info = NULL;
|
||||
|
||||
//Select a healing spell if less than 30% hp ONLY 33% of the time
|
||||
if (me->GetHealth()*100 / me->GetMaxHealth() < 30 && rand() % 3 == 0)
|
||||
info = SelectSpell(me, 0, 0, SELECT_TARGET_ANY_FRIEND, 0, 0, 0, 0, SELECT_EFFECT_HEALING);
|
||||
|
||||
//No healing spell available, See if we can cast a ranged spell (Range must be greater than ATTACK_DISTANCE)
|
||||
if (info) Healing = true;
|
||||
else info = SelectSpell(me->getVictim(), 0, 0, SELECT_TARGET_ANY_ENEMY, 0, 0, NOMINAL_MELEE_RANGE, 0, SELECT_EFFECT_DONTCARE);
|
||||
|
||||
//Found a spell, check if we arn't on cooldown
|
||||
if (info && !GlobalCooldown)
|
||||
{
|
||||
//If we are currently moving stop us and set the movement generator
|
||||
if ((*me).GetMotionMaster()->GetCurrentMovementGeneratorType() != IDLE_MOTION_TYPE)
|
||||
{
|
||||
(*me).GetMotionMaster()->Clear(false);
|
||||
(*me).GetMotionMaster()->MoveIdle();
|
||||
}
|
||||
|
||||
//Cast spell
|
||||
if (Healing) DoCastSpell(me,info);
|
||||
else DoCastSpell(me->getVictim(),info);
|
||||
|
||||
//Set our global cooldown
|
||||
GlobalCooldown = GENERIC_CREATURE_COOLDOWN;
|
||||
|
||||
} //If no spells available and we arn't moving run to target
|
||||
else if ((*me).GetMotionMaster()->GetCurrentMovementGeneratorType() != TARGETED_MOTION_TYPE)
|
||||
{
|
||||
//Cancel our current spell and then mutate new movement generator
|
||||
me->InterruptNonMeleeSpells(false);
|
||||
(*me).GetMotionMaster()->Clear(false);
|
||||
(*me).GetMotionMaster()->MoveChase(me->getVictim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void guardAI::DoReplyToTextEmote(uint32 em)
|
||||
{
|
||||
switch(em)
|
||||
{
|
||||
case TEXTEMOTE_KISS: me->HandleEmoteCommand(EMOTE_ONESHOT_BOW); break;
|
||||
case TEXTEMOTE_WAVE: me->HandleEmoteCommand(EMOTE_ONESHOT_WAVE); break;
|
||||
case TEXTEMOTE_SALUTE: me->HandleEmoteCommand(EMOTE_ONESHOT_SALUTE); break;
|
||||
case TEXTEMOTE_SHY: me->HandleEmoteCommand(EMOTE_ONESHOT_FLEX); break;
|
||||
case TEXTEMOTE_RUDE:
|
||||
case TEXTEMOTE_CHICKEN: me->HandleEmoteCommand(EMOTE_ONESHOT_POINT); break;
|
||||
}
|
||||
}
|
||||
|
||||
void guardAI_orgrimmar::ReceiveEmote(Player* pPlayer, uint32 text_emote)
|
||||
{
|
||||
if (pPlayer->GetTeam() == HORDE)
|
||||
DoReplyToTextEmote(text_emote);
|
||||
}
|
||||
|
||||
void guardAI_stormwind::ReceiveEmote(Player* pPlayer, uint32 text_emote)
|
||||
{
|
||||
if (pPlayer->GetTeam() == ALLIANCE)
|
||||
DoReplyToTextEmote(text_emote);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
#ifndef SC_GUARDAI_H
|
||||
#define SC_GUARDAI_H
|
||||
|
||||
#define GENERIC_CREATURE_COOLDOWN 5000
|
||||
|
||||
struct guardAI : public ScriptedAI
|
||||
{
|
||||
public:
|
||||
explicit guardAI(Creature* pCreature);
|
||||
~guardAI() {}
|
||||
|
||||
uint32 GlobalCooldown; //This variable acts like the global cooldown that players have (1.5 seconds)
|
||||
uint32 BuffTimer; //This variable keeps track of buffs
|
||||
|
||||
void Reset();
|
||||
|
||||
void EnterCombat(Unit * /*who*/);
|
||||
|
||||
void JustDied(Unit *Killer);
|
||||
|
||||
void UpdateAI(const uint32 diff);
|
||||
|
||||
//common used for guards in main cities
|
||||
void DoReplyToTextEmote(uint32 em);
|
||||
};
|
||||
|
||||
struct guardAI_orgrimmar : public guardAI
|
||||
{
|
||||
guardAI_orgrimmar(Creature *c) : guardAI(c) {}
|
||||
|
||||
void ReceiveEmote(Player *player, uint32 text_emote);
|
||||
};
|
||||
|
||||
struct guardAI_stormwind : public guardAI
|
||||
{
|
||||
guardAI_stormwind(Creature *c) : guardAI(c) {}
|
||||
|
||||
void ReceiveEmote(Player *player, uint32 text_emote);
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
#ifndef SC_INSTANCE_H
|
||||
#define SC_INSTANCE_H
|
||||
|
||||
#include "InstanceData.h"
|
||||
#include "Map.h"
|
||||
|
||||
#define OUT_SAVE_INST_DATA debug_log("TSCR: Saving Instance Data for Instance %s (Map %d, Instance Id %d)", instance->GetMapName(), instance->GetId(), instance->GetInstanceId())
|
||||
#define OUT_SAVE_INST_DATA_COMPLETE debug_log("TSCR: Saving Instance Data for Instance %s (Map %d, Instance Id %d) completed.", instance->GetMapName(), instance->GetId(), instance->GetInstanceId())
|
||||
#define OUT_LOAD_INST_DATA(a) debug_log("TSCR: Loading Instance Data for Instance %s (Map %d, Instance Id %d). Input is '%s'", instance->GetMapName(), instance->GetId(), instance->GetInstanceId(), a)
|
||||
#define OUT_LOAD_INST_DATA_COMPLETE debug_log("TSCR: Instance Data Load for Instance %s (Map %d, Instance Id: %d) is complete.",instance->GetMapName(), instance->GetId(), instance->GetInstanceId())
|
||||
#define OUT_LOAD_INST_DATA_FAIL error_log("TSCR: Unable to load Instance Data for Instance %s (Map %d, Instance Id: %d).",instance->GetMapName(), instance->GetId(), instance->GetInstanceId())
|
||||
|
||||
#define ScriptedInstance InstanceData
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
/* ScriptData
|
||||
SDName: SimpleAI
|
||||
SD%Complete: 100
|
||||
SDComment: Base Class for SimpleAI creatures
|
||||
SDCategory: Creatures
|
||||
EndScriptData */
|
||||
|
||||
#include "ScriptedPch.h"
|
||||
#include "ScriptedSimpleAI.h"
|
||||
|
||||
SimpleAI::SimpleAI(Creature *c) : ScriptedAI(c)
|
||||
{
|
||||
//Clear all data
|
||||
Aggro_TextId[0] = 0;
|
||||
Aggro_TextId[1] = 0;
|
||||
Aggro_TextId[2] = 0;
|
||||
Aggro_Sound[0] = 0;
|
||||
Aggro_Sound[1] = 0;
|
||||
Aggro_Sound[2] = 0;
|
||||
|
||||
Death_TextId[0] = 0;
|
||||
Death_TextId[1] = 0;
|
||||
Death_TextId[2] = 0;
|
||||
Death_Sound[0] = 0;
|
||||
Death_Sound[1] = 0;
|
||||
Death_Sound[2] = 0;
|
||||
Death_Spell = 0;
|
||||
Death_Target_Type = 0;
|
||||
|
||||
Kill_TextId[0] = 0;
|
||||
Kill_TextId[1] = 0;
|
||||
Kill_TextId[2] = 0;
|
||||
Kill_Sound[0] = 0;
|
||||
Kill_Sound[1] = 0;
|
||||
Kill_Sound[2] = 0;
|
||||
Kill_Spell = 0;
|
||||
Kill_Target_Type = 0;
|
||||
|
||||
memset(Spell,0,sizeof(Spell));
|
||||
|
||||
EnterEvadeMode();
|
||||
}
|
||||
|
||||
void SimpleAI::Reset()
|
||||
{
|
||||
}
|
||||
|
||||
void SimpleAI::EnterCombat(Unit *who)
|
||||
{
|
||||
//Reset cast timers
|
||||
if (Spell[0].First_Cast >= 0)
|
||||
Spell_Timer[0] = Spell[0].First_Cast;
|
||||
else Spell_Timer[0] = 1000;
|
||||
if (Spell[1].First_Cast >= 0)
|
||||
Spell_Timer[1] = Spell[1].First_Cast;
|
||||
else Spell_Timer[1] = 1000;
|
||||
if (Spell[2].First_Cast >= 0)
|
||||
Spell_Timer[2] = Spell[2].First_Cast;
|
||||
else Spell_Timer[2] = 1000;
|
||||
if (Spell[3].First_Cast >= 0)
|
||||
Spell_Timer[3] = Spell[3].First_Cast;
|
||||
else Spell_Timer[3] = 1000;
|
||||
if (Spell[4].First_Cast >= 0)
|
||||
Spell_Timer[4] = Spell[4].First_Cast;
|
||||
else Spell_Timer[4] = 1000;
|
||||
if (Spell[5].First_Cast >= 0)
|
||||
Spell_Timer[5] = Spell[5].First_Cast;
|
||||
else Spell_Timer[5] = 1000;
|
||||
if (Spell[6].First_Cast >= 0)
|
||||
Spell_Timer[6] = Spell[6].First_Cast;
|
||||
else Spell_Timer[6] = 1000;
|
||||
if (Spell[7].First_Cast >= 0)
|
||||
Spell_Timer[7] = Spell[7].First_Cast;
|
||||
else Spell_Timer[7] = 1000;
|
||||
if (Spell[8].First_Cast >= 0)
|
||||
Spell_Timer[8] = Spell[8].First_Cast;
|
||||
else Spell_Timer[8] = 1000;
|
||||
if (Spell[9].First_Cast >= 0)
|
||||
Spell_Timer[9] = Spell[9].First_Cast;
|
||||
else Spell_Timer[9] = 1000;
|
||||
|
||||
uint8 random_text = urand(0,2);
|
||||
|
||||
//Random text
|
||||
if (Aggro_TextId[random_text])
|
||||
DoScriptText(Aggro_TextId[random_text], me, who);
|
||||
|
||||
//Random sound
|
||||
if (Aggro_Sound[random_text])
|
||||
DoPlaySoundToSet(me, Aggro_Sound[random_text]);
|
||||
}
|
||||
|
||||
void SimpleAI::KilledUnit(Unit *victim)
|
||||
{
|
||||
uint8 random_text = urand(0,2);
|
||||
|
||||
//Random yell
|
||||
if (Kill_TextId[random_text])
|
||||
DoScriptText(Kill_TextId[random_text], me, victim);
|
||||
|
||||
//Random sound
|
||||
if (Kill_Sound[random_text])
|
||||
DoPlaySoundToSet(me, Kill_Sound[random_text]);
|
||||
|
||||
if (!Kill_Spell)
|
||||
return;
|
||||
|
||||
Unit *pTarget = NULL;
|
||||
|
||||
switch (Kill_Target_Type)
|
||||
{
|
||||
case CAST_SELF:
|
||||
pTarget = me;
|
||||
break;
|
||||
case CAST_HOSTILE_TARGET:
|
||||
pTarget = me->getVictim();
|
||||
break;
|
||||
case CAST_HOSTILE_SECOND_AGGRO:
|
||||
pTarget = SelectUnit(SELECT_TARGET_TOPAGGRO,1);
|
||||
break;
|
||||
case CAST_HOSTILE_LAST_AGGRO:
|
||||
pTarget = SelectUnit(SELECT_TARGET_BOTTOMAGGRO,0);
|
||||
break;
|
||||
case CAST_HOSTILE_RANDOM:
|
||||
pTarget = SelectUnit(SELECT_TARGET_RANDOM,0);
|
||||
break;
|
||||
case CAST_KILLEDUNIT_VICTIM:
|
||||
pTarget = victim;
|
||||
break;
|
||||
}
|
||||
|
||||
//Target is ok, cast a spell on it
|
||||
if (pTarget)
|
||||
DoCast(pTarget, Kill_Spell);
|
||||
}
|
||||
|
||||
void SimpleAI::DamageTaken(Unit *killer, uint32 &damage)
|
||||
{
|
||||
//Return if damage taken won't kill us
|
||||
if (me->GetHealth() > damage)
|
||||
return;
|
||||
|
||||
uint8 random_text = urand(0,2);
|
||||
|
||||
//Random yell
|
||||
if (Death_TextId[random_text])
|
||||
DoScriptText(Death_TextId[random_text], me, killer);
|
||||
|
||||
//Random sound
|
||||
if (Death_Sound[random_text])
|
||||
DoPlaySoundToSet(me, Death_Sound[random_text]);
|
||||
|
||||
if (!Death_Spell)
|
||||
return;
|
||||
|
||||
Unit *pTarget = NULL;
|
||||
|
||||
switch (Death_Target_Type)
|
||||
{
|
||||
case CAST_SELF:
|
||||
pTarget = me;
|
||||
break;
|
||||
case CAST_HOSTILE_TARGET:
|
||||
pTarget = me->getVictim();
|
||||
break;
|
||||
case CAST_HOSTILE_SECOND_AGGRO:
|
||||
pTarget = SelectUnit(SELECT_TARGET_TOPAGGRO,1);
|
||||
break;
|
||||
case CAST_HOSTILE_LAST_AGGRO:
|
||||
pTarget = SelectUnit(SELECT_TARGET_BOTTOMAGGRO,0);
|
||||
break;
|
||||
case CAST_HOSTILE_RANDOM:
|
||||
pTarget = SelectUnit(SELECT_TARGET_RANDOM,0);
|
||||
break;
|
||||
case CAST_JUSTDIED_KILLER:
|
||||
pTarget = killer;
|
||||
break;
|
||||
}
|
||||
|
||||
//Target is ok, cast a spell on it
|
||||
if (pTarget)
|
||||
DoCast(pTarget, Death_Spell);
|
||||
}
|
||||
|
||||
void SimpleAI::UpdateAI(const uint32 diff)
|
||||
{
|
||||
//Return since we have no target
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
//Spells
|
||||
for (uint32 i = 0; i < 10; ++i)
|
||||
{
|
||||
//Spell not valid
|
||||
if (!Spell[i].Enabled || !Spell[i].Spell_Id)
|
||||
continue;
|
||||
|
||||
if (Spell_Timer[i] <= diff)
|
||||
{
|
||||
//Check if this is a percentage based
|
||||
if (Spell[i].First_Cast < 0 && Spell[i].First_Cast > -100 && me->GetHealth()*100 / me->GetMaxHealth() > -Spell[i].First_Cast)
|
||||
continue;
|
||||
|
||||
//Check Current spell
|
||||
if (!(Spell[i].InterruptPreviousCast && me->IsNonMeleeSpellCasted(false)))
|
||||
{
|
||||
Unit *pTarget = NULL;
|
||||
|
||||
switch (Spell[i].Cast_Target_Type)
|
||||
{
|
||||
case CAST_SELF:
|
||||
pTarget = me;
|
||||
break;
|
||||
case CAST_HOSTILE_TARGET:
|
||||
pTarget = me->getVictim();
|
||||
break;
|
||||
case CAST_HOSTILE_SECOND_AGGRO:
|
||||
pTarget = SelectUnit(SELECT_TARGET_TOPAGGRO,1);
|
||||
break;
|
||||
case CAST_HOSTILE_LAST_AGGRO:
|
||||
pTarget = SelectUnit(SELECT_TARGET_BOTTOMAGGRO,0);
|
||||
break;
|
||||
case CAST_HOSTILE_RANDOM:
|
||||
pTarget = SelectUnit(SELECT_TARGET_RANDOM,0);
|
||||
break;
|
||||
}
|
||||
|
||||
//Target is ok, cast a spell on it and then do our random yell
|
||||
if (pTarget)
|
||||
{
|
||||
if (me->IsNonMeleeSpellCasted(false))
|
||||
me->InterruptNonMeleeSpells(false);
|
||||
|
||||
DoCast(pTarget, Spell[i].Spell_Id);
|
||||
|
||||
//Yell and sound use the same number so that you can make
|
||||
//the Creature yell with the correct sound effect attached
|
||||
uint8 random_text = urand(0,2);
|
||||
|
||||
//Random yell
|
||||
if (Spell[i].TextId[random_text])
|
||||
DoScriptText(Spell[i].TextId[random_text], me, pTarget);
|
||||
|
||||
//Random sound
|
||||
if (Spell[i].Text_Sound[random_text])
|
||||
DoPlaySoundToSet(me, Spell[i].Text_Sound[random_text]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Spell will cast agian when the cooldown is up
|
||||
if (Spell[i].CooldownRandomAddition)
|
||||
Spell_Timer[i] = Spell[i].Cooldown + (rand() % Spell[i].CooldownRandomAddition);
|
||||
else Spell_Timer[i] = Spell[i].Cooldown;
|
||||
|
||||
} else Spell_Timer[i] -= diff;
|
||||
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/* Copyright (C) 2006 - 2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
#ifndef SC_SIMPLEAI_H
|
||||
#define SC_SIMPLEAI_H
|
||||
|
||||
enum CastTarget
|
||||
{
|
||||
CAST_SELF = 0, //Self cast
|
||||
CAST_HOSTILE_TARGET, //Our current target (ie: highest aggro)
|
||||
CAST_HOSTILE_SECOND_AGGRO, //Second highest aggro (generaly used for cleaves and some special attacks)
|
||||
CAST_HOSTILE_LAST_AGGRO, //Dead last on aggro (no idea what this could be used for)
|
||||
CAST_HOSTILE_RANDOM, //Just any random target on our threat list
|
||||
CAST_FRIENDLY_RANDOM, //NOT YET IMPLEMENTED
|
||||
|
||||
//Special cases
|
||||
CAST_KILLEDUNIT_VICTIM, //Only works within KilledUnit function
|
||||
CAST_JUSTDIED_KILLER, //Only works within JustDied function
|
||||
};
|
||||
|
||||
struct SimpleAI : public ScriptedAI
|
||||
{
|
||||
SimpleAI(Creature *c);// : ScriptedAI(c);
|
||||
|
||||
void Reset();
|
||||
|
||||
void EnterCombat(Unit * /*who*/);
|
||||
|
||||
void KilledUnit(Unit * /*victim*/);
|
||||
|
||||
void DamageTaken(Unit *killer, uint32 &damage);
|
||||
|
||||
void UpdateAI(const uint32 diff);
|
||||
|
||||
public:
|
||||
|
||||
int32 Aggro_TextId[3];
|
||||
uint32 Aggro_Sound[3];
|
||||
|
||||
int32 Death_TextId[3];
|
||||
uint32 Death_Sound[3];
|
||||
uint32 Death_Spell;
|
||||
uint32 Death_Target_Type;
|
||||
|
||||
int32 Kill_TextId[3];
|
||||
uint32 Kill_Sound[3];
|
||||
uint32 Kill_Spell;
|
||||
uint32 Kill_Target_Type;
|
||||
|
||||
struct SimpleAI_Spell
|
||||
{
|
||||
uint32 Spell_Id; //Spell ID to cast
|
||||
int32 First_Cast; //Delay for first cast
|
||||
uint32 Cooldown; //Cooldown between casts
|
||||
uint32 CooldownRandomAddition; //Random addition to cooldown (in range from 0 - CooldownRandomAddition)
|
||||
uint32 Cast_Target_Type; //Target type (note that certain spells may ignore this)
|
||||
bool InterruptPreviousCast; //Interrupt a previous cast if this spell needs to be cast
|
||||
bool Enabled; //Spell enabled or disabled (default: false)
|
||||
|
||||
//3 texts to many?
|
||||
int32 TextId[3];
|
||||
uint32 Text_Sound[3];
|
||||
}Spell[10];
|
||||
|
||||
protected:
|
||||
uint32 Spell_Timer[10];
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
*
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
#include "ScriptedSmartAI.h"
|
||||
@@ -0,0 +1,8 @@
|
||||
/* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* Thanks to the original authors: ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
|
||||
*
|
||||
* This program is free software licensed under GPL version 2
|
||||
* Please see the included DOCS/LICENSE.TXT for more information */
|
||||
|
||||
#include "Creature.h"
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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(const Creature *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::MoveInLineOfSight(Unit *)
|
||||
{
|
||||
}
|
||||
|
||||
void TotemAI::EnterEvadeMode()
|
||||
{
|
||||
me->CombatStop(true);
|
||||
}
|
||||
|
||||
void
|
||||
TotemAI::UpdateAI(const uint32 /*diff*/)
|
||||
{
|
||||
if (me->ToTotem()->GetTotemType() != TOTEM_ACTIVE)
|
||||
return;
|
||||
|
||||
if (!me->isAlive() || me->IsNonMeleeSpellCasted(false))
|
||||
return;
|
||||
|
||||
// Search spell
|
||||
SpellEntry const *spellInfo = sSpellStore.LookupEntry(me->ToTotem()->GetSpell());
|
||||
if (!spellInfo)
|
||||
return;
|
||||
|
||||
// Get spell range
|
||||
SpellRangeEntry const* srange = sSpellRangeStore.LookupEntry(spellInfo->rangeIndex);
|
||||
float max_range = GetSpellMaxRangeForHostile(srange);
|
||||
|
||||
// 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() || !me->IsWithinDistInMap(victim, max_range) ||
|
||||
me->IsFriendlyTo(victim) || !victim->isVisibleForOrDetect(me,false))
|
||||
{
|
||||
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 *)
|
||||
{
|
||||
// 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();
|
||||
((Player*)me->GetOwner())->GetSession()->SendPacket(&data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#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 *);
|
||||
void AttackStart(Unit *);
|
||||
void EnterEvadeMode();
|
||||
|
||||
void UpdateAI(const uint32);
|
||||
static int Permissible(const Creature *);
|
||||
|
||||
private:
|
||||
uint64 i_victimGuid;
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "UnitAI.h"
|
||||
#include "Player.h"
|
||||
#include "Creature.h"
|
||||
#include "SpellAuras.h"
|
||||
#include "SpellAuraEffects.h"
|
||||
#include "SpellMgr.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_STAT_CASTING))
|
||||
return;
|
||||
|
||||
//Make sure our attack is ready and we aren't currently casting before checking distance
|
||||
if (me->isAttackReady())
|
||||
{
|
||||
//If we are within range melee the target
|
||||
if (me->IsWithinMeleeRange(me->getVictim()))
|
||||
{
|
||||
me->AttackerStateUpdate(me->getVictim());
|
||||
me->resetAttackTimer();
|
||||
}
|
||||
}
|
||||
if (me->haveOffhandWeapon() && me->isAttackReady(OFF_ATTACK))
|
||||
{
|
||||
//If we are within range melee the target
|
||||
if (me->IsWithinMeleeRange(me->getVictim()))
|
||||
{
|
||||
me->AttackerStateUpdate(me->getVictim(), OFF_ATTACK);
|
||||
me->resetAttackTimer(OFF_ATTACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UnitAI::DoSpellAttackIfReady(uint32 spell)
|
||||
{
|
||||
if (me->hasUnitState(UNIT_STAT_CASTING))
|
||||
return true;
|
||||
|
||||
if (me->isAttackReady())
|
||||
{
|
||||
if (me->IsWithinCombatRange(me->getVictim(), GetSpellMaxRange(spell, false)))
|
||||
{
|
||||
me->CastSpell(me->getVictim(), spell, false);
|
||||
me->resetAttackTimer();
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// pUnit: 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(const Unit *pUnit, float dist, bool playerOnly, int32 aura) : me(pUnit), m_dist(dist), m_playerOnly(playerOnly), m_aura(aura) {}
|
||||
|
||||
bool operator() (const Unit *pTarget)
|
||||
{
|
||||
if (!me)
|
||||
return false;
|
||||
|
||||
if (!pTarget)
|
||||
return false;
|
||||
|
||||
if (m_playerOnly && (pTarget->GetTypeId() != TYPEID_PLAYER))
|
||||
return false;
|
||||
|
||||
if (m_dist > 0.0f && !me->IsWithinCombatRange(pTarget, m_dist))
|
||||
return false;
|
||||
|
||||
if (m_dist < 0.0f && me->IsWithinCombatRange(pTarget, -m_dist))
|
||||
return false;
|
||||
|
||||
if (m_aura)
|
||||
{
|
||||
if (m_aura > 0)
|
||||
{
|
||||
if (!pTarget->HasAura(m_aura))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pTarget->HasAura(m_aura))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
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)
|
||||
{
|
||||
const std::list<HostileReference*> &threatlist = me->getThreatManager().getThreatList();
|
||||
|
||||
if (threatlist.empty())
|
||||
return;
|
||||
|
||||
DefaultTargetSelector targetSelector(me, dist,playerOnly, aura);
|
||||
for (std::list<HostileReference*>::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
if (targetSelector((*itr)->getTarget()))
|
||||
targetList.push_back((*itr)->getTarget());
|
||||
|
||||
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 (targetList.size() < num)
|
||||
return;
|
||||
|
||||
if (targetType == SELECT_TARGET_RANDOM)
|
||||
{
|
||||
while (num < targetList.size()) {
|
||||
std::list<Unit*>::iterator itr = targetList.begin();
|
||||
advance(itr, urand(0, targetList.size()-1));
|
||||
targetList.erase(itr);
|
||||
}
|
||||
}
|
||||
else
|
||||
targetList.resize(num);
|
||||
}
|
||||
|
||||
float UnitAI::DoGetSpellMaxRange(uint32 spellId, bool positive)
|
||||
{
|
||||
return GetSpellMaxRange(spellId, positive);
|
||||
}
|
||||
|
||||
void UnitAI::DoAddAuraToAllHostilePlayers(uint32 spellid)
|
||||
{
|
||||
if (me->isInCombat())
|
||||
{
|
||||
std::list<HostileReference*>& threatlist = me->getThreatManager().getThreatList();
|
||||
for (std::list<HostileReference*>::iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
{
|
||||
if (Unit *pTemp = Unit::GetUnit(*me,(*itr)->getUnitGuid()))
|
||||
if (pTemp->GetTypeId() == TYPEID_PLAYER)
|
||||
me->AddAura(spellid, pTemp);
|
||||
}
|
||||
}else
|
||||
return;
|
||||
}
|
||||
|
||||
void UnitAI::DoCastToAllHostilePlayers(uint32 spellid, bool triggered)
|
||||
{
|
||||
if (me->isInCombat())
|
||||
{
|
||||
std::list<HostileReference*>& threatlist = me->getThreatManager().getThreatList();
|
||||
for (std::list<HostileReference*>::iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
{
|
||||
if (Unit *pTemp = Unit::GetUnit(*me,(*itr)->getUnitGuid()))
|
||||
if (pTemp->GetTypeId() == TYPEID_PLAYER)
|
||||
me->CastSpell(pTemp, spellid, triggered);
|
||||
}
|
||||
}else
|
||||
return;
|
||||
}
|
||||
|
||||
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 SpellEntry * spellInfo = GetSpellStore()->LookupEntry(spellId);
|
||||
bool playerOnly = spellInfo->AttributesEx3 & SPELL_ATTR_EX3_PLAYERS_ONLY;
|
||||
//float range = GetSpellMaxRange(spellInfo, false);
|
||||
target = SelectTarget(SELECT_TARGET_RANDOM, 0, GetSpellMaxRange(spellInfo, false), playerOnly);
|
||||
break;
|
||||
}
|
||||
case AITARGET_ALLY: target = me; break;
|
||||
case AITARGET_BUFF: target = me; break;
|
||||
case AITARGET_DEBUFF:
|
||||
{
|
||||
const SpellEntry * spellInfo = GetSpellStore()->LookupEntry(spellId);
|
||||
bool playerOnly = spellInfo->AttributesEx3 & SPELL_ATTR_EX3_PLAYERS_ONLY;
|
||||
float range = GetSpellMaxRange(spellInfo, false);
|
||||
|
||||
DefaultTargetSelector targetSelector(me, range, playerOnly, -(int32)spellId);
|
||||
if (!(spellInfo->Attributes & SPELL_ATTR_BREAKABLE_BY_DAMAGE)
|
||||
&& !(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);
|
||||
}
|
||||
|
||||
#define UPDATE_TARGET(a) {if (AIInfo->target<a) AIInfo->target=a;}
|
||||
|
||||
void UnitAI::FillAISpellInfo()
|
||||
{
|
||||
AISpellInfo = new AISpellInfoType[GetSpellStore()->GetNumRows()];
|
||||
|
||||
AISpellInfoType *AIInfo = AISpellInfo;
|
||||
const SpellEntry * spellInfo;
|
||||
|
||||
for (uint32 i = 0; i < GetSpellStore()->GetNumRows(); ++i, ++AIInfo)
|
||||
{
|
||||
spellInfo = GetSpellStore()->LookupEntry(i);
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
if (spellInfo->Attributes & SPELL_ATTR_CASTABLE_WHILE_DEAD)
|
||||
AIInfo->condition = AICOND_DIE;
|
||||
else if (IsPassiveSpell(i) || GetSpellDuration(spellInfo) == -1)
|
||||
AIInfo->condition = AICOND_AGGRO;
|
||||
else
|
||||
AIInfo->condition = AICOND_COMBAT;
|
||||
|
||||
if (AIInfo->cooldown < spellInfo->RecoveryTime)
|
||||
AIInfo->cooldown = spellInfo->RecoveryTime;
|
||||
|
||||
if (!GetSpellMaxRange(spellInfo, false))
|
||||
UPDATE_TARGET(AITARGET_SELF)
|
||||
else
|
||||
{
|
||||
for (uint32 j = 0; j < 3; ++j)
|
||||
{
|
||||
uint32 targetType = spellInfo->EffectImplicitTargetA[j];
|
||||
|
||||
if (targetType == TARGET_UNIT_TARGET_ENEMY
|
||||
|| targetType == TARGET_DST_TARGET_ENEMY)
|
||||
UPDATE_TARGET(AITARGET_VICTIM)
|
||||
else if (targetType == TARGET_UNIT_AREA_ENEMY_DST)
|
||||
UPDATE_TARGET(AITARGET_ENEMY)
|
||||
|
||||
if (spellInfo->Effect[j] == SPELL_EFFECT_APPLY_AURA)
|
||||
{
|
||||
if (targetType == TARGET_UNIT_TARGET_ENEMY)
|
||||
UPDATE_TARGET(AITARGET_DEBUFF)
|
||||
else if (IsPositiveSpell(i))
|
||||
UPDATE_TARGET(AITARGET_BUFF)
|
||||
}
|
||||
}
|
||||
}
|
||||
AIInfo->realCooldown = spellInfo->RecoveryTime + spellInfo->StartRecoveryTime;
|
||||
SpellRangeEntry const* srange = sSpellRangeStore.LookupEntry(spellInfo->rangeIndex);
|
||||
if (srange)
|
||||
AIInfo->maxRange = srange->maxRangeHostile * 3 / 4;
|
||||
}
|
||||
}
|
||||
|
||||
//Enable PlayerAI when charmed
|
||||
void PlayerAI::OnCharmed(bool apply) { me->IsAIEnabled = apply; }
|
||||
|
||||
void SimpleCharmedAI::UpdateAI(const 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())
|
||||
{
|
||||
charmer->Kill(me);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!charmer->isInCombat())
|
||||
me->GetMotionMaster()->MoveFollow(charmer, PET_FOLLOW_DIST, me->GetFollowAngle());
|
||||
|
||||
Unit *target = me->getVictim();
|
||||
if (!target || !charmer->canAttack(target))
|
||||
AttackStart(charmer->SelectNearestTargetInAttackDistance());
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the 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, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifndef TRINITY_UNITAI_H
|
||||
#define TRINITY_UNITAI_H
|
||||
|
||||
#include "Platform/Define.h"
|
||||
#include <list>
|
||||
#include "Unit.h"
|
||||
|
||||
class Unit;
|
||||
class Player;
|
||||
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,
|
||||
};
|
||||
|
||||
class UnitAI
|
||||
{
|
||||
protected:
|
||||
Unit * const me;
|
||||
public:
|
||||
explicit UnitAI(Unit *u) : me(u) {}
|
||||
virtual ~UnitAI() {}
|
||||
|
||||
virtual bool CanAIAttack(const Unit * /*who*/) const { return true; }
|
||||
virtual void AttackStart(Unit *);
|
||||
virtual void UpdateAI(const 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(const int32 /*param*/ = 0) {}
|
||||
virtual uint32 GetData(uint32 /*id = 0*/) { return 0; }
|
||||
virtual void SetData(uint32 /*id*/, uint32 /*value*/) {}
|
||||
virtual void SetGUID(const uint64 &/*guid*/, int32 /*id*/ = 0) {}
|
||||
virtual uint64 GetGUID(int32 /*id*/ = 0) { return 0; }
|
||||
|
||||
Unit* SelectTarget(SelectAggroTarget targetType, uint32 position = 0, float dist = 0.0f, bool playerOnly = false, int32 aura = 0);
|
||||
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> Unit* SelectTarget(SelectAggroTarget targetType, uint32 position, PREDICATE predicate)
|
||||
{
|
||||
const std::list<HostileReference *> &threatlist = me->getThreatManager().getThreatList();
|
||||
std::list<Unit*> targetList;
|
||||
|
||||
if (position >= threatlist.size())
|
||||
return NULL;
|
||||
|
||||
for (std::list<HostileReference*>::const_iterator itr = threatlist.begin(); itr != threatlist.end(); ++itr)
|
||||
{
|
||||
HostileReference* ref = (*itr);
|
||||
if (predicate(ref->getTarget()))
|
||||
targetList.push_back(ref->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();
|
||||
advance(itr, position);
|
||||
return *itr;
|
||||
}
|
||||
break;
|
||||
|
||||
case SELECT_TARGET_FARTHEST:
|
||||
case SELECT_TARGET_BOTTOMAGGRO:
|
||||
{
|
||||
std::list<Unit*>::reverse_iterator ritr = targetList.rbegin();
|
||||
advance(ritr, position);
|
||||
return *ritr;
|
||||
}
|
||||
break;
|
||||
|
||||
case SELECT_TARGET_RANDOM:
|
||||
{
|
||||
std::list<Unit*>::iterator itr = targetList.begin();
|
||||
advance(itr, urand(position, targetList.size()-1));
|
||||
return *itr;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
class PlayerAI : public UnitAI
|
||||
{
|
||||
protected:
|
||||
Player* const me;
|
||||
public:
|
||||
explicit PlayerAI(Player *p) : UnitAI((Unit*)p), me(p) {}
|
||||
|
||||
void OnCharmed(bool apply);
|
||||
};
|
||||
|
||||
class SimpleCharmedAI : public PlayerAI
|
||||
{
|
||||
public:
|
||||
void UpdateAI(const uint32 diff);
|
||||
SimpleCharmedAI(Player *p): PlayerAI(p) {}
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user