From 5808fc6d258dda549f303323047c88f65a2fa823 Mon Sep 17 00:00:00 2001 From: brotalnia Date: Thu, 10 Dec 2020 17:12:43 +0200 Subject: [PATCH] Implement the creature spell lists system. (#123) * Implement creature spells system. * Fix remaining issues. * Don't let mobs cast without enough mana. * Update revsiion. --- src/game/ChatCommands/CreatureCommands.cpp | 2 + src/game/ChatCommands/ReloadCommands.cpp | 33 ++++ src/game/Object/AggressorAI.cpp | 8 +- src/game/Object/Creature.cpp | 104 +++++++++++ src/game/Object/Creature.h | 5 + src/game/Object/CreatureAI.cpp | 196 ++++++++++++++++++++- src/game/Object/CreatureAI.h | 37 +++- src/game/Object/CreatureEventAI.cpp | 6 + src/game/Object/GuardAI.cpp | 8 +- src/game/Object/Object.h | 51 ++++++ src/game/Object/ObjectMgr.cpp | 134 ++++++++++++++ src/game/Object/ObjectMgr.h | 34 ++++ src/game/Object/ReactorAI.cpp | 8 +- src/game/Object/SpellMgr.h | 31 ++++ src/game/Object/Unit.cpp | 75 ++++++++ src/game/Object/Unit.h | 26 +++ src/game/References/ThreatManager.h | 4 +- src/game/Server/SQLStorages.cpp | 4 +- src/game/WorldHandlers/Chat.cpp | 2 + src/game/WorldHandlers/Chat.h | 2 + src/game/WorldHandlers/GridNotifiers.h | 11 +- src/game/WorldHandlers/ScriptMgr.h | 3 +- src/game/WorldHandlers/Spell.cpp | 24 ++- src/game/WorldHandlers/Spell.h | 2 +- src/game/WorldHandlers/World.cpp | 3 + src/shared/revision.h | 6 +- 26 files changed, 795 insertions(+), 24 deletions(-) diff --git a/src/game/ChatCommands/CreatureCommands.cpp b/src/game/ChatCommands/CreatureCommands.cpp index 63ebc1a8..47ab2188 100644 --- a/src/game/ChatCommands/CreatureCommands.cpp +++ b/src/game/ChatCommands/CreatureCommands.cpp @@ -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()) { diff --git a/src/game/ChatCommands/ReloadCommands.cpp b/src/game/ChatCommands/ReloadCommands.cpp index 7715da0e..b7855f01 100644 --- a/src/game/ChatCommands/ReloadCommands.cpp +++ b/src/game/ChatCommands/ReloadCommands.cpp @@ -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()) diff --git a/src/game/Object/AggressorAI.cpp b/src/game/Object/AggressorAI.cpp index 7db17297..7f4039ba 100644 --- a/src/game/Object/AggressorAI.cpp +++ b/src/game/Object/AggressorAI.cpp @@ -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(); } diff --git a/src/game/Object/Creature.cpp b/src/game/Object/Creature.cpp index c54c8bc0..51f07301 100644 --- a/src/game/Object/Creature.cpp +++ b/src/game/Object/Creature.cpp @@ -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); +} diff --git a/src/game/Object/Creature.h b/src/game/Object/Creature.h index 54456a53..f28e184c 100644 --- a/src/game/Object/Creature.h +++ b/src/game/Object/Creature.h @@ -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); diff --git a/src/game/Object/CreatureAI.cpp b/src/game/Object/CreatureAI.cpp index f41e3f80..48e98061 100644 --- a/src/game/Object/CreatureAI.cpp +++ b/src/game/Object/CreatureAI.cpp @@ -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()); } } diff --git a/src/game/Object/CreatureAI.h b/src/game/Object/CreatureAI.h index 41057602..341461f7 100644 --- a/src/game/Object/CreatureAI.h +++ b/src/game/Object/CreatureAI.h @@ -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 m_CreatureSpells; // Contains the currently used creature_spells template }; struct SelectableAI : public FactoryHolder, public Permissible diff --git a/src/game/Object/CreatureEventAI.cpp b/src/game/Object/CreatureEventAI.cpp index cdccf31e..114a3795 100644 --- a/src/game/Object/CreatureEventAI.cpp +++ b/src/game/Object/CreatureEventAI.cpp @@ -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(); } } diff --git a/src/game/Object/GuardAI.cpp b/src/game/Object/GuardAI.cpp index 0df8517e..16b17e2f 100644 --- a/src/game/Object/GuardAI.cpp +++ b/src/game/Object/GuardAI.cpp @@ -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(); } diff --git a/src/game/Object/Object.h b/src/game/Object/Object.h index ee5e35ba..bc2b11ea 100644 --- a/src/game/Object/Object.h +++ b/src/game/Object/Object.h @@ -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(object) : nullptr; +} + +inline WorldObject const* ToWorldObject(Object const* object) +{ + return object && object->isType(TYPEMASK_WORLDOBJECT) ? static_cast(object) : nullptr; +} + +inline GameObject* ToGameObject(Object* object) +{ + return object && object->GetTypeId() == TYPEID_GAMEOBJECT ? reinterpret_cast(object) : nullptr; +} + +inline GameObject const* ToGameObject(Object const* object) +{ + return object && object->GetTypeId() == TYPEID_GAMEOBJECT ? reinterpret_cast(object) : nullptr; +} + +inline Unit* ToUnit(Object* object) +{ + return object && object->isType(TYPEMASK_UNIT) ? reinterpret_cast(object) : nullptr; +} + +inline Unit const* ToUnit(Object const* object) +{ + return object && object->isType(TYPEMASK_UNIT) ? reinterpret_cast(object) : nullptr; +} + +inline Creature* ToCreature(Object* object) +{ + return object && object->GetTypeId() == TYPEID_UNIT ? reinterpret_cast(object) : nullptr; +} + +inline Creature const* ToCreature(Object const* object) +{ + return object && object->GetTypeId() == TYPEID_UNIT ? reinterpret_cast(object) : nullptr; +} + +inline Player* ToPlayer(Object* object) +{ + return object && object->GetTypeId() == TYPEID_PLAYER ? reinterpret_cast(object) : nullptr; +} + +inline Player const* ToPlayer(Object const* object) +{ + return object && object->GetTypeId() == TYPEID_PLAYER ? reinterpret_cast(object) : nullptr; +} + #endif diff --git a/src/game/Object/ObjectMgr.cpp b/src/game/Object/ObjectMgr.cpp index e082655d..f3bea149 100644 --- a/src/game/Object/ObjectMgr.cpp +++ b/src/game/Object/ObjectMgr.cpp @@ -1188,6 +1188,140 @@ void ObjectMgr::LoadCreatureModelInfo() sLog.outString(); } +void ObjectMgr::LoadCreatureSpells() +{ + // First we need to collect all script ids. + std::set spellScriptSet; + + std::unique_ptr 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 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; diff --git a/src/game/Object/ObjectMgr.h b/src/game/Object/ObjectMgr.h index a0d70949..eabf5ba2 100644 --- a/src/game/Object/ObjectMgr.h +++ b/src/game/Object/ObjectMgr.h @@ -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 CreatureSpellsList; + +typedef UNORDERED_MAP 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; diff --git a/src/game/Object/ReactorAI.cpp b/src/game/Object/ReactorAI.cpp index 90a85c41..90c50011 100644 --- a/src/game/Object/ReactorAI.cpp +++ b/src/game/Object/ReactorAI.cpp @@ -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); } diff --git a/src/game/Object/SpellMgr.h b/src/game/Object/SpellMgr.h index 9efbd776..5684578a 100644 --- a/src/game/Object/SpellMgr.h +++ b/src/game/Object/SpellMgr.h @@ -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); diff --git a/src/game/Object/Unit.cpp b/src/game/Object/Unit.cpp index 1b2a0df8..bfe00288 100644 --- a/src/game/Object/Unit.cpp +++ b/src/game/Object/Unit.cpp @@ -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 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 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 targets; + + MaNGOS::FriendlyMissingBuffInRangeCheck u_check(this, range, spellid); + MaNGOS::UnitListSearcher 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 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) diff --git a/src/game/Object/Unit.h b/src/game/Object/Unit.h index ad904cb5..b100d419 100644 --- a/src/game/Object/Unit.h +++ b/src/game/Object/Unit.h @@ -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 diff --git a/src/game/References/ThreatManager.h b/src/game/References/ThreatManager.h index fe901346..c099ee95 100644 --- a/src/game/References/ThreatManager.h +++ b/src/game/References/ThreatManager.h @@ -102,6 +102,8 @@ class HostileReference : public Reference ObjectGuid const& getUnitGuid() const { return iUnitGuid; } + Unit* getSourceUnit(); + //================================================= // reference is not needed anymore. realy delete it ! @@ -124,8 +126,6 @@ class HostileReference : public Reference 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 diff --git a/src/game/Server/SQLStorages.cpp b/src/game/Server/SQLStorages.cpp index 5d83d7ea..642e2eb8 100644 --- a/src/game/Server/SQLStorages.cpp +++ b/src/game/Server/SQLStorages.cpp @@ -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"; diff --git a/src/game/WorldHandlers/Chat.cpp b/src/game/WorldHandlers/Chat.cpp index abc83ab1..f6f5597e 100644 --- a/src/game/WorldHandlers/Chat.cpp +++ b/src/game/WorldHandlers/Chat.cpp @@ -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 }, diff --git a/src/game/WorldHandlers/Chat.h b/src/game/WorldHandlers/Chat.h index 20c31c04..19052f27 100644 --- a/src/game/WorldHandlers/Chat.h +++ b/src/game/WorldHandlers/Chat.h @@ -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); diff --git a/src/game/WorldHandlers/GridNotifiers.h b/src/game/WorldHandlers/GridNotifiers.h index aa4e5751..40423b58 100644 --- a/src/game/WorldHandlers/GridNotifiers.h +++ b/src/game/WorldHandlers/GridNotifiers.h @@ -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 diff --git a/src/game/WorldHandlers/ScriptMgr.h b/src/game/WorldHandlers/ScriptMgr.h index 58fe0a2c..4383381f 100644 --- a/src/game/WorldHandlers/ScriptMgr.h +++ b/src/game/WorldHandlers/ScriptMgr.h @@ -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 diff --git a/src/game/WorldHandlers/Spell.cpp b/src/game/WorldHandlers/Spell.cpp index 71bdeb74..84d3e905 100644 --- a/src/game/WorldHandlers/Spell.cpp +++ b/src/game/WorldHandlers/Spell.cpp @@ -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; } diff --git a/src/game/WorldHandlers/Spell.h b/src/game/WorldHandlers/Spell.h index ab9cf0cc..42b30d71 100644 --- a/src/game/WorldHandlers/Spell.h +++ b/src/game/WorldHandlers/Spell.h @@ -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(); diff --git a/src/game/WorldHandlers/World.cpp b/src/game/WorldHandlers/World.cpp index 33805a15..70b9b82d 100644 --- a/src/game/WorldHandlers/World.cpp +++ b/src/game/WorldHandlers/World.cpp @@ -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 diff --git a/src/shared/revision.h b/src/shared/revision.h index 1ca24754..1d2cd78d 100644 --- a/src/shared/revision.h +++ b/src/shared/revision.h @@ -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__