server/src/game/CreatureEventAI.cpp
Schmoozerd ae7348f6b0 [12774] Merged cmangos last changes, special thanks for xfurry, Dramacydal, cala, Schmoozerd,
I'm not taking any credits of this commit.

Implement spell effects 62042, 62278 and 64767
Also limit the targets for 62577 and 62603
----------
Update git_id to reflect recent sql formatting changes
----------
Update to a safer code version and also add GO caster scenarios
----------
Allow aura 62038 to stack at every 3 seconds
----------
Improve handling of TargetMMGen
This will have impact on Chase and Follow Movement.
----------
* Refactor code to check if a new position is required for the MMGen into the new function RequiresNewPosition
* Refactor code to get the current targeted distance into function GetDynamicTargetDistance
* Change ChaseMMGen (angle = 0.0f case) chase to best contact point, not zero angle.

Thanks to Cala and X-Savior for testing. Special thanks to cala for also suggesting improved values for the magic numbers
----------
Improve ObjectPosSelector
Now a spot already occupied by the searcher will be prefered
----------
Get rid of bounding radius in GetNearPoint[2D] and ObjectPosSelector
----------
This changes how ObjectPosSelector is used.
It changes the way how the functions Object::GetNearPoint and Object::GetNearPoint2d behave.
----------
So you need to check all places where these functions are used if they are still used correctly.
----------
Especially check your scripts!
----------
Remove not required duplicate indexes
----------
Implement TARGET_92 as TARGET_SUMMONER
This target is used only as TargetA and the related spells are used only by temporary summoned creatures
----------
Implement some spell effects used by Hodir in Ulduar
Dummy spells 62797, 63499, 63545 and 64543
Periodic dummy auras: 61968, 62038, 62039 and 65272
Limit targets for spells: 62797, 63545, 64543, 62476 and 62477
----------
Implement some spells used at Algalon encounter
Spell aura entries 64345, 62018 and 64412
Positive target exception for spell 64996
Aura stacking exception for spells 62169, 62168, 65250 and 64417
----------
Implement effect for spell 63633
----------
EventAI - Ingame output of script state
----------
With this the command .npc aiinfo will give more output about the current state.
Remark that this output is only given if the LogFilter for EventAIDev mode is disabled
----------
FindGit.cmake already ships with CMake
No reason for us to ship it too. Also, we had an outdated version which
had not been used anyway, because we set the include path in such a way,
that the CMake delivered version is always found first.
----------
FindOpenSSL.cmake already ships with CMake
No reason for us to ship it too. Also, we had an outdated version which
had not been used anyway, because we set the include path in such a way,
that the CMake delivered version is always found first.
----------
EventAI - Add more developing error output
----------
Fix some target-type handling for EventAI
Also increase log-output for bad target-types
----------
Fix crash due to bad compiler (author Xfurry)
----------
Add special condition id for Ulduar
Will be used to check the availability of the siege vehicles for players
----------
Update spells 62374 and 62907
* limit spell targets of spell 62374
* implement spell effect for spell 62907
----------
Remove effect for spell 64503
Will be handled in script library. For details please check 8502cdfa64
----------
Implement spells 64489 and 64673
Both are used by Auriaya (Ulduar)
----------
Implement some spells for Ignis the Furnace Master
Spell entries: 62717, 62381, 62488, 62707, 64475 and 64503
----------
Implement spells 61187 and 61190
----------
CMake: generic way to build a script library
Added new parameter INCLUDE_BINDINGS_DIR which can be set to the name of a
folder inside src/bindings/.

Includes the script library in src/bindings/ with the defined name.
The name must correspond to the name of the folder and the folder must contain
a valid CMakeLists.txt

Note: if you currently use a script library, you will probably get a merge
problem on src/bindings/CMakeLists.txt as you will have modified this file
manually. Please use the new version of this file and rerun CMake once with the
parameter -DINCLUDE_BINDINGS_DIR=ScriptDev2 (if you are not using SD2 but
another script library, replace ScriptDev2 with the name of the folder in
src/bindings/).

If you do not use a script library you should not have any merge problems
and you don't need to do anything.
----------
Add CMake source groups to target 'game'
This is the exact same grouping as it is currently in the VC 2012 files.
These groupings will have to be refactored at some point as they are not
very logical.
----------
Add CMake source groups to target 'framework'
This is the exact same grouping as it is currently in the VC 2012 files.
This is part of cmangos/issues#67
----------
Add CMake source groups to target 'shared'
This is the exact same grouping as it is currently in the VC 2012 files.
----------
Add new parameter 'expansion' to command 'account create'
----------
Update some Sunwell Plateau spells

Limit targets and allow positive effect for spell 46650
Implement effect for spells 46289 and 46637
Remove effect for spell 44845 - will be handled in script library
----------
Implement some custom use for Effect Activate Object spells
This will fix the summoning events for the Wind Stones, Ice Stones, Skettis bosses and quest 11865
----------
Sync mangos.sql with other versions
----------
Redump sql databases to unify formatting
The main reason for this was because classic/cata has updated the sql formatting and manually syncing would be a pain so redumping from master->tbc->classic->cata is easier.

Only the formatting was changed. The values were not changed at all.

mysqldump was used however manual modifications had to be done.

Dump the database:
C:/mysql/bin/mysqldump.exe mangos > sql/mangos.sql

Split insert values into multiple rows:
Replace "),(" with "),\n("
Replace "VALUES (" with "VALUES\n("

Remove the character sets by replacing them with an empty string

Custom formatting of mangos.sql:
Move db_version to the very top
Create all dbscripts_on_* tables based on dbscripts_on_creature_movement
Preserve our custom insert formatting of spell_affect (tbc/classic), spell_bonus_data, spell_chain, spell_elixir, spell_proc_event, spell_proc_item_enchant, spell_template, spell_threat
Remove autoincrement values from insert values of pet_name_generation and remove AUTO_INCREMENT=261 value from its table structure

Custom formatting of characters.sql:
Move character_db_version to the very top

Custom formatting of realmd.sql:
Move realmd_db_version to the very top
----------
Immediately remove corpses when ForcedDespawn is used
Thanks to Neotmiren for pointing, special thanks to cala for testing!
----------
Fix use of config values related to quest-status and level
This fixes use of negative value in config values Quests.LowLevelHideDiff and Quests.HighLevelHideDiff
Also add some documentation around the related code
Thanks to Neotmiren for pointing and to cala for testing.
----------
Loot-System: Fix reference loading check
This fixes a false error output for loot references that are only used with spell loot.
Thanks to X-Savior for properly reporting both error messages and use case
----------
Add missing spell 61437 to playercreate spells for bloodelves
Thanks to NeatElves for porting from TC and pointing to this
----------
Fix load bar step for alendarMgr::LoadCalendarsFromDB
----------
Cody Style Improvements
Also remove an unused variable (thanks to Den for this!)
----------
Implement the spells used in the Chess Event encounter
Combat spell entries: 37775, 37824, 39338, 39342, 39341, 39344, 45260
Melee spells: 37142, 37143, 37147, 37149, 37150, 37220, 37227, 37228, 37337, 37339, 37345, 37348
Chess movement spells: 30012, 32312, 37388, 30284, 37144, 37146, 37148, 37151, 37152, 37153
Aura stacking exception: 32261 and 39400
----------
And more hotfixes with these format strings
----------
Hotfix to recent text loading functions
----------
Use possible changed model names with vmap extraction
----------
Fix some warnings
----------
Store how many texts are loaded for validity checks. Use this with EventAI
----------
EventAI: Use generic DoDisplayText and loading of additional text data
----------
Add generic DoDisplayText function and use additional data of dbscripts table
----------
Add const-correctness to Text related functions
----------
Add database changes to support more data for DB Script texts
----------
Add stacking exception for spells 39993 and 40041
----------
Allow spell effect 86 - Activate Object to use the misc value
----------
Allow player pets to swim
----------
Enable resummoning of warlock pets
----------
Do not remove FLY auras on Evade
Also consider npcs which have SPELL_AURA_FLY as being able to fly
----------
EventAI: Improve code
* Drop rather pointless bool to check if the number of assigned events is empty
* Before the phase was resetted on death if and only if the npc has Events defined
* DoMeleeAttackIfReady could have been called even though combat state could have changed while processing events
----------
EventAI: Implement ACTION_T_SET_THROW_MASK (46)
This Action can be used to set which AIEvents should be thrown automatically, if you need more flexibility, you can always use the manual ACTION_TH_THROW_AI_EVENT version.
* Also clean some error-log output a bit.
----------
Forward original caster GUID to script library
----------
Fixup commit 12511 Thanks to Zakamurite for pointing
Also thanks to him for giving a helping hand in correcting the commit
----------
Implement some spells for Felmyst encounter
Spell entries: 45714, 45717 and 45918. Limit targets of spell 45391
Also add stacking exception for auras 45068 and 45582
----------
Fix invisible spirit healers & such on death near them
This fixes an issue that occurs if you die close to a spirit healer/guide.
----------
Fix take ammo for most ranged spells
Fix spells like Arcane Shot not taking ammo while they should
----------
Check cast spell 51690
----------
Fix SpellDamage modifier of SPELL_AURA_MOD_DAMAGE_DONE_CREATURE
This aura modifies a flat value, not a percent value.
----------
Implement proc effect of spells 67712, 67758
related to items 47316, 47477.
----------
Improve proc of spell 50421
----------
Add and implement server-side spell 23770
----------
EventAI: Improve TargetSelection related ErrorLog output
----------
DBScripts Engine: Change behaviour to search for a different npc when using buddy-search
With this an npc buddy will be interpreted as "another npc with entry"
Also toggle command 31 - TERMINATE_SCRIPT to also look only for other npcs of entry
----------
DBScripts Engine: Allow pets as buddy
Add new flag SCRIPT_FLAG_BUDDY_IS_PET (0x20) that will search not for a normal npc with buddy-search, but also for pets
----------
DBScripts Engine: Support buddy search by guid
* Add new `data_flags` flag SCRIPT_FLAG_BUDDY_BY_GUID (0x10)
If this flag is set, the content of `search_distance` is interpreted as db-guid of the requested buddy
* Also switch most error log output to DB-error log output (though this will include false positives)
----------
2020-02-17 09:20:48 +00:00

