/* * Copyright (C) 2005-2011 MaNGOS * Copyright (C) 2009-2011 MaNGOSZero * Copyright (C) 2011-2016 Nostalrius * Copyright (C) 2016-2017 Elysium Project * * 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" #include "ScriptMgr.h" #include "CreatureAI.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.outErrorDb("CreatureEventAI: 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); } CreatureEventAI::CreatureEventAI(Creature *c) : CreatureAI(c) { // 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; const CreatureEventAI_Event_Vec& creatureEvent = creatureEventsItr->second; for (const auto& i : creatureEvent) { //Debug check #ifndef _DEBUG if (i.event_flags & EFLAG_DEBUG_ONLY) continue; #endif ++events_count; } //EventMap had events but they were not added because they must be for instance if (events_count == 0) sLog.outError("CreatureEventAI: Creature %u has events but no events added to list because of instance flags.", m_creature->GetEntry()); else { m_CreatureEventAIList.reserve(events_count); for (const auto& i : creatureEvent) { //Debug check #ifndef _DEBUG if (i.event_flags & EFLAG_DEBUG_ONLY) continue; #endif m_CreatureEventAIList.push_back(CreatureEventAIHolder(i)); } } } else sLog.outError("CreatureEventAI: EventMap for Creature %u is empty but creature is using CreatureEventAI.", m_creature->GetEntry()); m_bEmptyList = m_CreatureEventAIList.empty(); m_Phase = 0; m_AttackDistance = 0.0f; m_AttackAngle = 0.0f; m_InvinceabilityHpLevel = 0; //Handle Spawned Events c->SetAI(this); if (!m_bEmptyList) { for (auto& i : m_CreatureEventAIList) if (i.Event.event_type == EVENT_T_SPAWNED) ProcessEvent(i); } Reset(); } bool CreatureEventAI::ProcessEvent(CreatureEventAIHolder& pHolder, Unit* pActionInvoker) { 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)) return false; if (pHolder.Event.condition_id && !sObjectMgr.IsConditionSatisfied(pHolder.Event.condition_id, pActionInvoker ? pActionInvoker : m_creature->getVictim(), m_creature->GetMap(), m_creature, CONDITION_FROM_EVENTAI)) return false; 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: if (!m_creature->isInCombat()) return false; //Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.timer.repeatMin, event.timer.repeatMax); break; case EVENT_T_TIMER_OOC: if (m_creature->isInCombat()) return false; //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; //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; //Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.percent_range.repeatMin, event.percent_range.repeatMax); break; } case EVENT_T_AGGRO: break; case EVENT_T_KILL: { if (event.kill.playerOnly && !pActionInvoker->IsPlayer()) return false; //Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.kill.repeatMin, event.kill.repeatMax); break; } case EVENT_T_DEATH: case EVENT_T_EVADE: case EVENT_T_LEAVE_COMBAT: 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; //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; //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 = m_creature->DoSelectLowestHpFriendly((float)event.friendly_hp.radius, event.friendly_hp.hpDeficit); if (!pUnit) return false; pActionInvoker = pUnit; //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; Unit* pUnit = m_creature->DoFindFriendlyCC((float)event.friendly_is_cc.radius); if (!pUnit) return false; pActionInvoker = pUnit; //Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.friendly_is_cc.repeatMin, event.friendly_is_cc.repeatMax); break; } case EVENT_T_FRIENDLY_MISSING_BUFF: { Unit* pUnit = m_creature->DoFindFriendlyMissingBuff((float)event.friendly_buff.radius, event.friendly_buff.spellId); if (!pUnit) return false; pActionInvoker = pUnit; //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; //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; //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; //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; //Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.buffed.repeatMin, event.buffed.repeatMax); break; } case EVENT_T_MOVEMENT_INFORM: { //Repeat Timers pHolder.UpdateRepeatTimer(m_creature, event.move_inform.repeatMin, event.move_inform.repeatMax); break; } case EVENT_T_MAP_SCRIPT_EVENT: case EVENT_T_GROUP_MEMBER_DIED: break; case EVENT_T_VICTIM_ROOTED: { if (!m_creature->getVictim() || !m_creature->getVictim()->hasUnitState(UNIT_STAT_ROOT)) return false; pHolder.UpdateRepeatTimer(m_creature, event.victim_rooted.repeatMin, event.victim_rooted.repeatMax); break; } default: sLog.outErrorDb("CreatureEventAI: 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], pHolder.Event.event_id, pActionInvoker); } //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]) ++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]) { if (!idx) break; --idx; } } ProcessAction(pHolder.Event.action[j], pHolder.Event.event_id, pActionInvoker); } } return true; } void CreatureEventAI::ProcessAction(ScriptMap* action, uint32 EventId, Unit* pActionInvoker) { if (!action) return; WorldObject* target = pActionInvoker ? pActionInvoker : m_creature->getVictim(); Map* map = m_creature->GetMap(); for (const auto& x : *action) { map->ScriptCommandStartDirect(x.second, m_creature, target); } } void CreatureEventAI::JustRespawned() { Reset(); CreatureAI::JustRespawned(); if (m_bEmptyList) return; //Handle Spawned Events for (auto& i : m_CreatureEventAIList) if (i.Event.event_type == EVENT_T_SPAWNED) ProcessEvent(i); } void CreatureEventAI::Reset() { m_EventUpdateTime = EVENT_UPDATE_TIME; m_EventDiff = 0; if (m_bEmptyList) return; //Reset all events to enabled for (auto& i : m_CreatureEventAIList) { 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; } } } } void CreatureEventAI::JustReachedHome() { if (!m_bEmptyList) { for (auto& i : m_CreatureEventAIList) { if (i.Event.event_type == EVENT_T_REACHED_HOME) ProcessEvent(i); } } Reset(); } void CreatureEventAI::EnterEvadeMode() { CreatureAI::EnterEvadeMode(); if (m_bEmptyList) return; //Handle Evade events for (auto& i : m_CreatureEventAIList) { if (i.Event.event_type == EVENT_T_EVADE) ProcessEvent(i); } } void CreatureEventAI::OnCombatStop() { CreatureAI::OnCombatStop(); if (m_bEmptyList) return; //Handle Combat Stop events for (auto& i : m_CreatureEventAIList) { if (i.Event.event_type == EVENT_T_LEAVE_COMBAT) ProcessEvent(i); } } void CreatureEventAI::JustDied(Unit* killer) { Reset(); if (m_bEmptyList) return; //Handle Evade events for (auto& i : m_CreatureEventAIList) { 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 (m_bEmptyList || victim->GetTypeId() != TYPEID_PLAYER) return; for (auto& i : m_CreatureEventAIList) { if (i.Event.event_type == EVENT_T_KILL) ProcessEvent(i, victim); } } void CreatureEventAI::JustSummoned(Creature* pUnit) { if (m_bEmptyList || !pUnit) return; for (auto& i : m_CreatureEventAIList) { if (i.Event.event_type == EVENT_T_SUMMONED_UNIT) ProcessEvent(i, pUnit); } } void CreatureEventAI::SummonedCreatureJustDied(Creature* pUnit) { if (m_bEmptyList || !pUnit) return; for (auto& i : m_CreatureEventAIList) { if (i.Event.event_type == EVENT_T_SUMMONED_JUST_DIED) ProcessEvent(i, pUnit); } } void CreatureEventAI::SummonedCreatureDespawn(Creature* pUnit) { if (m_bEmptyList || !pUnit) return; for (auto& i : m_CreatureEventAIList) { if (i.Event.event_type == EVENT_T_SUMMONED_JUST_DESPAWN) ProcessEvent(i, pUnit); } } void CreatureEventAI::EnterCombat(Unit *enemy) { //Check for on combat start events if (!m_bEmptyList) { for (auto& i : m_CreatureEventAIList) { 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: 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_bMeleeAttack)) { m_creature->AddThreat(who); m_creature->SetInCombatWith(who); who->SetInCombatWith(m_creature); if (m_bCombatMovement) m_creature->GetMotionMaster()->MoveChase(who, m_AttackDistance, m_AttackAngle); } } void CreatureEventAI::MoveInLineOfSight(Unit *pWho) { if (!pWho) return; //Check for OOC LOS Event if (!m_bEmptyList && !m_creature->getVictim()) UpdateEventsOn_MoveInLineOfSight(pWho); if (m_creature->GetCreatureInfo()->flags_extra & CREATURE_FLAG_EXTRA_NO_AGGRO || m_creature->IsNeutralToAll()) return; // Check this now to prevent calling expensive functions (isInAccessablePlaceFor / IsWithinLOSInMap) if (m_creature->getVictim() && !m_creature->GetMap()->IsDungeon()) return; if (!m_creature->CanFly() && m_creature->GetDistanceZ(pWho) > CREATURE_Z_ATTACK_RANGE) return; if (m_creature->CanInitiateAttack() && pWho->isTargetableForAttack()) { float attackRadius = m_creature->GetAttackDistance(pWho); if (m_creature->IsWithinDistInMap(pWho, attackRadius) && m_creature->IsHostileTo(pWho)) { if (!m_creature->getVictim()) { if (m_creature->IsWithinLOSInMap(pWho) && pWho->isInAccessablePlaceFor(m_creature)) AttackStart(pWho); } else if (m_creature->GetMap()->IsDungeon()) { if (m_creature->IsWithinLOSInMap(pWho) && pWho->isInAccessablePlaceFor(m_creature)) { m_creature->AddThreat(pWho); pWho->SetInCombatWith(m_creature); } } } } } void CreatureEventAI::UpdateEventsOn_MoveInLineOfSight(Unit* pWho) { for (auto& itr : m_CreatureEventAIList) { 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(pWho, fMaxAllowedRange)) { //if friendly event&&who is not hostile OR hostile event&&who is hostile if (itr.Event.ooc_los.noHostile && !m_creature->IsHostileTo(pWho) || !itr.Event.ooc_los.noHostile && m_creature->IsHostileTo(pWho)) if (m_creature->IsWithinLOSInMap(pWho)) ProcessEvent(itr, pWho); } } } } void CreatureEventAI::SpellHit(Unit* pUnit, const SpellEntry* pSpell) { if (m_bEmptyList) return; for (auto& i : m_CreatureEventAIList) 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 (GetSchoolMask(pSpell->School) & i.Event.spell_hit.schoolMask) ProcessEvent(i, pUnit); } void CreatureEventAI::MovementInform(uint32 type, uint32 id) { if (m_bEmptyList) return; for (auto& i : m_CreatureEventAIList) if (i.Event.event_type == EVENT_T_MOVEMENT_INFORM) if (i.Event.move_inform.motionType == type && i.Event.move_inform.pointId == id) ProcessEvent(i); } void CreatureEventAI::UpdateAI(const uint32 diff) { //Check if we are in combat (also updates calls threat update code) m_creature->SelectHostileTarget(); bool Combat = m_creature->getVictim(); //Must return if creature isn't alive. Normally select hostil target and get victim prevent this if (!m_creature->isAlive()) return; if (!m_bEmptyList) UpdateEventsOn_UpdateAI(diff, Combat); if (Combat) { if (!m_CreatureSpells.empty()) DoSpellsListCasts(diff); DoMeleeAttackIfReady(); } } void CreatureEventAI::UpdateEventsOn_UpdateAI(const uint32 diff, bool Combat) { //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 (auto& i : m_CreatureEventAIList) { //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: ProcessEvent(i); break; case EVENT_T_TIMER: 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_VICTIM_ROOTED: 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; } } void CreatureEventAI::SetInvincibilityHealthLevel(uint32 hp_level, bool is_percent) { m_InvinceabilityHpLevel = is_percent ? m_creature->GetMaxHealth() * hp_level / 100 : hp_level; } //********************************* //*** Functions used globally *** void CreatureEventAI::ReceiveEmote(Player* pPlayer, uint32 text_emote) { if (m_bEmptyList) return; for (auto& itr : m_CreatureEventAIList) { if (itr.Event.event_type == EVENT_T_RECEIVE_EMOTE) { if (itr.Event.receive_emote.emoteId != text_emote) continue; ProcessEvent(itr, pPlayer); } } } void CreatureEventAI::DamageTaken(Unit* /*done_by*/, 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; } } void CreatureEventAI::MapScriptEventHappened(ScriptedEvent* pEvent, uint32 uiData) { if (m_bEmptyList) return; for (auto& i : m_CreatureEventAIList) { if (i.Event.event_type == EVENT_T_MAP_SCRIPT_EVENT) if ((i.Event.map_event.eventId == pEvent->m_uiEventId) && (i.Event.map_event.data == uiData)) ProcessEvent(i); } } void CreatureEventAI::GroupMemberJustDied(Creature* pUnit, bool isLeader) { if (m_bEmptyList) return; for (auto& i : m_CreatureEventAIList) { if (i.Event.event_type == EVENT_T_GROUP_MEMBER_DIED) { if (i.Event.group_member_died.creatureId && (i.Event.group_member_died.creatureId != pUnit->GetEntry())) continue; if (((bool)i.Event.group_member_died.isLeader) == isLeader) ProcessEvent(i); } } }