Implement the creature spell lists system. (#123)
* Implement creature spells system. * Fix remaining issues. * Don't let mobs cast without enough mana. * Update revsiion.
This commit is contained in:
parent
b3eeb0cc3c
commit
5808fc6d25
@ -318,6 +318,8 @@ bool ChatHandler::HandleNpcAIInfoCommand(char* /*args*/)
|
||||
strAI.empty() ? " - " : strAI.c_str(),
|
||||
cstrAIClass ? cstrAIClass : " - ",
|
||||
strScript.empty() ? " - " : strScript.c_str());
|
||||
PSendSysMessage("Motion Type: %u", pTarget->GetMotionMaster()->GetCurrentMovementGeneratorType());
|
||||
PSendSysMessage("Casting Spell: %s", pTarget->IsNonMeleeSpellCasted(true) ? "yes" : "no");
|
||||
|
||||
if (pTarget->AI())
|
||||
{
|
||||
|
@ -145,6 +145,7 @@ bool ChatHandler::HandleReloadAllScriptsCommand(char* /*args*/)
|
||||
HandleReloadDBScriptsOnQuestEndCommand((char*)"a");
|
||||
HandleReloadDBScriptsOnQuestStartCommand((char*)"a");
|
||||
HandleReloadDBScriptsOnSpellCommand((char*)"a");
|
||||
HandleReloadDBScriptsOnCreatureSpellCommand((char*)"a");
|
||||
SendGlobalSysMessage("DB tables `*_scripts` reloaded.", SEC_MODERATOR);
|
||||
HandleReloadDbScriptStringCommand((char*)"a");
|
||||
return true;
|
||||
@ -263,6 +264,14 @@ bool ChatHandler::HandleReloadCreaturesStatsCommand(char* /*args*/)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChatHandler::HandleReloadCreatureSpellsCommand(char* /*args*/)
|
||||
{
|
||||
sLog.outString("Re-Loading Creature Spells... (`creature_spells`)");
|
||||
sObjectMgr.LoadCreatureSpells();
|
||||
SendGlobalSysMessage("DB table `creature_spells` reloaded.", SEC_MODERATOR);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChatHandler::HandleReloadGossipMenuCommand(char* /*args*/)
|
||||
{
|
||||
sObjectMgr.LoadGossipMenus();
|
||||
@ -690,6 +699,30 @@ bool ChatHandler::HandleReloadDBScriptsOnSpellCommand(char* args)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChatHandler::HandleReloadDBScriptsOnCreatureSpellCommand(char* args)
|
||||
{
|
||||
if (sScriptMgr.IsScriptScheduled())
|
||||
{
|
||||
SendSysMessage("DB scripts used currently, please attempt reload later.");
|
||||
SetSentErrorMessage(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*args != 'a')
|
||||
{
|
||||
sLog.outString("Re-Loading Scripts from `db_scripts [type = DBS_ON_CREATURE_SPELL]`...");
|
||||
}
|
||||
|
||||
sScriptMgr.LoadDbScripts(DBS_ON_CREATURE_SPELL);
|
||||
|
||||
if (*args != 'a')
|
||||
{
|
||||
SendGlobalSysMessage("DB table `db_scripts [type = DBS_ON_CREATURE_SPELL]` reloaded.", SEC_MODERATOR);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChatHandler::HandleReloadDBScriptsOnQuestStartCommand(char* args)
|
||||
{
|
||||
if (sScriptMgr.IsScriptScheduled())
|
||||
|
@ -123,9 +123,12 @@ void AggressorAI::EnterEvadeMode()
|
||||
i_victimGuid.Clear();
|
||||
m_creature->CombatStop(true);
|
||||
m_creature->SetLootRecipient(NULL);
|
||||
|
||||
// Reset back to default spells template. This also resets timers.
|
||||
SetSpellsList(m_creature->GetCreatureInfo()->SpellListId);
|
||||
}
|
||||
|
||||
void AggressorAI::UpdateAI(const uint32 /*diff*/)
|
||||
void AggressorAI::UpdateAI(const uint32 diff)
|
||||
{
|
||||
// update i_victimGuid if m_creature->getVictim() !=0 and changed
|
||||
if (!m_creature->SelectHostileTarget() || !m_creature->getVictim())
|
||||
@ -135,6 +138,9 @@ void AggressorAI::UpdateAI(const uint32 /*diff*/)
|
||||
|
||||
i_victimGuid = m_creature->getVictim()->GetObjectGuid();
|
||||
|
||||
if (!m_CreatureSpells.empty())
|
||||
UpdateSpellsList(diff);
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,7 @@
|
||||
#include "movement/MoveSplineInit.h"
|
||||
#include "CreatureLinkingMgr.h"
|
||||
#include "DisableMgr.h"
|
||||
#include "MovementGenerator.h"
|
||||
#ifdef ENABLE_ELUNA
|
||||
#include "LuaEngine.h"
|
||||
#endif /* ENABLE_ELUNA */
|
||||
@ -3278,3 +3279,106 @@ void Creature::SetWaterWalk(bool enable)
|
||||
data << GetPackGUID();
|
||||
SendMessageToSet(&data, true);
|
||||
}
|
||||
|
||||
SpellCastResult Creature::TryToCast(Unit* pTarget, uint32 uiSpell, uint32 uiCastFlags, uint8 uiChance)
|
||||
{
|
||||
if (IsNonMeleeSpellCasted(false) && !(uiCastFlags & (CF_TRIGGERED | CF_INTERRUPT_PREVIOUS)))
|
||||
return SPELL_FAILED_SPELL_IN_PROGRESS;
|
||||
|
||||
const SpellEntry* pSpellInfo = sSpellStore.LookupEntry(uiSpell);
|
||||
|
||||
if (!pSpellInfo)
|
||||
{
|
||||
sLog.outError("TryToCast: attempt to cast unknown spell %u by creature with entry: %u", uiSpell, GetEntry());
|
||||
return SPELL_FAILED_SPELL_UNAVAILABLE;
|
||||
}
|
||||
|
||||
return TryToCast(pTarget, pSpellInfo, uiCastFlags, uiChance);
|
||||
}
|
||||
|
||||
SpellCastResult Creature::TryToCast(Unit* pTarget, const SpellEntry* pSpellInfo, uint32 uiCastFlags, uint8 uiChance)
|
||||
{
|
||||
if (!pTarget)
|
||||
return SPELL_FAILED_BAD_IMPLICIT_TARGETS;
|
||||
|
||||
// This spell should only be cast when target does not have the aura it applies.
|
||||
if ((uiCastFlags & CF_AURA_NOT_PRESENT) && pTarget->HasAura(pSpellInfo->Id))
|
||||
return SPELL_FAILED_MORE_POWERFUL_SPELL_ACTIVE;
|
||||
|
||||
if (GetMotionMaster()->GetCurrentMovementGeneratorType() == TIMED_FLEEING_MOTION_TYPE)
|
||||
return SPELL_FAILED_FLEEING;
|
||||
|
||||
// This spell is only used when target is in melee range.
|
||||
if ((uiCastFlags & CF_ONLY_IN_MELEE) && !CanReachWithMeleeAttack(pTarget))
|
||||
return SPELL_FAILED_OUT_OF_RANGE;
|
||||
|
||||
// This spell should not be used if target is in melee range.
|
||||
if ((uiCastFlags & CF_NOT_IN_MELEE) && CanReachWithMeleeAttack(pTarget))
|
||||
return SPELL_FAILED_TOO_CLOSE;
|
||||
|
||||
// This spell should only be cast when we cannot get into melee range.
|
||||
if ((uiCastFlags & CF_TARGET_UNREACHABLE) && (CanReachWithMeleeAttack(pTarget) || (GetMotionMaster()->GetCurrentMovementGeneratorType() != CHASE_MOTION_TYPE) || !(hasUnitState(UNIT_STAT_ROOT) || !GetMotionMaster()->GetCurrent()->IsReachable())))
|
||||
return SPELL_FAILED_MOVING;
|
||||
|
||||
// Custom checks
|
||||
if (!(uiCastFlags & CF_FORCE_CAST))
|
||||
{
|
||||
// Motion Master is not updated when this state is active.
|
||||
if (!hasUnitState(UNIT_STAT_CAN_NOT_MOVE))
|
||||
{
|
||||
// Can't cast while fleeing.
|
||||
switch (GetMotionMaster()->GetCurrentMovementGeneratorType())
|
||||
{
|
||||
case TIMED_FLEEING_MOTION_TYPE:
|
||||
return SPELL_FAILED_FLEEING;
|
||||
}
|
||||
}
|
||||
|
||||
// If the spell requires to be behind the target.
|
||||
if (pSpellInfo->AttributesEx2 == SPELL_ATTR_EX2_FACING_TARGETS_BACK && pSpellInfo->HasAttribute(SPELL_ATTR_EX_FACING_TARGET) && pTarget->HasInArc(M_PI_F, this))
|
||||
return SPELL_FAILED_UNIT_NOT_BEHIND;
|
||||
|
||||
if (!IsAreaOfEffectSpell(pSpellInfo))
|
||||
{
|
||||
// If the spell requires the target having a specific power type.
|
||||
if (!IsTargetPowerTypeValid(pSpellInfo, pTarget->GetPowerType()))
|
||||
return SPELL_FAILED_UNKNOWN;
|
||||
|
||||
// No point in casting if target is immune.
|
||||
if (pTarget->IsImmuneToDamage(GetSpellSchoolMask(pSpellInfo)))
|
||||
return SPELL_FAILED_IMMUNE;
|
||||
}
|
||||
|
||||
// Mind control abilities can't be used with just 1 attacker or mob will reset.
|
||||
if ((GetThreatManager().getThreatList().size() == 1) && (IsSpellHaveAura(pSpellInfo, SPELL_AURA_MOD_CHARM) || IsSpellHaveAura(pSpellInfo, SPELL_AURA_MOD_POSSESS)))
|
||||
return SPELL_FAILED_UNKNOWN;
|
||||
|
||||
// Do not use dismounting spells when target is not mounted (there are 4 such spells).
|
||||
if (!pTarget->IsMounted() && IsDismountSpell(pSpellInfo))
|
||||
return SPELL_FAILED_ONLY_MOUNTED;
|
||||
}
|
||||
|
||||
// Interrupt any previous spell
|
||||
if ((uiCastFlags & CF_INTERRUPT_PREVIOUS) && IsNonMeleeSpellCasted(false))
|
||||
InterruptNonMeleeSpells(false);
|
||||
|
||||
Spell *spell = new Spell(this, pSpellInfo, uiCastFlags & CF_TRIGGERED);
|
||||
|
||||
SpellCastTargets targets;
|
||||
targets.setUnitTarget(pTarget);
|
||||
|
||||
if (pSpellInfo->Targets & TARGET_FLAG_DEST_LOCATION)
|
||||
{
|
||||
targets.setDestination(pTarget->GetPositionX(), pTarget->GetPositionY(), pTarget->GetPositionZ());
|
||||
}
|
||||
if (pSpellInfo->Targets & TARGET_FLAG_SOURCE_LOCATION)
|
||||
{
|
||||
if (WorldObject* caster = spell->GetCastingObject())
|
||||
{
|
||||
targets.setSource(caster->GetPositionX(), caster->GetPositionY(), caster->GetPositionZ());
|
||||
}
|
||||
}
|
||||
|
||||
spell->m_CastItem = NULL;
|
||||
return spell->prepare(&targets, NULL, uiChance);
|
||||
}
|
||||
|
@ -136,6 +136,7 @@ struct CreatureInfo
|
||||
int32 ResistanceFrost;
|
||||
int32 ResistanceShadow;
|
||||
int32 ResistanceArcane;
|
||||
uint32 SpellListId;
|
||||
uint32 PetSpellDataId;
|
||||
uint32 MovementType;
|
||||
uint32 TrainerType;
|
||||
@ -732,6 +733,10 @@ class Creature : public Unit
|
||||
CreatureSpellCooldowns m_CreatureSpellCooldowns;
|
||||
CreatureSpellCooldowns m_CreatureCategoryCooldowns;
|
||||
|
||||
// Used by Creature Spells system to always know result of cast
|
||||
SpellCastResult TryToCast(Unit* pTarget, uint32 uiSpell, uint32 uiCastFlags, uint8 uiChance);
|
||||
SpellCastResult TryToCast(Unit* pTarget, SpellEntry const* pSpellInfo, uint32 uiCastFlags, uint8 uiChance);
|
||||
|
||||
float GetAttackDistance(Unit const* pl) const;
|
||||
|
||||
void SendAIReaction(AiReaction reactionType);
|
||||
|
@ -29,12 +29,15 @@
|
||||
#include "GridNotifiers.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "CellImpl.h"
|
||||
#include "movement/MoveSplineInit.h"
|
||||
#include "movement/MoveSpline.h"
|
||||
|
||||
static_assert(MAXIMAL_AI_EVENT_EVENTAI <= 32, "Maximal 32 AI_EVENTs supported with EventAI");
|
||||
|
||||
CreatureAI::CreatureAI(Creature* creature) : m_creature(creature), m_combatMovement(COMBAT_MOVEMENT_SCRIPT),
|
||||
m_attackDistance(0.0f), m_attackAngle(0.0f)
|
||||
m_attackDistance(0.0f), m_attackAngle(0.0f), m_meleeAttack(true), m_uiCastingDelay(0)
|
||||
{
|
||||
SetSpellsList(creature->GetCreatureInfo()->SpellListId);
|
||||
}
|
||||
|
||||
CreatureAI::~CreatureAI()
|
||||
@ -167,6 +170,195 @@ CanCastResult CreatureAI::DoCastSpellIfCan(Unit* pTarget, uint32 uiSpell, uint32
|
||||
return CAST_FAIL_IS_CASTING;
|
||||
}
|
||||
|
||||
// Values used in target_type column
|
||||
enum CreatureSpellTarget
|
||||
{
|
||||
TARGET_T_PROVIDED_TARGET = 0, //Object that was provided to the command.
|
||||
|
||||
TARGET_T_HOSTILE = 1, //Our current target (ie: highest aggro).
|
||||
TARGET_T_HOSTILE_SECOND_AGGRO = 2, //Second highest aggro (generaly used for cleaves and some special attacks).
|
||||
TARGET_T_HOSTILE_LAST_AGGRO = 3, //Dead last on aggro (no idea what this could be used for).
|
||||
TARGET_T_HOSTILE_RANDOM = 4, //Just any random target on our threat list.
|
||||
TARGET_T_HOSTILE_RANDOM_NOT_TOP = 5, //Any random target except top threat.
|
||||
|
||||
TARGET_T_FRIENDLY = 14, //Random friendly unit.
|
||||
//Param1 = search_radius
|
||||
//Param2 = (bool) exclude_target
|
||||
TARGET_T_FRIENDLY_INJURED = 15, //Friendly unit missing the most health.
|
||||
//Param1 = search_radius
|
||||
//Param2 = hp_percent
|
||||
TARGET_T_FRIENDLY_INJURED_EXCEPT = 16, //Friendly unit missing the most health but not provided target.
|
||||
//Param1 = search_radius
|
||||
//Param2 = hp_percent
|
||||
TARGET_T_FRIENDLY_MISSING_BUFF = 17, //Friendly unit without aura.
|
||||
//Param1 = search_radius
|
||||
//Param2 = spell_id
|
||||
TARGET_T_FRIENDLY_MISSING_BUFF_EXCEPT = 18, //Friendly unit without aura but not provided target.
|
||||
//Param1 = search_radius
|
||||
//Param2 = spell_id
|
||||
TARGET_T_FRIENDLY_CC = 19, //Friendly unit under crowd control.
|
||||
//Param1 = search_radius
|
||||
TARGET_T_END
|
||||
};
|
||||
|
||||
// Returns a target based on the type specified.
|
||||
Unit* GetTargetByType(Unit* pSource, Unit* pTarget, uint8 TargetType, uint32 Param1, uint32 Param2)
|
||||
{
|
||||
switch (TargetType)
|
||||
{
|
||||
case TARGET_T_PROVIDED_TARGET:
|
||||
return pTarget;
|
||||
case TARGET_T_HOSTILE:
|
||||
return pSource->getVictim();
|
||||
break;
|
||||
case TARGET_T_HOSTILE_SECOND_AGGRO:
|
||||
if (Creature* pCreatureSource = ToCreature(pSource))
|
||||
return pCreatureSource->SelectAttackingTarget(ATTACKING_TARGET_TOPAGGRO, 1);
|
||||
break;
|
||||
case TARGET_T_HOSTILE_LAST_AGGRO:
|
||||
if (Creature* pCreatureSource = ToCreature(pSource))
|
||||
return pCreatureSource->SelectAttackingTarget(ATTACKING_TARGET_BOTTOMAGGRO, 0);
|
||||
break;
|
||||
case TARGET_T_HOSTILE_RANDOM:
|
||||
if (Creature* pCreatureSource = ToCreature(pSource))
|
||||
return pCreatureSource->SelectAttackingTarget(ATTACKING_TARGET_RANDOM, 0);
|
||||
break;
|
||||
case TARGET_T_HOSTILE_RANDOM_NOT_TOP:
|
||||
if (Creature* pCreatureSource = ToCreature(pSource))
|
||||
return pCreatureSource->SelectAttackingTarget(ATTACKING_TARGET_RANDOM, 1);
|
||||
break;
|
||||
case TARGET_T_FRIENDLY:
|
||||
return pSource->SelectRandomFriendlyTarget(Param2 ? pTarget : nullptr, Param1 ? Param1 : 30.0f);
|
||||
case TARGET_T_FRIENDLY_INJURED:
|
||||
return pSource->FindLowestHpFriendlyUnit(Param1 ? Param1 : 30.0f, Param2 ? Param2 : 50, true);
|
||||
case TARGET_T_FRIENDLY_INJURED_EXCEPT:
|
||||
return pSource->FindLowestHpFriendlyUnit(Param1 ? Param1 : 30.0f, Param2 ? Param2 : 50, true, pTarget);;
|
||||
case TARGET_T_FRIENDLY_MISSING_BUFF:
|
||||
return pSource->FindFriendlyUnitMissingBuff(Param1 ? Param1 : 30.0f, Param2);
|
||||
case TARGET_T_FRIENDLY_MISSING_BUFF_EXCEPT:
|
||||
return pSource->FindFriendlyUnitMissingBuff(Param1 ? Param1 : 30.0f, Param2, pTarget);
|
||||
case TARGET_T_FRIENDLY_CC:
|
||||
return pSource->FindFriendlyUnitCC(Param1 ? Param1 : 30.0f);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void CreatureAI::SetSpellsList(uint32 entry)
|
||||
{
|
||||
if (entry == 0)
|
||||
m_CreatureSpells.clear();
|
||||
else if (CreatureSpellsList const* pSpellsTemplate = sObjectMgr.GetCreatureSpellsList(entry))
|
||||
SetSpellsList(pSpellsTemplate);
|
||||
else
|
||||
sLog.outError("CreatureAI: Attempt to set spells template of creature %u to non-existent entry %u.", m_creature->GetEntry(), entry);
|
||||
}
|
||||
|
||||
void CreatureAI::SetSpellsList(CreatureSpellsList const* pSpellsList)
|
||||
{
|
||||
m_CreatureSpells.clear();
|
||||
for (const auto& entry : *pSpellsList)
|
||||
{
|
||||
m_CreatureSpells.push_back(CreatureAISpellsEntry(entry));
|
||||
}
|
||||
m_CreatureSpells.shrink_to_fit();
|
||||
m_uiCastingDelay = 0;
|
||||
}
|
||||
|
||||
// Creature spell lists should be updated every 1.2 seconds according to research.
|
||||
// https://www.reddit.com/r/wowservers/comments/834nt5/felmyst_ai_system_research/
|
||||
#define CREATURE_CASTING_DELAY 1200
|
||||
|
||||
void CreatureAI::UpdateSpellsList(uint32 const uiDiff)
|
||||
{
|
||||
if (m_uiCastingDelay <= uiDiff)
|
||||
{
|
||||
uint32 const uiDesync = (uiDiff - m_uiCastingDelay);
|
||||
DoSpellsListCasts(CREATURE_CASTING_DELAY + uiDesync);
|
||||
m_uiCastingDelay = uiDesync < CREATURE_CASTING_DELAY ? CREATURE_CASTING_DELAY - uiDesync : 0;
|
||||
}
|
||||
else
|
||||
m_uiCastingDelay -= uiDiff;
|
||||
}
|
||||
|
||||
void CreatureAI::DoSpellsListCasts(uint32 const uiDiff)
|
||||
{
|
||||
bool bDontCast = false;
|
||||
for (auto& spell : m_CreatureSpells)
|
||||
{
|
||||
if (spell.cooldown <= uiDiff)
|
||||
{
|
||||
// Cooldown has expired.
|
||||
spell.cooldown = 0;
|
||||
|
||||
// Prevent casting multiple spells in the same update. Only update timers.
|
||||
if (!(spell.castFlags & (CF_TRIGGERED | CF_INTERRUPT_PREVIOUS)))
|
||||
{
|
||||
if (bDontCast || m_creature->IsNonMeleeSpellCasted(false))
|
||||
continue;
|
||||
}
|
||||
|
||||
// Checked on startup.
|
||||
SpellEntry const* pSpellInfo = sSpellStore.LookupEntry(spell.spellId);
|
||||
|
||||
Unit* pTarget = GetTargetByType(m_creature, m_creature, spell.castTarget, spell.targetParam1 ? spell.targetParam1 : sSpellRangeStore.LookupEntry(pSpellInfo->rangeIndex)->maxRange, spell.targetParam2);
|
||||
|
||||
SpellCastResult result = m_creature->TryToCast(pTarget, pSpellInfo, spell.castFlags, spell.probability);
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case SPELL_CAST_OK:
|
||||
{
|
||||
bDontCast = !(spell.castFlags & CF_TRIGGERED);
|
||||
spell.cooldown = urand(spell.delayRepeatMin, spell.delayRepeatMax);
|
||||
|
||||
if (spell.castFlags & CF_MAIN_RANGED_SPELL)
|
||||
{
|
||||
if (m_creature->m_movementInfo.HasMovementFlag(movementFlagsMask))
|
||||
m_creature->StopMoving();
|
||||
|
||||
SetCombatMovement(false);
|
||||
//SetMeleeAttack(false);
|
||||
}
|
||||
|
||||
// If there is a script for this spell, run it.
|
||||
if (spell.scriptId)
|
||||
m_creature->GetMap()->ScriptsStart(DBS_ON_CREATURE_SPELL, spell.scriptId, m_creature, pTarget);
|
||||
break;
|
||||
}
|
||||
case SPELL_FAILED_FLEEING:
|
||||
case SPELL_FAILED_SPELL_IN_PROGRESS:
|
||||
{
|
||||
// Do nothing so it will try again on next update.
|
||||
break;
|
||||
}
|
||||
case SPELL_FAILED_TRY_AGAIN:
|
||||
{
|
||||
// Chance roll failed, so we reset cooldown.
|
||||
spell.cooldown = urand(spell.delayRepeatMin, spell.delayRepeatMax);
|
||||
if (spell.castFlags & CF_MAIN_RANGED_SPELL)
|
||||
{
|
||||
SetCombatMovement(true);
|
||||
//SetMeleeAttack(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// other error
|
||||
if (spell.castFlags & CF_MAIN_RANGED_SPELL)
|
||||
{
|
||||
SetCombatMovement(true);
|
||||
//SetMeleeAttack(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
spell.cooldown -= uiDiff;
|
||||
}
|
||||
}
|
||||
|
||||
bool CreatureAI::DoMeleeAttackIfReady()
|
||||
{
|
||||
return m_creature->UpdateMeleeAttackingState();
|
||||
@ -214,6 +406,7 @@ void CreatureAI::SetChase(bool chase)
|
||||
case IDLE_MOTION_TYPE:
|
||||
case CHASE_MOTION_TYPE:
|
||||
case FOLLOW_MOTION_TYPE:
|
||||
m_meleeAttack = true;
|
||||
creatureMotion->Clear(false);
|
||||
creatureMotion->MoveChase(m_creature->getVictim(), 0.0f, 0.0f);
|
||||
break;
|
||||
@ -226,6 +419,7 @@ void CreatureAI::SetChase(bool chase)
|
||||
m_creature->StopMoving();
|
||||
if (!m_creature->CanReachWithMeleeAttack(m_creature->getVictim()))
|
||||
{
|
||||
m_meleeAttack = false;
|
||||
m_creature->SendMeleeAttackStop(m_creature->getVictim());
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "Dynamic/FactoryHolder.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "ObjectMgr.h"
|
||||
|
||||
class WorldObject;
|
||||
class GameObject;
|
||||
@ -65,6 +66,18 @@ enum CastFlags
|
||||
CAST_AURA_NOT_PRESENT = 0x20, // Only casts the spell if the target does not have an aura from the spell
|
||||
};
|
||||
|
||||
enum SpellListCastFlags
|
||||
{
|
||||
CF_INTERRUPT_PREVIOUS = 0x01, // Interrupt any spell casting
|
||||
CF_TRIGGERED = 0x02, // Triggered (this makes spell cost zero mana and have no cast time)
|
||||
CF_FORCE_CAST = 0x04, // Forces cast even if creature is out of mana or out of range
|
||||
CF_MAIN_RANGED_SPELL = 0x08, // To be used by ranged mobs only. Creature will not chase target until cast fails.
|
||||
CF_TARGET_UNREACHABLE = 0x10, // Will only use the ability if creature cannot currently get to target
|
||||
CF_AURA_NOT_PRESENT = 0x20, // Only casts the spell if the target does not have an aura from the spell
|
||||
CF_ONLY_IN_MELEE = 0x40, // Only casts if the creature is in melee range of the target
|
||||
CF_NOT_IN_MELEE = 0x80, // Only casts if the creature is not in melee range of the target
|
||||
};
|
||||
|
||||
enum CombatMovementFlags
|
||||
{
|
||||
COMBAT_MOVEMENT_SCRIPT = 0x01, // Combat movement enforced by script
|
||||
@ -103,6 +116,12 @@ enum AIEventType
|
||||
AI_EVENT_CUSTOM_F = 1005,
|
||||
};
|
||||
|
||||
struct CreatureAISpellsEntry : CreatureSpellsEntry
|
||||
{
|
||||
uint32 cooldown;
|
||||
CreatureAISpellsEntry(CreatureSpellsEntry const& EntryStruct) : CreatureSpellsEntry(EntryStruct), cooldown(urand(EntryStruct.delayInitialMin, EntryStruct.delayInitialMax)) {}
|
||||
};
|
||||
|
||||
class CreatureAI
|
||||
{
|
||||
public:
|
||||
@ -317,6 +336,17 @@ class CreatureAI
|
||||
*/
|
||||
virtual CanCastResult DoCastSpellIfCan(Unit* pTarget, uint32 uiSpell, uint32 uiCastFlags = 0, ObjectGuid OriginalCasterGuid = ObjectGuid());
|
||||
|
||||
// Assigns a creature_spells list to the AI.
|
||||
void SetSpellsList(uint32 entry);
|
||||
void SetSpellsList(CreatureSpellsList const* pSpellsList);
|
||||
|
||||
// Goes through the creature's spells list to update timers and cast spells.
|
||||
void UpdateSpellsList(uint32 const uiDiff);
|
||||
void DoSpellsListCasts(uint32 const uiDiff);
|
||||
|
||||
// Enables or disables melee attacks.
|
||||
void SetMeleeAttack(bool enabled);
|
||||
|
||||
/// Combat movement functions
|
||||
void SetCombatMovement(bool enable, bool stopOrStartMovement = false);
|
||||
bool IsCombatMovement() const { return m_combatMovement != 0; }
|
||||
@ -365,9 +395,12 @@ class CreatureAI
|
||||
/// How should an enemy be chased
|
||||
float m_attackDistance;
|
||||
float m_attackAngle;
|
||||
private:
|
||||
|
||||
/// Combat movement currently enabled
|
||||
uint8 m_combatMovement;
|
||||
bool m_meleeAttack; // If we allow melee auto attack
|
||||
uint8 m_combatMovement; // If we allow targeted movement gen (chasing target)
|
||||
uint32 m_uiCastingDelay; // Cooldown before updating spell list again
|
||||
std::vector<CreatureAISpellsEntry> m_CreatureSpells; // Contains the currently used creature_spells template
|
||||
};
|
||||
|
||||
struct SelectableAI : public FactoryHolder<CreatureAI>, public Permissible<Creature>
|
||||
|
@ -1348,6 +1348,9 @@ void CreatureEventAI::EnterEvadeMode()
|
||||
|
||||
m_creature->SetLootRecipient(NULL);
|
||||
|
||||
// Reset back to default spells template. This also resets timers.
|
||||
SetSpellsList(m_creature->GetCreatureInfo()->SpellListId);
|
||||
|
||||
// Handle Evade events
|
||||
for (CreatureEventAIList::iterator i = m_CreatureEventAIList.begin(); i != m_CreatureEventAIList.end(); ++i)
|
||||
{
|
||||
@ -1634,6 +1637,9 @@ void CreatureEventAI::UpdateAI(const uint32 diff)
|
||||
// Melee Auto-Attack (getVictim might be NULL as result of timer based events and actions)
|
||||
if (Combat && m_creature->getVictim() && m_MeleeEnabled)
|
||||
{
|
||||
if (!m_CreatureSpells.empty())
|
||||
UpdateSpellsList(diff);
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
}
|
||||
|
@ -112,9 +112,12 @@ void GuardAI::EnterEvadeMode()
|
||||
{
|
||||
m_creature->GetMotionMaster()->MoveTargetedHome();
|
||||
}
|
||||
|
||||
// Reset back to default spells template. This also resets timers.
|
||||
SetSpellsList(m_creature->GetCreatureInfo()->SpellListId);
|
||||
}
|
||||
|
||||
void GuardAI::UpdateAI(const uint32 /*diff*/)
|
||||
void GuardAI::UpdateAI(const uint32 diff)
|
||||
{
|
||||
// update i_victimGuid if i_creature.getVictim() !=0 and changed
|
||||
if (!m_creature->SelectHostileTarget() || !m_creature->getVictim())
|
||||
@ -124,6 +127,9 @@ void GuardAI::UpdateAI(const uint32 /*diff*/)
|
||||
|
||||
i_victimGuid = m_creature->getVictim()->GetObjectGuid();
|
||||
|
||||
if (!m_CreatureSpells.empty())
|
||||
UpdateSpellsList(diff);
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
|
@ -715,4 +715,55 @@ class WorldObject : public Object
|
||||
bool m_isActiveObject;
|
||||
};
|
||||
|
||||
// Helper functions to cast between different Object pointers. Useful when unsure that your object* is valid at all.
|
||||
inline WorldObject* ToWorldObject(Object* object)
|
||||
{
|
||||
return object && object->isType(TYPEMASK_WORLDOBJECT) ? static_cast<WorldObject*>(object) : nullptr;
|
||||
}
|
||||
|
||||
inline WorldObject const* ToWorldObject(Object const* object)
|
||||
{
|
||||
return object && object->isType(TYPEMASK_WORLDOBJECT) ? static_cast<WorldObject const*>(object) : nullptr;
|
||||
}
|
||||
|
||||
inline GameObject* ToGameObject(Object* object)
|
||||
{
|
||||
return object && object->GetTypeId() == TYPEID_GAMEOBJECT ? reinterpret_cast<GameObject*>(object) : nullptr;
|
||||
}
|
||||
|
||||
inline GameObject const* ToGameObject(Object const* object)
|
||||
{
|
||||
return object && object->GetTypeId() == TYPEID_GAMEOBJECT ? reinterpret_cast<GameObject const*>(object) : nullptr;
|
||||
}
|
||||
|
||||
inline Unit* ToUnit(Object* object)
|
||||
{
|
||||
return object && object->isType(TYPEMASK_UNIT) ? reinterpret_cast<Unit*>(object) : nullptr;
|
||||
}
|
||||
|
||||
inline Unit const* ToUnit(Object const* object)
|
||||
{
|
||||
return object && object->isType(TYPEMASK_UNIT) ? reinterpret_cast<Unit const*>(object) : nullptr;
|
||||
}
|
||||
|
||||
inline Creature* ToCreature(Object* object)
|
||||
{
|
||||
return object && object->GetTypeId() == TYPEID_UNIT ? reinterpret_cast<Creature*>(object) : nullptr;
|
||||
}
|
||||
|
||||
inline Creature const* ToCreature(Object const* object)
|
||||
{
|
||||
return object && object->GetTypeId() == TYPEID_UNIT ? reinterpret_cast<Creature const*>(object) : nullptr;
|
||||
}
|
||||
|
||||
inline Player* ToPlayer(Object* object)
|
||||
{
|
||||
return object && object->GetTypeId() == TYPEID_PLAYER ? reinterpret_cast<Player*>(object) : nullptr;
|
||||
}
|
||||
|
||||
inline Player const* ToPlayer(Object const* object)
|
||||
{
|
||||
return object && object->GetTypeId() == TYPEID_PLAYER ? reinterpret_cast<Player const*>(object) : nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1188,6 +1188,140 @@ void ObjectMgr::LoadCreatureModelInfo()
|
||||
sLog.outString();
|
||||
}
|
||||
|
||||
void ObjectMgr::LoadCreatureSpells()
|
||||
{
|
||||
// First we need to collect all script ids.
|
||||
std::set<uint32> spellScriptSet;
|
||||
|
||||
std::unique_ptr<QueryResult> result (WorldDatabase.PQuery("SELECT `id` FROM `db_scripts` WHERE `script_type` = %d", DBS_ON_CREATURE_SPELL));
|
||||
|
||||
if (result)
|
||||
{
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 id = fields[0].GetUInt32();;
|
||||
spellScriptSet.insert(id);
|
||||
} while (result->NextRow());
|
||||
}
|
||||
|
||||
std::set<uint32> spellScriptSetFull = spellScriptSet;
|
||||
|
||||
// Now we load creature_spells.
|
||||
m_CreatureSpellsMap.clear(); // for reload case
|
||||
|
||||
// 0 1 2 3 4 5 6 7 8 9 10 11
|
||||
result.reset(WorldDatabase.Query("SELECT `entry`, `spellId_1`, `probability_1`, `castTarget_1`, `targetParam1_1`, `targetParam2_1`, `castFlags_1`, `delayInitialMin_1`, `delayInitialMax_1`, `delayRepeatMin_1`, `delayRepeatMax_1`, `scriptId_1`, "
|
||||
// 12 13 14 15 16 17 18 19 20 21 22
|
||||
"`spellId_2`, `probability_2`, `castTarget_2`, `targetParam1_2`, `targetParam2_2`, `castFlags_2`, `delayInitialMin_2`, `delayInitialMax_2`, `delayRepeatMin_2`, `delayRepeatMax_2`, `scriptId_2`, "
|
||||
// 23 24 25 26 27 28 29 30 31 32 33
|
||||
"`spellId_3`, `probability_3`, `castTarget_3`, `targetParam1_3`, `targetParam2_3`, `castFlags_3`, `delayInitialMin_3`, `delayInitialMax_3`, `delayRepeatMin_3`, `delayRepeatMax_3`, `scriptId_3`, "
|
||||
// 34 35 36 37 38 39 40 41 42 43 44
|
||||
"`spellId_4`, `probability_4`, `castTarget_4`, `targetParam1_4`, `targetParam2_4`, `castFlags_4`, `delayInitialMin_4`, `delayInitialMax_4`, `delayRepeatMin_4`, `delayRepeatMax_4`, `scriptId_4`, "
|
||||
// 45 46 47 48 49 50 51 52 53 54 55
|
||||
"`spellId_5`, `probability_5`, `castTarget_5`, `targetParam1_5`, `targetParam2_5`, `castFlags_5`, `delayInitialMin_5`, `delayInitialMax_5`, `delayRepeatMin_5`, `delayRepeatMax_5`, `scriptId_5`, "
|
||||
// 56 57 58 59 60 61 62 63 64 65 66
|
||||
"`spellId_6`, `probability_6`, `castTarget_6`, `targetParam1_6`, `targetParam2_6`, `castFlags_6`, `delayInitialMin_6`, `delayInitialMax_6`, `delayRepeatMin_6`, `delayRepeatMax_6`, `scriptId_6`, "
|
||||
// 67 68 69 70 71 72 73 74 75 76 77
|
||||
"`spellId_7`, `probability_7`, `castTarget_7`, `targetParam1_7`, `targetParam2_7`, `castFlags_7`, `delayInitialMin_7`, `delayInitialMax_7`, `delayRepeatMin_7`, `delayRepeatMax_7`, `scriptId_7`, "
|
||||
// 78 79 80 81 82 83 84 85 86 87 88
|
||||
"`spellId_8`, `probability_8`, `castTarget_8`, `targetParam1_8`, `targetParam2_8`, `castFlags_8`, `delayInitialMin_8`, `delayInitialMax_8`, `delayRepeatMin_8`, `delayRepeatMax_8`, `scriptId_8` FROM `creature_spells`"));
|
||||
if (!result)
|
||||
{
|
||||
BarGoLink bar(1);
|
||||
bar.step();
|
||||
|
||||
sLog.outString();
|
||||
sLog.outString(">> Loaded 0 creature spell templates. DB table `creature_spells` is empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
BarGoLink bar(result->GetRowCount());
|
||||
|
||||
do
|
||||
{
|
||||
bar.step();
|
||||
Field* fields = result->Fetch();
|
||||
|
||||
uint32 entry = fields[0].GetUInt32();;
|
||||
|
||||
CreatureSpellsList spellsList;
|
||||
|
||||
for (uint8 i = 0; i < CREATURE_SPELLS_MAX_SPELLS; i++)
|
||||
{
|
||||
uint16 spellId = fields[1 + i * CREATURE_SPELLS_MAX_COLUMNS].GetUInt16();
|
||||
if (spellId)
|
||||
{
|
||||
if (!sSpellStore.LookupEntry(spellId))
|
||||
{
|
||||
sLog.outErrorDb("Entry %u in table `creature_spells` has non-existent spell %u used as spellId_%u, skipping spell.", entry, spellId, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8 probability = fields[2 + i * CREATURE_SPELLS_MAX_COLUMNS].GetUInt8();
|
||||
|
||||
if ((probability == 0) || (probability > 100))
|
||||
{
|
||||
sLog.outErrorDb("Entry %u in table `creature_spells` has invalid probability_%u value %u, setting it to 100 instead.", entry, i, probability);
|
||||
probability = 100;
|
||||
}
|
||||
|
||||
uint8 castTarget = fields[3 + i * CREATURE_SPELLS_MAX_COLUMNS].GetUInt8();
|
||||
uint32 targetParam1 = fields[4 + i * CREATURE_SPELLS_MAX_COLUMNS].GetUInt32();
|
||||
uint32 targetParam2 = fields[5 + i * CREATURE_SPELLS_MAX_COLUMNS].GetUInt32();
|
||||
|
||||
uint8 castFlags = fields[6 + i * CREATURE_SPELLS_MAX_COLUMNS].GetUInt8();
|
||||
|
||||
// in the database we store timers as seconds
|
||||
// based on screenshot of blizzard creature spells editor
|
||||
uint32 delayInitialMin = fields[7 + i * CREATURE_SPELLS_MAX_COLUMNS].GetUInt16() * IN_MILLISECONDS;
|
||||
uint32 delayInitialMax = fields[8 + i * CREATURE_SPELLS_MAX_COLUMNS].GetUInt16() * IN_MILLISECONDS;
|
||||
|
||||
if (delayInitialMin > delayInitialMax)
|
||||
{
|
||||
sLog.outErrorDb("Entry %u in table `creature_spells` has invalid initial timers (Min_%u = %u, Max_%u = %u), skipping spell.", entry, i, delayInitialMin, i, delayInitialMax);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32 delayRepeatMin = fields[9 + i * CREATURE_SPELLS_MAX_COLUMNS].GetUInt16() * IN_MILLISECONDS;
|
||||
uint32 delayRepeatMax = fields[10 + i * CREATURE_SPELLS_MAX_COLUMNS].GetUInt16() * IN_MILLISECONDS;
|
||||
|
||||
if (delayRepeatMin > delayRepeatMax)
|
||||
{
|
||||
sLog.outErrorDb("Entry %u in table `creature_spells` has invalid repeat timers (Min_%u = %u, Max_%u = %u), skipping spell.", entry, i, delayRepeatMin, i, delayRepeatMax);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32 scriptId = fields[11 + i * CREATURE_SPELLS_MAX_COLUMNS].GetUInt32();
|
||||
|
||||
if (scriptId)
|
||||
{
|
||||
if (spellScriptSetFull.find(scriptId) == spellScriptSetFull.end())
|
||||
{
|
||||
sLog.outErrorDb("Entry %u in table `creature_spells` has non-existent scriptId_%u = %u, setting it to 0 instead.", entry, i, scriptId);
|
||||
scriptId = 0;
|
||||
}
|
||||
else
|
||||
spellScriptSet.erase(scriptId);
|
||||
}
|
||||
|
||||
spellsList.emplace_back(spellId, probability, castTarget, targetParam1, targetParam2, castFlags, delayInitialMin, delayInitialMax, delayRepeatMin, delayRepeatMax, scriptId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!spellsList.empty())
|
||||
m_CreatureSpellsMap.insert(CreatureSpellsMap::value_type(entry, spellsList));
|
||||
|
||||
} while (result->NextRow());
|
||||
|
||||
for (const auto itr : spellScriptSet)
|
||||
sLog.outErrorDb("Table `creature_spells_scripts` contains unused script, id %u.", itr);
|
||||
|
||||
sLog.outString();
|
||||
sLog.outString(">> Loaded %lu creature spell templates.", (unsigned long)m_CreatureSpellsMap.size());
|
||||
}
|
||||
|
||||
void ObjectMgr::LoadCreatures()
|
||||
{
|
||||
uint32 count = 0;
|
||||
|
@ -263,6 +263,31 @@ struct PetCreateSpellEntry
|
||||
uint32 spellid[4];
|
||||
};
|
||||
|
||||
// Number of spells in one template
|
||||
#define CREATURE_SPELLS_MAX_SPELLS 8
|
||||
// Columns in the db for each spell
|
||||
#define CREATURE_SPELLS_MAX_COLUMNS 11
|
||||
|
||||
struct CreatureSpellsEntry
|
||||
{
|
||||
uint16 const spellId;
|
||||
uint8 const probability;
|
||||
uint8 const castTarget;
|
||||
uint32 const targetParam1;
|
||||
uint32 const targetParam2;
|
||||
uint8 const castFlags;
|
||||
uint32 const delayInitialMin;
|
||||
uint32 const delayInitialMax;
|
||||
uint32 const delayRepeatMin;
|
||||
uint32 const delayRepeatMax;
|
||||
uint32 const scriptId;
|
||||
CreatureSpellsEntry(uint16 Id, uint8 Probability, uint8 CastTarget, uint32 TargetParam1, uint32 TargetParam2, uint8 CastFlags, uint32 InitialMin, uint32 InitialMax, uint32 RepeatMin, uint32 RepeatMax, uint32 ScriptId) : spellId(Id), probability(Probability), castTarget(CastTarget), targetParam1(TargetParam1), targetParam2(TargetParam2), castFlags(CastFlags), delayInitialMin(InitialMin), delayInitialMax(InitialMax), delayRepeatMin(RepeatMin), delayRepeatMax(RepeatMax), scriptId(ScriptId) {}
|
||||
};
|
||||
|
||||
typedef std::vector<CreatureSpellsEntry> CreatureSpellsList;
|
||||
|
||||
typedef UNORDERED_MAP<uint32, CreatureSpellsList> CreatureSpellsMap;
|
||||
|
||||
struct GraveYardData
|
||||
{
|
||||
uint32 safeLocId;
|
||||
@ -688,6 +713,7 @@ class ObjectMgr
|
||||
void LoadCreatureClassLvlStats();
|
||||
void LoadCreatureModelInfo();
|
||||
void LoadCreatureItemTemplates();
|
||||
void LoadCreatureSpells();
|
||||
void LoadEquipmentTemplates();
|
||||
void LoadGameObjectLocales();
|
||||
void LoadGameObjects();
|
||||
@ -905,6 +931,13 @@ class ObjectMgr
|
||||
|
||||
void GetCreatureLocaleStrings(uint32 entry, int32 loc_idx, char const** namePtr, char const** subnamePtr = NULL) const;
|
||||
|
||||
CreatureSpellsList const* GetCreatureSpellsList(uint32 entry) const
|
||||
{
|
||||
auto itr = m_CreatureSpellsMap.find(entry);
|
||||
if (itr == m_CreatureSpellsMap.end()) return nullptr;
|
||||
return &itr->second;
|
||||
}
|
||||
|
||||
GameObjectLocale const* GetGameObjectLocale(uint32 entry) const
|
||||
{
|
||||
GameObjectLocaleMap::const_iterator itr = mGameObjectLocaleMap.find(entry);
|
||||
@ -1334,6 +1367,7 @@ class ObjectMgr
|
||||
LocalTransportGuidsOnMap m_localTransports;
|
||||
CreatureDataMap mCreatureDataMap;
|
||||
CreatureLocaleMap mCreatureLocaleMap;
|
||||
CreatureSpellsMap m_CreatureSpellsMap;
|
||||
GameObjectDataMap mGameObjectDataMap;
|
||||
GameObjectLocaleMap mGameObjectLocaleMap;
|
||||
ItemLocaleMap mItemLocaleMap;
|
||||
|
@ -73,7 +73,7 @@ ReactorAI::IsVisible(Unit*) const
|
||||
}
|
||||
|
||||
void
|
||||
ReactorAI::UpdateAI(const uint32 /*time_diff*/)
|
||||
ReactorAI::UpdateAI(const uint32 diff)
|
||||
{
|
||||
// update i_victimGuid if i_creature.getVictim() !=0 and changed
|
||||
if (!m_creature->SelectHostileTarget() || !m_creature->getVictim())
|
||||
@ -83,6 +83,9 @@ ReactorAI::UpdateAI(const uint32 /*time_diff*/)
|
||||
|
||||
i_victimGuid = m_creature->getVictim()->GetObjectGuid();
|
||||
|
||||
if (!m_CreatureSpells.empty())
|
||||
UpdateSpellsList(diff);
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
@ -130,4 +133,7 @@ ReactorAI::EnterEvadeMode()
|
||||
{
|
||||
m_creature->GetMotionMaster()->MoveTargetedHome();
|
||||
}
|
||||
|
||||
// Reset back to default spells template. This also resets timers.
|
||||
SetSpellsList(m_creature->GetCreatureInfo()->SpellListId);
|
||||
}
|
||||
|
@ -537,6 +537,16 @@ inline bool IsOnlySelfTargeting(SpellEntry const* spellInfo)
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool IsDismountSpell(SpellEntry const* spellInfo)
|
||||
{
|
||||
for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i)
|
||||
{
|
||||
if ((spellInfo->Effect[i] == SPELL_EFFECT_APPLY_AURA) && (spellInfo->EffectApplyAuraName[i] == SPELL_AURA_MECHANIC_IMMUNITY) && (spellInfo->EffectMiscValue[i] == MECHANIC_MOUNT))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool IsDispelSpell(SpellEntry const* spellInfo)
|
||||
{
|
||||
return spellInfo->HasSpellEffect(SPELL_EFFECT_DISPEL);
|
||||
@ -582,6 +592,27 @@ inline bool IsNeedCastSpellAtOutdoor(SpellEntry const* spellInfo)
|
||||
return (spellInfo->HasAttribute(SPELL_ATTR_OUTDOORS_ONLY) && spellInfo->HasAttribute(SPELL_ATTR_PASSIVE));
|
||||
}
|
||||
|
||||
// Spell effects require a specific power type on the target
|
||||
inline bool IsTargetPowerTypeValid(SpellEntry const* spellInfo, Powers powerType)
|
||||
{
|
||||
for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
|
||||
{
|
||||
if (spellInfo->Effect[i] == SPELL_EFFECT_NONE)
|
||||
continue;
|
||||
|
||||
if ((spellInfo->Effect[i] == SPELL_EFFECT_POWER_BURN ||
|
||||
spellInfo->Effect[i] == SPELL_EFFECT_POWER_DRAIN ||
|
||||
spellInfo->EffectApplyAuraName[i] == SPELL_AURA_PERIODIC_MANA_LEECH ||
|
||||
spellInfo->EffectApplyAuraName[i] == SPELL_AURA_POWER_BURN_MANA) &&
|
||||
int32(powerType) != spellInfo->EffectMiscValue[i])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool NeedsComboPoints(SpellEntry const* spellInfo)
|
||||
{
|
||||
return spellInfo->HasAttribute(SPELL_ATTR_EX_REQ_TARGET_COMBO_POINTS) || spellInfo->HasAttribute(SPELL_ATTR_EX_REQ_COMBO_POINTS);
|
||||
|
@ -10589,6 +10589,81 @@ Unit* Unit::SelectRandomFriendlyTarget(Unit* except /*= NULL*/, float radius /*=
|
||||
return *tcIter;
|
||||
}
|
||||
|
||||
// Returns friendly unit with the most amount of hp missing from max hp
|
||||
Unit* Unit::FindLowestHpFriendlyUnit(float fRange, uint32 uiMinHPDiff, bool bPercent, Unit* except) const
|
||||
{
|
||||
std::list<Unit*> targets;
|
||||
|
||||
if (Unit* pVictim = getVictim())
|
||||
{
|
||||
HostileReference* pReference = pVictim->GetHostileRefManager().getFirst();
|
||||
|
||||
while (pReference)
|
||||
{
|
||||
if (Unit* pTarget = pReference->getSourceUnit())
|
||||
{
|
||||
if (pTarget->IsAlive() && IsFriendlyTo(pTarget) && IsWithinDistInMap(pTarget, fRange) &&
|
||||
((bPercent && (100 - pTarget->GetHealthPercent() > uiMinHPDiff)) || (!bPercent && (pTarget->GetMaxHealth() - pTarget->GetHealth() > uiMinHPDiff))))
|
||||
{
|
||||
targets.push_back(pTarget);
|
||||
}
|
||||
}
|
||||
pReference = pReference->next();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MaNGOS::MostHPMissingInRangeCheck u_check(this, fRange, uiMinHPDiff, bPercent);
|
||||
MaNGOS::UnitListSearcher<MaNGOS::MostHPMissingInRangeCheck> searcher(targets, u_check);
|
||||
|
||||
Cell::VisitAllObjects(this, searcher, fRange);
|
||||
}
|
||||
|
||||
// remove current target
|
||||
if (except)
|
||||
targets.remove(except);
|
||||
|
||||
// no appropriate targets
|
||||
if (targets.empty())
|
||||
return nullptr;
|
||||
|
||||
return *targets.begin();
|
||||
}
|
||||
|
||||
// Returns friendly unit that does not have an aura from the provided spellid
|
||||
Unit* Unit::FindFriendlyUnitMissingBuff(float range, uint32 spellid, Unit* except) const
|
||||
{
|
||||
std::list<Unit*> targets;
|
||||
|
||||
MaNGOS::FriendlyMissingBuffInRangeCheck u_check(this, range, spellid);
|
||||
MaNGOS::UnitListSearcher<MaNGOS::FriendlyMissingBuffInRangeCheck> searcher(targets, u_check);
|
||||
|
||||
Cell::VisitGridObjects(this, searcher, range);
|
||||
|
||||
// remove current target
|
||||
if (except)
|
||||
targets.remove(except);
|
||||
|
||||
// no appropriate targets
|
||||
if (targets.empty())
|
||||
return nullptr;
|
||||
|
||||
return *targets.begin();
|
||||
}
|
||||
|
||||
// Returns friendly unit that is under a crowd control effect
|
||||
Unit* Unit::FindFriendlyUnitCC(float range) const
|
||||
{
|
||||
Unit* pUnit = nullptr;
|
||||
|
||||
MaNGOS::FriendlyCCedInRangeCheck u_check(this, range);
|
||||
MaNGOS::UnitSearcher<MaNGOS::FriendlyCCedInRangeCheck> searcher(pUnit, u_check);
|
||||
|
||||
Cell::VisitGridObjects(this, searcher, range);
|
||||
|
||||
return pUnit;
|
||||
}
|
||||
|
||||
bool Unit::hasNegativeAuraWithInterruptFlag(uint32 flag)
|
||||
{
|
||||
for (SpellAuraHolderMap::const_iterator iter = m_spellAuraHolders.begin(); iter != m_spellAuraHolders.end(); ++iter)
|
||||
|
@ -1411,6 +1411,32 @@ class Unit : public WorldObject
|
||||
* \see Cell::VisitAllObjects
|
||||
*/
|
||||
Unit* SelectRandomFriendlyTarget(Unit* except = NULL, float radius = ATTACK_DISTANCE) const;
|
||||
/**
|
||||
* @param fRange how big the radius for our search should be
|
||||
* @param uiMinHPDiff how much health the unit has to be missing
|
||||
* @param bPercent if the previous param is percent or raw value
|
||||
* @param except select any target but this one, usually your current target
|
||||
* @return The injured friendly target found, NULL if no targets were found
|
||||
* \see Mangos::UnitListSearcher
|
||||
* \see Cell::VisitAllObjects
|
||||
*/
|
||||
Unit* FindLowestHpFriendlyUnit(float fRange, uint32 uiMinHPDiff = 1, bool bPercent = false, Unit* except = nullptr) const;
|
||||
/**
|
||||
* @param range how big the radius for our search should be
|
||||
* @param spellid buff to check
|
||||
* @param except select any target but this one, usually your current target
|
||||
* @return The friendly target found, NULL if no targets were found
|
||||
* \see Mangos::UnitListSearcher
|
||||
* \see Cell::VisitAllObjects
|
||||
*/
|
||||
Unit* FindFriendlyUnitMissingBuff(float range, uint32 spellid, Unit* except = nullptr) const;
|
||||
/**
|
||||
* @param range how big the radius for our search should be
|
||||
* @return The friendly target found, NULL if no targets were found
|
||||
* \see Mangos::UnitListSearcher
|
||||
* \see Cell::VisitAllObjects
|
||||
*/
|
||||
Unit* FindFriendlyUnitCC(float range) const;
|
||||
/**
|
||||
* Checks if we have a negative aura with the given interrupt flag/s
|
||||
* @param flag The interrupt flag/s to check for, see SpellAuraInterruptFlags
|
||||
|
@ -102,6 +102,8 @@ class HostileReference : public Reference<Unit, ThreatManager>
|
||||
|
||||
ObjectGuid const& getUnitGuid() const { return iUnitGuid; }
|
||||
|
||||
Unit* getSourceUnit();
|
||||
|
||||
//=================================================
|
||||
// reference is not needed anymore. realy delete it !
|
||||
|
||||
@ -124,8 +126,6 @@ class HostileReference : public Reference<Unit, ThreatManager>
|
||||
private:
|
||||
// Inform the source, that the status of that reference was changed
|
||||
void fireStatusChanged(ThreatRefStatusChangeEvent& pThreatRefStatusChangeEvent);
|
||||
|
||||
Unit* getSourceUnit();
|
||||
private:
|
||||
float iThreat;
|
||||
float iTempThreatModifyer; // used for taunt
|
||||
|
@ -43,8 +43,8 @@
|
||||
// DBC_FF_LOGIC = 'l' // Logical (boolean)
|
||||
// };
|
||||
//
|
||||
const char CreatureInfosrcfmt[] = "issiiiiiiiifiiiiliiiiiffiiffffffiiiiffffiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis";
|
||||
const char CreatureInfodstfmt[] = "issiiiiiiiifiiiiliiiiiffiiffffffiiiiffffiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis";
|
||||
const char CreatureInfosrcfmt[] = "issiiiiiiiifiiiiliiiiiffiiffffffiiiiffffiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis";
|
||||
const char CreatureInfodstfmt[] = "issiiiiiiiifiiiiliiiiiffiiffffffiiiiffffiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis";
|
||||
const char CreatureDataAddonInfofmt[] = "iiibbiis";
|
||||
const char CreatureModelfmt[] = "iffbii";
|
||||
const char CreatureInfoAddonInfofmt[] = "iiibbiis";
|
||||
|
@ -503,6 +503,7 @@ ChatCommand* ChatHandler::getCommandTable()
|
||||
{ "creature_loot_template", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadLootTemplatesCreatureCommand, "", NULL },
|
||||
{ "creature_quest_start", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadCreatureQuestRelationsCommand, "", NULL },
|
||||
{ "creature_template_classlevelstats", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadCreaturesStatsCommand, "", NULL },
|
||||
{ "creature_spells", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadCreatureSpellsCommand, "", NULL },
|
||||
{ "db_script_string", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadDbScriptStringCommand, "", NULL },
|
||||
{ "dbscripts_on_creature_death", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadDBScriptsOnCreatureDeathCommand, "", NULL },
|
||||
{ "dbscripts_on_event", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadDBScriptsOnEventCommand, "", NULL },
|
||||
@ -511,6 +512,7 @@ ChatCommand* ChatHandler::getCommandTable()
|
||||
{ "dbscripts_on_quest_end", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadDBScriptsOnQuestEndCommand, "", NULL },
|
||||
{ "dbscripts_on_quest_start", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadDBScriptsOnQuestStartCommand, "", NULL },
|
||||
{ "dbscripts_on_spell", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadDBScriptsOnSpellCommand, "", NULL },
|
||||
{ "dbscripts_on_creature_spell", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadDBScriptsOnCreatureSpellCommand,"", NULL },
|
||||
{ "disables", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadDisablesCommand, "", NULL },
|
||||
{ "disenchant_loot_template", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadLootTemplatesDisenchantCommand, "", NULL },
|
||||
{ "fishing_loot_template", SEC_ADMINISTRATOR, true, &ChatHandler::HandleReloadLootTemplatesFishingCommand, "", NULL },
|
||||
|
@ -481,6 +481,7 @@ class ChatHandler
|
||||
bool HandleReloadCreatureQuestRelationsCommand(char* args);
|
||||
bool HandleReloadCreatureQuestInvRelationsCommand(char* args);
|
||||
bool HandleReloadCreaturesStatsCommand(char* args);
|
||||
bool HandleReloadCreatureSpellsCommand(char* args);
|
||||
bool HandleReloadDbScriptStringCommand(char* args);
|
||||
bool HandleReloadDBScriptsOnCreatureDeathCommand(char* args);
|
||||
bool HandleReloadDBScriptsOnEventCommand(char* args);
|
||||
@ -489,6 +490,7 @@ class ChatHandler
|
||||
bool HandleReloadDBScriptsOnQuestEndCommand(char* args);
|
||||
bool HandleReloadDBScriptsOnQuestStartCommand(char* args);
|
||||
bool HandleReloadDBScriptsOnSpellCommand(char* args);
|
||||
bool HandleReloadDBScriptsOnCreatureSpellCommand(char* args);
|
||||
|
||||
bool HandleReloadEventAITextsCommand(char* args);
|
||||
bool HandleReloadEventAISummonsCommand(char* args);
|
||||
|
@ -759,14 +759,16 @@ namespace MaNGOS
|
||||
class MostHPMissingInRangeCheck
|
||||
{
|
||||
public:
|
||||
MostHPMissingInRangeCheck(Unit const* obj, float range, uint32 hp) : i_obj(obj), i_range(range), i_hp(hp) {}
|
||||
MostHPMissingInRangeCheck(Unit const* obj, float range, uint32 hp, bool percent = false) : i_obj(obj), i_range(range), i_hp(hp), i_percent(percent) {}
|
||||
WorldObject const& GetFocusObject() const { return *i_obj; }
|
||||
bool operator()(Unit* u)
|
||||
{
|
||||
if (u->IsAlive() && u->IsInCombat() && !i_obj->IsHostileTo(u) && i_obj->IsWithinDistInMap(u, i_range) && u->GetMaxHealth() - u->GetHealth() > i_hp)
|
||||
if (u->IsAlive() && u->IsInCombat() && i_obj->IsFriendlyTo(u) && i_obj->IsWithinDistInMap(u, i_range))
|
||||
{
|
||||
i_hp = u->GetMaxHealth() - u->GetHealth();
|
||||
return true;
|
||||
if (i_percent)
|
||||
return 100 - u->GetHealthPercent() > i_hp;
|
||||
|
||||
return u->GetMaxHealth() - u->GetHealth() > i_hp;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -774,6 +776,7 @@ namespace MaNGOS
|
||||
Unit const* i_obj;
|
||||
float i_range;
|
||||
uint32 i_hp;
|
||||
bool i_percent;
|
||||
};
|
||||
|
||||
class FriendlyCCedInRangeCheck
|
||||
|
@ -61,7 +61,8 @@ enum DBScriptType
|
||||
DBS_ON_GO_USE = 6,
|
||||
DBS_ON_GOT_USE = 7,
|
||||
DBS_ON_EVENT = 8,
|
||||
DBS_END = 9,
|
||||
DBS_ON_CREATURE_SPELL = 9,
|
||||
DBS_END = 10,
|
||||
};
|
||||
#define DBS_START DBS_ON_QUEST_START
|
||||
|
||||
|
@ -2954,7 +2954,7 @@ void Spell::SetTargetMap(SpellEffectIndex effIndex, uint32 targetMode, UnitList&
|
||||
}
|
||||
}
|
||||
|
||||
void Spell::prepare(SpellCastTargets const* targets, Aura* triggeredByAura)
|
||||
SpellCastResult Spell::prepare(SpellCastTargets const* targets, Aura* triggeredByAura, uint32 chance)
|
||||
{
|
||||
m_targets = *targets;
|
||||
|
||||
@ -2977,14 +2977,14 @@ void Spell::prepare(SpellCastTargets const* targets, Aura* triggeredByAura)
|
||||
{
|
||||
SendCastResult(SPELL_FAILED_SPELL_IN_PROGRESS);
|
||||
finish(false);
|
||||
return;
|
||||
return SPELL_FAILED_SPELL_IN_PROGRESS;
|
||||
}
|
||||
|
||||
if (DisableMgr::IsDisabledFor(DISABLE_TYPE_SPELL, m_spellInfo->Id, m_caster))
|
||||
{
|
||||
SendCastResult(SPELL_FAILED_SPELL_UNAVAILABLE);
|
||||
finish(false);
|
||||
return;
|
||||
return SPELL_FAILED_SPELL_UNAVAILABLE;
|
||||
}
|
||||
|
||||
// Fill cost data
|
||||
@ -3000,7 +3000,17 @@ void Spell::prepare(SpellCastTargets const* targets, Aura* triggeredByAura)
|
||||
}
|
||||
SendCastResult(result);
|
||||
finish(false);
|
||||
return;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Roll chance to cast from spell list (must be after cast checks, this is why its here)
|
||||
if (chance)
|
||||
{
|
||||
if (!roll_chance_i(chance))
|
||||
{
|
||||
finish(false);
|
||||
return SPELL_FAILED_TRY_AGAIN;
|
||||
}
|
||||
}
|
||||
|
||||
m_spellState = SPELL_STATE_PREPARING;
|
||||
@ -3047,6 +3057,8 @@ void Spell::prepare(SpellCastTargets const* targets, Aura* triggeredByAura)
|
||||
cast(true);
|
||||
}
|
||||
}
|
||||
|
||||
return SPELL_CAST_OK;
|
||||
}
|
||||
|
||||
void Spell::cancel()
|
||||
@ -3150,6 +3162,7 @@ void Spell::cast(bool skipCheck)
|
||||
SpellCastResult castResult = CheckPower();
|
||||
if (castResult != SPELL_CAST_OK)
|
||||
{
|
||||
SendInterrupted(castResult);
|
||||
SendCastResult(castResult);
|
||||
finish(false);
|
||||
m_caster->DecreaseCastCounter();
|
||||
@ -3163,6 +3176,7 @@ void Spell::cast(bool skipCheck)
|
||||
castResult = CheckCast(false);
|
||||
if (castResult != SPELL_CAST_OK)
|
||||
{
|
||||
SendInterrupted(castResult);
|
||||
SendCastResult(castResult);
|
||||
finish(false);
|
||||
m_caster->DecreaseCastCounter();
|
||||
@ -6776,7 +6790,7 @@ SpellCastResult Spell::CheckPower()
|
||||
if (m_caster->GetTypeId() != TYPEID_PLAYER)
|
||||
{
|
||||
// power for pets should be checked
|
||||
if (!m_caster->GetObjectGuid().IsPet() && m_caster->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_QUESTGIVER) != m_caster->IsInCombat())
|
||||
if (!m_caster->GetObjectGuid().IsPet() && m_caster->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_QUESTGIVER) && !m_caster->IsInCombat())
|
||||
{
|
||||
return SPELL_CAST_OK;
|
||||
}
|
||||
|
@ -320,7 +320,7 @@ class Spell
|
||||
Spell(Unit* caster, SpellEntry const* info, bool triggered, ObjectGuid originalCasterGUID = ObjectGuid(), SpellEntry const* triggeredBy = NULL);
|
||||
~Spell();
|
||||
|
||||
void prepare(SpellCastTargets const* targets, Aura* triggeredByAura = NULL);
|
||||
SpellCastResult prepare(SpellCastTargets const* targets, Aura* triggeredByAura = NULL, uint32 chance = 0);
|
||||
|
||||
void cancel();
|
||||
|
||||
|
@ -1117,6 +1117,9 @@ void World::SetInitialWorldSettings()
|
||||
sLog.outString("Loading Creature template spells...");
|
||||
sObjectMgr.LoadCreatureTemplateSpells();
|
||||
|
||||
sLog.outString("Loading Creature spells...");
|
||||
sObjectMgr.LoadCreatureSpells();
|
||||
|
||||
sLog.outString("Loading SpellsScriptTarget...");
|
||||
sSpellMgr.LoadSpellScriptTarget(); // must be after LoadCreatureTemplates and LoadGameobjectInfo
|
||||
|
||||
|
@ -37,7 +37,7 @@
|
||||
#define CHAR_DB_UPDATE_DESCRIPTION "Add_Field_Comments"
|
||||
|
||||
#define WORLD_DB_VERSION_NR 21
|
||||
#define WORLD_DB_STRUCTURE_NR 22
|
||||
#define WORLD_DB_CONTENT_NR 024
|
||||
#define WORLD_DB_UPDATE_DESCRIPTION "NPC_1434_position_fix"
|
||||
#define WORLD_DB_STRUCTURE_NR 23
|
||||
#define WORLD_DB_CONTENT_NR 001
|
||||
#define WORLD_DB_UPDATE_DESCRIPTION "creature_spells"
|
||||
#endif // __REVISION_H__
|
||||
|
Loading…
x
Reference in New Issue
Block a user