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:
brotalnia 2020-12-10 17:12:43 +02:00 committed by GitHub
parent b3eeb0cc3c
commit 5808fc6d25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 795 additions and 24 deletions

View File

@ -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())
{

View File

@ -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())

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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());
}
}

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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);

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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";

View File

@ -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 },

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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();

View File

@ -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

View File

@ -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__