1520 lines
61 KiB
C++

/**
* This code is part of MaNGOS. Contributor & Copyright details are in AUTHORS/THANKS.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "Common.h"
#include "CreatureEventAI.h"
#include "CreatureEventAIMgr.h"
#include "ObjectMgr.h"
#include "Spell.h"
#include "World.h"
#include "Cell.h"
#include "CellImpl.h"
#include "GameEventMgr.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "InstanceData.h"
#include "Chat.h"
#include "Language.h"
bool CreatureEventAIHolder::UpdateRepeatTimer(Creature* creature, uint32 repeatMin, uint32 repeatMax)
{
if (repeatMin == repeatMax)
Time = repeatMin;
else if (repeatMax > repeatMin)
Time = urand(repeatMin, repeatMax);
else
{
sLog.outErrorEventAI("Creature %u using Event %u (Type = %u) has RandomMax < RandomMin. Event repeating disabled.", creature->GetEntry(), Event.event_id, Event.event_type);
Enabled = false;
return false;
}
return true;
}
int CreatureEventAI::Permissible(const Creature* creature)
{
if (creature->GetAIName() == "EventAI")
return PERMIT_BASE_SPECIAL;
return PERMIT_BASE_NO;
}
void CreatureEventAI::GetAIInformation(ChatHandler& reader)
{
reader.PSendSysMessage(LANG_NPC_EVENTAI_PHASE, (uint32)m_Phase);
reader.PSendSysMessage(LANG_NPC_EVENTAI_MOVE, reader.GetOnOffStr(m_isCombatMovement));
reader.PSendSysMessage(LANG_NPC_EVENTAI_COMBAT, reader.GetOnOffStr(m_MeleeEnabled));
if (sLog.HasLogFilter(LOG_FILTER_EVENT_AI_DEV)) // Give some more details if in EventAI Dev Mode
return;
reader.PSendSysMessage("Current events of this creature:");
for (CreatureEventAIList::const_iterator itr = m_CreatureEventAIList.begin(); itr != m_CreatureEventAIList.end(); ++itr)
{
if (itr->Event.action[2].type != ACTION_T_NONE)
reader.PSendSysMessage("%u Type%3u (%s) Timer(%3us) actions[type(param1)]: %2u(%5u) -- %2u(%u) -- %2u(%5u)", itr->Event.event_id, itr->Event.event_type, itr->Enabled ? "On" : "Off", itr->Time/1000, itr->Event.action[0].type, itr->Event.action[0].raw.param1, itr->Event.action[1].type, itr->Event.action[1].raw.param1, itr->Event.action[2].type, itr->Event.action[2].raw.param1);
else if (itr->Event.action[1].type != ACTION_T_NONE)
reader.PSendSysMessage("%u Type%3u (%s) Timer(%3us) actions[type(param1)]: %2u(%5u) -- %2u(%5u)", itr->Event.event_id, itr->Event.event_type, itr->Enabled ? "On" : "Off", itr->Time/1000, itr->Event.action[0].type, itr->Event.action[0].raw.param1, itr->Event.action[1].type, itr->Event.action[1].raw.param1);
else
reader.PSendSysMessage("%u Type%3u (%s) Timer(%3us) action[type(param1)]: %2u(%5u)", itr->Event.event_id, itr->Event.event_type, itr->Enabled ? "On" : "Off", itr->Time/1000, itr->Event.action[0].type, itr->Event.action[0].raw.param1);
}
}
// For Non Dungeon map only allow non-difficulty flags or EFLAG_DIFFICULTY_0 mode
inline bool IsEventFlagsFitForNormalMap(uint8 eFlags)
{
return !(eFlags & (EFLAG_DIFFICULTY_0 | EFLAG_DIFFICULTY_1 | EFLAG_DIFFICULTY_2 | EFLAG_DIFFICULTY_3)) ||
(eFlags & EFLAG_DIFFICULTY_0);
}
CreatureEventAI::CreatureEventAI(Creature* c) : CreatureAI(c),
m_Phase(0),
m_MeleeEnabled(true),
m_InvinceabilityHpLevel(0),
m_throwAIEventMask(0),
m_throwAIEventStep(0)
{
// Need make copy for filter unneeded steps and safe in case table reload
CreatureEventAI_Event_Map::const_iterator creatureEventsItr = sEventAIMgr.GetCreatureEventAIMap().find(m_creature->GetEntry());
if (creatureEventsItr != sEventAIMgr.GetCreatureEventAIMap().end())
{
uint32 events_count = 0;
for (CreatureEventAI_Event_Vec::const_iterator i = creatureEventsItr->second.begin(); i != creatureEventsItr->second.end(); ++i)
{
// Debug check
#ifndef MANGOS_DEBUG
if (i->event_flags & EFLAG_DEBUG_ONLY)
continue;
#endif
if (m_creature->GetMap()->IsDungeon())
{
if ((1 << (m_creature->GetMap()->GetSpawnMode() + 1)) & i->event_flags)
{
++events_count;
}
}
else if (IsEventFlagsFitForNormalMap(i->event_flags))
++events_count;
}
// EventMap had events but they were not added because they must be for instance
if (events_count == 0)
sLog.outErrorEventAI("Creature %u has events but no events added to list because of instance flags.", m_creature->GetEntry());
else
{
m_CreatureEventAIList.reserve(events_count);
for (CreatureEventAI_Event_Vec::const_iterator i = creatureEventsItr->second.begin(); i != creatureEventsItr->second.end(); ++i)
{
// Debug check
#ifndef MANGOS_DEBUG
if (i->event_flags & EFLAG_DEBUG_ONLY)
continue;
#endif
if (m_creature->GetMap()->IsDungeon())
{
if ((1 << (m_creature->GetMap()->GetSpawnMode() + 1)) & i->event_flags)
{
// event flagged for instance mode
m_CreatureEventAIList.push_back(CreatureEventAIHolder(*i));
}
}
else if (IsEventFlagsFitForNormalMap(i->event_flags))
m_CreatureEventAIList.push_back(CreatureEventAIHolder(*i));
}
}
}
else
sLog.outErrorEventAI("EventMap for Creature %u is empty but creature is using CreatureEventAI.", m_creature->GetEntry());
// Handle Spawned Events, also calls Reset()
JustRespawned();
}
#define LOG_PROCESS_EVENT \
DEBUG_FILTER_LOG(LOG_FILTER_EVENT_AI_DEV, "CreatureEventAI: Event type %u (script %u) triggered for %s (invoked by %s)", \
pHolder.Event.event_type, pHolder.Event.event_id, m_creature->GetGuidStr().c_str(), pActionInvoker ? pActionInvoker->GetGuidStr().c_str() : "<no invoker>")
inline bool IsTimerBasedEvent(EventAI_Type type)
{
switch (type)
{
case EVENT_T_TIMER_IN_COMBAT:
case EVENT_T_TIMER_OOC:
case EVENT_T_TIMER_GENERIC:
case EVENT_T_MANA:
case EVENT_T_HP:
case EVENT_T_TARGET_HP:
case EVENT_T_TARGET_CASTING:
case EVENT_T_FRIENDLY_HP:
case EVENT_T_AURA:
case EVENT_T_TARGET_AURA:
case EVENT_T_MISSING_AURA:
case EVENT_T_TARGET_MISSING_AURA:
case EVENT_T_RANGE:
return true;
default:
return false;
}
}
bool CreatureEventAI::ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pActionInvoker, Creature* pAIEventSender)
{
if (!pHolder.Enabled || pHolder.Time)
return false;
// Check the inverse phase mask (event doesn't trigger if current phase bit is set in mask)
if (pHolder.Event.event_inverse_phase_mask & (1 << m_Phase))
{
if (!IsTimerBasedEvent(pHolder.Event.event_type))
DEBUG_FILTER_LOG(LOG_FILTER_EVENT_AI_DEV, "CreatureEventAI: Event %u skipped because of phasemask %u. Current phase %u", pHolder.Event.event_id, pHolder.Event.event_inverse_phase_mask, m_Phase);
return false;
}
if (!IsTimerBasedEvent(pHolder.Event.event_type))
LOG_PROCESS_EVENT;
CreatureEventAI_Event const& event = pHolder.Event;
// Check event conditions based on the event type, also reset events
switch (event.event_type)
{
case EVENT_T_TIMER_IN_COMBAT:
if (!m_creature->isInCombat())
return false;
LOG_PROCESS_EVENT;
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.timer.repeatMin, event.timer.repeatMax);
break;
case EVENT_T_TIMER_OOC:
if (m_creature->isInCombat() || m_creature->IsInEvadeMode())
return false;
LOG_PROCESS_EVENT;
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.timer.repeatMin, event.timer.repeatMax);
break;
case EVENT_T_TIMER_GENERIC:
LOG_PROCESS_EVENT;
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.timer.repeatMin, event.timer.repeatMax);
break;
case EVENT_T_HP:
{
if (!m_creature->isInCombat() || !m_creature->GetMaxHealth())
return false;
uint32 perc = (m_creature->GetHealth() * 100) / m_creature->GetMaxHealth();
if (perc > event.percent_range.percentMax || perc < event.percent_range.percentMin)
return false;
LOG_PROCESS_EVENT;
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.percent_range.repeatMin, event.percent_range.repeatMax);
break;
}
case EVENT_T_MANA:
{
if (!m_creature->isInCombat() || !m_creature->GetMaxPower(POWER_MANA))
return false;
uint32 perc = (m_creature->GetPower(POWER_MANA) * 100) / m_creature->GetMaxPower(POWER_MANA);
if (perc > event.percent_range.percentMax || perc < event.percent_range.percentMin)
return false;
LOG_PROCESS_EVENT;
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.percent_range.repeatMin, event.percent_range.repeatMax);
break;
}
case EVENT_T_AGGRO:
break;
case EVENT_T_KILL:
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.kill.repeatMin, event.kill.repeatMax);
break;
case EVENT_T_DEATH:
case EVENT_T_EVADE:
break;
case EVENT_T_SPELLHIT:
// Spell hit is special case, param1 and param2 handled within CreatureEventAI::SpellHit
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.spell_hit.repeatMin, event.spell_hit.repeatMax);
break;
case EVENT_T_RANGE:
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.range.repeatMin, event.range.repeatMax);
break;
case EVENT_T_OOC_LOS:
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.ooc_los.repeatMin, event.ooc_los.repeatMax);
break;
case EVENT_T_SPAWNED:
break;
case EVENT_T_TARGET_HP:
{
if (!m_creature->isInCombat() || !m_creature->getVictim() || !m_creature->getVictim()->GetMaxHealth())
return false;
uint32 perc = (m_creature->getVictim()->GetHealth() * 100) / m_creature->getVictim()->GetMaxHealth();
if (perc > event.percent_range.percentMax || perc < event.percent_range.percentMin)
return false;
LOG_PROCESS_EVENT;
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.percent_range.repeatMin, event.percent_range.repeatMax);
break;
}
case EVENT_T_TARGET_CASTING:
if (!m_creature->isInCombat() || !m_creature->getVictim() || !m_creature->getVictim()->IsNonMeleeSpellCasted(false, false, true))
return false;
LOG_PROCESS_EVENT;
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.target_casting.repeatMin, event.target_casting.repeatMax);
break;
case EVENT_T_FRIENDLY_HP:
{
if (!m_creature->isInCombat())
return false;
Unit* pUnit = DoSelectLowestHpFriendly((float)event.friendly_hp.radius, event.friendly_hp.hpDeficit);
if (!pUnit)
return false;
pActionInvoker = pUnit;
LOG_PROCESS_EVENT;
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.friendly_hp.repeatMin, event.friendly_hp.repeatMax);
break;
}
case EVENT_T_FRIENDLY_IS_CC:
{
if (!m_creature->isInCombat())
return false;
std::list<Creature*> pList;
DoFindFriendlyCC(pList, (float)event.friendly_is_cc.radius);
// List is empty
if (pList.empty())
return false;
// We don't really care about the whole list, just return first available
pActionInvoker = *(pList.begin());
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.friendly_is_cc.repeatMin, event.friendly_is_cc.repeatMax);
break;
}
case EVENT_T_FRIENDLY_MISSING_BUFF:
{
std::list<Creature*> pList;
DoFindFriendlyMissingBuff(pList, (float)event.friendly_buff.radius, event.friendly_buff.spellId);
// List is empty
if (pList.empty())
return false;
// We don't really care about the whole list, just return first available
pActionInvoker = *(pList.begin());
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.friendly_buff.repeatMin, event.friendly_buff.repeatMax);
break;
}
case EVENT_T_SUMMONED_UNIT:
case EVENT_T_SUMMONED_JUST_DIED:
case EVENT_T_SUMMONED_JUST_DESPAWN:
{
// Prevent event from occuring on no unit or non creatures
if (!pActionInvoker || pActionInvoker->GetTypeId() != TYPEID_UNIT)
return false;
// Creature id doesn't match up
if (((Creature*)pActionInvoker)->GetEntry() != event.summoned.creatureId)
return false;
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.summoned.repeatMin, event.summoned.repeatMax);
break;
}
case EVENT_T_TARGET_MANA:
{
if (!m_creature->isInCombat() || !m_creature->getVictim() || !m_creature->getVictim()->GetMaxPower(POWER_MANA))
return false;
uint32 perc = (m_creature->getVictim()->GetPower(POWER_MANA) * 100) / m_creature->getVictim()->GetMaxPower(POWER_MANA);
if (perc > event.percent_range.percentMax || perc < event.percent_range.percentMin)
return false;
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.percent_range.repeatMin, event.percent_range.repeatMax);
break;
}
case EVENT_T_REACHED_HOME:
case EVENT_T_RECEIVE_EMOTE:
break;
case EVENT_T_AURA:
{
SpellAuraHolder* holder = m_creature->GetSpellAuraHolder(event.buffed.spellId);
if (!holder || holder->GetStackAmount() < event.buffed.amount)
return false;
LOG_PROCESS_EVENT;
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.buffed.repeatMin, event.buffed.repeatMax);
break;
}
case EVENT_T_TARGET_AURA:
{
if (!m_creature->isInCombat() || !m_creature->getVictim())
return false;
SpellAuraHolder* holder = m_creature->getVictim()->GetSpellAuraHolder(event.buffed.spellId);
if (!holder || holder->GetStackAmount() < event.buffed.amount)
return false;
LOG_PROCESS_EVENT;
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.buffed.repeatMin, event.buffed.repeatMax);
break;
}
case EVENT_T_MISSING_AURA:
{
SpellAuraHolder* holder = m_creature->GetSpellAuraHolder(event.buffed.spellId);
if (holder && holder->GetStackAmount() >= event.buffed.amount)
return false;
LOG_PROCESS_EVENT;
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.buffed.repeatMin, event.buffed.repeatMax);
break;
}
case EVENT_T_TARGET_MISSING_AURA:
{
if (!m_creature->isInCombat() || !m_creature->getVictim())
return false;
SpellAuraHolder* holder = m_creature->getVictim()->GetSpellAuraHolder(event.buffed.spellId);
if (holder && holder->GetStackAmount() >= event.buffed.amount)
return false;
LOG_PROCESS_EVENT;
// Repeat Timers
pHolder.UpdateRepeatTimer(m_creature, event.buffed.repeatMin, event.buffed.repeatMax);
break;
}
case EVENT_T_RECEIVE_AI_EVENT:
break;
default:
sLog.outErrorEventAI("Creature %u using Event %u has invalid Event Type(%u), missing from ProcessEvent() Switch.", m_creature->GetEntry(), pHolder.Event.event_id, pHolder.Event.event_type);
break;
}
// Disable non-repeatable events
if (!(pHolder.Event.event_flags & EFLAG_REPEATABLE))
pHolder.Enabled = false;
// Store random here so that all random actions match up
uint32 rnd = rand();
// Return if chance for event is not met
if (pHolder.Event.event_chance <= rnd % 100)
return false;
// Process actions, normal case
if (!(pHolder.Event.event_flags & EFLAG_RANDOM_ACTION))
{
for (uint32 j = 0; j < MAX_ACTIONS; ++j)
ProcessAction(pHolder.Event.action[j], rnd, pHolder.Event.event_id, pActionInvoker, pAIEventSender);
}
// Process actions, random case
else
{
// amount of real actions
uint32 count = 0;
for (uint32 j = 0; j < MAX_ACTIONS; ++j)
if (pHolder.Event.action[j].type != ACTION_T_NONE)
++count;
if (count)
{
// select action number from found amount
uint32 idx = urand(0, count - 1);
// find selected action, skipping not used
uint32 j = 0;
for (; ; ++j)
{
if (pHolder.Event.action[j].type != ACTION_T_NONE)
{
if (!idx)
break;
--idx;
}
}
ProcessAction(pHolder.Event.action[j], rnd, pHolder.Event.event_id, pActionInvoker, pAIEventSender);
}
}
return true;
}
void CreatureEventAI::ProcessAction(CreatureEventAI_Action const& action, uint32 rnd, uint32 EventId, Unit* pActionInvoker, Creature* pAIEventSender)
{
if (action.type == ACTION_T_NONE)
return;
DEBUG_FILTER_LOG(LOG_FILTER_EVENT_AI_DEV, "CreatureEventAI: Process action %u (script %u) triggered for %s (invoked by %s)",
action.type, EventId, m_creature->GetGuidStr().c_str(), pActionInvoker ? pActionInvoker->GetGuidStr().c_str() : "<no invoker>");
bool reportTargetError = false;
switch (action.type)
{
case ACTION_T_TEXT:
case ACTION_T_CHANCED_TEXT:
{
if (!action.text.TextId[0])
return;
int32 textId = 0;
if (action.type == ACTION_T_TEXT)
{
if (action.text.TextId[1] && action.text.TextId[2])
textId = action.text.TextId[urand(0, 2)];
else if (action.text.TextId[1] && urand(0, 1))
textId = action.text.TextId[1];
else
textId = action.text.TextId[0];
}
// ACTION_T_CHANCED_TEXT, chance hits
else if (urand(0, 99) < action.chanced_text.chance)
{
if (action.chanced_text.TextId[0] && action.chanced_text.TextId[1])
textId = action.chanced_text.TextId[urand(0, 1)];
else
textId = action.chanced_text.TextId[0];
}
if (textId)
{
Unit* target = NULL;
if (pActionInvoker)
{
if (pActionInvoker->GetTypeId() == TYPEID_PLAYER)
target = pActionInvoker;
else if (Unit* owner = pActionInvoker->GetOwner())
{
if (owner->GetTypeId() == TYPEID_PLAYER)
target = owner;
}
}
else if ((target = m_creature->getVictim()))
{
if (target->GetTypeId() != TYPEID_PLAYER)
if (Unit* owner = target->GetOwner())
if (owner->GetTypeId() == TYPEID_PLAYER)
target = owner;
}
if (!DoDisplayText(m_creature, textId, target))
sLog.outErrorEventAI("Error attempting to display text %i, used by script %u", textId, EventId);
}
break;
}
case ACTION_T_SET_FACTION:
{
if (action.set_faction.factionId)
m_creature->SetFactionTemporary(action.set_faction.factionId, action.set_faction.factionFlags);
else // no id provided, assume reset and then use default
m_creature->ClearTemporaryFaction();
break;
}
case ACTION_T_MORPH_TO_ENTRY_OR_MODEL:
{
if (action.morph.creatureId || action.morph.modelId)
{
// set model based on entry from creature_template
if (action.morph.creatureId)
{
if (CreatureInfo const* ci = ObjectMgr::GetCreatureTemplate(action.morph.creatureId))
{
uint32 display_id = Creature::ChooseDisplayId(ci);
m_creature->SetDisplayId(display_id);
}
}
// if no param1, then use value from param2 (modelId)
else
m_creature->SetDisplayId(action.morph.modelId);
}
else
m_creature->DeMorph();
break;
}
case ACTION_T_SOUND:
m_creature->PlayDirectSound(action.sound.soundId);
break;
case ACTION_T_EMOTE:
m_creature->HandleEmote(action.emote.emoteId);
break;
case ACTION_T_RANDOM_SOUND:
{
int32 temp = GetRandActionParam(rnd, action.random_sound.soundId1, action.random_sound.soundId2, action.random_sound.soundId3);
if (temp >= 0)
m_creature->PlayDirectSound(temp);
break;
}
case ACTION_T_RANDOM_EMOTE:
{
int32 temp = GetRandActionParam(rnd, action.random_emote.emoteId1, action.random_emote.emoteId2, action.random_emote.emoteId3);
if (temp >= 0)
m_creature->HandleEmote(temp);
break;
}
case ACTION_T_CAST:
{
uint32 selectFlags = 0;
uint32 spellId = 0;
if (!(action.cast.castFlags & (CAST_TRIGGERED | CAST_FORCE_CAST | CAST_FORCE_TARGET_SELF)))
{
spellId = action.cast.spellId;
selectFlags = SELECT_FLAG_IN_LOS;
}
Unit* target = GetTargetByType(action.cast.target, pActionInvoker, pAIEventSender, reportTargetError, spellId, selectFlags);
if (!target)
{
if (reportTargetError)
sLog.outErrorEventAI("NULL target for ACTION_T_CAST creature entry %u casting spell id %u", m_creature->GetEntry(), action.cast.spellId);
return;
}
CanCastResult castResult = DoCastSpellIfCan(target, action.cast.spellId, action.cast.castFlags);
switch (castResult)
{
case CAST_FAIL_POWER:
case CAST_FAIL_TOO_FAR:
{
// Melee current victim if flag not set
if (!(action.cast.castFlags & CAST_NO_MELEE_IF_OOM))
{
switch (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType())
{
case CHASE_MOTION_TYPE:
case FOLLOW_MOTION_TYPE:
m_attackDistance = 0.0f;
m_attackAngle = 0.0f;
m_creature->GetMotionMaster()->Clear(false);
m_creature->GetMotionMaster()->MoveChase(m_creature->getVictim(), m_attackDistance, m_attackAngle);
break;
default:
break;
}
}
break;
}
default:
break;
}
break;
}
case ACTION_T_SUMMON:
{
Unit* target = GetTargetByType(action.summon.target, pActionInvoker, pAIEventSender, reportTargetError);
if (!target && reportTargetError)
sLog.outErrorEventAI("Event %u - NULL target for ACTION_T_SUMMON(%u), target-type %u", EventId, action.type, action.summon.target);
Creature* pCreature = NULL;
if (action.summon.duration)
pCreature = m_creature->SummonCreature(action.summon.creatureId, 0.0f, 0.0f, 0.0f, 0.0f, TEMPSUMMON_TIMED_OOC_OR_DEAD_DESPAWN, action.summon.duration);
else
pCreature = m_creature->SummonCreature(action.summon.creatureId, 0.0f, 0.0f, 0.0f, 0.0f, TEMPSUMMON_TIMED_OOC_DESPAWN, 0);
if (!pCreature)
sLog.outErrorEventAI("failed to spawn creature %u. Spawn event %d is on creature %d", action.summon.creatureId, EventId, m_creature->GetEntry());
else if (action.summon.target != TARGET_T_SELF && target)
pCreature->AI()->AttackStart(target);
break;
}
case ACTION_T_THREAT_SINGLE_PCT:
if (Unit* target = GetTargetByType(action.threat_single_pct.target, pActionInvoker, pAIEventSender, reportTargetError))
m_creature->getThreatManager().modifyThreatPercent(target, action.threat_single_pct.percent);
else if (reportTargetError)
sLog.outErrorEventAI("Event %u - NULL target for ACTION_T_THREAT_SINGLE_PCT(%u), target-type %u", EventId, action.type, action.threat_single_pct.target);
break;
case ACTION_T_THREAT_ALL_PCT:
{
ThreatList const& threatList = m_creature->getThreatManager().getThreatList();
for (ThreatList::const_iterator i = threatList.begin(); i != threatList.end(); ++i)
if (Unit* Temp = m_creature->GetMap()->GetUnit((*i)->getUnitGuid()))
m_creature->getThreatManager().modifyThreatPercent(Temp, action.threat_all_pct.percent);
break;
}
case ACTION_T_QUEST_EVENT:
if (Unit* target = GetTargetByType(action.quest_event.target, pActionInvoker, pAIEventSender, reportTargetError))
{
if (target->GetTypeId() == TYPEID_PLAYER)
((Player*)target)->AreaExploredOrEventHappens(action.quest_event.questId);
}
else if (reportTargetError)
sLog.outErrorEventAI("Event %u - NULL target for ACTION_T_QUEST_EVENT(%u), target-type %u", EventId, action.type, action.quest_event.target);
break;
case ACTION_T_CAST_EVENT:
if (Unit* target = GetTargetByType(action.cast_event.target, pActionInvoker, pAIEventSender, reportTargetError, 0, SELECT_FLAG_PLAYER))
{
if (target->GetTypeId() == TYPEID_PLAYER)
((Player*)target)->CastedCreatureOrGO(action.cast_event.creatureId, m_creature->GetObjectGuid(), action.cast_event.spellId);
}
else if (reportTargetError)
sLog.outErrorEventAI("Event %u - NULL target for ACTION_T_CST_EVENT(%u), target-type %u", EventId, action.type, action.cast_event.target);
break;
case ACTION_T_SET_UNIT_FIELD:
{
Unit* target = GetTargetByType(action.set_unit_field.target, pActionInvoker, pAIEventSender, reportTargetError);
// not allow modify important for integrity object fields
if (action.set_unit_field.field < OBJECT_END || action.set_unit_field.field >= UNIT_END)
return;
else if (reportTargetError)
sLog.outErrorEventAI("Event %u - NULL target for ACTION_T_SET_UNIT_FIELD(%u), target-type %u", EventId, action.type, action.set_unit_field.target);
break;
}
case ACTION_T_SET_UNIT_FLAG:
if (Unit* target = GetTargetByType(action.unit_flag.target, pActionInvoker, pAIEventSender, reportTargetError))
target->SetFlag(UNIT_FIELD_FLAGS, action.unit_flag.value);
else if (reportTargetError)
sLog.outErrorEventAI("Event %u - NULL target for ACTION_T_SET_UNIT_FLAG(%u), target-type %u", EventId, action.type, action.unit_flag.target);
break;
case ACTION_T_REMOVE_UNIT_FLAG:
if (Unit* target = GetTargetByType(action.unit_flag.target, pActionInvoker, pAIEventSender, reportTargetError))
target->RemoveFlag(UNIT_FIELD_FLAGS, action.unit_flag.value);
else if (reportTargetError)
sLog.outErrorEventAI("Event %u - NULL target for ACTION_T_REMOVE_UNIT_FLAG(%u), target-type %u", EventId, action.type, action.unit_flag.target);
case ACTION_T_AUTO_ATTACK:
m_MeleeEnabled = action.auto_attack.state != 0;
break;
case ACTION_T_COMBAT_MOVEMENT:
// ignore no affect case
if (m_isCombatMovement == (action.combat_movement.state != 0))
return;
SetCombatMovement(action.combat_movement.state != 0, true);
if (m_isCombatMovement && action.combat_movement.melee && m_creature->isInCombat() && m_creature->getVictim())
m_creature->SendMeleeAttackStart(m_creature->getVictim());
else if (action.combat_movement.melee && m_creature->isInCombat() && m_creature->getVictim())
m_creature->SendMeleeAttackStop(m_creature->getVictim());
break;
case ACTION_T_SET_PHASE:
m_Phase = action.set_phase.phase;
DEBUG_FILTER_LOG(LOG_FILTER_EVENT_AI_DEV, "CreatureEventAI: ACTION_T_SET_PHASE - script %u for %s, phase is now %u", EventId, m_creature->GetGuidStr().c_str(), m_Phase);
break;
case ACTION_T_INC_PHASE:
{
int32 new_phase = int32(m_Phase) + action.set_inc_phase.step;
if (new_phase < 0)
{
sLog.outErrorEventAI("Event %d decrease Phase under 0. CreatureEntry = %d", EventId, m_creature->GetEntry());
m_Phase = 0;
}
else if (new_phase >= MAX_PHASE)
{
sLog.outErrorEventAI("Event %d incremented Phase above %u. Phase mask cannot be used with phases past %u. CreatureEntry = %d", EventId, MAX_PHASE - 1, MAX_PHASE - 1, m_creature->GetEntry());
m_Phase = MAX_PHASE - 1;
}
else
m_Phase = new_phase;
DEBUG_FILTER_LOG(LOG_FILTER_EVENT_AI_DEV, "CreatureEventAI: ACTION_T_INC_PHASE - script %u for %s, phase is now %u", EventId, m_creature->GetGuidStr().c_str(), m_Phase);
break;
}
case ACTION_T_EVADE:
EnterEvadeMode();
break;
case ACTION_T_FLEE_FOR_ASSIST:
m_creature->DoFleeToGetAssistance();
break;
case ACTION_T_QUEST_EVENT_ALL:
if (pActionInvoker && pActionInvoker->GetTypeId() == TYPEID_PLAYER)
((Player*)pActionInvoker)->GroupEventHappens(action.quest_event_all.questId, m_creature);
break;
case ACTION_T_CAST_EVENT_ALL:
{
ThreatList const& threatList = m_creature->getThreatManager().getThreatList();
for (ThreatList::const_iterator i = threatList.begin(); i != threatList.end(); ++i)
if (Player* temp = m_creature->GetMap()->GetPlayer((*i)->getUnitGuid()))
temp->CastedCreatureOrGO(action.cast_event_all.creatureId, m_creature->GetObjectGuid(), action.cast_event_all.spellId);
break;
}
case ACTION_T_REMOVEAURASFROMSPELL:
if (Unit* target = GetTargetByType(action.remove_aura.target, pActionInvoker, pAIEventSender, reportTargetError))
target->RemoveAurasDueToSpell(action.remove_aura.spellId);
else if (reportTargetError)
sLog.outErrorEventAI("Event %u - NULL target for ACTION_T_REMOVEAURASFROMSPELL(%u), target-type %u", EventId, action.type, action.remove_aura.target);
break;
case ACTION_T_RANGED_MOVEMENT:
m_attackDistance = (float)action.ranged_movement.distance;
m_attackAngle = action.ranged_movement.angle / 180.0f * M_PI_F;
if (m_isCombatMovement)
{
if (m_creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE)
{
// Drop current movement gen
m_creature->GetMotionMaster()->Clear(false);
m_creature->GetMotionMaster()->MoveChase(m_creature->getVictim(), m_attackDistance, m_attackAngle);
}
}
break;
case ACTION_T_RANDOM_PHASE:
m_Phase = GetRandActionParam(rnd, action.random_phase.phase1, action.random_phase.phase2, action.random_phase.phase3);
DEBUG_FILTER_LOG(LOG_FILTER_EVENT_AI_DEV, "CreatureEventAI: ACTION_T_RANDOM_PHASE - script %u for %s, phase is now %u", EventId, m_creature->GetGuidStr().c_str(), m_Phase);
break;
case ACTION_T_RANDOM_PHASE_RANGE:
if (action.random_phase_range.phaseMax > action.random_phase_range.phaseMin)
m_Phase = action.random_phase_range.phaseMin + (rnd % (action.random_phase_range.phaseMax - action.random_phase_range.phaseMin));
else
sLog.outErrorEventAI("ACTION_T_RANDOM_PHASE_RANGE cannot have Param2 <= Param1. Divide by Zero. Event = %d. CreatureEntry = %d", EventId, m_creature->GetEntry());
break;
case ACTION_T_SUMMON_ID:
{
Unit* target = GetTargetByType(action.summon_id.target, pActionInvoker, pAIEventSender, reportTargetError);
if (!target && reportTargetError)
sLog.outErrorEventAI("Event %u - NULL target for ACTION_T_SUMMON_ID(%u), target-type %u", EventId, action.type, action.summon_id.target);
CreatureEventAI_Summon_Map::const_iterator i = sEventAIMgr.GetCreatureEventAISummonMap().find(action.summon_id.spawnId);
if (i == sEventAIMgr.GetCreatureEventAISummonMap().end())
{
sLog.outErrorEventAI("failed to spawn creature %u. Summon map index %u does not exist. EventID %d. CreatureID %d", action.summon_id.creatureId, action.summon_id.spawnId, EventId, m_creature->GetEntry());
return;
}
Creature* pCreature = NULL;
if ((*i).second.SpawnTimeSecs)
pCreature = m_creature->SummonCreature(action.summon_id.creatureId, (*i).second.position_x, (*i).second.position_y, (*i).second.position_z, (*i).second.orientation, TEMPSUMMON_TIMED_OOC_OR_DEAD_DESPAWN, (*i).second.SpawnTimeSecs);
else
pCreature = m_creature->SummonCreature(action.summon_id.creatureId, (*i).second.position_x, (*i).second.position_y, (*i).second.position_z, (*i).second.orientation, TEMPSUMMON_TIMED_OOC_DESPAWN, 0);
if (!pCreature)
sLog.outErrorEventAI("failed to spawn creature %u. EventId %d.Creature %d", action.summon_id.creatureId, EventId, m_creature->GetEntry());
else if (action.summon_id.target != TARGET_T_SELF && target)
pCreature->AI()->AttackStart(target);
break;
}
case ACTION_T_KILLED_MONSTER:
// first attempt player/group who tapped creature
if (Player* pPlayer = m_creature->GetLootRecipient())
pPlayer->RewardPlayerAndGroupAtEvent(action.killed_monster.creatureId, m_creature);
else
{
// if not available, use pActionInvoker
if (Unit* pTarget = GetTargetByType(action.killed_monster.target, pActionInvoker, pAIEventSender, reportTargetError, 0, SELECT_FLAG_PLAYER))
{
if (Player* pPlayer2 = pTarget->GetCharmerOrOwnerPlayerOrPlayerItself())
pPlayer2->RewardPlayerAndGroupAtEvent(action.killed_monster.creatureId, m_creature);
}
else if (reportTargetError)
sLog.outErrorEventAI("Event %u - NULL target for ACTION_T_KILLED_MONSTER(%u), target-type %u", EventId, action.type, action.killed_monster.target);
}
break;
case ACTION_T_SET_INST_DATA:
{
InstanceData* pInst = m_creature->GetInstanceData();
if (!pInst)
{
sLog.outErrorEventAI("Event %d attempt to set instance data without instance script. Creature %d", EventId, m_creature->GetEntry());
return;
}
pInst->SetData(action.set_inst_data.field, action.set_inst_data.value);
break;
}
case ACTION_T_SET_INST_DATA64:
{
Unit* target = GetTargetByType(action.set_inst_data64.target, pActionInvoker, pAIEventSender, reportTargetError);
if (!target)
{
if (reportTargetError)
sLog.outErrorEventAI("Event %d attempt to set instance data64 but Target == NULL. Creature %d", EventId, m_creature->GetEntry());
return;
}
InstanceData* pInst = m_creature->GetInstanceData();
if (!pInst)
{
sLog.outErrorEventAI("Event %d attempt to set instance data64 without instance script. Creature %d", EventId, m_creature->GetEntry());
return;
}
pInst->SetData64(action.set_inst_data64.field, target->GetObjectGuid().GetRawValue());
break;
}
case ACTION_T_UPDATE_TEMPLATE:
if (m_creature->GetEntry() == action.update_template.creatureId)
{
sLog.outErrorEventAI("Event %d ACTION_T_UPDATE_TEMPLATE call with param1 == current entry. Creature %d", EventId, m_creature->GetEntry());
return;
}
m_creature->UpdateEntry(action.update_template.creatureId, action.update_template.team ? HORDE : ALLIANCE);
break;
case ACTION_T_DIE:
if (m_creature->isDead())
{
sLog.outErrorEventAI("Event %d ACTION_T_DIE on dead creature. Creature %d", EventId, m_creature->GetEntry());
return;
}
m_creature->DealDamage(m_creature, m_creature->GetMaxHealth(), NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false);
break;
case ACTION_T_ZONE_COMBAT_PULSE:
{
m_creature->SetInCombatWithZone();
break;
}
case ACTION_T_CALL_FOR_HELP:
{
m_creature->CallForHelp((float)action.call_for_help.radius);
break;
}
case ACTION_T_SET_SHEATH:
{
m_creature->SetSheath(SheathState(action.set_sheath.sheath));
break;
}
case ACTION_T_FORCE_DESPAWN:
{
m_creature->ForcedDespawn(action.forced_despawn.msDelay);
break;
}
case ACTION_T_SET_INVINCIBILITY_HP_LEVEL:
{
if (action.invincibility_hp_level.is_percent)
m_InvinceabilityHpLevel = m_creature->GetMaxHealth() * action.invincibility_hp_level.hp_level / 100;
else
m_InvinceabilityHpLevel = action.invincibility_hp_level.hp_level;
break;
}
case ACTION_T_MOUNT_TO_ENTRY_OR_MODEL:
{
if (action.mount.creatureId || action.mount.modelId)
{
// set model based on entry from creature_template
if (action.mount.creatureId)
{
if (CreatureInfo const* cInfo = ObjectMgr::GetCreatureTemplate(action.mount.creatureId))
{
uint32 display_id = Creature::ChooseDisplayId(cInfo);
m_creature->Mount(display_id);
}
}
// if no param1, then use value from param2 (modelId)
else
m_creature->Mount(action.mount.modelId);
}
else
m_creature->Unmount();
break;
}
case ACTION_T_THROW_AI_EVENT:
{
SendAIEventAround(AIEventType(action.throwEvent.eventType), pActionInvoker, 0, action.throwEvent.radius);
break;
}
case ACTION_T_SET_THROW_MASK:
{
m_throwAIEventMask = action.setThrowMask.eventTypeMask;
break;
}
}
}
void CreatureEventAI::JustRespawned() // NOTE that this is called from the AI's constructor as well
{
Reset();
for (CreatureEventAIList::iterator i = m_CreatureEventAIList.begin(); i != m_CreatureEventAIList.end(); ++i)
{
// Reset generic timer
if (i->Event.event_type == EVENT_T_TIMER_GENERIC)
{
if (i->UpdateRepeatTimer(m_creature, i->Event.timer.initialMin, i->Event.timer.initialMax))
i->Enabled = true;
}
// Handle Spawned Events
else if (SpawnedEventConditionsCheck((*i).Event))
ProcessEvent(*i);
}
}
void CreatureEventAI::Reset()
{
m_EventUpdateTime = EVENT_UPDATE_TIME;
m_EventDiff = 0;
m_throwAIEventStep = 0;
// Reset all events to enabled
for (CreatureEventAIList::iterator i = m_CreatureEventAIList.begin(); i != m_CreatureEventAIList.end(); ++i)
{
CreatureEventAI_Event const& event = (*i).Event;
switch (event.event_type)
{
// Reset all out of combat timers
case EVENT_T_TIMER_OOC:
{
if ((*i).UpdateRepeatTimer(m_creature, event.timer.initialMin, event.timer.initialMax))
(*i).Enabled = true;
break;
}
// default:
// TODO: enable below code line / verify this is correct to enable events previously disabled (ex. aggro yell), instead of enable this in void Aggro()
//(*i).Enabled = true;
//(*i).Time = 0;
// break;
}
}
}
void CreatureEventAI::JustReachedHome()
{
for (CreatureEventAIList::iterator i = m_CreatureEventAIList.begin(); i != m_CreatureEventAIList.end(); ++i)
{
if ((*i).Event.event_type == EVENT_T_REACHED_HOME)
ProcessEvent(*i);
}
Reset();
}
void CreatureEventAI::EnterEvadeMode()
{
m_creature->RemoveAllAurasOnEvade();
m_creature->DeleteThreatList();
m_creature->CombatStop(true);
if (m_creature->isAlive())
m_creature->GetMotionMaster()->MoveTargetedHome();
m_creature->SetLootRecipient(NULL);
if (m_bEmptyList)
return;
// Handle Evade events
for (CreatureEventAIList::iterator i = m_CreatureEventAIList.begin(); i != m_CreatureEventAIList.end(); ++i)
{
if ((*i).Event.event_type == EVENT_T_EVADE)
ProcessEvent(*i);
}
}
void CreatureEventAI::JustDied(Unit* killer)
{
Reset();
if (m_creature->IsGuard())
{
// Send Zone Under Attack message to the LocalDefense and WorldDefense Channels
if (Player* pKiller = killer->GetCharmerOrOwnerPlayerOrPlayerItself())
m_creature->SendZoneUnderAttackMessage(pKiller);
}
if (m_throwAIEventMask & (1 << AI_EVENT_JUST_DIED))
SendAIEventAround(AI_EVENT_JUST_DIED, killer, 0, AIEVENT_DEFAULT_THROW_RADIUS);
// Handle On Death events
for (CreatureEventAIList::iterator i = m_CreatureEventAIList.begin(); i != m_CreatureEventAIList.end(); ++i)
{
if ((*i).Event.event_type == EVENT_T_DEATH)
ProcessEvent(*i, killer);
}
// reset phase after any death state events
m_Phase = 0;
}
void CreatureEventAI::KilledUnit(Unit* victim)
{
if (victim->GetTypeId() != TYPEID_PLAYER)
return;
for (CreatureEventAIList::iterator i = m_CreatureEventAIList.begin(); i != m_CreatureEventAIList.end(); ++i)
{
if ((*i).Event.event_type == EVENT_T_KILL)
ProcessEvent(*i, victim);
}
}
void CreatureEventAI::JustSummoned(Creature* pUnit)
{
for (CreatureEventAIList::iterator i = m_CreatureEventAIList.begin(); i != m_CreatureEventAIList.end(); ++i)
{
if ((*i).Event.event_type == EVENT_T_SUMMONED_UNIT)
ProcessEvent(*i, pUnit);
}
}
void CreatureEventAI::SummonedCreatureJustDied(Creature* pUnit)
{
for (CreatureEventAIList::iterator i = m_CreatureEventAIList.begin(); i != m_CreatureEventAIList.end(); ++i)
{
if ((*i).Event.event_type == EVENT_T_SUMMONED_JUST_DIED)
ProcessEvent(*i, pUnit);
}
}
void CreatureEventAI::SummonedCreatureDespawn(Creature* pUnit)
{
for (CreatureEventAIList::iterator i = m_CreatureEventAIList.begin(); i != m_CreatureEventAIList.end(); ++i)
{
if ((*i).Event.event_type == EVENT_T_SUMMONED_JUST_DESPAWN)
ProcessEvent(*i, pUnit);
}
}
void CreatureEventAI::ReceiveAIEvent(AIEventType eventType, Creature* pSender, Unit* pInvoker, uint32 miscValue)
{
MANGOS_ASSERT(pSender);
for (CreatureEventAIList::iterator itr = m_CreatureEventAIList.begin(); itr != m_CreatureEventAIList.end(); ++itr)
{
if (itr->Event.event_type == EVENT_T_RECEIVE_AI_EVENT &&
itr->Event.receiveAIEvent.eventType == eventType && (!itr->Event.receiveAIEvent.senderEntry || itr->Event.receiveAIEvent.senderEntry == pSender->GetEntry()))
ProcessEvent(*itr, pInvoker, pSender);
}
}
void CreatureEventAI::EnterCombat(Unit* enemy)
{
// Check for on combat start events
for (CreatureEventAIList::iterator i = m_CreatureEventAIList.begin(); i != m_CreatureEventAIList.end(); ++i)
{
CreatureEventAI_Event const& event = (*i).Event;
switch (event.event_type)
{
case EVENT_T_AGGRO:
(*i).Enabled = true;
ProcessEvent(*i, enemy);
break;
// Reset all in combat timers
case EVENT_T_TIMER_IN_COMBAT:
if ((*i).UpdateRepeatTimer(m_creature, event.timer.initialMin, event.timer.initialMax))
(*i).Enabled = true;
break;
// All normal events need to be re-enabled and their time set to 0
default:
(*i).Enabled = true;
(*i).Time = 0;
break;
}
}
m_EventUpdateTime = EVENT_UPDATE_TIME;
m_EventDiff = 0;
}
void CreatureEventAI::AttackStart(Unit* who)
{
if (!who)
return;
if (m_creature->Attack(who, m_MeleeEnabled))
{
m_creature->AddThreat(who);
m_creature->SetInCombatWith(who);
who->SetInCombatWith(m_creature);
HandleMovementOnAttackStart(who);
}
}
void CreatureEventAI::MoveInLineOfSight(Unit* who)
{
if (!who)
return;
// Check for OOC LOS Event
if (!m_creature->getVictim())
{
for (CreatureEventAIList::iterator itr = m_CreatureEventAIList.begin(); itr != m_CreatureEventAIList.end(); ++itr)
{
if ((*itr).Event.event_type == EVENT_T_OOC_LOS)
{
// can trigger if closer than fMaxAllowedRange
float fMaxAllowedRange = (float)(*itr).Event.ooc_los.maxRange;
// if range is ok and we are actually in LOS
if (m_creature->IsWithinDistInMap(who, fMaxAllowedRange) && m_creature->IsWithinLOSInMap(who))
{
// if friendly event&&who is not hostile OR hostile event&&who is hostile
if (((*itr).Event.ooc_los.noHostile && !m_creature->IsHostileTo(who)) ||
((!(*itr).Event.ooc_los.noHostile) && m_creature->IsHostileTo(who)))
ProcessEvent(*itr, who);
}
}
}
}
if (m_creature->IsCivilian() || m_creature->IsNeutralToAll())
return;
if (m_creature->CanInitiateAttack() && who->isTargetableForAttack() &&
m_creature->IsHostileTo(who) && who->isInAccessablePlaceFor(m_creature))
{
if (!m_creature->CanFly() && m_creature->GetDistanceZ(who) > CREATURE_Z_ATTACK_RANGE)
return;
float attackRadius = m_creature->GetAttackDistance(who);
if (m_creature->IsWithinDistInMap(who, attackRadius) && m_creature->IsWithinLOSInMap(who))
{
if (!m_creature->getVictim())
{
AttackStart(who);
who->RemoveSpellsCausingAura(SPELL_AURA_MOD_STEALTH);
}
else if (m_creature->GetMap()->IsDungeon())
{
m_creature->AddThreat(who);
who->SetInCombatWith(m_creature);
}
}
}
}
void CreatureEventAI::SpellHit(Unit* pUnit, const SpellEntry* pSpell)
{
for (CreatureEventAIList::iterator i = m_CreatureEventAIList.begin(); i != m_CreatureEventAIList.end(); ++i)
if ((*i).Event.event_type == EVENT_T_SPELLHIT)
// If spell id matches (or no spell id) & if spell school matches (or no spell school)
if (!(*i).Event.spell_hit.spellId || pSpell->Id == (*i).Event.spell_hit.spellId)
if (pSpell->SchoolMask & (*i).Event.spell_hit.schoolMask)
ProcessEvent(*i, pUnit);
}
void CreatureEventAI::UpdateAI(const uint32 diff)
{
// Check if we are in combat (also updates calls threat update code)
bool Combat = m_creature->SelectHostileTarget() && m_creature->getVictim();
// Events are only updated once every EVENT_UPDATE_TIME ms to prevent lag with large amount of events
if (m_EventUpdateTime < diff)
{
m_EventDiff += diff;
// Check for time based events
for (CreatureEventAIList::iterator i = m_CreatureEventAIList.begin(); i != m_CreatureEventAIList.end(); ++i)
{
// Decrement Timers
if ((*i).Time)
{
if ((*i).Time > m_EventDiff)
{
// Do not decrement timers if event cannot trigger in this phase
if (!((*i).Event.event_inverse_phase_mask & (1 << m_Phase)))
(*i).Time -= m_EventDiff;
// Skip processing of events that have time remaining
continue;
}
else (*i).Time = 0;
}
// Events that are updated every EVENT_UPDATE_TIME
switch ((*i).Event.event_type)
{
case EVENT_T_TIMER_OOC:
case EVENT_T_TIMER_GENERIC:
ProcessEvent(*i);
break;
case EVENT_T_TIMER_IN_COMBAT:
case EVENT_T_MANA:
case EVENT_T_HP:
case EVENT_T_TARGET_HP:
case EVENT_T_TARGET_CASTING:
case EVENT_T_FRIENDLY_HP:
case EVENT_T_AURA:
case EVENT_T_TARGET_AURA:
case EVENT_T_MISSING_AURA:
case EVENT_T_TARGET_MISSING_AURA:
if (Combat)
ProcessEvent(*i);
break;
case EVENT_T_RANGE:
if (Combat)
{
if (m_creature->getVictim() && m_creature->IsInMap(m_creature->getVictim()))
if (m_creature->IsInRange(m_creature->getVictim(), (float)(*i).Event.range.minDist, (float)(*i).Event.range.maxDist))
ProcessEvent(*i);
}
break;
}
m_EventDiff = 0;
m_EventUpdateTime = EVENT_UPDATE_TIME;
}
}
else
{
m_EventDiff += diff;
m_EventUpdateTime -= diff;
}
// Melee Auto-Attack (recheck m_creature->getVictim in case of combat state was changed while processing events)
if (Combat && m_MeleeEnabled && m_creature->getVictim())
DoMeleeAttackIfReady();
}
bool CreatureEventAI::IsVisible(Unit* pl) const
{
return m_creature->IsWithinDist(pl, sWorld.getConfig(CONFIG_FLOAT_SIGHT_MONSTER))
&& pl->isVisibleForOrDetect(m_creature, m_creature, true);
}
inline uint32 CreatureEventAI::GetRandActionParam(uint32 rnd, uint32 param1, uint32 param2, uint32 param3)
{
switch (rnd % 3)
{
case 0: return param1;
case 1: return param2;
case 2: return param3;
}
return 0;
}
inline int32 CreatureEventAI::GetRandActionParam(uint32 rnd, int32 param1, int32 param2, int32 param3)
{
switch (rnd % 3)
{
case 0: return param1;
case 1: return param2;
case 2: return param3;
}
return 0;
}
inline Unit* CreatureEventAI::GetTargetByType(uint32 Target, Unit* pActionInvoker, Creature* pAIEventSender, bool& isError, uint32 forSpellId, uint32 selectFlags)
{
Unit* resTarget;
switch (Target)
{
case TARGET_T_SELF:
return m_creature;
case TARGET_T_HOSTILE:
resTarget = m_creature->getVictim();
if (!resTarget)
isError = true;
return resTarget;
case TARGET_T_HOSTILE_SECOND_AGGRO:
resTarget = m_creature->SelectAttackingTarget(ATTACKING_TARGET_TOPAGGRO, 1, forSpellId, selectFlags);
if (!resTarget && ((forSpellId == 0 && selectFlags == 0 && m_creature->getThreatManager().getThreatList().size() > 1) || m_creature->getThreatManager().getThreatList().empty()))
isError = true;
return resTarget;
case TARGET_T_HOSTILE_LAST_AGGRO:
resTarget = m_creature->SelectAttackingTarget(ATTACKING_TARGET_BOTTOMAGGRO, 0, forSpellId, selectFlags);
if (!resTarget && m_creature->getThreatManager().getThreatList().empty())
isError = true;
return resTarget;
case TARGET_T_HOSTILE_RANDOM:
resTarget = m_creature->SelectAttackingTarget(ATTACKING_TARGET_RANDOM, 0, forSpellId, selectFlags);
if (!resTarget && m_creature->getThreatManager().getThreatList().empty())
isError = true;
return resTarget;
case TARGET_T_HOSTILE_RANDOM_NOT_TOP:
resTarget = m_creature->SelectAttackingTarget(ATTACKING_TARGET_RANDOM, 1, forSpellId, selectFlags);
if (!resTarget && ((forSpellId == 0 && selectFlags == 0 && m_creature->getThreatManager().getThreatList().size() > 1) || m_creature->getThreatManager().getThreatList().empty()))
isError = true;
return resTarget;
case TARGET_T_HOSTILE_RANDOM_PLAYER:
resTarget = m_creature->SelectAttackingTarget(ATTACKING_TARGET_RANDOM, 0, forSpellId, SELECT_FLAG_PLAYER | selectFlags);
if (!resTarget)
isError = true;
return resTarget;
case TARGET_T_HOSTILE_RANDOM_NOT_TOP_PLAYER:
resTarget = m_creature->SelectAttackingTarget(ATTACKING_TARGET_RANDOM, 1, forSpellId, SELECT_FLAG_PLAYER | selectFlags);
if (!resTarget && ((forSpellId == 0 && selectFlags == 0 && m_creature->getThreatManager().getThreatList().size() > 1) || m_creature->getThreatManager().getThreatList().empty()))
isError = true;
return resTarget;
case TARGET_T_ACTION_INVOKER:
if (!pActionInvoker)
isError = true;
return pActionInvoker;
case TARGET_T_ACTION_INVOKER_OWNER:
resTarget = pActionInvoker ? pActionInvoker->GetCharmerOrOwnerOrSelf() : NULL;
if (!resTarget)
isError = true;
return resTarget;
case TARGET_T_EVENT_SENDER:
if (!pAIEventSender)
isError = true;
return pAIEventSender;
default:
isError = true;
return NULL;
};
}
Unit* CreatureEventAI::DoSelectLowestHpFriendly(float range, uint32 MinHPDiff)
{
Unit* pUnit = NULL;
MaNGOS::MostHPMissingInRangeCheck u_check(m_creature, range, MinHPDiff);
MaNGOS::UnitLastSearcher<MaNGOS::MostHPMissingInRangeCheck> searcher(pUnit, u_check);
/*
typedef TYPELIST_4(GameObject, Creature*except pets*, DynamicObject, Corpse*Bones*) AllGridObjectTypes;
This means that if we only search grid then we cannot possibly return pets or players so this is safe
*/
Cell::VisitGridObjects(m_creature, searcher, range);
return pUnit;
}
void CreatureEventAI::DoFindFriendlyCC(std::list<Creature*>& _list, float range)
{
MaNGOS::FriendlyCCedInRangeCheck u_check(m_creature, range);
MaNGOS::CreatureListSearcher<MaNGOS::FriendlyCCedInRangeCheck> searcher(_list, u_check);
Cell::VisitGridObjects(m_creature, searcher, range);
}
void CreatureEventAI::DoFindFriendlyMissingBuff(std::list<Creature*>& _list, float range, uint32 spellid)
{
MaNGOS::FriendlyMissingBuffInRangeCheck u_check(m_creature, range, spellid);
MaNGOS::CreatureListSearcher<MaNGOS::FriendlyMissingBuffInRangeCheck> searcher(_list, u_check);
Cell::VisitGridObjects(m_creature, searcher, range);
}
//*********************************
//*** Functions used globally ***
void CreatureEventAI::ReceiveEmote(Player* pPlayer, uint32 text_emote)
{
for (CreatureEventAIList::iterator itr = m_CreatureEventAIList.begin(); itr != m_CreatureEventAIList.end(); ++itr)
{
if ((*itr).Event.event_type == EVENT_T_RECEIVE_EMOTE)
{
if ((*itr).Event.receive_emote.emoteId != text_emote)
return;
PlayerCondition pcon(0, (*itr).Event.receive_emote.condition, (*itr).Event.receive_emote.conditionValue1, (*itr).Event.receive_emote.conditionValue2);
if (pcon.Meets(pPlayer, m_creature->GetMap(), m_creature, CONDITION_FROM_EVENTAI))
{
DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "CreatureEventAI: ReceiveEmote CreatureEventAI: Condition ok, processing");
ProcessEvent(*itr, pPlayer);
}
}
}
}
#define HEALTH_STEPS 3
void CreatureEventAI::DamageTaken(Unit* dealer, uint32& damage)
{
if (m_InvinceabilityHpLevel > 0 && m_creature->GetHealth() < m_InvinceabilityHpLevel + damage)
{
if (m_creature->GetHealth() <= m_InvinceabilityHpLevel)
damage = 0;
else
damage = m_creature->GetHealth() - m_InvinceabilityHpLevel;
}
uint32 step = m_throwAIEventStep != 100 ? m_throwAIEventStep : 0;
if (step < HEALTH_STEPS)
{
// Throw at 90%, 50% and 10% health
float healthSteps[HEALTH_STEPS] = { 90.0f, 50.0f, 10.0f };
float newHealthPercent = (m_creature->GetHealth() - damage) * 100.0f / m_creature->GetMaxHealth();
AIEventType sendEvent[HEALTH_STEPS] = { AI_EVENT_LOST_SOME_HEALTH, AI_EVENT_LOST_HEALTH, AI_EVENT_CRITICAL_HEALTH };
if (newHealthPercent > healthSteps[step])
return; // Not reached the next mark
// search for highest reached mark (with actual event attached)
for (uint32 i = HEALTH_STEPS - 1; i > step; --i)
{
if (newHealthPercent < healthSteps[i] && (m_throwAIEventMask & (1 << sendEvent[i])))
{
step = i;
break;
}
}
if (m_throwAIEventMask & (1 << sendEvent[step]))
SendAIEventAround(sendEvent[step], dealer, 0, AIEVENT_DEFAULT_THROW_RADIUS);
m_throwAIEventStep = step + 1;
}
}
void CreatureEventAI::HealedBy(Unit* healer, uint32& healedAmount)
{
if (m_throwAIEventStep == 100)
return;
if (m_creature->GetHealth() + healedAmount >= m_creature->GetMaxHealth())
{
if (m_throwAIEventMask & (1 << AI_EVENT_GOT_FULL_HEALTH))
SendAIEventAround(AI_EVENT_GOT_FULL_HEALTH, healer, 0, AIEVENT_DEFAULT_THROW_RADIUS);
m_throwAIEventStep = 100;
}
}
bool CreatureEventAI::SpawnedEventConditionsCheck(CreatureEventAI_Event const& event)
{
if (event.event_type != EVENT_T_SPAWNED)
return false;
switch (event.spawned.condition)
{
case SPAWNED_EVENT_ALWAY:
// always
return true;
case SPAWNED_EVENT_MAP:
// map ID check
return m_creature->GetMapId() == event.spawned.conditionValue1;
case SPAWNED_EVENT_ZONE:
{
// zone ID check
uint32 zone, area;
m_creature->GetZoneAndAreaId(zone, area);
return zone == event.spawned.conditionValue1 || area == event.spawned.conditionValue1;
}
default:
break;
}
return false;
}