/* * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information * * 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, see . */ #include "SmartScript.h" #include "Cell.h" #include "CellImpl.h" #include "ChatTextBuilder.h" #include "CreatureTextMgr.h" #include "GameEventMgr.h" #include "GossipDef.h" #include "GridDefines.h" #include "GridNotifiers.h" #include "Group.h" #include "InstanceScript.h" #include "Language.h" #include "MoveSplineInit.h" #include "ObjectDefines.h" #include "ObjectMgr.h" #include "ScriptedCreature.h" #include "ScriptedGossip.h" #include "SmartAI.h" #include "SpellMgr.h" #include "Vehicle.h" #include "WorldState.h" /// @todo: this import is not necessary for compilation and marked as unused by the IDE // however, for some reasons removing it would cause a damn linking issue // there is probably some underlying problem with imports which should properly addressed // see: https://github.com/azerothcore/azerothcore-wotlk/issues/9766 #include "GridNotifiersImpl.h" SmartScript::SmartScript() { go = nullptr; me = nullptr; trigger = nullptr; mEventPhase = 0; mPathId = 0; mTextTimer = 0; mLastTextID = 0; mUseTextTimer = false; mTalkerEntry = 0; mTemplate = SMARTAI_TEMPLATE_BASIC; mScriptType = SMART_SCRIPT_TYPE_CREATURE; isProcessingTimedActionList = false; mCurrentPriority = 0; mEventSortingRequired = false; _allowPhaseReset = true; } SmartScript::~SmartScript() { } bool SmartScript::IsSmart(Creature* c, bool silent) const { if (!c) return false; bool smart = true; if (!dynamic_cast(c->AI())) smart = false; if (!smart && !silent) LOG_ERROR("sql.sql", "SmartScript: Action target Creature(entry: {}) is not using SmartAI, action skipped to prevent crash.", c ? c->GetEntry() : (me ? me->GetEntry() : 0)); return smart; } bool SmartScript::IsSmart(GameObject* g, bool silent) const { if (!g) return false; bool smart = true; if (!dynamic_cast(g->AI())) smart = false; if (!smart && !silent) LOG_ERROR("sql.sql", "SmartScript: Action target GameObject(entry: {}) is not using SmartGameObjectAI, action skipped to prevent crash.", g ? g->GetEntry() : (go ? go->GetEntry() : 0)); return smart; } bool SmartScript::IsSmart(bool silent) const { if (me) return IsSmart(me, silent); if (go) return IsSmart(go, silent); return false; } void SmartScript::OnReset() { // xinef: check if we allow phase reset if (AllowPhaseReset()) SetPhase(0); ResetBaseObject(); for (SmartAIEventList::iterator i = mEvents.begin(); i != mEvents.end(); ++i) { if (!((*i).event.event_flags & SMART_EVENT_FLAG_DONT_RESET)) { InitTimer((*i)); (*i).runOnce = false; } if ((*i).priority != SmartScriptHolder::DEFAULT_PRIORITY) { (*i).priority = SmartScriptHolder::DEFAULT_PRIORITY; mEventSortingRequired = true; } } ProcessEventsFor(SMART_EVENT_RESET); mLastInvoker.Clear(); mCounterList.clear(); } void SmartScript::ProcessEventsFor(SMART_EVENT e, Unit* unit, uint32 var0, uint32 var1, bool bvar, SpellInfo const* spell, GameObject* gob) { for (SmartAIEventList::iterator i = mEvents.begin(); i != mEvents.end(); ++i) { SMART_EVENT eventType = SMART_EVENT((*i).GetEventType()); if (eventType == SMART_EVENT_LINK)//special handling continue; if (eventType == e) { ConditionList conds = sConditionMgr->GetConditionsForSmartEvent((*i).entryOrGuid, (*i).event_id, (*i).source_type); ConditionSourceInfo info = ConditionSourceInfo(unit, GetBaseObject(), me ? me->GetVictim() : nullptr); if (sConditionMgr->IsObjectMeetToConditions(info, conds)) { ASSERT(executionStack.empty()); executionStack.emplace_back(SmartScriptFrame{ *i, unit, var0, var1, bvar, spell, gob }); while (!executionStack.empty()) { auto [stack_holder , stack_unit, stack_var0, stack_var1, stack_bvar, stack_spell, stack_gob] = executionStack.back(); executionStack.pop_back(); ProcessEvent(stack_holder, stack_unit, stack_var0, stack_var1, stack_bvar, stack_spell, stack_gob); } } } } } void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, uint32 var1, bool bvar, SpellInfo const* spell, GameObject* gob) { e.runOnce = true;//used for repeat check //calc random if (e.event.event_chance < 100 && e.event.event_chance && !(e.event.event_flags & SMART_EVENT_FLAG_TEMP_IGNORE_CHANCE_ROLL)) { uint32 rnd = urand(1, 100); if (e.event.event_chance <= rnd) return; } // Remove SMART_EVENT_FLAG_TEMP_IGNORE_CHANCE_ROLL flag after processing roll chances as it's not needed anymore e.event.event_flags &= ~SMART_EVENT_FLAG_TEMP_IGNORE_CHANCE_ROLL; if (unit) mLastInvoker = unit->GetGUID(); else if (gob) mLastInvoker = gob->GetGUID(); if (WorldObject* tempInvoker = GetLastInvoker()) LOG_DEBUG("sql.sql", "SmartScript::ProcessAction: Invoker: {} ({})", tempInvoker->GetName(), tempInvoker->GetGUID().ToString()); bool isControlled = e.action.moveToPos.controlled > 0; ObjectVector targets; WorldObject* invoker = nullptr; if (unit) invoker = unit; else if (gob) invoker = gob; GetTargets(targets, e, invoker); switch (e.GetActionType()) { case SMART_ACTION_TALK: { Creature* talker = e.target.type == 0 ? me : nullptr; WorldObject* talkTarget = nullptr; for (WorldObject* target : targets) { if (IsCreature((target)) && !target->ToCreature()->IsPet()) // Prevented sending text to pets. { if (e.action.talk.useTalkTarget) { talker = me; talkTarget = target->ToCreature(); } else talker = target->ToCreature(); break; } else if (IsPlayer((target))) { talker = me; // xinef: added talkTarget = target->ToPlayer(); break; } } if (!talkTarget) talkTarget = GetLastInvoker(); if (!talker) break; if (!sCreatureTextMgr->TextExist(talker->GetEntry(), uint8(e.action.talk.textGroupID))) { LOG_ERROR("sql.sql", "SmartScript::ProcessAction: SMART_ACTION_TALK: EntryOrGuid {} SourceType {} EventType {} TargetType {} using non-existent Text id {} for talker {}, ignored.", e.entryOrGuid, e.GetScriptType(), e.GetEventType(), e.GetTargetType(), e.action.talk.textGroupID, talker->GetEntry()); break; } mTalkerEntry = talker->GetEntry(); mLastTextID = e.action.talk.textGroupID; mTextTimer = e.action.talk.duration; mUseTextTimer = true; talker->AI()->Talk(e.action.talk.textGroupID, talkTarget, Milliseconds(e.action.talk.delay)); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction: SMART_ACTION_TALK: talker: {} ({}), textId: {}", talker->GetName(), talker->GetGUID().ToString(), mLastTextID); break; } case SMART_ACTION_SIMPLE_TALK: { for (WorldObject* target : targets) { if (IsCreature(target)) sCreatureTextMgr->SendChat(target->ToCreature(), uint8(e.action.simpleTalk.textGroupID), IsPlayer(GetLastInvoker()) ? GetLastInvoker() : 0); else if (IsPlayer(target) && me) { WorldObject* templastInvoker = GetLastInvoker(); sCreatureTextMgr->SendChat(me, uint8(e.action.simpleTalk.textGroupID), IsPlayer(templastInvoker) ? templastInvoker : 0, CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_NORMAL, 0, TEAM_NEUTRAL, false, target->ToPlayer()); } LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_SIMPLE_TALK: talker: {} ({}), textGroupId: {}", target->GetName(), target->GetGUID().ToString(), uint8(e.action.simpleTalk.textGroupID)); } break; } case SMART_ACTION_PLAY_EMOTE: { for (WorldObject* target : targets) { if (IsUnit(target)) { target->ToUnit()->HandleEmoteCommand(e.action.emote.emote); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_PLAY_EMOTE: target: {} ({}), emote: {}", target->GetName(), target->GetGUID().ToString(), e.action.emote.emote); } } break; } case SMART_ACTION_SOUND: { for (WorldObject* target : targets) { if (IsUnit(target)) { if (e.action.sound.distance == 1) target->PlayDistanceSound(e.action.sound.sound, e.action.sound.onlySelf ? target->ToPlayer() : nullptr); else target->PlayDirectSound(e.action.sound.sound, e.action.sound.onlySelf ? target->ToPlayer() : nullptr); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_SOUND: target: {} ({}), sound: {}, onlyself: {}", target->GetName(), target->GetGUID().ToString(), e.action.sound.sound, e.action.sound.onlySelf); } } break; } case SMART_ACTION_RANDOM_SOUND: { uint32 sounds[4]; sounds[0] = e.action.randomSound.sound1; sounds[1] = e.action.randomSound.sound2; sounds[2] = e.action.randomSound.sound3; sounds[3] = e.action.randomSound.sound4; uint32 temp[4]; uint32 count = 0; for (unsigned int sound : sounds) { if (sound) { temp[count] = sound; ++count; } } if (count == 0) { break; } for (WorldObject* target : targets) { if (IsUnit(target)) { uint32 sound = temp[urand(0, count - 1)]; target->PlayDirectSound(sound, e.action.randomSound.onlySelf ? target->ToPlayer() : nullptr); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_RANDOM_SOUND: target: {} ({}), sound: {}, onlyself: {}", target->GetName(), target->GetGUID().ToString(), sound, e.action.randomSound.onlySelf); } } break; } case SMART_ACTION_MUSIC: { ObjectVector targets; if (e.action.music.type > 0) { if (me && me->FindMap()) { Map::PlayerList const& players = me->GetMap()->GetPlayers(); if (!players.IsEmpty()) { for (Map::PlayerList::const_iterator i = players.begin(); i != players.end(); ++i) if (Player* player = i->GetSource()) { if (player->GetZoneId() == me->GetZoneId()) { if (e.action.music.type > 1) { if (player->GetAreaId() == me->GetAreaId()) targets.push_back(player); } else targets.push_back(player); } } } } } else GetTargets(targets, e); if (!targets.empty()) { for (WorldObject* target : targets) { if (IsUnit(target)) { target->SendPlayMusic(e.action.music.sound, e.action.music.onlySelf > 0); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_MUSIC: target: {} ({}), sound: {}, onlySelf: {}, type: {}", target->GetName(), target->GetGUID().ToString(), e.action.music.sound, e.action.music.onlySelf, e.action.music.type); } } } break; } case SMART_ACTION_RANDOM_MUSIC: { ObjectVector targets; if (e.action.randomMusic.type > 0) { if (me && me->FindMap()) { Map::PlayerList const& players = me->GetMap()->GetPlayers(); if (!players.IsEmpty()) { for (Map::PlayerList::const_iterator i = players.begin(); i != players.end(); ++i) if (Player* player = i->GetSource()) { if (player->GetZoneId() == me->GetZoneId()) { if (e.action.randomMusic.type > 1) { if (player->GetAreaId() == me->GetAreaId()) targets.push_back(player); } else targets.push_back(player); } } } } } else GetTargets(targets, e); if (targets.empty()) break; uint32 sounds[4]; sounds[0] = e.action.randomMusic.sound1; sounds[1] = e.action.randomMusic.sound2; sounds[2] = e.action.randomMusic.sound3; sounds[3] = e.action.randomMusic.sound4; uint32 temp[4]; uint32 count = 0; for (unsigned int sound : sounds) { if (sound) { temp[count] = sound; ++count; } } if (count == 0) { break; } for (WorldObject* target : targets) { if (IsUnit(target)) { uint32 sound = temp[urand(0, count - 1)]; target->SendPlayMusic(sound, e.action.randomMusic.onlySelf > 0); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_RANDOM_MUSIC: target: {} ({}), sound: {}, onlyself: {}, type: {}", target->GetName(), target->GetGUID().ToString(), sound, e.action.randomMusic.onlySelf, e.action.randomMusic.type); } } break; } case SMART_ACTION_SET_FACTION: { for (WorldObject* target : targets) { if (IsCreature(target)) { if (e.action.faction.factionID) { target->ToCreature()->SetFaction(e.action.faction.factionID); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SET_FACTION: Creature entry {}, GuidLow {} set faction to {}", target->GetEntry(), target->GetGUID().ToString(), e.action.faction.factionID); } else { if (CreatureTemplate const* ci = sObjectMgr->GetCreatureTemplate(target->ToCreature()->GetEntry())) { if (target->ToCreature()->GetFaction() != ci->faction) { target->ToCreature()->SetFaction(ci->faction); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SET_FACTION: Creature entry {}, GuidLow {} set faction to {}", target->GetEntry(), target->GetGUID().ToString(), ci->faction); } } } } } break; } case SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: { for (WorldObject* target : targets) { if (!IsCreature(target)) continue; if (e.action.morphOrMount.creature || e.action.morphOrMount.model) { //set model based on entry from creature_template if (e.action.morphOrMount.creature) { if (CreatureTemplate const* ci = sObjectMgr->GetCreatureTemplate(e.action.morphOrMount.creature)) { CreatureModel const* model = ObjectMgr::ChooseDisplayId(ci); target->ToCreature()->SetDisplayId(model->CreatureDisplayID, model->DisplayScale); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: Creature entry {}, GuidLow {} set displayid to {}", target->GetEntry(), target->GetGUID().ToString(), model->CreatureDisplayID); } } //if no param1, then use value from param2 (modelId) else { target->ToCreature()->SetDisplayId(e.action.morphOrMount.model); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: Creature entry {}, GuidLow {} set displayid to {}", target->GetEntry(), target->GetGUID().ToString(), e.action.morphOrMount.model); } } else { target->ToCreature()->DeMorph(); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: Creature entry {}, GuidLow {} demorphs.", target->GetEntry(), target->GetGUID().ToString()); } } break; } case SMART_ACTION_FAIL_QUEST: { for (WorldObject* target : targets) { if (IsPlayer(target)) { target->ToPlayer()->FailQuest(e.action.quest.quest); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_FAIL_QUEST: Player guidLow {} fails quest {}", target->GetGUID().ToString(), e.action.quest.quest); } } break; } case SMART_ACTION_OFFER_QUEST: { for (WorldObject* target : targets) { if (Player* player = target->ToPlayer()) { if (Quest const* q = sObjectMgr->GetQuestTemplate(e.action.questOffer.questID)) { if (me && e.action.questOffer.directAdd == 0) { if (player->CanTakeQuest(q, true)) { if (WorldSession* session = player->GetSession()) { PlayerMenu menu(session); menu.SendQuestGiverQuestDetails(q, me->GetGUID(), true); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_OFFER_QUEST: Player guidLow {} - offering quest {}", player->GetGUID().ToString(), e.action.questOffer.questID); } } } else { player->AddQuestAndCheckCompletion(q, nullptr); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_OFFER_QUEST: Player guidLow {} - quest {} added", player->GetGUID().ToString(), e.action.questOffer.questID); } } } } break; } case SMART_ACTION_SET_REACT_STATE: { for (WorldObject* target : targets) { if (!IsCreature(target)) continue; target->ToCreature()->SetReactState(ReactStates(e.action.react.state)); } break; } case SMART_ACTION_RANDOM_EMOTE: { std::vector emotes; std::copy_if(e.action.randomEmote.emotes.begin(), e.action.randomEmote.emotes.end(), std::back_inserter(emotes), [](uint32 emote) { return emote != 0; }); for (WorldObject* target : targets) { if (IsUnit(target)) { uint32 emote = Acore::Containers::SelectRandomContainerElement(emotes); target->ToUnit()->HandleEmoteCommand(emote); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_RANDOM_EMOTE: Creature guidLow {} handle random emote {}", target->GetGUID().ToString(), emote); } } break; } case SMART_ACTION_THREAT_ALL_PCT: { if (!me) break; ThreatContainer::StorageType threatList = me->GetThreatMgr().GetThreatList(); for (ThreatContainer::StorageType::const_iterator i = threatList.begin(); i != threatList.end(); ++i) { if (Unit* target = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid())) { me->GetThreatMgr().ModifyThreatByPercent(target, e.action.threatPCT.threatINC ? (int32)e.action.threatPCT.threatINC : -(int32)e.action.threatPCT.threatDEC); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_THREAT_ALL_PCT: Creature {} modify threat for unit {}, value {}", me->GetGUID().ToString(), target->GetGUID().ToString(), e.action.threatPCT.threatINC ? (int32)e.action.threatPCT.threatINC : -(int32)e.action.threatPCT.threatDEC); } } break; } case SMART_ACTION_THREAT_SINGLE_PCT: { if (!me) break; for (WorldObject* target : targets) { if (IsUnit(target)) { me->GetThreatMgr().ModifyThreatByPercent(target->ToUnit(), e.action.threatPCT.threatINC ? (int32)e.action.threatPCT.threatINC : -(int32)e.action.threatPCT.threatDEC); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_THREAT_SINGLE_PCT: Creature guidLow {} modify threat for unit {}, value {}", me->GetGUID().ToString(), target->GetGUID().ToString(), e.action.threatPCT.threatINC ? (int32)e.action.threatPCT.threatINC : -(int32)e.action.threatPCT.threatDEC); } } break; } case SMART_ACTION_CALL_AREAEXPLOREDOREVENTHAPPENS: { for (WorldObject* target : targets) { // Special handling for vehicles if (IsUnit(target)) if (Vehicle* vehicle = target->ToUnit()->GetVehicleKit()) for (auto & Seat : vehicle->Seats) if (Player* player = ObjectAccessor::GetPlayer(*target, Seat.second.Passenger.Guid)) player->AreaExploredOrEventHappens(e.action.quest.quest); if (IsPlayer(target)) { target->ToPlayer()->AreaExploredOrEventHappens(e.action.quest.quest); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_CALL_AREAEXPLOREDOREVENTHAPPENS: Player guidLow {} credited quest {}", target->GetGUID().ToString(), e.action.quest.quest); } } break; } case SMART_ACTION_CAST: { if (targets.empty()) break; Unit* caster = me; // Areatrigger Cast! if (e.IsAreatriggerScript()) caster = unit->SummonTrigger(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ(), unit->GetOrientation(), 5000); if (e.action.cast.targetsLimit) Acore::Containers::RandomResize(targets, e.action.cast.targetsLimit); bool failedSpellCast = false, successfulSpellCast = false; for (WorldObject* target : targets) { // may be nullptr if (go) go->CastSpell(target->ToUnit(), e.action.cast.spell); if (!IsUnit(target)) continue; if (caster && caster != me) // Areatrigger cast { caster->CastSpell(target->ToUnit(), e.action.cast.spell, (e.action.cast.castFlags & SMARTCAST_TRIGGERED)); } else if (me) { // If target has the aura, skip if ((e.action.cast.castFlags & SMARTCAST_AURA_NOT_PRESENT) && target->ToUnit()->HasAura(e.action.cast.spell)) continue; // If the threatlist is a singleton, cancel if (e.action.cast.castFlags & SMARTCAST_THREATLIST_NOT_SINGLE) if (me->GetThreatMgr().GetThreatListSize() <= 1) break; // If target does not use mana, skip if ((e.action.cast.castFlags & SMARTCAST_TARGET_POWER_MANA) && !target->ToUnit()->GetPower(POWER_MANA)) continue; // Interrupts current spellcast if (e.action.cast.castFlags & SMARTCAST_INTERRUPT_PREVIOUS) me->InterruptNonMeleeSpells(false); SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(e.action.cast.spell); float distanceToTarget = me->GetDistance(target->ToUnit()); float spellMaxRange = me->GetSpellMaxRangeForTarget(target->ToUnit(), spellInfo); float spellMinRange = me->GetSpellMinRangeForTarget(target->ToUnit(), spellInfo); float meleeRange = me->GetMeleeRange(target->ToUnit()); bool isWithinLOSInMap = me->IsWithinLOSInMap(target->ToUnit(), VMAP::ModelIgnoreFlags::M2); bool isWithinMeleeRange = distanceToTarget <= meleeRange; bool isRangedAttack = spellMaxRange > NOMINAL_MELEE_RANGE; bool isTargetRooted = target->ToUnit()->HasUnitState(UNIT_STATE_ROOT); // To prevent running back and forth when OOM, we must have more than 10% mana. bool canCastSpell = me->GetPowerPct(POWER_MANA) > 10.0f && spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask()) < (int32)me->GetPower(POWER_MANA) && !me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED); bool isSpellIgnoreLOS = spellInfo->HasAttribute(SPELL_ATTR2_IGNORE_LINE_OF_SIGHT); // If target is rooted we move out of melee range before casting, but not further than spell max range. if (isWithinLOSInMap && isWithinMeleeRange && isRangedAttack && isTargetRooted && canCastSpell && !me->IsVehicle()) { failedSpellCast = true; // Mark spellcast as failed so we can retry it later if (me->IsRooted()) // Rooted inhabit type, never move/reposition continue; float minDistance = std::max(meleeRange, spellMinRange) - distanceToTarget + NOMINAL_MELEE_RANGE; CAST_AI(SmartAI, me->AI())->DistanceYourself(std::min(minDistance, spellMaxRange)); continue; } // Let us not try to cast spell if we know it is going to fail anyway. Stick to chasing and continue. if (distanceToTarget > spellMaxRange && isWithinLOSInMap) { failedSpellCast = true; if (me->IsRooted()) // Rooted inhabit type, never move/reposition continue; CAST_AI(SmartAI, me->AI())->SetCurrentRangeMode(true, std::max(spellMaxRange - NOMINAL_MELEE_RANGE, 0.0f)); continue; } else if (distanceToTarget < spellMinRange || !(isWithinLOSInMap || isSpellIgnoreLOS)) { failedSpellCast = true; if (me->IsRooted()) // Rooted inhabit type, never move/reposition continue; CAST_AI(SmartAI, me->AI())->SetCurrentRangeMode(true, 0.f); if (e.action.cast.castFlags & SMARTCAST_ENABLE_COMBAT_MOVE_ON_LOS) CAST_AI(SmartAI, me->AI())->SetCombatMovement(true, true); continue; } TriggerCastFlags triggerFlags = TRIGGERED_NONE; if (e.action.cast.castFlags & SMARTCAST_TRIGGERED) { if (e.action.cast.triggerFlags) triggerFlags = TriggerCastFlags(e.action.cast.triggerFlags); else triggerFlags = TRIGGERED_FULL_MASK; } SpellCastResult result = me->CastSpell(target->ToUnit(), e.action.cast.spell, triggerFlags); bool spellCastFailed = (result != SPELL_CAST_OK && result != SPELL_FAILED_SPELL_IN_PROGRESS); if (e.action.cast.castFlags & SMARTCAST_COMBAT_MOVE) { // If cast flag SMARTCAST_COMBAT_MOVE is set combat movement will not be allowed unless target is outside spell range, out of mana, or LOS. if (result == SPELL_FAILED_OUT_OF_RANGE || result == SPELL_CAST_OK) // if we are just out of range, we only chase until we are back in spell range. CAST_AI(SmartAI, me->AI())->SetCurrentRangeMode(true, std::max(spellMaxRange - NOMINAL_MELEE_RANGE, 0.0f)); else // move into melee on any other fail // if spell fail for any other reason, we chase to melee range, or stay where we are if spellcast was successful. CAST_AI(SmartAI, me->AI())->SetCurrentRangeMode(false, 0.f); } if (spellCastFailed) failedSpellCast = true; else successfulSpellCast = true; LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_CAST: Unit {} casts spell {} on target {} with castflags {}", me->GetGUID().ToString(), e.action.cast.spell, target->GetGUID().ToString(), e.action.cast.castFlags); } } // If there is at least 1 failed cast and no successful casts at all, retry again on next loop if (failedSpellCast && !successfulSpellCast) { RetryLater(e, true); // Don't execute linked events return; } break; } case SMART_ACTION_SELF_CAST: { if (targets.empty()) break; if (e.action.cast.targetsLimit) Acore::Containers::RandomResize(targets, e.action.cast.targetsLimit); TriggerCastFlags triggerFlags = TRIGGERED_NONE; if (e.action.cast.castFlags & SMARTCAST_TRIGGERED) { if (e.action.cast.triggerFlags) { triggerFlags = TriggerCastFlags(e.action.cast.triggerFlags); } else { triggerFlags = TRIGGERED_FULL_MASK; } } for (WorldObject* target : targets) { Unit* uTarget = target->ToUnit(); if (!uTarget) continue; if (!(e.action.cast.castFlags & SMARTCAST_AURA_NOT_PRESENT) || !uTarget->HasAura(e.action.cast.spell)) { if (e.action.cast.castFlags & SMARTCAST_INTERRUPT_PREVIOUS) { uTarget->InterruptNonMeleeSpells(false); } uTarget->CastSpell(uTarget, e.action.cast.spell, triggerFlags); } } break; } case SMART_ACTION_INVOKER_CAST: { // Can be used for area trigger cast WorldObject* tempLastInvoker = GetLastInvoker(unit); if (!tempLastInvoker) break; if (targets.empty()) break; if (e.action.cast.targetsLimit) Acore::Containers::RandomResize(targets, e.action.cast.targetsLimit); for (WorldObject* target : targets) { if (!IsUnit(target)) continue; if (!IsUnit(tempLastInvoker)) continue; if (!(e.action.cast.castFlags & SMARTCAST_AURA_NOT_PRESENT) || !target->ToUnit()->HasAura(e.action.cast.spell)) { if (e.action.cast.castFlags & SMARTCAST_INTERRUPT_PREVIOUS) { tempLastInvoker->ToUnit()->InterruptNonMeleeSpells(false); } TriggerCastFlags triggerFlags = TRIGGERED_NONE; if (e.action.cast.castFlags & SMARTCAST_TRIGGERED) { if (e.action.cast.triggerFlags) { triggerFlags = TriggerCastFlags(e.action.cast.triggerFlags); } else { triggerFlags = TRIGGERED_FULL_MASK; } } tempLastInvoker->ToUnit()->CastSpell(target->ToUnit(), e.action.cast.spell, triggerFlags); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_INVOKER_CAST: Invoker {} casts spell {} on target {} with castflags {}", tempLastInvoker->GetGUID().ToString(), e.action.cast.spell, target->GetGUID().ToString(), e.action.cast.castFlags); } else { LOG_DEBUG("scripts.ai", "Spell {} not cast because it has flag SMARTCAST_AURA_NOT_PRESENT and the target {} already has the aura", e.action.cast.spell, target->GetGUID().ToString()); } } break; } case SMART_ACTION_ADD_AURA: { for (WorldObject* target : targets) { if (IsUnit(target)) { target->ToUnit()->AddAura(e.action.cast.spell, target->ToUnit()); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_ADD_AURA: Adding aura {} to unit {}", e.action.cast.spell, target->GetGUID().ToString()); } } break; } case SMART_ACTION_ACTIVATE_GOBJECT: { for (WorldObject* target : targets) { if (IsGameObject(target)) { GameObject* go = target->ToGameObject(); // Activate if (go->GetGoType() != GAMEOBJECT_TYPE_DOOR) { go->SetLootState(GO_READY); } go->UseDoorOrButton(0, e.action.activateObject.alternative, unit); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_ACTIVATE_GOBJECT. Gameobject {} activated", go->GetGUID().ToString()); } } break; } case SMART_ACTION_RESET_GOBJECT: { for (WorldObject* target : targets) { if (IsGameObject(target)) { target->ToGameObject()->ResetDoorOrButton(); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_RESET_GOBJECT. Gameobject {} (entry: {}) reset", target->GetGUID().ToString(), target->GetEntry()); } } break; } case SMART_ACTION_SET_EMOTE_STATE: { for (WorldObject* target : targets) { if (IsUnit(target)) { target->ToUnit()->SetUInt32Value(UNIT_NPC_EMOTESTATE, e.action.emote.emote); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SET_EMOTE_STATE. Unit {} set emotestate to {}", target->GetGUID().ToString(), e.action.emote.emote); } } break; } case SMART_ACTION_SET_UNIT_FLAG: { for (WorldObject* target : targets) { if (IsUnit(target)) { if (!e.action.unitFlag.type) { target->ToUnit()->SetFlag(UNIT_FIELD_FLAGS, e.action.unitFlag.flag); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SET_UNIT_FLAG. Unit {} added flag {} to UNIT_FIELD_FLAGS", target->GetGUID().ToString(), e.action.unitFlag.flag); } else { target->ToUnit()->SetFlag(UNIT_FIELD_FLAGS_2, e.action.unitFlag.flag); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SET_UNIT_FLAG. Unit {} added flag {} to UNIT_FIELD_FLAGS_2", target->GetGUID().ToString(), e.action.unitFlag.flag); } } } break; } case SMART_ACTION_REMOVE_UNIT_FLAG: { for (WorldObject* target : targets) { if (IsUnit(target)) { if (!e.action.unitFlag.type) { target->ToUnit()->RemoveFlag(UNIT_FIELD_FLAGS, e.action.unitFlag.flag); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_REMOVE_UNIT_FLAG. Unit {} removed flag {} to UNIT_FIELD_FLAGS", target->GetGUID().ToString(), e.action.unitFlag.flag); } else { target->ToUnit()->RemoveFlag(UNIT_FIELD_FLAGS_2, e.action.unitFlag.flag); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_REMOVE_UNIT_FLAG. Unit {} removed flag {} to UNIT_FIELD_FLAGS_2", target->GetGUID().ToString(), e.action.unitFlag.flag); } } } break; } case SMART_ACTION_AUTO_ATTACK: { if (!IsSmart()) break; CAST_AI(SmartAI, me->AI())->SetAutoAttack(e.action.autoAttack.attack); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_AUTO_ATTACK: Creature: {} bool on = {}", me->GetGUID().ToString(), e.action.autoAttack.attack); break; } case SMART_ACTION_ALLOW_COMBAT_MOVEMENT: { if (!IsSmart()) break; bool move = e.action.combatMove.move; CAST_AI(SmartAI, me->AI())->SetCombatMovement(move, true); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_ALLOW_COMBAT_MOVEMENT: Creature {} bool on = {}", me->GetGUID().ToString(), e.action.combatMove.move); break; } case SMART_ACTION_SET_EVENT_PHASE: { if (!GetBaseObject()) break; SetPhase(e.action.setEventPhase.phase); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_SET_EVENT_PHASE: Creature {} set event phase {}", GetBaseObject()->GetGUID().ToString(), e.action.setEventPhase.phase); break; } case SMART_ACTION_INC_EVENT_PHASE: { if (!GetBaseObject()) break; IncPhase(e.action.incEventPhase.inc); DecPhase(e.action.incEventPhase.dec); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_INC_EVENT_PHASE: Creature {} inc event phase by {}, " "decrease by {}", GetBaseObject()->GetGUID().ToString(), e.action.incEventPhase.inc, e.action.incEventPhase.dec); break; } case SMART_ACTION_EVADE: { if (!GetBaseObject()) break; for (WorldObject* target : targets) if (IsCreature(target)) if (target->ToCreature()->IsAIEnabled) target->ToCreature()->AI()->EnterEvadeMode(); break; } case SMART_ACTION_FLEE_FOR_ASSIST: { // Xinef: do not allow to flee without control (stun, fear etc) if (!me || me->HasUnitState(UNIT_STATE_LOST_CONTROL) || me->GetSpeed(MOVE_RUN) < 0.1f) break; me->DoFleeToGetAssistance(); if (e.action.flee.withEmote) { Acore::BroadcastTextBuilder builder(me, CHAT_MSG_MONSTER_EMOTE, BROADCAST_TEXT_FLEE_FOR_ASSIST, me->getGender()); sCreatureTextMgr->SendChatPacket(me, builder, CHAT_MSG_MONSTER_EMOTE); } LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_FLEE_FOR_ASSIST: Creature {} DoFleeToGetAssistance", me->GetGUID().ToString()); break; } case SMART_ACTION_COMBAT_STOP: { if (!me) break; me->CombatStop(true); break; } case SMART_ACTION_CALL_GROUPEVENTHAPPENS: { for (WorldObject* target : targets) { if (!IsUnit(target)) continue; Unit* unitTarget = target->ToUnit(); // If invoker was pet or charm Player* player = unitTarget->GetCharmerOrOwnerPlayerOrPlayerItself(); if (player && GetBaseObject()) { player->GroupEventHappens(e.action.quest.quest, GetBaseObject()); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_CALL_GROUPEVENTHAPPENS: Player {}, group credit for quest {}", player->GetGUID().ToString(), e.action.quest.quest); } // Special handling for vehicles if (Vehicle* vehicle = unitTarget->GetVehicleKit()) { for (auto& Seat : vehicle->Seats) { if (Player* player = ObjectAccessor::GetPlayer(*unitTarget, Seat.second.Passenger.Guid)) { player->GroupEventHappens(e.action.quest.quest, GetBaseObject()); } } } } break; } case SMART_ACTION_REMOVEAURASFROMSPELL: { for (WorldObject* target : targets) { if (!IsUnit(target)) continue; if (e.action.removeAura.spell) { if (e.action.removeAura.charges) { if (Aura* aur = target->ToUnit()->GetAura(e.action.removeAura.spell)) aur->ModCharges(-static_cast(e.action.removeAura.charges), AURA_REMOVE_BY_EXPIRE); } else target->ToUnit()->RemoveAurasDueToSpell(e.action.removeAura.spell); } else target->ToUnit()->RemoveAllAuras(); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_REMOVEAURASFROMSPELL: Unit {}, spell {}", target->GetGUID().ToString(), e.action.removeAura.spell); } break; } case SMART_ACTION_FOLLOW: { if (!IsSmart()) break; if (e.target.type == SMART_TARGET_NONE || e.target.type == SMART_TARGET_SELF) { CAST_AI(SmartAI, me->AI())->StopFollow(false); break; } for (WorldObject* target : targets) { if (IsUnit(target)) { float angle = e.action.follow.angle > 6 ? (e.action.follow.angle * M_PI / 180.0f) : e.action.follow.angle; CAST_AI(SmartAI, me->AI())->SetFollow(target->ToUnit(), float(e.action.follow.dist) + 0.1f, angle, e.action.follow.credit, e.action.follow.entry, e.action.follow.creditType, e.action.follow.aliveState); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_FOLLOW: Creature {} following target {}", me->GetGUID().ToString(), target->GetGUID().ToString()); break; } } break; } case SMART_ACTION_RANDOM_PHASE: { if (!GetBaseObject()) break; std::vector phases; std::copy_if(e.action.randomPhase.phases.begin(), e.action.randomPhase.phases.end(), std::back_inserter(phases), [](uint32 phase) { return phase != 0; }); uint32 phase = Acore::Containers::SelectRandomContainerElement(phases); SetPhase(phase); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_RANDOM_PHASE: Creature {} sets event phase to {}", GetBaseObject()->GetGUID().ToString(), phase); break; } case SMART_ACTION_RANDOM_PHASE_RANGE: { if (!GetBaseObject()) break; uint32 phase = urand(e.action.randomPhaseRange.phaseMin, e.action.randomPhaseRange.phaseMax); SetPhase(phase); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction: SMART_ACTION_RANDOM_PHASE_RANGE: Creature {} sets event phase to {}", GetBaseObject()->GetGUID().ToString(), phase); break; } case SMART_ACTION_CALL_KILLEDMONSTER: { if (trigger && IsPlayer(unit)) { unit->ToPlayer()->RewardPlayerAndGroupAtEvent(e.action.killedMonster.creature, unit); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction: SMART_ACTION_CALL_KILLEDMONSTER: (trigger == true) Player {}, Killcredit: {}", unit->GetGUID().ToString(), e.action.killedMonster.creature); } else if (e.target.type == SMART_TARGET_NONE || e.target.type == SMART_TARGET_SELF) // Loot recipient and his group members { if (!me) break; if (Player* player = me->GetLootRecipient()) { player->RewardPlayerAndGroupAtEvent(e.action.killedMonster.creature, player); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction: SMART_ACTION_CALL_KILLEDMONSTER: Player {}, Killcredit: {}", player->GetGUID().ToString(), e.action.killedMonster.creature); } } else // Specific target type { for (WorldObject* target : targets) { if (!IsUnit(target)) continue; Player* player = target->ToUnit()->GetCharmerOrOwnerPlayerOrPlayerItself(); if (!player) continue; player->RewardPlayerAndGroupAtEvent(e.action.killedMonster.creature, player); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction: SMART_ACTION_CALL_KILLEDMONSTER: Player {}, Killcredit: {}", target->GetGUID().ToString(), e.action.killedMonster.creature); } } break; } case SMART_ACTION_SET_INST_DATA: { WorldObject* obj = GetBaseObject(); if (!obj) obj = unit; if (!obj) break; InstanceScript* instance = obj->GetInstanceScript(); if (!instance) { LOG_ERROR("scripts.ai.sai", "SmartScript: Event {} attempt to set instance data without instance script. EntryOrGuid {}", e.GetEventType(), e.entryOrGuid); break; } switch (e.action.setInstanceData.type) { case 0: { instance->SetData(e.action.setInstanceData.field, e.action.setInstanceData.data); LOG_DEBUG("scripts.ai.sai", "SmartScript::ProcessAction: SMART_ACTION_SET_INST_DATA: Field: {}, data: {}", e.action.setInstanceData.field, e.action.setInstanceData.data); } break; case 1: { instance->SetBossState(e.action.setInstanceData.field, static_cast(e.action.setInstanceData.data)); LOG_DEBUG("scripts.ai.sai", "SmartScript::ProcessAction: SMART_ACTION_SET_INST_DATA: SetBossState BossId: {}, State: {} ({})", e.action.setInstanceData.field, e.action.setInstanceData.data, InstanceScript::GetBossStateName(e.action.setInstanceData.data)); } break; default: { break; } } break; } case SMART_ACTION_SET_INST_DATA64: { WorldObject* obj = GetBaseObject(); if (!obj) obj = unit; if (!obj) break; InstanceScript* instance = obj->GetInstanceScript(); if (!instance) { LOG_ERROR("sql.sql", "SmartScript: Event {} attempt to set instance data without instance script. EntryOrGuid {}", e.GetEventType(), e.entryOrGuid); break; } if (targets.empty()) break; instance->SetGuidData(e.action.setInstanceData64.field, targets.front()->GetGUID()); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_SET_INST_DATA64: Field: {}, data: {}", e.action.setInstanceData64.field, targets.front()->GetGUID().ToString()); break; } case SMART_ACTION_UPDATE_TEMPLATE: { for (WorldObject* target : targets) if (IsCreature(target)) target->ToCreature()->UpdateEntry(e.action.updateTemplate.creature, target->ToCreature()->GetCreatureData(), e.action.updateTemplate.updateLevel != 0); break; } case SMART_ACTION_DIE: { if (e.action.die.milliseconds) { if (me && !me->isDead()) { me->m_Events.AddEventAtOffset([&] { // We need to check again to see if we didn't die in the process. if (me && !me->isDead()) { me->KillSelf(); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction: SMART_ACTION_DIE: Creature {}", me->GetGUID().ToString()); } }, Milliseconds(e.action.die.milliseconds)); } } else if (me && !me->isDead()) { me->KillSelf(); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction: SMART_ACTION_DIE: Creature {}", me->GetGUID().ToString()); } break; } case SMART_ACTION_SET_IN_COMBAT_WITH_ZONE: { if (targets.empty()) break; if (!me->GetMap()->IsDungeon()) { ObjectVector units; GetWorldObjectsInDist(units, static_cast(e.target.unitRange.maxDist)); if (!units.empty() && GetBaseObject()) for (WorldObject* unit : units) if (IsPlayer(unit) && !unit->ToPlayer()->isDead()) { me->SetInCombatWith(unit->ToPlayer()); unit->ToPlayer()->SetInCombatWith(me); me->AddThreat(unit->ToPlayer(), 0.0f); } } else { for (WorldObject* target : targets) { if (IsCreature(target)) { target->ToCreature()->SetInCombatWithZone(); LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_SET_IN_COMBAT_WITH_ZONE: Creature {}, target: {}", me->GetGUID().ToString(), target->GetGUID().ToString()); } } } break; } case SMART_ACTION_CALL_FOR_HELP: { for (WorldObject* target : targets) { if (IsCreature(target)) { target->ToCreature()->CallForHelp(float(e.action.callHelp.range)); if (e.action.callHelp.withEmote) { Acore::BroadcastTextBuilder builder(target, CHAT_MSG_MONSTER_EMOTE, BROADCAST_TEXT_CALL_FOR_HELP, LANG_UNIVERSAL, nullptr); sCreatureTextMgr->SendChatPacket(target, builder, CHAT_MSG_MONSTER_EMOTE); } LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_CALL_FOR_HELP: Creature {}, target: {}", me->GetGUID().ToString(), target->GetGUID().ToString()); } } break; } case SMART_ACTION_SET_SHEATH: { if (me) { me->SetSheath(SheathState(e.action.setSheath.sheath)); LOG_DEBUG("sql.sql", "SmartScript::ProcessAction: SMART_ACTION_SET_SHEATH: Creature {}, State: {}", me->GetGUID().ToString(), e.action.setSheath.sheath); } break; } case SMART_ACTION_FORCE_DESPAWN: { for (WorldObject* target : targets) { if (e.action.forceDespawn.removeObjectFromWorld) { if (e.action.forceDespawn.delay || e.action.forceDespawn.forceRespawnTimer) LOG_ERROR("sql.sql", "SmartScript: SMART_ACTION_FORCE_DESPAWN has removeObjectFromWorld set. delay and forceRespawnTimer ignored."); if (Creature* creature = target->ToCreature()) creature->AddObjectToRemoveList(); else if (GameObject* go = target->ToGameObject()) go->AddObjectToRemoveList(); } else { Milliseconds despawnDelay(e.action.forceDespawn.delay); // Wait at least one world update tick before despawn, so it doesn't break linked actions. if (despawnDelay <= 0ms) despawnDelay = 1ms; Seconds forceRespawnTimer(e.action.forceDespawn.forceRespawnTimer); if (Creature* creature = target->ToCreature()) creature->DespawnOrUnsummon(despawnDelay, forceRespawnTimer); else if (GameObject* go = target->ToGameObject()) go->DespawnOrUnsummon(despawnDelay, forceRespawnTimer); } } break; } case SMART_ACTION_SET_INGAME_PHASE_MASK: { for (WorldObject* target : targets) { if (IsUnit(target)) target->ToUnit()->SetPhaseMask(e.action.ingamePhaseMask.mask, true); else if (IsGameObject(target)) target->ToGameObject()->SetPhaseMask(e.action.ingamePhaseMask.mask, true); } break; } case SMART_ACTION_MOUNT_TO_ENTRY_OR_MODEL: { for (WorldObject* target : targets) { if (!IsUnit(target)) continue; if (e.action.morphOrMount.creature || e.action.morphOrMount.model) { if (e.action.morphOrMount.creature > 0) { if (CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(e.action.morphOrMount.creature)) target->ToUnit()->Mount(ObjectMgr::ChooseDisplayId(cInfo)->CreatureDisplayID); } else target->ToUnit()->Mount(e.action.morphOrMount.model); } else target->ToUnit()->Dismount(); } break; } case SMART_ACTION_SET_INVINCIBILITY_HP_LEVEL: { for (WorldObject* target : targets) { if (IsCreature(target)) { SmartAI* ai = CAST_AI(SmartAI, target->ToCreature()->AI()); if (!ai) continue; if (e.action.invincHP.percent) ai->SetInvincibilityHpLevel(target->ToCreature()->CountPctFromMaxHealth(e.action.invincHP.percent)); else ai->SetInvincibilityHpLevel(e.action.invincHP.minHP); } } break; } case SMART_ACTION_SET_DATA: { for (WorldObject* target : targets) { if (Creature* cTarget = target->ToCreature()) { CreatureAI* ai = cTarget->AI(); // Make sure we check that the sender is either a creature or gameobject if (IsSmart(cTarget, true) && (me || go)) { if (me) ENSURE_AI(SmartAI, ai)->SetData(e.action.setData.field, e.action.setData.data, me); else ENSURE_AI(SmartAI, ai)->SetData(e.action.setData.field, e.action.setData.data, go); } else ai->SetData(e.action.setData.field, e.action.setData.data); } else if (GameObject* oTarget = target->ToGameObject()) { GameObjectAI* ai = oTarget->AI(); // Make sure we check that the sender is either a creature or gameobject if (IsSmart(oTarget, true) && (me || go)) { if (me) ENSURE_AI(SmartGameObjectAI, ai)->SetData(e.action.setData.field, e.action.setData.data, me); else ENSURE_AI(SmartGameObjectAI, ai)->SetData(e.action.setData.field, e.action.setData.data, go); } else ai->SetData(e.action.setData.field, e.action.setData.data); } } break; } case SMART_ACTION_MOVE_FORWARD: { if (!me) break; float x, y, z; me->GetClosePoint(x, y, z, me->GetObjectSize() / 3, (float)e.action.moveRandom.distance); me->GetMotionMaster()->MovePoint(SMART_RANDOM_POINT, x, y, z); break; } case SMART_ACTION_RISE_UP: { if (!me) break; me->GetMotionMaster()->MovePoint(SMART_RANDOM_POINT, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ() + (float)e.action.moveRandom.distance); break; } case SMART_ACTION_SET_VISIBILITY: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->SetVisible(e.action.visibility.state); break; } case SMART_ACTION_SET_ACTIVE: { for (WorldObject* target : targets) target->setActive(e.action.setActive.state); break; } case SMART_ACTION_ATTACK_START: { if (!me) break; if (targets.empty()) break; // attack random target if (Unit* target = Acore::Containers::SelectRandomContainerElement(targets)->ToUnit()) me->AI()->AttackStart(target); break; } case SMART_ACTION_ATTACK_STOP: { for (WorldObject* target : targets) if (Unit* unitTarget = target->ToUnit()) unitTarget->AttackStop(); break; } case SMART_ACTION_SUMMON_CREATURE: { EnumFlag flags(static_cast(e.action.summonCreature.flags)); bool preferUnit = flags.HasFlag(SmartActionSummonCreatureFlags::PreferUnit); WorldObject* summoner = preferUnit ? unit : Coalesce(GetBaseObject(), unit); if (!summoner) break; bool personalSpawn = flags.HasFlag(SmartActionSummonCreatureFlags::PersonalSpawn); if (e.GetTargetType() == SMART_TARGET_RANDOM_POINT) { float range = (float)e.target.randomPoint.range; Position randomPoint; Position srcPos = { e.target.x, e.target.y, e.target.z, e.target.o }; for (uint32 i = 0; i < e.target.randomPoint.amount; i++) { if (e.target.randomPoint.self > 0) randomPoint = me->GetRandomPoint(me->GetPosition(), range); else randomPoint = me->GetRandomPoint(srcPos, range); if (Creature* summon = summoner->SummonCreature(e.action.summonCreature.creature, randomPoint, (TempSummonType)e.action.summonCreature.type, e.action.summonCreature.duration, 0, nullptr, personalSpawn)) { if (unit && e.action.summonCreature.attackInvoker) summon->AI()->AttackStart(unit); else if (me && e.action.summonCreature.attackScriptOwner) summon->AI()->AttackStart(me); } } break; } float x, y, z, o; for (WorldObject* target : targets) { target->GetPosition(x, y, z, o); x += e.target.x; y += e.target.y; z += e.target.z; o += e.target.o; if (Creature* summon = summoner->SummonCreature(e.action.summonCreature.creature, x, y, z, o, (TempSummonType)e.action.summonCreature.type, e.action.summonCreature.duration, nullptr, personalSpawn)) { if (e.action.summonCreature.attackInvoker == 2) // pussywizard: proper attackInvoker implementation summon->AI()->AttackStart(unit); else if (e.action.summonCreature.attackInvoker) summon->AI()->AttackStart(target->ToUnit()); else if (me && e.action.summonCreature.attackScriptOwner) summon->AI()->AttackStart(me); } } if (e.GetTargetType() != SMART_TARGET_POSITION) break; if (Creature* summon = summoner->SummonCreature(e.action.summonCreature.creature, e.target.x, e.target.y, e.target.z, e.target.o, (TempSummonType)e.action.summonCreature.type, e.action.summonCreature.duration)) { if (unit && e.action.summonCreature.attackInvoker) summon->AI()->AttackStart(unit); else if (me && e.action.summonCreature.attackScriptOwner) summon->AI()->AttackStart(me); } break; } case SMART_ACTION_SUMMON_GO: { if (!GetBaseObject()) break; if (!targets.empty()) { float x, y, z, o; for (WorldObject* target : targets) { // xinef: allow gameobjects to summon gameobjects! //if (!IsUnit((*itr))) // continue; target->GetPosition(x, y, z, o); x += e.target.x; y += e.target.y; z += e.target.z; o += e.target.o; if (!e.action.summonGO.targetsummon) GetBaseObject()->SummonGameObject(e.action.summonGO.entry, x, y, z, o, 0, 0, 0, 0, e.action.summonGO.despawnTime); else target->SummonGameObject(e.action.summonGO.entry, GetBaseObject()->GetPositionX(), GetBaseObject()->GetPositionY(), GetBaseObject()->GetPositionZ(), GetBaseObject()->GetOrientation(), 0, 0, 0, 0, e.action.summonGO.despawnTime); } } if (e.GetTargetType() != SMART_TARGET_POSITION) break; GetBaseObject()->SummonGameObject(e.action.summonGO.entry, e.target.x, e.target.y, e.target.z, e.target.o, 0, 0, 0, 0, e.action.summonGO.despawnTime); break; } case SMART_ACTION_KILL_UNIT: { for (WorldObject* target : targets) { if (!IsUnit(target)) continue; Unit::Kill(target->ToUnit(), target->ToUnit()); } break; } case SMART_ACTION_INSTALL_AI_TEMPLATE: { InstallTemplate(e); break; } case SMART_ACTION_ADD_ITEM: { for (WorldObject* target : targets) { if (!IsPlayer(target)) continue; target->ToPlayer()->AddItem(e.action.item.entry, e.action.item.count); } break; } case SMART_ACTION_REMOVE_ITEM: { for (WorldObject* target : targets) { if (!IsPlayer(target)) continue; target->ToPlayer()->DestroyItemCount(e.action.item.entry, e.action.item.count, true); } break; } case SMART_ACTION_STORE_TARGET_LIST: { StoreTargetList(targets, e.action.storeTargets.id); break; } case SMART_ACTION_TELEPORT: { for (WorldObject* target : targets) { if (IsPlayer(target)) target->ToPlayer()->TeleportTo(e.action.teleport.mapID, e.target.x, e.target.y, e.target.z, e.target.o); else if (IsCreature(target)) target->ToCreature()->NearTeleportTo(e.target.x, e.target.y, e.target.z, e.target.o); } break; } case SMART_ACTION_SET_FLY: { if (!IsSmart()) break; CAST_AI(SmartAI, me->AI())->SetFly(e.action.setFly.fly); // Xinef: Set speed if any if (e.action.setFly.speed) me->SetSpeed(MOVE_RUN, float(e.action.setFly.speed / 100.0f), true); // Xinef: this wil be executed only if state is different me->SetDisableGravity(e.action.setFly.disableGravity); break; } case SMART_ACTION_SET_RUN: { for (WorldObject* target : targets) { if (IsCreature(target)) { target->ToCreature()->SetWalk(e.action.setRun.run ? false : true); } } break; } case SMART_ACTION_SET_SWIM: { if (!IsSmart()) break; CAST_AI(SmartAI, me->AI())->SetSwim(e.action.setSwim.swim); break; } case SMART_ACTION_SET_COUNTER: { if (!targets.empty()) { for (WorldObject* target : targets) { if (IsCreature(target)) { if (SmartAI* ai = CAST_AI(SmartAI, target->ToCreature()->AI())) ai->GetScript()->StoreCounter(e.action.setCounter.counterId, e.action.setCounter.value, e.action.setCounter.reset, e.action.setCounter.subtract); else LOG_ERROR("sql.sql", "SmartScript: Action target for SMART_ACTION_SET_COUNTER is not using SmartAI, skipping"); } else if (IsGameObject(target)) { if (SmartGameObjectAI* ai = CAST_AI(SmartGameObjectAI, target->ToGameObject()->AI())) ai->GetScript()->StoreCounter(e.action.setCounter.counterId, e.action.setCounter.value, e.action.setCounter.reset, e.action.setCounter.subtract); else LOG_ERROR("sql.sql", "SmartScript: Action target for SMART_ACTION_SET_COUNTER is not using SmartGameObjectAI, skipping"); } } } else StoreCounter(e.action.setCounter.counterId, e.action.setCounter.value, e.action.setCounter.reset, e.action.setCounter.subtract); break; } case SMART_ACTION_ESCORT_START: { if (!IsSmart()) break; ForcedMovement forcedMovement = static_cast(e.action.wpStart.forcedMovement); uint32 entry = e.action.wpStart.pathID; bool repeat = e.action.wpStart.repeat != 0; for (WorldObject* target : targets) { if (IsPlayer(target)) { StoreTargetList(targets, SMART_ESCORT_TARGETS); break; } } me->SetReactState((ReactStates)e.action.wpStart.reactState); CAST_AI(SmartAI, me->AI())->StartPath(forcedMovement, entry, repeat, unit); uint32 quest = e.action.wpStart.quest; uint32 DespawnTime = e.action.wpStart.despawnTime; CAST_AI(SmartAI, me->AI())->mEscortQuestID = quest; CAST_AI(SmartAI, me->AI())->SetDespawnTime(DespawnTime); break; } case SMART_ACTION_ESCORT_PAUSE: { if (!IsSmart()) break; uint32 delay = e.action.wpPause.delay; CAST_AI(SmartAI, me->AI())->PausePath(delay, e.GetEventType() == SMART_EVENT_ESCORT_REACHED ? false : true); break; } case SMART_ACTION_ESCORT_STOP: { if (!IsSmart()) break; uint32 DespawnTime = e.action.wpStop.despawnTime; uint32 quest = e.action.wpStop.quest; bool fail = e.action.wpStop.fail; CAST_AI(SmartAI, me->AI())->StopPath(DespawnTime, quest, fail); break; } case SMART_ACTION_ESCORT_RESUME: { if (!IsSmart()) break; CAST_AI(SmartAI, me->AI())->SetWPPauseTimer(0); break; } case SMART_ACTION_SET_ORIENTATION: { if (!me) break; if (e.action.orientation.random > 0) { float randomOri = frand(0.0f, 2 * M_PI); me->SetFacingTo(randomOri); if (e.action.orientation.quickChange) me->SetOrientation(randomOri); break; } if (e.action.orientation.turnAngle) { float turnOri = me->GetOrientation() + (static_cast(e.action.orientation.turnAngle) * M_PI / 180.0f); me->SetFacingTo(turnOri); if (e.action.orientation.quickChange) me->SetOrientation(turnOri); break; } if (e.GetTargetType() == SMART_TARGET_SELF) { me->SetFacingTo((me->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && me->GetTransGUID() ? me->GetTransportHomePosition() : me->GetHomePosition()).GetOrientation()); if (e.action.orientation.quickChange) me->SetOrientation((me->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && me->GetTransGUID() ? me->GetTransportHomePosition() : me->GetHomePosition()).GetOrientation()); } else if (e.GetTargetType() == SMART_TARGET_POSITION) { me->SetFacingTo(e.target.o); if (e.action.orientation.quickChange) me->SetOrientation(e.target.o); } else if (!targets.empty()) { me->SetFacingToObject(*targets.begin()); if (e.action.orientation.quickChange) me->SetInFront(*targets.begin()); } break; } case SMART_ACTION_PLAYMOVIE: { for (WorldObject* target : targets) { if (!IsPlayer(target)) continue; target->ToPlayer()->SendMovieStart(e.action.movie.entry); } break; } case SMART_ACTION_MOVE_TO_POS: { if (!IsSmart()) break; WorldObject* target = nullptr; SAIBool isForced = !e.action.moveToPos.disableForceDestination; switch (e.GetTargetType()) { case SMART_TARGET_POSITION: { G3D::Vector3 dest(e.target.x, e.target.y, e.target.z); if (e.action.moveToPos.transport) if (TransportBase* trans = me->GetDirectTransport()) trans->CalculatePassengerPosition(dest.x, dest.y, dest.z); me->GetMotionMaster()->MovePoint(e.action.moveToPos.pointId, dest.x, dest.y, dest.z, FORCED_MOVEMENT_NONE, 0.f, e.target.o, true, isForced, isControlled ? MOTION_SLOT_CONTROLLED : MOTION_SLOT_ACTIVE); break; } case SMART_TARGET_RANDOM_POINT: { if (me) { float range = (float)e.target.randomPoint.range; Position srcPos = { e.target.x, e.target.y, e.target.z, e.target.o }; Position randomPoint = me->GetRandomPoint(srcPos, range); me->GetMotionMaster()->MovePoint( e.action.moveToPos.pointId, randomPoint.m_positionX, randomPoint.m_positionY, randomPoint.m_positionZ, FORCED_MOVEMENT_NONE, 0.f, 0.f, true, isForced, isControlled ? MOTION_SLOT_CONTROLLED : MOTION_SLOT_ACTIVE ); } break; } // Can use target floats as offset default: { // we want to move to random element if (targets.empty()) return; else target = Acore::Containers::SelectRandomContainerElement(targets); float x, y, z; target->GetPosition(x, y, z); if (e.action.moveToPos.combatReach) target->GetNearPoint(me, x, y, z, target->GetCombatReach() + e.action.moveToPos.ContactDistance, 0, target->GetAngle(me)); else if (e.action.moveToPos.ContactDistance) target->GetNearPoint(me, x, y, z, e.action.moveToPos.ContactDistance, 0, target->GetAngle(me)); me->GetMotionMaster()->MovePoint(e.action.moveToPos.pointId, x + e.target.x, y + e.target.y, z + e.target.z, FORCED_MOVEMENT_NONE, 0.f, 0.f, true, isForced, isControlled ? MOTION_SLOT_CONTROLLED : MOTION_SLOT_ACTIVE); break; } } break; } case SMART_ACTION_MOVE_TO_POS_TARGET: { for (WorldObject* target : targets) { if (IsCreature(target)) { SAIBool isForced = !e.action.moveToPosTarget.disableForceDestination; Creature* ctarget = target->ToCreature(); ctarget->GetMotionMaster()->MovePoint(e.action.moveToPosTarget.pointId, e.target.x, e.target.y, e.target.z, FORCED_MOVEMENT_NONE, 0.f, 0.f, true, isForced, isControlled ? MOTION_SLOT_CONTROLLED : MOTION_SLOT_ACTIVE); } } break; } case SMART_ACTION_RESPAWN_TARGET: { for (WorldObject* target : targets) { if (IsCreature(target)) target->ToCreature()->Respawn(); else if (IsGameObject(target)) { // do not modify respawndelay of already spawned gameobjects if (target->ToGameObject()->isSpawnedByDefault()) target->ToGameObject()->Respawn(); else target->ToGameObject()->SetRespawnTime(e.action.RespawnTarget.goRespawnTime); } } break; } case SMART_ACTION_CLOSE_GOSSIP: { for (WorldObject* target : targets) if (IsPlayer(target)) target->ToPlayer()->PlayerTalkClass->SendCloseGossip(); break; } case SMART_ACTION_EQUIP: { for (WorldObject* target : targets) { if (Creature* npc = target->ToCreature()) { std::array slot; if (int8 equipId = static_cast(e.action.equip.entry)) { EquipmentInfo const* eInfo = sObjectMgr->GetEquipmentInfo(npc->GetEntry(), equipId); if (!eInfo) { LOG_ERROR("sql.sql", "SmartScript: SMART_ACTION_EQUIP uses non-existent equipment info id {} for creature {}", equipId, npc->GetEntry()); break; } npc->SetCurrentEquipmentId(equipId); std::copy(std::begin(eInfo->ItemEntry), std::end(eInfo->ItemEntry), std::begin(slot)); } else std::copy(std::begin(e.action.equip.slots), std::end(e.action.equip.slots), std::begin(slot)); for (uint32 i = 0; i < MAX_EQUIPMENT_ITEMS; ++i) if (!e.action.equip.mask || (e.action.equip.mask & (1 << i))) npc->SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + i, slot[i]); } } break; } case SMART_ACTION_CREATE_TIMED_EVENT: { SmartEvent ne = SmartEvent(); ne.type = (SMART_EVENT)SMART_EVENT_UPDATE; ne.event_chance = e.action.timeEvent.chance; if (!ne.event_chance) ne.event_chance = 100; ne.minMaxRepeat.min = e.action.timeEvent.min; ne.minMaxRepeat.max = e.action.timeEvent.max; ne.minMaxRepeat.repeatMin = e.action.timeEvent.repeatMin; ne.minMaxRepeat.repeatMax = e.action.timeEvent.repeatMax; ne.event_flags = 0; if (!ne.minMaxRepeat.repeatMin && !ne.minMaxRepeat.repeatMax) ne.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; SmartAction ac = SmartAction(); ac.type = (SMART_ACTION)SMART_ACTION_TRIGGER_TIMED_EVENT; ac.timeEvent.id = e.action.timeEvent.id; SmartScriptHolder ev = SmartScriptHolder(); ev.event = ne; ev.event_id = e.action.timeEvent.id; ev.target = e.target; ev.action = ac; InitTimer(ev); mStoredEvents.push_back(ev); break; } case SMART_ACTION_TRIGGER_TIMED_EVENT: ProcessEventsFor((SMART_EVENT)SMART_EVENT_TIMED_EVENT_TRIGGERED, nullptr, e.action.timeEvent.id); // xinef: remove this event if not repeatable if (e.event.event_flags & SMART_EVENT_FLAG_NOT_REPEATABLE) mRemIDs.push_back(e.action.timeEvent.id); break; case SMART_ACTION_REMOVE_TIMED_EVENT: mRemIDs.push_back(e.action.timeEvent.id); break; case SMART_ACTION_OVERRIDE_SCRIPT_BASE_OBJECT: { for (WorldObject* target : targets) { if (IsCreature(target)) { if (!meOrigGUID) meOrigGUID = me ? me->GetGUID() : ObjectGuid::Empty; if (!goOrigGUID) goOrigGUID = go ? go->GetGUID() : ObjectGuid::Empty; go = nullptr; me = target->ToCreature(); break; } else if (IsGameObject(target)) { if (!meOrigGUID) meOrigGUID = me ? me->GetGUID() : ObjectGuid::Empty; if (!goOrigGUID) goOrigGUID = go ? go->GetGUID() : ObjectGuid::Empty; go = target->ToGameObject(); me = nullptr; break; } } break; } case SMART_ACTION_RESET_SCRIPT_BASE_OBJECT: ResetBaseObject(); break; case SMART_ACTION_CALL_SCRIPT_RESET: OnReset(); break; case SMART_ACTION_SET_RANGED_MOVEMENT: { if (!IsSmart()) break; float attackDistance = float(e.action.setRangedMovement.distance); float attackAngle = float(e.action.setRangedMovement.angle) / 180.0f * float(M_PI); for (WorldObject* target : targets) if (Creature* creature = target->ToCreature()) if (IsSmart(creature) && creature->GetVictim()) if (!creature->HasUnitState(UNIT_STATE_NO_COMBAT_MOVEMENT)) creature->GetMotionMaster()->MoveChase(creature->GetVictim(), attackDistance, attackAngle); break; } case SMART_ACTION_CALL_TIMED_ACTIONLIST: { if (e.GetTargetType() == SMART_TARGET_NONE) { LOG_ERROR("sql.sql", "SmartScript: Entry {} SourceType {} Event {} Action {} is using TARGET_NONE(0) for Script9 target. Please correct target_type in database.", e.entryOrGuid, e.GetScriptType(), e.GetEventType(), e.GetActionType()); break; } for (WorldObject* target : targets) { if (Creature* creature = target->ToCreature()) { if (IsSmart(creature)) CAST_AI(SmartAI, creature->AI())->SetScript9(e, e.action.timedActionList.id, GetLastInvoker()); } else if (GameObject* go = target->ToGameObject()) { if (IsSmart(go)) CAST_AI(SmartGameObjectAI, go->AI())->SetScript9(e, e.action.timedActionList.id, GetLastInvoker()); } } break; } case SMART_ACTION_SET_NPC_FLAG: { for (WorldObject* target : targets) if (IsCreature(target)) target->ToUnit()->ReplaceAllNpcFlags(NPCFlags(e.action.unitFlag.flag)); break; } case SMART_ACTION_ADD_NPC_FLAG: { for (WorldObject* target : targets) if (IsCreature(target)) target->ToUnit()->SetNpcFlag(NPCFlags(e.action.unitFlag.flag)); break; } case SMART_ACTION_REMOVE_NPC_FLAG: { for (WorldObject* target : targets) if (IsCreature(target)) target->ToUnit()->RemoveNpcFlag(NPCFlags(e.action.unitFlag.flag)); break; } case SMART_ACTION_CROSS_CAST: { if (targets.empty()) break; ObjectVector casters; GetTargets(casters, CreateSmartEvent(SMART_EVENT_UPDATE_IC, 0, 0, 0, 0, 0, 0, 0, SMART_ACTION_NONE, 0, 0, 0, 0, 0, 0, (SMARTAI_TARGETS)e.action.crossCast.targetType, e.action.crossCast.targetParam1, e.action.crossCast.targetParam2, e.action.crossCast.targetParam3, 0, 0), unit); for (WorldObject* caster : casters) { if (!IsUnit(caster)) continue; Unit* casterUnit = caster->ToUnit(); bool interruptedSpell = false; for (WorldObject* target : targets) { if (!IsUnit(target)) continue; if (!(e.action.crossCast.flags & SMARTCAST_AURA_NOT_PRESENT) || !target->ToUnit()->HasAura(e.action.crossCast.spell)) { if (!interruptedSpell && e.action.crossCast.flags & SMARTCAST_INTERRUPT_PREVIOUS) { casterUnit->InterruptNonMeleeSpells(false); interruptedSpell = true; } casterUnit->CastSpell(target->ToUnit(), e.action.crossCast.spell, (e.action.crossCast.flags & SMARTCAST_TRIGGERED) != 0); } else LOG_DEBUG("scripts.ai", "Spell {} not cast because it has flag SMARTCAST_AURA_NOT_PRESENT and the target ({}) already has the aura", e.action.crossCast.spell, target->GetGUID().ToString()); } } break; } case SMART_ACTION_CALL_RANDOM_TIMED_ACTIONLIST: { std::vector actionLists; std::copy_if(e.action.randTimedActionList.actionLists.begin(), e.action.randTimedActionList.actionLists.end(), std::back_inserter(actionLists), [](uint32 actionList) { return actionList != 0; }); uint32 id = Acore::Containers::SelectRandomContainerElement(actionLists); if (e.GetTargetType() == SMART_TARGET_NONE) { LOG_ERROR("sql.sql", "SmartScript: Entry {} SourceType {} Event {} Action {} is using TARGET_NONE(0) for Script9 target. Please correct target_type in database.", e.entryOrGuid, e.GetScriptType(), e.GetEventType(), e.GetActionType()); break; } for (WorldObject* target : targets) { if (Creature* creature = target->ToCreature()) { if (IsSmart(creature)) CAST_AI(SmartAI, creature->AI())->SetScript9(e, id, GetLastInvoker()); } else if (GameObject* go = target->ToGameObject()) { if (IsSmart(go)) CAST_AI(SmartGameObjectAI, go->AI())->SetScript9(e, id, GetLastInvoker()); } } break; } case SMART_ACTION_CALL_RANDOM_RANGE_TIMED_ACTIONLIST: { uint32 id = urand(e.action.randTimedActionList.actionLists[0], e.action.randTimedActionList.actionLists[1]); if (e.GetTargetType() == SMART_TARGET_NONE) { LOG_ERROR("sql.sql", "SmartScript: Entry {} SourceType {} Event {} Action {} is using TARGET_NONE(0) for Script9 target. Please correct target_type in database.", e.entryOrGuid, e.GetScriptType(), e.GetEventType(), e.GetActionType()); break; } for (WorldObject* target : targets) { if (Creature* creature = target->ToCreature()) { if (IsSmart(creature)) CAST_AI(SmartAI, creature->AI())->SetScript9(e, id, GetLastInvoker()); } else if (GameObject* go = target->ToGameObject()) { if (IsSmart(go)) CAST_AI(SmartGameObjectAI, go->AI())->SetScript9(e, id, GetLastInvoker()); } } break; } case SMART_ACTION_ACTIVATE_TAXI: { for (WorldObject* target : targets) if (IsPlayer(target)) target->ToPlayer()->ActivateTaxiPathTo(e.action.taxi.id); break; } case SMART_ACTION_RANDOM_MOVE: { bool foundTarget = false; for (WorldObject* target : targets) { if (IsCreature((target))) { foundTarget = true; if (e.action.moveRandom.distance) target->ToCreature()->GetMotionMaster()->MoveRandom(float(e.action.moveRandom.distance)); else target->ToCreature()->GetMotionMaster()->MoveIdle(); } } if (!foundTarget && me && IsCreature(me) && me->IsAlive()) { if (e.action.moveRandom.distance) me->GetMotionMaster()->MoveRandom(float(e.action.moveRandom.distance)); else me->GetMotionMaster()->MoveIdle(); } break; } case SMART_ACTION_SET_UNIT_FIELD_BYTES_1: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->SetByteFlag(UNIT_FIELD_BYTES_1, e.action.setunitByte.type, e.action.setunitByte.byte1); break; } case SMART_ACTION_REMOVE_UNIT_FIELD_BYTES_1: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->RemoveByteFlag(UNIT_FIELD_BYTES_1, e.action.delunitByte.type, e.action.delunitByte.byte1); break; } case SMART_ACTION_INTERRUPT_SPELL: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->InterruptNonMeleeSpells(e.action.interruptSpellCasting.withDelayed != 0, e.action.interruptSpellCasting.spell_id, e.action.interruptSpellCasting.withInstant != 0); break; } case SMART_ACTION_SEND_GO_CUSTOM_ANIM: { for (WorldObject* target : targets) if (IsGameObject(target)) target->ToGameObject()->SendCustomAnim(e.action.sendGoCustomAnim.anim); break; } case SMART_ACTION_SET_DYNAMIC_FLAG: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->SetUInt32Value(UNIT_DYNAMIC_FLAGS, e.action.unitFlag.flag); break; } case SMART_ACTION_ADD_DYNAMIC_FLAG: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->SetFlag(UNIT_DYNAMIC_FLAGS, e.action.unitFlag.flag); break; } case SMART_ACTION_REMOVE_DYNAMIC_FLAG: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->RemoveFlag(UNIT_DYNAMIC_FLAGS, e.action.unitFlag.flag); break; } case SMART_ACTION_JUMP_TO_POS: { if (e.GetTargetType() == SMART_TARGET_RANDOM_POINT) { if (me) { float range = (float)e.target.randomPoint.range; Position srcPos = { e.target.x, e.target.y, e.target.z, e.target.o }; Position randomPoint = me->GetRandomPoint(srcPos, range); me->GetMotionMaster()->MoveJump(randomPoint, (float)e.action.jump.speedxy, (float)e.action.jump.speedz); } break; } if (targets.empty()) break; // xinef: my implementation if (e.action.jump.selfJump) { if (WorldObject* target = Acore::Containers::SelectRandomContainerElement(targets)) if (me) me->GetMotionMaster()->MoveJump(target->GetPositionX() + e.target.x, target->GetPositionY() + e.target.y, target->GetPositionZ() + e.target.z, (float)e.action.jump.speedxy, (float)e.action.jump.speedz); } else { for (WorldObject* target : targets) if (WorldObject* obj = (target)) { if (Creature* creature = obj->ToCreature()) creature->GetMotionMaster()->MoveJump(e.target.x, e.target.y, e.target.z, (float)e.action.jump.speedxy, (float)e.action.jump.speedz); } } break; } case SMART_ACTION_GO_SET_LOOT_STATE: { for (WorldObject* target : targets) if (IsGameObject(target)) target->ToGameObject()->SetLootState((LootState)e.action.setGoLootState.state); break; } case SMART_ACTION_GO_SET_GO_STATE: { for (WorldObject* target : targets) if (IsGameObject(target)) target->ToGameObject()->SetGoState((GOState)e.action.goState.state); break; } case SMART_ACTION_SEND_TARGET_TO_TARGET: { WorldObject* ref = GetBaseObject(); if (!ref) ref = unit; if (!ref) break; ObjectVector const* storedTargets = GetStoredTargetVector(e.action.sendTargetToTarget.id, *ref); if (!storedTargets) break; for (WorldObject* target : targets) { if (IsCreature(target)) { if (SmartAI* ai = CAST_AI(SmartAI, target->ToCreature()->AI())) ai->GetScript()->StoreTargetList(ObjectVector(*storedTargets), e.action.sendTargetToTarget.id); // store a copy of target list else LOG_ERROR("sql.sql", "SmartScript: Action target for SMART_ACTION_SEND_TARGET_TO_TARGET is not using SmartAI, skipping"); } else if (IsGameObject(target)) { if (SmartGameObjectAI* ai = CAST_AI(SmartGameObjectAI, target->ToGameObject()->AI())) ai->GetScript()->StoreTargetList(ObjectVector(*storedTargets), e.action.sendTargetToTarget.id); // store a copy of target list else LOG_ERROR("sql.sql", "SmartScript: Action target for SMART_ACTION_SEND_TARGET_TO_TARGET is not using SmartGameObjectAI, skipping"); } } break; } case SMART_ACTION_SEND_GOSSIP_MENU: { if (!GetBaseObject()) break; LOG_DEBUG("sql.sql", "SmartScript::ProcessAction:: SMART_ACTION_SEND_GOSSIP_MENU: gossipMenuId {}, gossipNpcTextId {}", e.action.sendGossipMenu.gossipMenuId, e.action.sendGossipMenu.gossipNpcTextId); for (WorldObject* target : targets) if (Player* player = target->ToPlayer()) { if (e.action.sendGossipMenu.gossipMenuId) player->PrepareGossipMenu(GetBaseObject(), e.action.sendGossipMenu.gossipMenuId, true); else ClearGossipMenuFor(player); SendGossipMenuFor(player, e.action.sendGossipMenu.gossipNpcTextId, GetBaseObject()->GetGUID()); } break; } case SMART_ACTION_SET_HOME_POS: { if (!targets.empty()) { float x, y, z, o; for (WorldObject* target : targets) if (IsCreature(target)) { if (e.action.setHomePos.spawnPos) { target->ToCreature()->GetRespawnPosition(x, y, z, &o); target->ToCreature()->SetHomePosition(x, y, z, o); } else target->ToCreature()->SetHomePosition(target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), target->GetOrientation()); } } else if (me && e.GetTargetType() == SMART_TARGET_POSITION) { if (e.action.setHomePos.spawnPos) { float x, y, z, o; me->GetRespawnPosition(x, y, z, &o); me->SetHomePosition(x, y, z, o); } else me->SetHomePosition(e.target.x, e.target.y, e.target.z, e.target.o); } break; } case SMART_ACTION_SET_HEALTH_REGEN: { for (WorldObject* target : targets) if (IsCreature(target)) target->ToCreature()->SetRegeneratingHealth(e.action.setHealthRegen.regenHealth); break; } case SMART_ACTION_SET_ROOT: { for (WorldObject* target : targets) if (IsCreature(target)) target->ToCreature()->SetControlled(e.action.setRoot.root != 0, UNIT_STATE_ROOT); break; } case SMART_ACTION_SET_GO_FLAG: { for (WorldObject* target : targets) if (IsGameObject(target)) target->ToGameObject()->SetUInt32Value(GAMEOBJECT_FLAGS, e.action.goFlag.flag); break; } case SMART_ACTION_ADD_GO_FLAG: { for (WorldObject* target : targets) if (IsGameObject(target)) target->ToGameObject()->SetFlag(GAMEOBJECT_FLAGS, e.action.goFlag.flag); break; } case SMART_ACTION_REMOVE_GO_FLAG: { for (WorldObject* target : targets) if (IsGameObject(target)) target->ToGameObject()->RemoveFlag(GAMEOBJECT_FLAGS, e.action.goFlag.flag); break; } case SMART_ACTION_SUMMON_CREATURE_GROUP: { std::list summonList; GetBaseObject()->SummonCreatureGroup(e.action.creatureGroup.group, &summonList); for (std::list::const_iterator itr = summonList.begin(); itr != summonList.end(); ++itr) { if (unit && e.action.creatureGroup.attackInvoker) (*itr)->AI()->AttackStart(unit); else if (me && e.action.creatureGroup.attackScriptOwner) (*itr)->AI()->AttackStart(me); } break; } case SMART_ACTION_SET_POWER: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->SetPower(Powers(e.action.power.powerType), e.action.power.newPower); break; } case SMART_ACTION_ADD_POWER: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->SetPower(Powers(e.action.power.powerType), target->ToUnit()->GetPower(Powers(e.action.power.powerType)) + e.action.power.newPower); break; } case SMART_ACTION_REMOVE_POWER: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->SetPower(Powers(e.action.power.powerType), target->ToUnit()->GetPower(Powers(e.action.power.powerType)) - e.action.power.newPower); break; } case SMART_ACTION_GAME_EVENT_STOP: { uint32 eventId = e.action.gameEventStop.id; if (!sGameEventMgr->IsActiveEvent(eventId)) { LOG_ERROR("scripts.ai.sai", "SmartScript::ProcessAction: At case SMART_ACTION_GAME_EVENT_STOP, inactive event (id: {})", eventId); break; } sGameEventMgr->StopEvent(eventId, true); break; } case SMART_ACTION_GAME_EVENT_START: { uint32 eventId = e.action.gameEventStart.id; if (sGameEventMgr->IsActiveEvent(eventId)) { LOG_ERROR("scripts.ai.sai", "SmartScript::ProcessAction: At case SMART_ACTION_GAME_EVENT_START, already activated event (id: {})", eventId); break; } sGameEventMgr->StartEvent(eventId, true); break; } case SMART_ACTION_START_CLOSEST_WAYPOINT: { float distanceToClosest = std::numeric_limits::max(); uint32 closestWpId = 0; for (WorldObject* target : targets) { if (Creature* creature = target->ToCreature()) { if (IsSmart(creature)) { for (uint32 wp = e.action.startClosestWaypoint.pathId1; wp <= e.action.startClosestWaypoint.pathId2; ++wp) { WaypointPath* path = sSmartWaypointMgr->GetPath(wp); if (!path || path->empty()) continue; auto itrWp = path->find(1); if (itrWp != path->end()) { WaypointData& wpData = itrWp->second; float distToThisPath = creature->GetExactDistSq(wpData.x, wpData.y, wpData.z); if (distToThisPath < distanceToClosest) { distanceToClosest = distToThisPath; closestWpId = wp; } } } if (closestWpId) { bool repeat = e.action.startClosestWaypoint.repeat; ForcedMovement forcedMovement = static_cast(e.action.startClosestWaypoint.forcedMovement); CAST_AI(SmartAI, creature->AI())->StartPath(forcedMovement, closestWpId, repeat); } } } } break; } case SMART_ACTION_EXIT_VEHICLE: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->ExitVehicle(); break; } case SMART_ACTION_SET_UNIT_MOVEMENT_FLAGS: { for (WorldObject* target : targets) if (IsUnit(target)) { target->ToUnit()->SetUnitMovementFlags(e.action.movementFlag.flag); target->ToUnit()->SendMovementFlagUpdate(); } break; } case SMART_ACTION_SET_COMBAT_DISTANCE: { for (WorldObject* target : targets) if (IsCreature(target)) target->ToCreature()->m_CombatDistance = e.action.combatDistance.dist; break; } case SMART_ACTION_SET_SIGHT_DIST: { for (WorldObject* const target : targets) if (IsCreature(target)) target->ToCreature()->m_SightDistance = e.action.sightDistance.dist; break; } case SMART_ACTION_FLEE: { for (WorldObject* const target : targets) if (IsCreature(target)) target->ToCreature()->GetMotionMaster()->MoveFleeing(me, e.action.flee.withEmote); break; } case SMART_ACTION_ADD_THREAT: { for (WorldObject* const target : targets) if (IsUnit(target)) me->AddThreat(target->ToUnit(), float(e.action.threatPCT.threatINC) - float(e.action.threatPCT.threatDEC)); break; } case SMART_ACTION_LOAD_EQUIPMENT: { for (WorldObject* const target : targets) if (IsCreature(target)) target->ToCreature()->LoadEquipment(e.action.loadEquipment.id, e.action.loadEquipment.force != 0); break; } case SMART_ACTION_TRIGGER_RANDOM_TIMED_EVENT: { uint32 eventId = urand(e.action.randomTimedEvent.minId, e.action.randomTimedEvent.maxId); ProcessEventsFor((SMART_EVENT)SMART_EVENT_TIMED_EVENT_TRIGGERED, nullptr, eventId); break; } case SMART_ACTION_DISMOUNT: { for (WorldObject* const target : targets) if (IsUnit(target)) target->ToUnit()->Dismount(); break; } case SMART_ACTION_SET_HOVER: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->SetHover(e.action.setHover.state); break; } case SMART_ACTION_ADD_IMMUNITY: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->ApplySpellImmune(e.action.immunity.id, e.action.immunity.type, e.action.immunity.value, true); break; } case SMART_ACTION_REMOVE_IMMUNITY: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->ApplySpellImmune(e.action.immunity.id, e.action.immunity.type, e.action.immunity.value, false); break; } case SMART_ACTION_FALL: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->GetMotionMaster()->MoveFall(); break; } case SMART_ACTION_SET_EVENT_FLAG_RESET: { SetPhaseReset(e.action.setActive.state); break; } case SMART_ACTION_REMOVE_ALL_GAMEOBJECTS: { for (WorldObject* const target : targets) if (IsUnit(target)) target->ToUnit()->RemoveAllGameObjects(); break; } case SMART_ACTION_STOP_MOTION: { for (WorldObject* const target : targets) { if (IsUnit(target)) { if (e.action.stopMotion.stopMovement) target->ToUnit()->StopMoving(); if (e.action.stopMotion.movementExpired) target->ToUnit()->GetMotionMaster()->MovementExpired(); } } break; } case SMART_ACTION_NO_ENVIRONMENT_UPDATE: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->AddUnitState(UNIT_STATE_NO_ENVIRONMENT_UPD); break; } case SMART_ACTION_ZONE_UNDER_ATTACK: { for (WorldObject* target : targets) if (IsUnit(target)) if (Player* player = target->ToUnit()->GetCharmerOrOwnerPlayerOrPlayerItself()) { me->SendZoneUnderAttackMessage(player); break; } break; } case SMART_ACTION_LOAD_GRID: { if (me && me->FindMap()) me->FindMap()->LoadGrid(e.target.x, e.target.y); else if (go && go->FindMap()) go->FindMap()->LoadGrid(e.target.x, e.target.y); break; } case SMART_ACTION_PLAYER_TALK: { std::string text = sObjectMgr->GetAcoreString(e.action.playerTalk.textId, DEFAULT_LOCALE); if (!targets.empty()) for (WorldObject* target : targets) if (IsPlayer(target)) !e.action.playerTalk.flag ? target->ToPlayer()->Say(text, LANG_UNIVERSAL) : target->ToPlayer()->Yell(text, LANG_UNIVERSAL); break; } case SMART_ACTION_CUSTOM_CAST: { if (!me) break; for (WorldObject* target : targets) { if (IsUnit(target)) { if (e.action.castCustom.flags & SMARTCAST_INTERRUPT_PREVIOUS) { me->InterruptNonMeleeSpells(false); } if (!(e.action.castCustom.flags & SMARTCAST_AURA_NOT_PRESENT) || !target->ToUnit()->HasAura(e.action.castCustom.spell)) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(e.action.castCustom.spell); CustomSpellValues values; if (e.action.castCustom.bp1) values.AddSpellMod(SPELLVALUE_BASE_POINT0, e.action.castCustom.bp1); if (e.action.castCustom.bp2) values.AddSpellMod(SPELLVALUE_BASE_POINT1, e.action.castCustom.bp2); if (e.action.castCustom.bp3) values.AddSpellMod(SPELLVALUE_BASE_POINT2, e.action.castCustom.bp3); SpellCastResult result = me->CastCustomSpell(spellInfo, values, target->ToUnit(), (e.action.castCustom.flags & SMARTCAST_TRIGGERED) ? TRIGGERED_FULL_MASK : TRIGGERED_NONE); float spellMaxRange = me->GetSpellMaxRangeForTarget(target->ToUnit(), spellInfo); if (e.action.cast.castFlags & SMARTCAST_COMBAT_MOVE) { // If cast flag SMARTCAST_COMBAT_MOVE is set combat movement will not be allowed unless target is outside spell range, out of mana, or LOS. if (result == SPELL_FAILED_OUT_OF_RANGE || result == SPELL_CAST_OK) // if we are just out of range, we only chase until we are back in spell range. CAST_AI(SmartAI, me->AI())->SetCurrentRangeMode(true, std::max(spellMaxRange - NOMINAL_MELEE_RANGE, 0.0f)); else // move into melee on any other fail // if spell fail for any other reason, we chase to melee range, or stay where we are if spellcast was successful. CAST_AI(SmartAI, me->AI())->SetCurrentRangeMode(false, 0.f); } } } } break; } case SMART_ACTION_VORTEX_SUMMON: { if (!me) break; if (targets.empty()) break; TempSummonType summon_type = (e.action.summonVortex.summonDuration > 0) ? TEMPSUMMON_TIMED_DESPAWN : TEMPSUMMON_CORPSE_DESPAWN; float a = static_cast(e.action.summonVortex.a); float k = static_cast(e.action.summonVortex.k) / 1000.0f; float r_max = static_cast(e.action.summonVortex.r_max); float delta_phi = M_PI * static_cast(e.action.summonVortex.phi_delta) / 180.0f; // r(phi) = a * e ^ (k * phi) // r(phi + delta_phi) = a * e ^ (k * (phi + delta_phi)) // r(phi + delta_phi) = a * e ^ (k * phi) * e ^ (k * delta_phi) // r(phi + delta_phi) = r(phi) * e ^ (k * delta_phi) float factor = std::exp(k * delta_phi); // r(0) = a * e ^ (k * 0) = a * e ^ 0 = a * 1 = a float summonRadius = a; for (WorldObject* target : targets) { // Offset by orientation, should not count into radius calculation, // but is needed for vortex direction (polar coordinates) float phi = target->GetOrientation(); do { Position summonPosition(*target); summonPosition.RelocatePolarOffset(phi, summonRadius); me->SummonCreature(e.action.summonVortex.summonEntry, summonPosition, summon_type, e.action.summonVortex.summonDuration); phi += delta_phi; summonRadius *= factor; } while (summonRadius <= r_max); } break; } case SMART_ACTION_CONE_SUMMON: { if (!me) break; TempSummonType spawnType = (e.action.coneSummon.summonDuration > 0) ? TEMPSUMMON_TIMED_DESPAWN : TEMPSUMMON_CORPSE_DESPAWN; float distInARow = static_cast(e.action.coneSummon.distanceBetweenSummons); float coneAngle = static_cast(e.action.coneSummon.coneAngle) * M_PI / 180.0f; for (uint32 radius = 0; radius <= e.action.coneSummon.coneLength; radius += e.action.coneSummon.distanceBetweenRings) { float deltaAngle = 0.0f; if (radius > 0) deltaAngle = distInARow / radius; uint32 count = 1; if (deltaAngle > 0) count += coneAngle / deltaAngle; float currentAngle = -static_cast(count) * deltaAngle / 2.0f; if (e.GetTargetType() == SMART_TARGET_SELF || e.GetTargetType() == SMART_TARGET_NONE) currentAngle += G3D::fuzzyGt(e.target.o, 0.0f) ? (e.target.o - me->GetOrientation()) : 0.0f; else if (!targets.empty()) { currentAngle += (me->GetAngle(targets.front()) - me->GetOrientation()); } for (uint32 index = 0; index < count; ++index) { Position spawnPosition(*me); spawnPosition.RelocatePolarOffset(currentAngle, radius); currentAngle += deltaAngle; me->SummonCreature(e.action.coneSummon.summonEntry, spawnPosition, spawnType, e.action.coneSummon.summonDuration); } } break; } case SMART_ACTION_CU_ENCOUNTER_START: { for (WorldObject* target : targets) { if (Player* playerTarget = target->ToPlayer()) { playerTarget->RemoveArenaSpellCooldowns(); playerTarget->RemoveAurasDueToSpell(57724); // Spell Shaman Debuff - Sated (Heroism) playerTarget->RemoveAurasDueToSpell(57723); // Spell Shaman Debuff - Exhaustion (Bloodlust) playerTarget->RemoveAurasDueToSpell(2825); // Bloodlust playerTarget->RemoveAurasDueToSpell(32182); // Heroism } } break; } case SMART_ACTION_DO_ACTION: { int32 const actionId = e.action.doAction.isNegative ? -e.action.doAction.actionId : e.action.doAction.actionId; if (!e.action.doAction.instanceTarget) { if (targets.empty()) break; for (WorldObject* objTarget : targets) { if (Creature const* unitTarget = objTarget->ToCreature()) { if (unitTarget->IsAIEnabled) { unitTarget->AI()->DoAction(actionId); } } else if (GameObject const* gobjTarget = objTarget->ToGameObject()) { gobjTarget->AI()->DoAction(actionId); } } } else { InstanceScript* instanceScript = nullptr; if (WorldObject* baseObj = GetBaseObject()) { instanceScript = baseObj->GetInstanceScript(); } // Action is triggered by AreaTrigger else if (trigger && IsPlayer(unit)) { instanceScript = unit->GetInstanceScript(); } if (instanceScript) { instanceScript->DoAction(actionId); } } break; } case SMART_ACTION_DISABLE_EVADE: { if (!IsSmart()) break; CAST_AI(SmartAI, me->AI())->SetEvadeDisabled(e.action.disableEvade.disable != 0); break; } case SMART_ACTION_SET_CORPSE_DELAY: { for (WorldObject* const target : targets) { if (IsCreature(target)) target->ToCreature()->SetCorpseDelay(e.action.corpseDelay.timer); } break; } case SMART_ACTION_SET_HEALTH_PCT: { for (WorldObject* target : targets) if (Unit* targetUnit = target->ToUnit()) targetUnit->SetHealth(targetUnit->CountPctFromMaxHealth(e.action.setHealthPct.percent)); break; } case SMART_ACTION_SET_MOVEMENT_SPEED: { uint32 speedInteger = e.action.movementSpeed.speedInteger; uint32 speedFraction = e.action.movementSpeed.speedFraction; float speed = float(speedInteger) + float(speedFraction) / std::pow(10, std::floor(std::log10(float(speedFraction ? speedFraction : 1)) + 1)); for (WorldObject* target : targets) if (IsCreature(target)) target->ToCreature()->SetSpeed(UnitMoveType(e.action.movementSpeed.movementType), speed); break; } case SMART_ACTION_PLAY_CINEMATIC: { for (WorldObject* target : targets) { if (!IsPlayer(target)) continue; target->ToPlayer()->SendCinematicStart(e.action.cinematic.entry); } break; } case SMART_ACTION_SET_GUID: { for (WorldObject* target : targets) { ObjectGuid guidToSend = me ? me->GetGUID() : go->GetGUID(); if (e.action.setGuid.invokerGUID) { if (WorldObject* invoker = GetLastInvoker()) { guidToSend = invoker->GetGUID(); } } if (Creature* creature = target->ToCreature()) { creature->AI()->SetGUID(guidToSend, e.action.setGuid.index); } else if (GameObject* object = target->ToGameObject()) { object->AI()->SetGUID(guidToSend, e.action.setGuid.index); } } break; } case SMART_ACTION_SCRIPTED_SPAWN: { // Enable Scripted Spawns switch (e.action.scriptSpawn.state) { case 0: // Disable Respawn { for (WorldObject* target : targets) { if (Creature* c = target->ToCreature()) { CAST_AI(SmartAI, c->AI())->SetCanRespawn(false); if (!e.action.scriptSpawn.dontDespawn) c->DespawnOrUnsummon(); } } break; } case 1: // Respawn Once { for (WorldObject* target : targets) { if (Creature* c = target->ToCreature()) { CAST_AI(SmartAI, c->AI())->SetCanRespawn(true); c->Respawn(true); CAST_AI(SmartAI, c->AI())->SetCanRespawn(false); } } break; } case 2: // Enable Respawning { for (WorldObject* target : targets) { if (Creature* c = target->ToCreature()) { CAST_AI(SmartAI, c->AI())->SetCanRespawn(true); // If 0, respawn immediately if (e.action.scriptSpawn.spawnTimerMax) c->SetRespawnTime(urand(e.action.scriptSpawn.spawnTimerMin, e.action.scriptSpawn.spawnTimerMax)); else c->Respawn(true); // If 0, use DB values if (e.action.scriptSpawn.respawnDelay) c->SetRespawnDelay(e.action.scriptSpawn.respawnDelay); // If 0, use default if (e.action.scriptSpawn.corpseDelay) c->SetCorpseDelay(e.action.scriptSpawn.corpseDelay); } } break; } default: break; } break; } case SMART_ACTION_SET_SCALE: { float scale = static_cast(e.action.setScale.scale) / 100.0f; for (WorldObject* target : targets) { if (IsUnit(target)) { target->ToUnit()->SetObjectScale(scale); } } break; } case SMART_ACTION_SUMMON_RADIAL: { if (!me) break; TempSummonType spawnType = (e.action.radialSummon.summonDuration > 0) ? TEMPSUMMON_TIMED_DESPAWN : TEMPSUMMON_CORPSE_DESPAWN; float startAngle = me->GetOrientation() + (static_cast(e.action.radialSummon.startAngle) * M_PI / 180.0f); float stepAngle = static_cast(e.action.radialSummon.stepAngle) * M_PI / 180.0f; if (e.action.radialSummon.dist) { for (uint32 itr = 0; itr < e.action.radialSummon.repetitions; itr++) { Position summonPos = me->GetPosition(); summonPos.RelocatePolarOffset(itr * stepAngle, static_cast(e.action.radialSummon.dist)); me->SummonCreature(e.action.radialSummon.summonEntry, summonPos, spawnType, e.action.radialSummon.summonDuration); } break; } for (uint32 itr = 0; itr < e.action.radialSummon.repetitions; itr++) { float currentAngle = startAngle + (itr * stepAngle); me->SummonCreature(e.action.radialSummon.summonEntry, me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), currentAngle, spawnType, e.action.radialSummon.summonDuration); } break; } case SMART_ACTION_PLAY_SPELL_VISUAL: { for (WorldObject* target : targets) { if (IsUnit(target)) { if (e.action.spellVisual.visualId) target->ToUnit()->SendPlaySpellVisual(e.action.spellVisual.visualId); } } break; } case SMART_ACTION_FOLLOW_GROUP: { if (!e.action.followGroup.followState) { for (WorldObject* target : targets) if (IsUnit(target)) target->ToCreature()->GetMotionMaster()->MoveIdle(); break; } uint8 membCount = targets.size(); uint8 itr = 1; float dist = float(e.action.followGroup.dist / 100); switch (e.action.followGroup.followType) { case FOLLOW_TYPE_CIRCLE: { float angle = (membCount > 4 ? (M_PI * 2)/membCount : (M_PI / 2)); // 90 degrees is the maximum angle for (WorldObject* target : targets) { if (IsCreature(target)) { target->ToCreature()->GetMotionMaster()->MoveFollow(me, dist, angle * itr); itr++; } } break; } case FOLLOW_TYPE_SEMI_CIRCLE_BEHIND: { for (WorldObject* target : targets) { if (IsCreature(target)) { target->ToCreature()->GetMotionMaster()->MoveFollow(me, dist, (M_PI / 2.0f) + (M_PI / membCount) * (itr - 1)); itr++; } } break; } case FOLLOW_TYPE_SEMI_CIRCLE_FRONT: { for (WorldObject* target : targets) { if (IsCreature(target)) { target->ToCreature()->GetMotionMaster()->MoveFollow(me, dist, (M_PI + (M_PI / 2.0f) + (M_PI / membCount) * (itr - 1))); itr++; } } break; } case FOLLOW_TYPE_LINE: { for (WorldObject* target : targets) { if (IsCreature(target)) { target->ToCreature()->GetMotionMaster()->MoveFollow(me, dist * (((itr - 1) / 2) + 1), itr % 2 ? 0.f : M_PI); itr++; } } break; } case FOLLOW_TYPE_COLUMN: { for (WorldObject* target : targets) { if (IsCreature(target)) { target->ToCreature()->GetMotionMaster()->MoveFollow(me, dist * (((itr - 1) / 2) + 1), itr % 2 ? (M_PI / 2) : (M_PI * 1.5f)); itr++; } } break; } case FOLLOW_TYPE_ANGULAR: { for (WorldObject* target : targets) { if (IsCreature(target)) { target->ToCreature()->GetMotionMaster()->MoveFollow(me, dist * (((itr - 1) / 2) + 1), itr % 2 ? M_PI - (M_PI / 4) : M_PI + (M_PI / 4)); itr++; } } break; } default: break; } break; } case SMART_ACTION_SET_ORIENTATION_TARGET: { switch (e.action.orientationTarget.type) { case 0: // Reset { for (WorldObject* target : targets) target->ToCreature()->SetFacingTo((target->ToCreature()->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && target->ToCreature()->GetTransGUID() ? target->ToCreature()->GetTransportHomePosition() : target->ToCreature()->GetHomePosition()).GetOrientation()); break; } case 1: // Target target.o { for (WorldObject* target : targets) target->ToCreature()->SetFacingTo(e.target.o); break; } case 2: // Target source { for (WorldObject* target : targets) target->ToCreature()->SetFacingToObject(me); break; } case 3: // Target parameters { ObjectVector facingTargets; GetTargets(facingTargets, CreateSmartEvent(SMART_EVENT_UPDATE_IC, 0, 0, 0, 0, 0, 0, 0, SMART_ACTION_NONE, 0, 0, 0, 0, 0, 0, (SMARTAI_TARGETS)e.action.orientationTarget.targetType, e.action.orientationTarget.targetParam1, e.action.orientationTarget.targetParam2, e.action.orientationTarget.targetParam3, e.action.orientationTarget.targetParam4, 0), unit); for (WorldObject* facingTarget : facingTargets) for (WorldObject* target : targets) target->ToCreature()->SetFacingToObject(facingTarget); break; } default: break; } break; } case SMART_ACTION_WAYPOINT_START: { if (e.action.wpData.pathId) { for (WorldObject* target : targets) { if (IsCreature(target)) { target->ToCreature()->LoadPath(e.action.wpData.pathId); target->ToCreature()->GetMotionMaster()->MoveWaypoint(e.action.wpData.pathId, e.action.wpData.repeat, e.action.wpData.pathSource); } } } break; } case SMART_ACTION_WAYPOINT_DATA_RANDOM: { if (e.action.wpDataRandom.pathId1 && e.action.wpDataRandom.pathId2) { for (WorldObject* target : targets) { if (IsCreature(target)) { uint32 path = urand(e.action.wpDataRandom.pathId1, e.action.wpDataRandom.pathId2); target->ToCreature()->LoadPath(path); target->ToCreature()->GetMotionMaster()->MoveWaypoint(path, e.action.wpDataRandom.repeat); } } } break; } case SMART_ACTION_MOVEMENT_STOP: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->StopMoving(); break; } case SMART_ACTION_MOVEMENT_PAUSE: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->PauseMovement(e.action.move.timer); break; } case SMART_ACTION_MOVEMENT_RESUME: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->ResumeMovement(e.action.move.timer); break; } case SMART_ACTION_WORLD_SCRIPT: { sWorldState->HandleExternalEvent(static_cast(e.action.worldStateScript.eventId), e.action.worldStateScript.param); break; } case SMART_ACTION_DISABLE_REWARD: { for (WorldObject* target : targets) if (IsCreature(target)) { target->ToCreature()->SetReputationRewardDisabled(static_cast(e.action.reward.reputation)); target->ToCreature()->SetLootRewardDisabled(static_cast(e.action.reward.loot)); } break; } default: LOG_ERROR("sql.sql", "SmartScript::ProcessAction: Entry {} SourceType {}, Event {}, Unhandled Action type {}", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType()); break; } if (e.link && e.link != e.event_id) { auto linked = FindLinkedEvent(e.link); if (linked.has_value()) { auto& linkedEvent = linked.value().get(); if (linkedEvent.GetEventType() == SMART_EVENT_LINK) executionStack.emplace_back(SmartScriptFrame{ linkedEvent, unit, var0, var1, bvar, spell, gob }); else LOG_ERROR("sql.sql", "SmartScript::ProcessAction: Entry {} SourceType {}, Event {}, Link Event {} found but has wrong type (should be 61, is {}).", e.entryOrGuid, e.GetScriptType(), e.event_id, e.link, linkedEvent.GetEventType()); } else LOG_ERROR("sql.sql", "SmartScript::ProcessAction: Entry {} SourceType {}, Event {}, Link Event {} not found, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.link); } } void SmartScript::ProcessTimedAction(SmartScriptHolder& e, uint32 const& min, uint32 const& max, Unit* unit, uint32 var0, uint32 var1, bool bvar, SpellInfo const* spell, GameObject* gob) { // xinef: extended by selfs victim ConditionList const conds = sConditionMgr->GetConditionsForSmartEvent(e.entryOrGuid, e.event_id, e.source_type); ConditionSourceInfo info = ConditionSourceInfo(unit, GetBaseObject(), me ? me->GetVictim() : nullptr); if (sConditionMgr->IsObjectMeetToConditions(info, conds)) { ProcessAction(e, unit, var0, var1, bvar, spell, gob); RecalcTimer(e, min, max); } else RecalcTimer(e, 5000, 5000); } void SmartScript::InstallTemplate(SmartScriptHolder const& e) { if (!GetBaseObject()) return; if (mTemplate != SMARTAI_TEMPLATE_BASIC) { LOG_ERROR("sql.sql", "SmartScript::InstallTemplate: Entry {} SourceType {} AI Template can not be set more then once, skipped.", e.entryOrGuid, e.GetScriptType()); return; } mTemplate = (SMARTAI_TEMPLATE)e.action.installTtemplate.id; switch ((SMARTAI_TEMPLATE)e.action.installTtemplate.id) { case SMARTAI_TEMPLATE_CASTER: { AddEvent(SMART_EVENT_UPDATE_IC, 0, 0, 0, e.action.installTtemplate.param2, e.action.installTtemplate.param3, 0, 0, SMART_ACTION_CAST, e.action.installTtemplate.param1, e.target.raw.param1, 0, 0, 0, 0, SMART_TARGET_VICTIM, 0, 0, 0, 0, 1); AddEvent(SMART_EVENT_RANGE, 0, e.action.installTtemplate.param4, 300, 0, 0, 0, 0, SMART_ACTION_ALLOW_COMBAT_MOVEMENT, 1, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0, 1); AddEvent(SMART_EVENT_RANGE, 0, 0, e.action.installTtemplate.param4 > 10 ? e.action.installTtemplate.param4 - 10 : 0, 0, 0, 0, 0, SMART_ACTION_ALLOW_COMBAT_MOVEMENT, 0, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0, 1); AddEvent(SMART_EVENT_MANA_PCT, 0, e.action.installTtemplate.param5 - 15 > 100 ? 100 : e.action.installTtemplate.param5 + 15, 100, 1000, 1000, 0, 0, SMART_ACTION_SET_EVENT_PHASE, 1, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0, 0); AddEvent(SMART_EVENT_MANA_PCT, 0, 0, e.action.installTtemplate.param5, 1000, 1000, 0, 0, SMART_ACTION_SET_EVENT_PHASE, 0, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0, 0); AddEvent(SMART_EVENT_MANA_PCT, 0, 0, e.action.installTtemplate.param5, 1000, 1000, 0, 0, SMART_ACTION_ALLOW_COMBAT_MOVEMENT, 1, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0, 0); break; } case SMARTAI_TEMPLATE_TURRET: { AddEvent(SMART_EVENT_UPDATE_IC, 0, 0, 0, e.action.installTtemplate.param2, e.action.installTtemplate.param3, 0, 0, SMART_ACTION_CAST, e.action.installTtemplate.param1, e.target.raw.param1, 0, 0, 0, 0, SMART_TARGET_VICTIM, 0, 0, 0, 0, 0); AddEvent(SMART_EVENT_JUST_CREATED, 0, 0, 0, 0, 0, 0, 0, SMART_ACTION_ALLOW_COMBAT_MOVEMENT, 0, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0, 0); break; } case SMARTAI_TEMPLATE_CAGED_NPC_PART: { if (!me) return; //store cage as id1 AddEvent(SMART_EVENT_DATA_SET, 0, 0, 0, 0, 0, 0, 0, SMART_ACTION_STORE_TARGET_LIST, 1, 0, 0, 0, 0, 0, SMART_TARGET_CLOSEST_GAMEOBJECT, e.action.installTtemplate.param1, 10, 0, 0, 0); //reset(close) cage on hostage(me) respawn AddEvent(SMART_EVENT_UPDATE, SMART_EVENT_FLAG_NOT_REPEATABLE, 0, 0, 0, 0, 0, 0, SMART_ACTION_RESET_GOBJECT, 0, 0, 0, 0, 0, 0, SMART_TARGET_GAMEOBJECT_DISTANCE, e.action.installTtemplate.param1, 5, 0, 0, 0); AddEvent(SMART_EVENT_DATA_SET, 0, 0, 0, 0, 0, 0, 0, SMART_ACTION_SET_RUN, e.action.installTtemplate.param3, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0, 0); AddEvent(SMART_EVENT_DATA_SET, 0, 0, 0, 0, 0, 0, 0, SMART_ACTION_SET_EVENT_PHASE, 1, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0, 0); AddEvent(SMART_EVENT_UPDATE, SMART_EVENT_FLAG_NOT_REPEATABLE, 1000, 1000, 0, 0, 0, 0, SMART_ACTION_MOVE_FORWARD, e.action.installTtemplate.param4, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0, 1); //phase 1: give quest credit on movepoint reached AddEvent(SMART_EVENT_MOVEMENTINFORM, 0, POINT_MOTION_TYPE, SMART_RANDOM_POINT, 0, 0, 0, 0, SMART_ACTION_SET_DATA, 0, 0, 0, 0, 0, 0, SMART_TARGET_STORED, 1, 0, 0, 0, 1); //phase 1: despawn after time on movepoint reached AddEvent(SMART_EVENT_MOVEMENTINFORM, 0, POINT_MOTION_TYPE, SMART_RANDOM_POINT, 0, 0, 0, 0, SMART_ACTION_FORCE_DESPAWN, e.action.installTtemplate.param2, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0, 1); if (sCreatureTextMgr->TextExist(me->GetEntry(), (uint8)e.action.installTtemplate.param5)) AddEvent(SMART_EVENT_MOVEMENTINFORM, 0, POINT_MOTION_TYPE, SMART_RANDOM_POINT, 0, 0, 0, 0, SMART_ACTION_TALK, e.action.installTtemplate.param5, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0, 1); break; } case SMARTAI_TEMPLATE_CAGED_GO_PART: { if (!go) return; //store hostage as id1 AddEvent(SMART_EVENT_GO_STATE_CHANGED, 0, 2, 0, 0, 0, 0, 0, SMART_ACTION_STORE_TARGET_LIST, 1, 0, 0, 0, 0, 0, SMART_TARGET_CLOSEST_CREATURE, e.action.installTtemplate.param1, 10, 0, 0, 0); //store invoker as id2 AddEvent(SMART_EVENT_GO_STATE_CHANGED, 0, 2, 0, 0, 0, 0, 0, SMART_ACTION_STORE_TARGET_LIST, 2, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0, 0); //signal hostage AddEvent(SMART_EVENT_GO_STATE_CHANGED, 0, 2, 0, 0, 0, 0, 0, SMART_ACTION_SET_DATA, 0, 0, 0, 0, 0, 0, SMART_TARGET_STORED, 1, 0, 0, 0, 0); //when hostage raeched end point, give credit to invoker if (e.action.installTtemplate.param2) AddEvent(SMART_EVENT_DATA_SET, 0, 0, 0, 0, 0, 0, 0, SMART_ACTION_CALL_KILLEDMONSTER, e.action.installTtemplate.param1, 0, 0, 0, 0, 0, SMART_TARGET_STORED, 2, 0, 0, 0, 0); else AddEvent(SMART_EVENT_GO_STATE_CHANGED, 0, 2, 0, 0, 0, 0, 0, SMART_ACTION_CALL_KILLEDMONSTER, e.action.installTtemplate.param1, 0, 0, 0, 0, 0, SMART_TARGET_STORED, 2, 0, 0, 0, 0); break; } case SMARTAI_TEMPLATE_BASIC: default: return; } } void SmartScript::AddEvent(SMART_EVENT e, uint32 event_flags, uint32 event_param1, uint32 event_param2, uint32 event_param3, uint32 event_param4, uint32 event_param5, uint32 event_param6, SMART_ACTION action, uint32 action_param1, uint32 action_param2, uint32 action_param3, uint32 action_param4, uint32 action_param5, uint32 action_param6, SMARTAI_TARGETS t, uint32 target_param1, uint32 target_param2, uint32 target_param3, uint32 target_param4, uint32 phaseMask) { mInstallEvents.push_back(CreateSmartEvent(e, event_flags, event_param1, event_param2, event_param3, event_param4, event_param5, event_param6, action, action_param1, action_param2, action_param3, action_param4, action_param5, action_param6, t, target_param1, target_param2, target_param3, target_param4, phaseMask)); } SmartScriptHolder SmartScript::CreateSmartEvent(SMART_EVENT e, uint32 event_flags, uint32 event_param1, uint32 event_param2, uint32 event_param3, uint32 event_param4, uint32 event_param5, uint32 event_param6, SMART_ACTION action, uint32 action_param1, uint32 action_param2, uint32 action_param3, uint32 action_param4, uint32 action_param5, uint32 action_param6, SMARTAI_TARGETS t, uint32 target_param1, uint32 target_param2, uint32 target_param3, uint32 target_param4, uint32 phaseMask) { SmartScriptHolder script; script.event.type = e; script.event.raw.param1 = event_param1; script.event.raw.param2 = event_param2; script.event.raw.param3 = event_param3; script.event.raw.param4 = event_param4; script.event.raw.param5 = event_param5; script.event.raw.param6 = event_param6; script.event.event_phase_mask = phaseMask; script.event.event_flags = event_flags; script.event.event_chance = 100; script.action.type = action; script.action.raw.param1 = action_param1; script.action.raw.param2 = action_param2; script.action.raw.param3 = action_param3; script.action.raw.param4 = action_param4; script.action.raw.param5 = action_param5; script.action.raw.param6 = action_param6; script.target.type = t; script.target.raw.param1 = target_param1; script.target.raw.param2 = target_param2; script.target.raw.param3 = target_param3; script.target.raw.param4 = target_param4; script.source_type = SMART_SCRIPT_TYPE_CREATURE; InitTimer(script); return script; } void SmartScript::GetTargets(ObjectVector& targets, SmartScriptHolder const& e, WorldObject* invoker /*= nullptr*/) const { WorldObject* scriptTrigger = nullptr; if (invoker) scriptTrigger = invoker; else if (WorldObject* tempLastInvoker = GetLastInvoker()) scriptTrigger = tempLastInvoker; WorldObject* baseObject = GetBaseObject(); switch (e.GetTargetType()) { case SMART_TARGET_SELF: if (baseObject) targets.push_back(baseObject); break; case SMART_TARGET_VICTIM: if (me) if (Unit* victim = me->GetVictim()) targets.push_back(victim); break; case SMART_TARGET_HOSTILE_SECOND_AGGRO: if (me) { if (e.target.hostileRandom.powerType) { if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::MaxThreat, 0, PowerUsersSelector(me, Powers(e.target.hostileRandom.powerType - 1), (float)e.target.hostileRandom.maxDist, e.target.hostileRandom.playerOnly, false))) targets.push_back(u); } else if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::MaxThreat, 0, (float)e.target.hostileRandom.maxDist, e.target.hostileRandom.playerOnly, false, -e.target.hostileRandom.aura)) targets.push_back(u); } break; case SMART_TARGET_HOSTILE_LAST_AGGRO: if (me) { if (e.target.hostileRandom.powerType) { if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::MinThreat, 0, PowerUsersSelector(me, Powers(e.target.hostileRandom.powerType - 1), (float)e.target.hostileRandom.maxDist, e.target.hostileRandom.playerOnly))) targets.push_back(u); } else if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::MinThreat, 0, (float)e.target.hostileRandom.maxDist, e.target.hostileRandom.playerOnly, true, -e.target.hostileRandom.aura)) targets.push_back(u); } break; case SMART_TARGET_HOSTILE_RANDOM: if (me) { if (e.target.hostileRandom.powerType) { if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::Random, 0, PowerUsersSelector(me, Powers(e.target.hostileRandom.powerType - 1), (float)e.target.hostileRandom.maxDist, e.target.hostileRandom.playerOnly))) targets.push_back(u); } else if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::Random, 0, (float)e.target.hostileRandom.maxDist, e.target.hostileRandom.playerOnly, true, -e.target.hostileRandom.aura)) targets.push_back(u); } break; case SMART_TARGET_HOSTILE_RANDOM_NOT_TOP: if (me) { if (e.target.hostileRandom.powerType) { if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::Random, 0, PowerUsersSelector(me, Powers(e.target.hostileRandom.powerType - 1), (float)e.target.hostileRandom.maxDist, e.target.hostileRandom.playerOnly, false))) targets.push_back(u); } else if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::Random, 0, (float)e.target.hostileRandom.maxDist, e.target.hostileRandom.playerOnly, false, -e.target.hostileRandom.aura)) targets.push_back(u); } break; case SMART_TARGET_FARTHEST: if (me) { if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::MinDistance, 0, RangeSelector(me, e.target.farthest.maxDist, e.target.farthest.playerOnly, e.target.farthest.isInLos, e.target.farthest.minDist))) targets.push_back(u); } break; case SMART_TARGET_ACTION_INVOKER: if (scriptTrigger) targets.push_back(scriptTrigger); break; case SMART_TARGET_ACTION_INVOKER_VEHICLE: if (scriptTrigger && IsUnit(scriptTrigger)) if (scriptTrigger->ToUnit()->GetVehicle() && scriptTrigger->ToUnit()->GetVehicle()->GetBase()) targets.push_back(scriptTrigger->ToUnit()->GetVehicle()->GetBase()); break; case SMART_TARGET_INVOKER_PARTY: if (scriptTrigger) { if (Player* player = scriptTrigger->ToPlayer()) { if (Group* group = player->GetGroup()) { for (GroupReference* groupRef = group->GetFirstMember(); groupRef != nullptr; groupRef = groupRef->next()) if (Player* member = groupRef->GetSource()) { if (member->IsInMap(player)) targets.push_back(member); if (e.target.invokerParty.includePets) if (Creature* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*member, member->GetPetGUID())) if (pet->IsPet() && pet->IsInMap(player)) targets.push_back(pet); } } // We still add the player to the list if there is no group. If we do // this even if there is a group (thus the else-check), it will add the // same player to the list twice. We don't want that to happen. else targets.push_back(scriptTrigger); } } break; case SMART_TARGET_CREATURE_RANGE: { WorldObject* ref = baseObject; if (!ref) ref = scriptTrigger; if (!ref) { LOG_ERROR("scripts.ai.sai", "SMART_TARGET_CREATURE_RANGE: Entry {} SourceType {} Event {} Action {} Target {} is missing base object or invoker", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.GetTargetType()); break; } ObjectVector units; GetWorldObjectsInDist(units, static_cast(e.target.unitRange.maxDist)); for (WorldObject* unit : units) { if (!IsCreature(unit)) continue; if (me && me->GetGUID() == unit->GetGUID()) continue; // check alive state - 1 alive, 2 dead, 0 both if (uint32 state = e.target.unitRange.livingState) { if (unit->ToCreature()->IsAlive() && state == 2) continue; if (!unit->ToCreature()->IsAlive() && state == 1) continue; } if (((e.target.unitRange.creature && unit->ToCreature()->GetEntry() == e.target.unitRange.creature) || !e.target.unitRange.creature) && ref->IsInRange(unit, (float)e.target.unitRange.minDist, (float)e.target.unitRange.maxDist)) targets.push_back(unit); } break; } case SMART_TARGET_CREATURE_DISTANCE: { ObjectVector units; GetWorldObjectsInDist(units, static_cast(e.target.unitDistance.dist)); for (WorldObject* unit : units) { if (!IsCreature(unit)) continue; if (me && me->GetGUID() == unit->GetGUID()) continue; // check alive state - 1 alive, 2 dead, 0 both if (uint32 state = e.target.unitDistance.livingState) { if (unit->ToCreature()->IsAlive() && state == 2) continue; if (!unit->ToCreature()->IsAlive() && state == 1) continue; } if ((e.target.unitDistance.creature && unit->ToCreature()->GetEntry() == e.target.unitDistance.creature) || !e.target.unitDistance.creature) targets.push_back(unit); } break; } case SMART_TARGET_GAMEOBJECT_DISTANCE: { ObjectVector units; GetWorldObjectsInDist(units, static_cast(e.target.goDistance.dist)); for (WorldObject* unit : units) { if (!IsGameObject(unit)) continue; if (go && go->GetGUID() == unit->GetGUID()) continue; if ((e.target.goDistance.entry && unit->ToGameObject()->GetEntry() == e.target.goDistance.entry) || !e.target.goDistance.entry) targets.push_back(unit); } break; } case SMART_TARGET_GAMEOBJECT_RANGE: { WorldObject* ref = baseObject; if (!ref) ref = scriptTrigger; if (!ref) { LOG_ERROR("scripts.ai.sai", "SMART_TARGET_GAMEOBJECT_RANGE: Entry: {} SourceType {} Event {} Action {} Target {} is missing base object or invoker.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.GetTargetType()); break; } ObjectVector units; GetWorldObjectsInDist(units, static_cast(e.target.goRange.maxDist)); for (WorldObject* unit : units) { if (!IsGameObject(unit)) continue; if (go && go->GetGUID() == unit->GetGUID()) continue; if (((e.target.goRange.entry && IsGameObject(unit) && unit->ToGameObject()->GetEntry() == e.target.goRange.entry) || !e.target.goRange.entry) && ref->IsInRange((unit), (float)e.target.goRange.minDist, (float)e.target.goRange.maxDist)) targets.push_back(unit); } break; } case SMART_TARGET_CREATURE_GUID: { if (!scriptTrigger && !baseObject) { LOG_ERROR("scripts.ai.sai", "SMART_TARGET_CREATURE_GUID: Entry {} SourceType {} Event {} Action {} Target {} is missing base object or invoker.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.GetTargetType()); break; } Creature* target = FindCreatureNear(scriptTrigger ? scriptTrigger : GetBaseObject(), e.target.unitGUID.dbGuid); if (target && (!e.target.unitGUID.entry || target->GetEntry() == e.target.unitGUID.entry)) targets.push_back(target); break; } case SMART_TARGET_GAMEOBJECT_GUID: { if (!scriptTrigger && !GetBaseObject()) { LOG_ERROR("scripts.ai.sai", "SMART_TARGET_GAMEOBJECT_GUID: Entry {} SourceType {} Event {} Action {} Target {} is missing base object or invoker.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.GetTargetType()); break; } GameObject* target = FindGameObjectNear(scriptTrigger ? scriptTrigger : GetBaseObject(), e.target.goGUID.dbGuid); if (target && (!e.target.goGUID.entry || target->GetEntry() == e.target.goGUID.entry)) targets.push_back(target); break; } case SMART_TARGET_PLAYER_RANGE: { ObjectVector units; GetWorldObjectsInDist(units, static_cast(e.target.playerRange.maxDist)); if (!units.empty() && baseObject) for (WorldObject* unit : units) if (IsPlayer(unit) && !unit->ToPlayer()->IsGameMaster() && baseObject->IsInRange(unit, float(e.target.playerRange.minDist), float(e.target.playerRange.maxDist))) targets.push_back(unit); if (e.target.playerRange.maxCount) Acore::Containers::RandomResize(targets, e.target.playerRange.maxCount); break; } case SMART_TARGET_PLAYER_DISTANCE: { ObjectVector units; GetWorldObjectsInDist(units, static_cast(e.target.playerDistance.dist)); for (WorldObject* unit : units) if (IsPlayer(unit)) targets.push_back(unit); break; } case SMART_TARGET_STORED: { WorldObject* ref = baseObject; if (!ref) ref = scriptTrigger; if (!ref) { LOG_ERROR("scripts.ai.sai", "SMART_TARGET_STORED: Entry {} SourceType {} Event {} Action {} Target {} is missing base object or invoker.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.GetTargetType()); break; } if (ObjectVector const* stored = GetStoredTargetVector(e.target.stored.id, *ref)) targets.assign(stored->begin(), stored->end()); break; } case SMART_TARGET_CLOSEST_CREATURE: { WorldObject* ref = baseObject; if (!ref) ref = scriptTrigger; if (!ref) { LOG_ERROR("scripts.ai.sai", "SMART_TARGET_CLOSEST_CREATURE: Entry {} SourceType {} Event {} Action {} Target {} is missing base object or invoker.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.GetTargetType()); break; } Creature* target = GetClosestCreatureWithEntry(ref, e.target.unitClosest.entry, (float)(e.target.unitClosest.dist ? e.target.unitClosest.dist : 100), !e.target.unitClosest.dead); if (target) targets.push_back(target); break; } case SMART_TARGET_CLOSEST_GAMEOBJECT: { WorldObject* ref = baseObject; if (!ref) ref = scriptTrigger; if (!ref) { LOG_ERROR("scripts.ai.sai", "SMART_TARGET_CLOSEST_GAMEOBJECT: Entry {} SourceType {} Event {} Action {} Target {} is missing base object or invoker.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.GetTargetType()); break; } GameObject* target = GetClosestGameObjectWithEntry(ref, e.target.goClosest.entry, (float)(e.target.goClosest.dist ? e.target.goClosest.dist : 100), e.target.goClosest.onlySpawned); if (target) targets.push_back(target); break; } case SMART_TARGET_CLOSEST_PLAYER: { WorldObject* ref = baseObject; if (!ref) ref = scriptTrigger; if (!ref) { LOG_ERROR("scripts.ai.sai", "SMART_TARGET_CLOSEST_PLAYER: Entry {} SourceType {} Event {} Action {} Target {} is missing base object or invoker.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.GetTargetType()); break; } if (Player* target = ref->SelectNearestPlayer((float)e.target.playerDistance.dist)) targets.push_back(target); break; } case SMART_TARGET_OWNER_OR_SUMMONER: /* * Owners/Summoners should be WorldObjects. This allows to have other objects * such as gameobjects to execute SmartScripts using this type of target. * Otherwise, only Units like creatures can summon other creatures. */ { if (me) { if (WorldObject* owner = ObjectAccessor::GetWorldObject(*me, me->GetCharmerOrOwnerGUID())) { targets.push_back(owner); } else if (me->IsSummon() && me->ToTempSummon()->GetSummonerUnit()) { targets.push_back(me->ToTempSummon()->GetSummonerUnit()); } } else if (go) { if (WorldObject* owner = ObjectAccessor::GetWorldObject(*go, go->GetOwnerGUID())) { targets.push_back(owner); } } // xinef: Get owner of owner if (e.target.owner.useCharmerOrOwner && !targets.empty()) { if (WorldObject* owner = targets.front()) { targets.clear(); if (IsCreature(owner)) { if (Unit* base = ObjectAccessor::GetUnit(*owner, owner->ToCreature()->GetCharmerOrOwnerGUID())) targets.push_back(base); } else if (IsGameObject(owner)) { if (Unit* base = ObjectAccessor::GetUnit(*owner, owner->ToGameObject()->GetOwnerGUID())) targets.push_back(base); } else if (IsPlayer(owner)) { if (Unit* base = owner->ToPlayer()->GetCharmerOrOwner()) targets.push_back(base); } } } break; } case SMART_TARGET_THREAT_LIST: { if (me) { ThreatContainer::StorageType threatList = me->GetThreatMgr().GetThreatList(); for (ThreatContainer::StorageType::const_iterator i = threatList.begin(); i != threatList.end(); ++i) if (Unit* temp = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid())) // Xinef: added distance check if (e.target.threatList.maxDist == 0 || me->IsWithinCombatRange(temp, (float)e.target.threatList.maxDist)) targets.push_back(temp); } break; } case SMART_TARGET_CLOSEST_ENEMY: { if (me) if (Unit* target = me->SelectNearestTarget(e.target.closestAttackable.maxDist, e.target.closestAttackable.playerOnly)) targets.push_back(target); break; } case SMART_TARGET_CLOSEST_FRIENDLY: { if (me) if (Unit* target = DoFindClosestFriendlyInRange(e.target.closestFriendly.maxDist, e.target.closestFriendly.playerOnly)) targets.push_back(target); break; } case SMART_TARGET_PLAYER_WITH_AURA: { ObjectVector units; GetWorldObjectsInDist(units, static_cast(e.target.playerDistance.dist)); for (WorldObject* unit : units) if (IsPlayer(unit) && unit->ToPlayer()->IsAlive() && !unit->ToPlayer()->IsGameMaster()) if (GetBaseObject()->IsInRange(unit, (float)e.target.playerWithAura.distMin, (float)e.target.playerWithAura.distMax)) if (bool(e.target.playerWithAura.negation) != unit->ToPlayer()->HasAura(e.target.playerWithAura.spellId)) targets.push_back(unit); if (e.target.o > 0) Acore::Containers::RandomResize(targets, e.target.o); break; } case SMART_TARGET_ROLE_SELECTION: { ObjectVector units; GetWorldObjectsInDist(units, static_cast(e.target.playerDistance.dist)); // 1 = Tanks, 2 = Healer, 4 = Damage uint32 roleMask = e.target.roleSelection.roleMask; for (WorldObject* unit : units) if (Player* targetPlayer = unit->ToPlayer()) if (targetPlayer->IsAlive() && !targetPlayer->IsGameMaster()) { if (roleMask & SMART_TARGET_ROLE_FLAG_TANKS) { if (targetPlayer->HasTankSpec()) { targets.push_back(unit); continue; } } if (roleMask & SMART_TARGET_ROLE_FLAG_HEALERS) { if (targetPlayer->HasHealSpec()) { targets.push_back(unit); continue; } } if (roleMask & SMART_TARGET_ROLE_FLAG_DAMAGERS) { if (targetPlayer->HasCasterSpec() || targetPlayer->HasMeleeSpec()) { targets.push_back(unit); continue; } } } if (e.target.roleSelection.resize > 0) Acore::Containers::RandomResize(targets, e.target.roleSelection.resize); break; } case SMART_TARGET_VEHICLE_PASSENGER: { if (me && me->IsVehicle()) { if (Unit* target = me->GetVehicleKit()->GetPassenger(e.target.vehicle.seatMask)) { targets.push_back(target); } } break; } case SMART_TARGET_LOOT_RECIPIENTS: { if (me) { if (Group* lootGroup = me->GetLootRecipientGroup()) { for (GroupReference* it = lootGroup->GetFirstMember(); it != nullptr; it = it->next()) { if (Player* recipient = it->GetSource()) { targets.push_back(recipient); } } } else { if (Player* recipient = me->GetLootRecipient()) { targets.push_back(recipient); } } } break; } case SMART_TARGET_SUMMONED_CREATURES: { if (me) { for (ObjectGuid const& guid : _summonList) { if (!e.target.summonedCreatures.entry || guid.GetEntry() == e.target.summonedCreatures.entry) { if (Creature* creature = me->GetMap()->GetCreature(guid)) { targets.push_back(creature); } } } } break; } case SMART_TARGET_INSTANCE_STORAGE: { InstanceScript* instance = nullptr; if (e.IsAreatriggerScript() && scriptTrigger) instance = scriptTrigger->GetInstanceScript(); else instance = GetBaseObject()->GetInstanceScript(); if (!instance) { LOG_ERROR("scripts.ai.sai", "SMART_TARGET_INSTANCE_STORAGE: Entry {} SourceType {} Event {} Action {} Target {} called outside an instance map.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.GetTargetType()); return; } if (e.target.instanceStorage.type == 1) { if (Creature* creature = instance->GetCreature(e.target.instanceStorage.index)) targets.push_back(creature); } else if (e.target.instanceStorage.type == 2) { if (GameObject* go = instance->GetGameObject(e.target.instanceStorage.index)) targets.push_back(go); } break; } case SMART_TARGET_NONE: case SMART_TARGET_POSITION: default: break; } } void SmartScript::GetWorldObjectsInDist(ObjectVector& targets, float dist) const { WorldObject* obj = GetBaseObject(); if (!obj) return; Acore::AllWorldObjectsInRange u_check(obj, dist); Acore::WorldObjectListSearcher searcher(obj, targets, u_check); Cell::VisitObjects(obj, searcher, dist); } void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, uint32 var1, bool bvar, SpellInfo const* spell, GameObject* gob) { if (!e.active && e.GetEventType() != SMART_EVENT_LINK) return; if ((e.event.event_phase_mask && !IsInPhase(e.event.event_phase_mask)) || ((e.event.event_flags & SMART_EVENT_FLAG_NOT_REPEATABLE) && e.runOnce)) return; if (!(e.event.event_flags & SMART_EVENT_FLAG_WHILE_CHARMED) && IsCharmedCreature(me)) return; switch (e.GetEventType()) { case SMART_EVENT_LINK://special handling ProcessAction(e, unit, var0, var1, bvar, spell, gob); break; //called from Update tick case SMART_EVENT_UPDATE: ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); break; case SMART_EVENT_UPDATE_OOC: if (me && me->IsEngaged()) return; ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); break; case SMART_EVENT_UPDATE_IC: if (!me || !me->IsEngaged()) return; ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); break; case SMART_EVENT_HEALTH_PCT: { if (!me || !me->IsEngaged() || !me->GetMaxHealth()) return; uint32 perc = (uint32)me->GetHealthPct(); if (perc > e.event.minMaxRepeat.max || perc < e.event.minMaxRepeat.min) return; ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); break; } case SMART_EVENT_TARGET_HEALTH_PCT: { if (!me || !me->IsEngaged() || !me->GetVictim() || !me->GetVictim()->GetMaxHealth()) return; uint32 perc = (uint32)me->GetVictim()->GetHealthPct(); if (perc > e.event.minMaxRepeat.max || perc < e.event.minMaxRepeat.min) return; ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax, me->GetVictim()); break; } case SMART_EVENT_MANA_PCT: { if (!me || !me->IsEngaged() || !me->GetMaxPower(POWER_MANA)) return; uint32 perc = uint32(me->GetPowerPct(POWER_MANA)); if (perc > e.event.minMaxRepeat.max || perc < e.event.minMaxRepeat.min) return; ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); break; } case SMART_EVENT_TARGET_MANA_PCT: { if (!me || !me->IsEngaged() || !me->GetVictim() || !me->GetVictim()->GetMaxPower(POWER_MANA)) return; uint32 perc = uint32(me->GetVictim()->GetPowerPct(POWER_MANA)); if (perc > e.event.minMaxRepeat.max || perc < e.event.minMaxRepeat.min) return; ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax, me->GetVictim()); break; } case SMART_EVENT_RANGE: { if (!me || !me->IsEngaged() || !me->GetVictim()) return; if (me->IsInRange(me->GetVictim(), (float)e.event.minMaxRepeat.rangeMin, (float)e.event.minMaxRepeat.rangeMax)) ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax, me->GetVictim()); else RecalcTimer(e, 1200, 1200); // make it predictable break; } case SMART_EVENT_VICTIM_CASTING: { if (!me || !me->IsEngaged()) return; Unit* victim = me->GetVictim(); if (!victim || !victim->IsNonMeleeSpellCast(false, false, true)) return; if (e.event.targetCasting.spellId > 0) if (Spell* currSpell = victim->GetCurrentSpell(CURRENT_GENERIC_SPELL)) if (currSpell->m_spellInfo->Id != e.event.targetCasting.spellId) return; ProcessTimedAction(e, e.event.targetCasting.repeatMin, e.event.targetCasting.repeatMax, me->GetVictim()); break; } case SMART_EVENT_FRIENDLY_HEALTH: { if (!me || !me->IsEngaged()) return; Unit* target = DoSelectLowestHpFriendly((float)e.event.friendlyHealth.radius, e.event.friendlyHealth.hpDeficit); if (!target || !target->IsInCombat()) { // Xinef: if there are at least two same npcs, they will perform the same action immediately even if this is useless... RecalcTimer(e, 1000, 3000); return; } ProcessTimedAction(e, e.event.friendlyHealth.repeatMin, e.event.friendlyHealth.repeatMax, target); break; } case SMART_EVENT_FRIENDLY_IS_CC: { if (!me || !me->IsEngaged()) return; std::vector creatures; DoFindFriendlyCC(creatures, float(e.event.friendlyCC.radius)); if (creatures.empty()) { // Xinef: if there are at least two same npcs, they will perform the same action immediately even if this is useless... RecalcTimer(e, 1000, 3000); return; } ProcessTimedAction(e, e.event.friendlyCC.repeatMin, e.event.friendlyCC.repeatMax, Acore::Containers::SelectRandomContainerElement(creatures)); break; } case SMART_EVENT_FRIENDLY_MISSING_BUFF: { if (e.event.missingBuff.onlyInCombat && !me->IsEngaged()) { return; } std::vector creatures; DoFindFriendlyMissingBuff(creatures, float(e.event.missingBuff.radius), e.event.missingBuff.spell); if (creatures.empty()) return; ProcessTimedAction(e, e.event.missingBuff.repeatMin, e.event.missingBuff.repeatMax, Acore::Containers::SelectRandomContainerElement(creatures)); break; } case SMART_EVENT_HAS_AURA: { if (!me) return; uint32 count = me->GetAuraCount(e.event.aura.spell); if ((!e.event.aura.count && !count) || (e.event.aura.count && count >= e.event.aura.count)) ProcessTimedAction(e, e.event.aura.repeatMin, e.event.aura.repeatMax); break; } case SMART_EVENT_TARGET_BUFFED: { if (!me || !me->GetVictim()) return; uint32 count = me->GetVictim()->GetAuraCount(e.event.aura.spell); if (count < e.event.aura.count) return; ProcessTimedAction(e, e.event.aura.repeatMin, e.event.aura.repeatMax, me->GetVictim()); break; } case SMART_EVENT_CHARMED: { if (bvar == (e.event.charm.onRemove != 1)) ProcessAction(e, unit, var0, var1, bvar, spell, gob); break; } //no params case SMART_EVENT_AGGRO: case SMART_EVENT_DEATH: case SMART_EVENT_EVADE: case SMART_EVENT_REACHED_HOME: case SMART_EVENT_CHARMED_TARGET: case SMART_EVENT_CORPSE_REMOVED: case SMART_EVENT_AI_INIT: case SMART_EVENT_TRANSPORT_ADDPLAYER: case SMART_EVENT_TRANSPORT_REMOVE_PLAYER: case SMART_EVENT_QUEST_ACCEPTED: case SMART_EVENT_QUEST_OBJ_COMPLETION: case SMART_EVENT_QUEST_COMPLETION: case SMART_EVENT_QUEST_REWARDED: case SMART_EVENT_QUEST_FAIL: case SMART_EVENT_JUST_SUMMONED: case SMART_EVENT_RESET: case SMART_EVENT_JUST_CREATED: case SMART_EVENT_FOLLOW_COMPLETED: case SMART_EVENT_ON_SPELLCLICK: ProcessAction(e, unit, var0, var1, bvar, spell, gob); break; case SMART_EVENT_GOSSIP_HELLO: switch (e.event.gossipHello.filter) { case 0: // no filter set, always execute action break; case 1: // GossipHello only filter set, skip action if reportUse if (var0) { return; } break; case 2: // reportUse only filter set, skip action if GossipHello if (!var0) { return; } break; default: // Ignore any other value break; } ProcessAction(e, unit, var0, var1, bvar, spell, gob); break; case SMART_EVENT_IS_BEHIND_TARGET: { if (!me) return; if (Unit* victim = me->GetVictim()) { if (e.event.minMaxRepeat.rangeMax && (me->IsInRange(victim, (float)e.event.minMaxRepeat.rangeMin, (float)e.event.minMaxRepeat.rangeMax))) if (!victim->HasInArc(static_cast(M_PI), me)) ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax, victim); } break; } case SMART_EVENT_IS_IN_MELEE_RANGE: { if (!me) return; if (Unit* victim = me->GetVictim()) if ((!e.event.meleeRange.invert && me->IsWithinMeleeRange(victim, static_cast(e.event.meleeRange.dist))) || (e.event.meleeRange.invert && !me->IsWithinMeleeRange(victim, static_cast(e.event.meleeRange.dist)))) ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax, victim); break; } case SMART_EVENT_RECEIVE_EMOTE: if (e.event.emote.emote == var0) { ProcessAction(e, unit); RecalcTimer(e, e.event.emote.cooldownMin, e.event.emote.cooldownMax); } break; case SMART_EVENT_KILL: { if (!me || !unit) return; if (e.event.kill.playerOnly && !unit->IsPlayer()) return; if (e.event.kill.creature && unit->GetEntry() != e.event.kill.creature) return; RecalcTimer(e, e.event.kill.cooldownMin, e.event.kill.cooldownMax); ProcessAction(e, unit); break; } case SMART_EVENT_SPELLHIT_TARGET: case SMART_EVENT_SPELLHIT: { if (!spell) return; if ((!e.event.spellHit.spell || spell->Id == e.event.spellHit.spell) && (!e.event.spellHit.school || (spell->SchoolMask & e.event.spellHit.school))) { RecalcTimer(e, e.event.spellHit.cooldownMin, e.event.spellHit.cooldownMax); ProcessAction(e, unit, 0, 0, bvar, spell); } break; } case SMART_EVENT_OOC_LOS: { if (!me || me->IsEngaged()) return; //can trigger if closer than fMaxAllowedRange float range = (float)e.event.los.maxDist; //if range is ok and we are actually in LOS if (me->IsWithinDistInMap(unit, range) && me->IsWithinLOSInMap(unit)) { SmartEvent::LOSHostilityMode hostilityMode = static_cast(e.event.los.hostilityMode); //if friendly event&&who is not hostile OR hostile event&&who is hostile if ((hostilityMode == SmartEvent::LOSHostilityMode::Any) || (hostilityMode == SmartEvent::LOSHostilityMode::NotHostile && !me->IsHostileTo(unit)) || (hostilityMode == SmartEvent::LOSHostilityMode::Hostile && me->IsHostileTo(unit))) { if (e.event.los.playerOnly && !unit->IsPlayer()) return; RecalcTimer(e, e.event.los.cooldownMin, e.event.los.cooldownMax); ProcessAction(e, unit); } } break; } case SMART_EVENT_IC_LOS: { if (!me || !me->IsEngaged()) return; //can trigger if closer than fMaxAllowedRange float range = (float)e.event.los.maxDist; //if range is ok and we are actually in LOS if (me->IsWithinDistInMap(unit, range) && me->IsWithinLOSInMap(unit)) { SmartEvent::LOSHostilityMode hostilityMode = static_cast(e.event.los.hostilityMode); //if friendly event&&who is not hostile OR hostile event&&who is hostile if ((hostilityMode == SmartEvent::LOSHostilityMode::Any) || (hostilityMode == SmartEvent::LOSHostilityMode::NotHostile && !me->IsHostileTo(unit)) || (hostilityMode == SmartEvent::LOSHostilityMode::Hostile && me->IsHostileTo(unit))) { if (e.event.los.playerOnly && !unit->IsPlayer()) return; RecalcTimer(e, e.event.los.cooldownMin, e.event.los.cooldownMax); ProcessAction(e, unit); } } break; } case SMART_EVENT_RESPAWN: { if (!GetBaseObject()) return; if (e.event.respawn.type == SMART_SCRIPT_RESPAWN_CONDITION_MAP && GetBaseObject()->GetMapId() != e.event.respawn.map) return; if (e.event.respawn.type == SMART_SCRIPT_RESPAWN_CONDITION_AREA && GetBaseObject()->GetZoneId() != e.event.respawn.area) return; ProcessAction(e); break; } case SMART_EVENT_SUMMONED_UNIT: case SMART_EVENT_SUMMONED_UNIT_DIES: case SMART_EVENT_SUMMONED_UNIT_EVADE: { if (!IsCreature(unit)) return; if (e.event.summoned.creature && unit->GetEntry() != e.event.summoned.creature) return; RecalcTimer(e, e.event.summoned.cooldownMin, e.event.summoned.cooldownMax); ProcessAction(e, unit); break; } case SMART_EVENT_RECEIVE_HEAL: case SMART_EVENT_DAMAGED: case SMART_EVENT_DAMAGED_TARGET: { if (var0 > e.event.minMaxRepeat.max || var0 < e.event.minMaxRepeat.min) return; RecalcTimer(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); ProcessAction(e, unit); break; } case SMART_EVENT_MOVEMENTINFORM: { if ((e.event.movementInform.type && var0 != e.event.movementInform.type) || (e.event.movementInform.id && var1 != e.event.movementInform.id)) return; if (e.event.movementInform.pathId != 0 && e.event.movementInform.pathId != me->GetWaypointPath()) return; ProcessAction(e, unit, var0, var1); break; } case SMART_EVENT_TRANSPORT_RELOCATE: case SMART_EVENT_ESCORT_START: { if (e.event.waypoint.pathID && var0 != e.event.waypoint.pathID) return; ProcessAction(e, unit, var0); break; } case SMART_EVENT_ESCORT_REACHED: case SMART_EVENT_ESCORT_RESUMED: case SMART_EVENT_ESCORT_PAUSED: case SMART_EVENT_ESCORT_STOPPED: case SMART_EVENT_ESCORT_ENDED: { if (!me || (e.event.waypoint.pointID && var0 != e.event.waypoint.pointID) || (e.event.waypoint.pathID && GetPathId() != e.event.waypoint.pathID)) return; ProcessAction(e, unit); break; } case SMART_EVENT_SUMMON_DESPAWNED: { if (e.event.summoned.creature && e.event.summoned.creature != var0) return; RecalcTimer(e, e.event.summoned.cooldownMin, e.event.summoned.cooldownMax); ProcessAction(e, unit, var0); break; } case SMART_EVENT_INSTANCE_PLAYER_ENTER: { if (e.event.instancePlayerEnter.team && var0 != e.event.instancePlayerEnter.team) return; RecalcTimer(e, e.event.instancePlayerEnter.cooldownMin, e.event.instancePlayerEnter.cooldownMax); ProcessAction(e, unit, var0); break; } case SMART_EVENT_ACCEPTED_QUEST: case SMART_EVENT_REWARD_QUEST: { if (e.event.quest.quest && var0 != e.event.quest.quest) return; RecalcTimer(e, e.event.quest.cooldownMin, e.event.quest.cooldownMax); ProcessAction(e, unit, var0); break; } case SMART_EVENT_TRANSPORT_ADDCREATURE: { if (e.event.transportAddCreature.creature && var0 != e.event.transportAddCreature.creature) return; ProcessAction(e, unit, var0); break; } case SMART_EVENT_AREATRIGGER_ONTRIGGER: { if (e.event.areatrigger.id && var0 != e.event.areatrigger.id) return; ProcessAction(e, unit, var0); break; } case SMART_EVENT_TEXT_OVER: { if (var0 != e.event.textOver.textGroupID || (e.event.textOver.creatureEntry && e.event.textOver.creatureEntry != var1)) return; ProcessAction(e, unit, var0); break; } case SMART_EVENT_DATA_SET: { if (e.event.dataSet.id != var0 || e.event.dataSet.value != var1) return; RecalcTimer(e, e.event.dataSet.cooldownMin, e.event.dataSet.cooldownMax); ProcessAction(e, unit, var0, var1, false, nullptr, gob); break; } case SMART_EVENT_PASSENGER_REMOVED: case SMART_EVENT_PASSENGER_BOARDED: { if (!unit) return; RecalcTimer(e, e.event.minMax.repeatMin, e.event.minMax.repeatMax); ProcessAction(e, unit); break; } case SMART_EVENT_TIMED_EVENT_TRIGGERED: { if (e.event.timedEvent.id == var0) ProcessAction(e, unit); break; } case SMART_EVENT_GOSSIP_SELECT: { LOG_DEBUG("sql.sql", "SmartScript: Gossip Select: menu {} action {}", var0, var1); //little help for scripters if (e.event.gossip.sender != var0 || e.event.gossip.action != var1) return; ProcessAction(e, unit, var0, var1); break; } case SMART_EVENT_EVENT_PHASE_CHANGE: { if (!IsInPhase(e.event.eventPhaseChange.phasemask)) return; WorldObject* templastInvoker = GetLastInvoker(); if (!templastInvoker) return; if (!IsUnit(templastInvoker)) return; ProcessAction(e, templastInvoker->ToUnit()); break; } case SMART_EVENT_GAME_EVENT_START: case SMART_EVENT_GAME_EVENT_END: { if (e.event.gameEvent.gameEventId != var0) return; ProcessAction(e, nullptr, var0); break; } case SMART_EVENT_GO_STATE_CHANGED: { if (e.event.goStateChanged.state != var0) return; ProcessAction(e, unit, var0, var1); break; } case SMART_EVENT_GO_EVENT_INFORM: { if (e.event.eventInform.eventId != var0) return; ProcessAction(e, nullptr, var0); break; } case SMART_EVENT_ACTION_DONE: { if (e.event.doAction.eventId != var0) return; ProcessAction(e, unit, var0); break; } case SMART_EVENT_FRIENDLY_HEALTH_PCT: { if (!me || !me->IsEngaged()) return; Unit* unitTarget = nullptr; switch (e.GetTargetType()) { case SMART_TARGET_CREATURE_RANGE: case SMART_TARGET_CREATURE_GUID: case SMART_TARGET_CREATURE_DISTANCE: case SMART_TARGET_CLOSEST_CREATURE: case SMART_TARGET_CLOSEST_PLAYER: case SMART_TARGET_PLAYER_RANGE: case SMART_TARGET_PLAYER_DISTANCE: { ObjectVector targets; GetTargets(targets, e); for (WorldObject* target : targets) { if (IsUnit(target) && me->IsFriendlyTo(target->ToUnit()) && target->ToUnit()->IsAlive() && target->ToUnit()->IsInCombat()) { uint32 healthPct = uint32(target->ToUnit()->GetHealthPct()); if (healthPct > e.event.friendlyHealthPct.hpPct) { continue; } unitTarget = target->ToUnit(); break; } } break; } case SMART_TARGET_SELF: case SMART_TARGET_ACTION_INVOKER: unitTarget = DoSelectLowestHpPercentFriendly((float)e.event.friendlyHealthPct.radius, 0, e.event.friendlyHealthPct.hpPct); break; default: return; } if (!unitTarget) return; ProcessTimedAction(e, e.event.friendlyHealthPct.repeatMin, e.event.friendlyHealthPct.repeatMax, unitTarget); break; } case SMART_EVENT_DISTANCE_CREATURE: { if (!me) return; WorldObject* creature = nullptr; if (e.event.distance.guid != 0) { creature = FindCreatureNear(me, e.event.distance.guid); if (!creature) return; if (!me->IsInRange(creature, 0, (float)e.event.distance.dist)) return; } else if (e.event.distance.entry != 0) { std::list list; me->GetCreatureListWithEntryInGrid(list, e.event.distance.entry, (float)e.event.distance.dist); if (!list.empty()) creature = list.front(); } if (creature) ProcessTimedAction(e, e.event.distance.repeat, e.event.distance.repeat); break; } case SMART_EVENT_DISTANCE_GAMEOBJECT: { if (!me) return; WorldObject* gameobject = nullptr; if (e.event.distance.guid != 0) { gameobject = FindGameObjectNear(me, e.event.distance.guid); if (!gameobject) return; if (!me->IsInRange(gameobject, 0, (float)e.event.distance.dist)) return; } else if (e.event.distance.entry != 0) { std::list list; me->GetGameObjectListWithEntryInGrid(list, e.event.distance.entry, (float)e.event.distance.dist); if (!list.empty()) gameobject = list.front(); } if (gameobject) ProcessTimedAction(e, e.event.distance.repeat, e.event.distance.repeat); break; } case SMART_EVENT_COUNTER_SET: if (e.event.counter.id != var0 || GetCounterValue(e.event.counter.id) != e.event.counter.value) return; ProcessTimedAction(e, e.event.counter.cooldownMin, e.event.counter.cooldownMax); break; case SMART_EVENT_NEAR_PLAYERS: { uint32 playerCount = 0; ObjectVector targets; GetWorldObjectsInDist(targets, static_cast(e.event.nearPlayer.radius)); if (!targets.empty()) { for (WorldObject* target : targets) if (IsPlayer(target) && !target->ToPlayer()->IsGameMaster()) playerCount++; if (playerCount >= e.event.nearPlayer.minCount) ProcessAction(e, unit); } RecalcTimer(e, e.event.nearPlayer.repeatMin, e.event.nearPlayer.repeatMax); break; } case SMART_EVENT_NEAR_PLAYERS_NEGATION: { uint32 playerCount = 0; ObjectVector targets; GetWorldObjectsInDist(targets, static_cast(e.event.nearPlayerNegation.radius)); if (!targets.empty()) { for (WorldObject* target : targets) if (IsPlayer(target) && !target->ToPlayer()->IsGameMaster()) playerCount++; if (playerCount < e.event.nearPlayerNegation.maxCount) ProcessAction(e, unit); } RecalcTimer(e, e.event.nearPlayerNegation.repeatMin, e.event.nearPlayerNegation.repeatMax); break; } case SMART_EVENT_NEAR_UNIT: { uint32 unitCount = 0; ObjectVector targets; GetWorldObjectsInDist(targets, static_cast(e.event.nearUnit.range)); if (!targets.empty()) { if (e.event.nearUnit.type) { for (WorldObject* target : targets) { if (IsGameObject(target) && target->GetEntry() == e.event.nearUnit.entry) unitCount++; } } else { for (WorldObject* target : targets) { if (IsCreature(target) && target->GetEntry() == e.event.nearUnit.entry) unitCount++; } } if (unitCount >= e.event.nearUnit.count) ProcessAction(e, unit); } RecalcTimer(e, e.event.nearUnit.timer, e.event.nearUnit.timer); break; } case SMART_EVENT_NEAR_UNIT_NEGATION: { uint32 unitCount = 0; ObjectVector targets; GetWorldObjectsInDist(targets, static_cast(e.event.nearUnitNegation.range)); if (!targets.empty()) { if (e.event.nearUnitNegation.type) { for (WorldObject* target : targets) { if (IsGameObject(target) && target->GetEntry() == e.event.nearUnitNegation.entry) unitCount++; } } else { for (WorldObject* target : targets) { if (IsCreature(target) && target->GetEntry() == e.event.nearUnitNegation.entry) unitCount++; } } if (unitCount < e.event.nearUnitNegation.count) ProcessAction(e, unit); } RecalcTimer(e, e.event.nearUnitNegation.timer, e.event.nearUnitNegation.timer); break; } case SMART_EVENT_AREA_CASTING: { if (!me || !me->IsEngaged()) return; ThreatContainer::StorageType threatList = me->GetThreatMgr().GetThreatList(); for (ThreatContainer::StorageType::const_iterator i = threatList.begin(); i != threatList.end(); ++i) { if (Unit* target = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid())) { if (!target || !IsPlayer(target) || !target->IsNonMeleeSpellCast(false, false, true)) continue; if (!(me->IsInRange(target, (float)e.event.minMaxRepeat.rangeMin, (float)e.event.minMaxRepeat.rangeMax))) continue; ProcessAction(e, target); RecalcTimer(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); return; } // If no targets are found and it's off cooldown, check again in 1200ms RecalcTimer(e, 1200, 1200); break; } break; } case SMART_EVENT_AREA_RANGE: { if (!me || !me->IsEngaged()) return; ThreatContainer::StorageType threatList = me->GetThreatMgr().GetThreatList(); for (ThreatContainer::StorageType::const_iterator i = threatList.begin(); i != threatList.end(); ++i) { if (Unit* target = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid())) { if (!(me->IsInRange(target, (float)e.event.minMaxRepeat.rangeMin, (float)e.event.minMaxRepeat.rangeMax))) continue; ProcessAction(e, target); RecalcTimer(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); return; } } // If no targets are found and it's off cooldown, check again RecalcTimer(e, 1200, 1200); break; } case SMART_EVENT_WAYPOINT_REACHED: case SMART_EVENT_WAYPOINT_ENDED: { if (!me || (e.event.wpData.pointId && var0 != e.event.wpData.pointId) || (e.event.wpData.pathId && me->GetWaypointPath() != e.event.wpData.pathId)) return; ProcessAction(e, unit); break; } default: LOG_ERROR("sql.sql", "SmartScript::ProcessEvent: Unhandled Event type {}", e.GetEventType()); break; } } void SmartScript::InitTimer(SmartScriptHolder& e) { switch (e.GetEventType()) { //set only events which have initial timers case SMART_EVENT_NEAR_PLAYERS: case SMART_EVENT_NEAR_PLAYERS_NEGATION: RecalcTimer(e, e.event.nearPlayer.firstTimer, e.event.nearPlayer.firstTimer); break; case SMART_EVENT_UPDATE: case SMART_EVENT_UPDATE_IC: case SMART_EVENT_UPDATE_OOC: case SMART_EVENT_RANGE: case SMART_EVENT_AREA_RANGE: case SMART_EVENT_AREA_CASTING: case SMART_EVENT_IS_BEHIND_TARGET: case SMART_EVENT_FRIENDLY_HEALTH_PCT: case SMART_EVENT_IS_IN_MELEE_RANGE: RecalcTimer(e, e.event.minMaxRepeat.min, e.event.minMaxRepeat.max); break; case SMART_EVENT_DISTANCE_CREATURE: case SMART_EVENT_DISTANCE_GAMEOBJECT: RecalcTimer(e, e.event.distance.repeat, e.event.distance.repeat); break; case SMART_EVENT_NEAR_UNIT: case SMART_EVENT_NEAR_UNIT_NEGATION: RecalcTimer(e, e.event.nearUnit.timer, e.event.nearUnit.timer); break; default: e.active = true; break; } } void SmartScript::RecalcTimer(SmartScriptHolder& e, uint32 min, uint32 max) { // min/max was checked at loading! e.timer = urand(uint32(min), uint32(max)); e.active = e.timer ? false : true; } void SmartScript::UpdateTimer(SmartScriptHolder& e, uint32 const diff) { if (e.GetEventType() == SMART_EVENT_LINK) return; if (e.event.event_phase_mask && !IsInPhase(e.event.event_phase_mask)) return; if (e.GetEventType() == SMART_EVENT_UPDATE_IC && (!me || !me->IsEngaged())) return; if (e.GetEventType() == SMART_EVENT_UPDATE_OOC && (me && me->IsEngaged()))//can be used with me=nullptr (go script) return; if (e.timer < diff) { // delay spell cast for another AI tick if another spell is being cast if (e.GetActionType() == SMART_ACTION_CAST) { if (!(e.action.cast.castFlags & SMARTCAST_INTERRUPT_PREVIOUS)) { if (me && me->HasUnitState(UNIT_STATE_CASTING)) { RaisePriority(e); return; } } } // Delay flee for assist event if casting if (e.GetActionType() == SMART_ACTION_FLEE_FOR_ASSIST && me && me->HasUnitState(UNIT_STATE_CASTING)) { e.timer = 1200; return; } // @TODO: Can't these be handled by the action themselves instead? Less expensive e.active = true;//activate events with cooldown switch (e.GetEventType())//process ONLY timed events { case SMART_EVENT_NEAR_PLAYERS: case SMART_EVENT_NEAR_PLAYERS_NEGATION: case SMART_EVENT_NEAR_UNIT: case SMART_EVENT_NEAR_UNIT_NEGATION: case SMART_EVENT_UPDATE: case SMART_EVENT_UPDATE_OOC: case SMART_EVENT_UPDATE_IC: case SMART_EVENT_HEALTH_PCT: case SMART_EVENT_TARGET_HEALTH_PCT: case SMART_EVENT_MANA_PCT: case SMART_EVENT_TARGET_MANA_PCT: case SMART_EVENT_RANGE: case SMART_EVENT_AREA_RANGE: case SMART_EVENT_VICTIM_CASTING: case SMART_EVENT_AREA_CASTING: case SMART_EVENT_FRIENDLY_HEALTH: case SMART_EVENT_FRIENDLY_IS_CC: case SMART_EVENT_FRIENDLY_MISSING_BUFF: case SMART_EVENT_HAS_AURA: case SMART_EVENT_TARGET_BUFFED: case SMART_EVENT_IS_BEHIND_TARGET: case SMART_EVENT_FRIENDLY_HEALTH_PCT: case SMART_EVENT_DISTANCE_CREATURE: case SMART_EVENT_DISTANCE_GAMEOBJECT: case SMART_EVENT_IS_IN_MELEE_RANGE: { ASSERT(executionStack.empty()); executionStack.emplace_back(SmartScriptFrame{ e, nullptr, 0, 0, false, nullptr, nullptr }); while (!executionStack.empty()) { auto [stack_holder, stack_unit, stack_var0, stack_var1, stack_bvar, stack_spell, stack_gob] = executionStack.back(); executionStack.pop_back(); ProcessEvent(stack_holder, stack_unit, stack_var0, stack_var1, stack_bvar, stack_spell, stack_gob); } if (e.GetScriptType() == SMART_SCRIPT_TYPE_TIMED_ACTIONLIST) { e.enableTimed = false;//disable event if it is in an ActionList and was processed once for (SmartAIEventList::iterator i = mTimedActionList.begin(); i != mTimedActionList.end(); ++i) { //find the first event which is not the current one and enable it if (i->event_id > e.event_id) { i->enableTimed = true; break; } } } break; } } if (e.priority != SmartScriptHolder::DEFAULT_PRIORITY) { // Reset priority to default one only if the event hasn't been rescheduled again to next loop if (e.timer > 1) { // Re-sort events if this was moved to the top of the queue mEventSortingRequired = true; // Reset priority to default one e.priority = SmartScriptHolder::DEFAULT_PRIORITY; } } } else e.timer -= diff; } bool SmartScript::CheckTimer(SmartScriptHolder const& e) const { return e.active; } void SmartScript::InstallEvents() { if (!mInstallEvents.empty()) { for (SmartAIEventList::iterator i = mInstallEvents.begin(); i != mInstallEvents.end(); ++i) mEvents.push_back(*i);//must be before UpdateTimers mInstallEvents.clear(); } } void SmartScript::OnUpdate(uint32 const diff) { if ((mScriptType == SMART_SCRIPT_TYPE_CREATURE || mScriptType == SMART_SCRIPT_TYPE_GAMEOBJECT) && !GetBaseObject()) return; InstallEvents();//before UpdateTimers if (mEventSortingRequired) { SortEvents(mEvents); mEventSortingRequired = false; } for (SmartAIEventList::iterator i = mEvents.begin(); i != mEvents.end(); ++i) UpdateTimer(*i, diff); if (!mStoredEvents.empty()) { SmartAIEventStoredList::iterator i, icurr; for (i = mStoredEvents.begin(); i != mStoredEvents.end();) { icurr = i++; UpdateTimer(*icurr, diff); } } bool needCleanup = true; if (!mTimedActionList.empty()) { isProcessingTimedActionList = true; for (SmartAIEventList::iterator i = mTimedActionList.begin(); i != mTimedActionList.end(); ++i) { if ((*i).enableTimed) { UpdateTimer(*i, diff); needCleanup = false; } } isProcessingTimedActionList = false; } if (needCleanup) mTimedActionList.clear(); if (!mRemIDs.empty()) { for (std::list::const_iterator i = mRemIDs.begin(); i != mRemIDs.end(); ++i) RemoveStoredEvent(*i); // xinef: clear list after cleaning... mRemIDs.clear(); } if (mUseTextTimer && me) { if (mTextTimer < diff) { uint32 textID = mLastTextID; mLastTextID = 0; uint32 entry = mTalkerEntry; mTalkerEntry = 0; mTextTimer = 0; mUseTextTimer = false; ProcessEventsFor(SMART_EVENT_TEXT_OVER, nullptr, textID, entry); } else mTextTimer -= diff; } } void SmartScript::SortEvents(SmartAIEventList& events) { std::sort(events.begin(), events.end()); } void SmartScript::RaisePriority(SmartScriptHolder& e) { e.timer = 1200; // Change priority only if it's set to default, otherwise keep the current order of events if (e.priority == SmartScriptHolder::DEFAULT_PRIORITY) { e.priority = mCurrentPriority++; mEventSortingRequired = true; } } void SmartScript::RetryLater(SmartScriptHolder& e, bool ignoreChanceRoll) { RaisePriority(e); // This allows to retry the action later without rolling again the chance roll (which might fail and end up not executing the action) if (ignoreChanceRoll) e.event.event_flags |= SMART_EVENT_FLAG_TEMP_IGNORE_CHANCE_ROLL; e.runOnce = false; } void SmartScript::FillScript(SmartAIEventList e, WorldObject* obj, AreaTrigger const* at) { (void)at; // ensure that the variable is referenced even if extra logs are disabled in order to pass compiler checks if (e.empty()) { if (obj) LOG_DEBUG("sql.sql", "SmartScript: EventMap for Entry {} is empty but is using SmartScript.", obj->GetEntry()); if (at) LOG_DEBUG("sql.sql", "SmartScript: EventMap for AreaTrigger {} is empty but is using SmartScript.", at->entry); return; } for (SmartAIEventList::iterator i = e.begin(); i != e.end(); ++i) { #ifndef ACORE_DEBUG if ((*i).event.event_flags & SMART_EVENT_FLAG_DEBUG_ONLY) continue; #endif if ((*i).event.event_flags & SMART_EVENT_FLAG_DIFFICULTY_ALL)//if has instance flag add only if in it { if (obj && obj->GetMap()->IsDungeon()) { if ((1 << (obj->GetMap()->GetSpawnMode() + 1)) & (*i).event.event_flags) { mEvents.push_back((*i)); } } continue; } mEvents.push_back((*i));//NOTE: 'world(0)' events still get processed in ANY instance mode } } void SmartScript::GetScript() { SmartAIEventList e; if (me) { e = sSmartScriptMgr->GetScript(-((int32)me->GetSpawnId()), mScriptType); if (e.empty()) e = sSmartScriptMgr->GetScript((int32)me->GetEntry(), mScriptType); FillScript(e, me, nullptr); if (CreatureTemplate const* cInfo = me->GetCreatureTemplate()) { if (cInfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_DONT_OVERRIDE_ENTRY_SAI)) { e = sSmartScriptMgr->GetScript((int32)me->GetEntry(), mScriptType); FillScript(e, me, nullptr); } } } else if (go) { e = sSmartScriptMgr->GetScript(-((int32)go->GetSpawnId()), mScriptType); if (e.empty()) e = sSmartScriptMgr->GetScript((int32)go->GetEntry(), mScriptType); FillScript(e, go, nullptr); } else if (trigger) { e = sSmartScriptMgr->GetScript((int32)trigger->entry, mScriptType); FillScript(e, nullptr, trigger); } } void SmartScript::OnInitialize(WorldObject* obj, AreaTrigger const* at) { if (obj)//handle object based scripts { switch (obj->GetTypeId()) { case TYPEID_UNIT: mScriptType = SMART_SCRIPT_TYPE_CREATURE; me = obj->ToCreature(); LOG_DEBUG("sql.sql", "SmartScript::OnInitialize: source is Creature {}", me->GetEntry()); break; case TYPEID_GAMEOBJECT: mScriptType = SMART_SCRIPT_TYPE_GAMEOBJECT; go = obj->ToGameObject(); LOG_DEBUG("sql.sql", "SmartScript::OnInitialize: source is GameObject {}", go->GetEntry()); break; default: LOG_ERROR("scripts.ai.sai", "SmartScript::OnInitialize: Unhandled TypeID !WARNING!"); return; } } else if (at) { mScriptType = SMART_SCRIPT_TYPE_AREATRIGGER; trigger = at; LOG_DEBUG("sql.sql", "SmartScript::OnInitialize: source is AreaTrigger {}", trigger->entry); } else { LOG_ERROR("scripts.ai.sai", "SmartScript::OnInitialize: !WARNING! Initialized objects are nullptr."); return; } GetScript();//load copy of script for (SmartScriptHolder& event : mEvents) InitTimer(event);//calculate timers for first time use ProcessEventsFor(SMART_EVENT_AI_INIT); InstallEvents(); ProcessEventsFor(SMART_EVENT_JUST_CREATED); } void SmartScript::OnMoveInLineOfSight(Unit* who) { if (!me) return; ProcessEventsFor(me->IsEngaged() ? SMART_EVENT_IC_LOS : SMART_EVENT_OOC_LOS, who); } /* void SmartScript::UpdateAIWhileCharmed(const uint32 diff) { } void SmartScript::DoAction(int32 param) { } uint32 SmartScript::GetData(uint32 id) { return 0; } void SmartScript::SetData(uint32 id, uint32 value) { } void SmartScript::SetGUID(ObjectGuid guid, int32 id) { } ObjectGuid SmartScript::GetGUID(int32 id) { return ObjectGuid::Empty; } void SmartScript::MovepointStart(uint32 id) { } void SmartScript::SetRun(bool run) { } void SmartScript::SetMovePathEndAction(SMART_ACTION action) { } uint32 SmartScript::DoChat(int8 id, ObjectGuid whisperGuid) { return 0; }*/ // SmartScript end Unit* SmartScript::DoSelectLowestHpFriendly(float range, uint32 MinHPDiff) const { if (!me) return nullptr; Unit* unit = nullptr; Acore::MostHPMissingInRange u_check(me, range, MinHPDiff); Acore::UnitLastSearcher searcher(me, unit, u_check); Cell::VisitObjects(me, searcher, range); return unit; } Unit* SmartScript::DoSelectLowestHpPercentFriendly(float range, uint32 minHpPct, uint32 maxHpPct) const { if (!me) { return nullptr; } Unit* unit = nullptr; Acore::MostHPPercentMissingInRange u_check(me, range, minHpPct, maxHpPct); Acore::UnitLastSearcher searcher(me, unit, u_check); Cell::VisitObjects(me, searcher, range); return unit; } void SmartScript::DoFindFriendlyCC(std::vector& creatures, float range) const { if (!me) return; Acore::FriendlyCCedInRange u_check(me, range); Acore::CreatureListSearcher searcher(me, creatures, u_check); Cell::VisitObjects(me, searcher, range); } void SmartScript::DoFindFriendlyMissingBuff(std::vector& creatures, float range, uint32 spellid) const { if (!me) return; Acore::FriendlyMissingBuffInRange u_check(me, range, spellid); Acore::CreatureListSearcher searcher(me, creatures, u_check); Cell::VisitObjects(me, searcher, range); } Unit* SmartScript::DoFindClosestFriendlyInRange(float range, bool playerOnly) const { if (!me) return nullptr; Unit* unit = nullptr; Acore::AnyFriendlyNotSelfUnitInObjectRangeCheck u_check(me, me, range, playerOnly); Acore::UnitLastSearcher searcher(me, unit, u_check); Cell::VisitObjects(me, searcher, range); return unit; } void SmartScript::SetScript9(SmartScriptHolder& e, uint32 entry) { //do NOT clear mTimedActionList if it's being iterated because it will invalidate the iterator and delete // any SmartScriptHolder contained like the "e" parameter passed to this function if (isProcessingTimedActionList) { LOG_ERROR("scripts.ai.sai", "Entry {} SourceType {} Event {} Action {} is trying to overwrite timed action list from a timed action, this is not allowed!.", e.entryOrGuid, e.GetScriptType(), e.GetEventType(), e.GetActionType()); return; } // Do NOT allow to start a new actionlist if a previous one is already running, unless explicitly allowed. We need to always finish the current actionlist if (!e.action.timedActionList.allowOverride && !mTimedActionList.empty()) { return; } mTimedActionList.clear(); mTimedActionList = sSmartScriptMgr->GetScript(entry, SMART_SCRIPT_TYPE_TIMED_ACTIONLIST); if (mTimedActionList.empty()) return; for (SmartAIEventList::iterator i = mTimedActionList.begin(); i != mTimedActionList.end(); ++i) { i->enableTimed = i == mTimedActionList.begin();//enable processing only for the first action if (e.action.timedActionList.timerType == 0) i->event.type = SMART_EVENT_UPDATE_OOC; else if (e.action.timedActionList.timerType == 1) i->event.type = SMART_EVENT_UPDATE_IC; else if (e.action.timedActionList.timerType > 1) i->event.type = SMART_EVENT_UPDATE; InitTimer((*i)); } } WorldObject* SmartScript::GetLastInvoker(WorldObject* invoker) const { // Xinef: Look for invoker only on map of base object... Prevents multithreaded crashes if (GetBaseObject()) return ObjectAccessor::GetWorldObject(*GetBaseObject(), mLastInvoker); // xinef: used for area triggers invoker cast else if (invoker) return ObjectAccessor::GetWorldObject(*invoker, mLastInvoker); return nullptr; } bool SmartScript::IsUnit(WorldObject* obj) { return obj && (obj->IsCreature() || obj->IsPlayer()); } bool SmartScript::IsPlayer(WorldObject* obj) { return obj && obj->IsPlayer(); } bool SmartScript::IsCreature(WorldObject* obj) { return obj && obj->IsCreature(); } bool SmartScript::IsCharmedCreature(WorldObject* obj) { if (!obj) return false; if (Creature* creatureObj = obj->ToCreature()) return creatureObj->IsCharmed(); return false; } bool SmartScript::IsGameObject(WorldObject* obj) { return obj && obj->IsGameObject(); } void SmartScript::IncPhase(uint32 p) { // protect phase from overflowing SetPhase(std::min(SMART_EVENT_PHASE_12, mEventPhase + p)); } void SmartScript::DecPhase(uint32 p) { if (p >= mEventPhase) { SetPhase(0); } else { SetPhase(mEventPhase - p); } } void SmartScript::SetPhase(uint32 p) { uint32 oldPhase = mEventPhase; mEventPhase = p; if (oldPhase != mEventPhase) { ProcessEventsFor(SMART_EVENT_EVENT_PHASE_CHANGE); } } bool SmartScript::IsInPhase(uint32 p) const { if (mEventPhase == 0) { return false; } return ((1 << (mEventPhase - 1)) & p) != 0; } void SmartScript::AddCreatureSummon(ObjectGuid const& guid) { _summonList.insert(guid); } void SmartScript::RemoveCreatureSummon(ObjectGuid const& guid) { _summonList.erase(guid); }