/* * This file is part of the TrinityCore 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 "CellImpl.h" #include "ChatTextBuilder.h" #include "Containers.h" #include "Creature.h" #include "CreatureTextMgr.h" #include "CreatureTextMgrImpl.h" #include "DB2Stores.h" #include "GameEventMgr.h" #include "GameEventSender.h" #include "GameObject.h" #include "GameTime.h" #include "GossipDef.h" #include "GridNotifiersImpl.h" #include "Group.h" #include "InstanceScript.h" #include "Language.h" #include "Log.h" #include "Map.h" #include "MotionMaster.h" #include "MovementTypedefs.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "PhasingHandler.h" #include "Random.h" #include "ScriptActions.h" #include "SmartAI.h" #include "SpellMgr.h" #include "SpellAuras.h" #include "TemporarySummon.h" #include "Vehicle.h" #include "WaypointDefines.h" #include "WaypointManager.h" SmartScript::SmartScript() { go = nullptr; me = nullptr; player = nullptr; trigger = nullptr; areaTrigger = nullptr; sceneTemplate = nullptr; quest = nullptr; event = 0; mEventPhase = 0; mPathId = 0; mTextTimer = 0; mLastTextID = 0; mUseTextTimer = false; mTalkerEntry = 0; mScriptType = SMART_SCRIPT_TYPE_CREATURE; isProcessingTimedActionList = false; mCurrentPriority = 0; mEventSortingRequired = false; mNestedEventsCounter = 0; mAllEventFlags = 0; } SmartScript::SmartScript(SmartScript const& other) = default; SmartScript::SmartScript(SmartScript&& other) noexcept = default; SmartScript& SmartScript::operator=(SmartScript const& other) = default; SmartScript& SmartScript::operator=(SmartScript&& other) noexcept = default; SmartScript::~SmartScript() = default; 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) TC_LOG_ERROR("sql.sql", "SmartScript: Action target Creature (GUID: {} Entry: {}) is not using SmartAI, action called by Creature (GUID: {} Entry: {}) skipped to prevent crash.", c->GetSpawnId(), c->GetEntry(), uint64(me ? me->GetSpawnId() : UI64LIT(0)), 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) TC_LOG_ERROR("sql.sql", "SmartScript: Action target GameObject (GUID: {} Entry: {}) is not using SmartGameObjectAI, action called by GameObject (GUID: {} Entry: {}) skipped to prevent crash.", g->GetSpawnId(), g->GetEntry(), uint64(go ? go->GetSpawnId() : UI64LIT(0)), 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::ClearTargetList(uint32 id) { _storedTargets.erase(id); } void SmartScript::StoreTargetList(ObjectVector const& targets, uint32 id) { // insert or replace _storedTargets.erase(id); _storedTargets.emplace(id, ObjectGuidVector(targets)); } void SmartScript::AddToStoredTargetList(ObjectVector const& targets, uint32 id) { auto [itr, inserted] = _storedTargets.try_emplace(id, targets); if (!inserted) for (WorldObject* obj : targets) itr->second.AddGuid(obj->GetGUID()); } ObjectVector const* SmartScript::GetStoredTargetVector(uint32 id, WorldObject const& ref) const { auto itr = _storedTargets.find(id); if (itr != _storedTargets.end()) return itr->second.GetObjectVector(ref); return nullptr; } void SmartScript::StoreCounter(uint32 id, uint32 value, uint32 reset) { CounterMap::iterator itr = mCounterList.find(id); if (itr != mCounterList.end()) { if (reset == 0) itr->second += value; else itr->second = value; } else mCounterList.insert(std::make_pair(id, value)); ProcessEventsFor(SMART_EVENT_COUNTER_SET, nullptr, id); } uint32 SmartScript::GetCounterValue(uint32 id) const { CounterMap::const_iterator itr = mCounterList.find(id); if (itr != mCounterList.end()) return itr->second; return 0; } GameObject* SmartScript::FindGameObjectNear(WorldObject* searchObject, ObjectGuid::LowType guid) const { auto bounds = searchObject->GetMap()->GetGameObjectBySpawnIdStore().equal_range(guid); if (bounds.first == bounds.second) return nullptr; return bounds.first->second; } Creature* SmartScript::FindCreatureNear(WorldObject* searchObject, ObjectGuid::LowType guid) const { auto bounds = searchObject->GetMap()->GetCreatureBySpawnIdStore().equal_range(guid); if (bounds.first == bounds.second) return nullptr; auto creatureItr = std::find_if(bounds.first, bounds.second, [](Map::CreatureBySpawnIdContainer::value_type const& pair) { return pair.second->IsAlive(); }); return creatureItr != bounds.second ? creatureItr->second : bounds.first->second; } void SmartScript::OnReset() { ResetBaseObject(); for (SmartScriptHolder& event : mEvents) { if (!(event.event.event_flags & SMART_EVENT_FLAG_DONT_RESET)) { InitTimer(event); event.runOnce = false; } if (event.priority != SmartScriptHolder::DEFAULT_PRIORITY) { event.priority = SmartScriptHolder::DEFAULT_PRIORITY; mEventSortingRequired = true; } } ProcessEventsFor(SMART_EVENT_RESET); mLastInvoker.Clear(); } void SmartScript::ResetBaseObject() { WorldObject* lookupRoot = me; if (!lookupRoot) lookupRoot = go; if (lookupRoot) { if (!meOrigGUID.IsEmpty()) { if (Creature* m = ObjectAccessor::GetCreature(*lookupRoot, meOrigGUID)) { me = m; go = nullptr; areaTrigger = nullptr; player = nullptr; } } if (!goOrigGUID.IsEmpty()) { if (GameObject* o = ObjectAccessor::GetGameObject(*lookupRoot, goOrigGUID)) { me = nullptr; go = o; areaTrigger = nullptr; player = nullptr; } } } goOrigGUID.Clear(); meOrigGUID.Clear(); } void SmartScript::ProcessEventsFor(SMART_EVENT e, Unit* unit, uint32 var0, uint32 var1, bool bvar, SpellInfo const* spell, GameObject* gob, std::string_view varString) { mNestedEventsCounter++; // Allow only a fixed number of nested ProcessEventsFor calls if (mNestedEventsCounter > MAX_NESTED_EVENTS) { TC_LOG_WARN("scripts.ai", "SmartScript::ProcessEventsFor: reached the limit of max allowed nested ProcessEventsFor() calls with event {}, skipping!\n{}", e, GetBaseObject()->GetDebugInfo()); } else { for (SmartScriptHolder& event : mEvents) { SMART_EVENT eventType = SMART_EVENT(event.GetEventType()); if (eventType == SMART_EVENT_LINK)//special handling continue; if (eventType == e) if (sConditionMgr->IsObjectMeetingSmartEventConditions(event.entryOrGuid, event.event_id, event.source_type, unit, GetBaseObject())) ProcessEvent(event, unit, var0, var1, bvar, spell, gob, varString); } } --mNestedEventsCounter; } namespace { template , typename... Args> static std::shared_ptr CreateTimedActionListWaitEventFor(SmartScriptHolder const& e, Args&&... args) { if (e.GetScriptType() != SMART_SCRIPT_TYPE_TIMED_ACTIONLIST) return nullptr; if (!(e.event.event_flags & SMART_EVENT_FLAG_ACTIONLIST_WAITS)) return nullptr; return std::make_shared(std::forward(args)...); } template struct MultiActionResult : Scripting::v2::ActionResult { std::vector> Results; explicit MultiActionResult(std::size_t estimatedSize) { Results.reserve(estimatedSize); } bool IsReady() const noexcept override { return std::ranges::all_of(Results, [](Scripting::v2::ActionResult const& result) { return result.IsReady(); }); } }; } void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, uint32 var1, bool bvar, SpellInfo const* spell, GameObject* gob, std::string_view varString) { e.runOnce = true; //used for repeat check // calc random if (e.GetEventType() != SMART_EVENT_LINK && e.event.event_chance < 100 && e.event.event_chance && !(e.event.event_flags & SMART_EVENT_FLAG_TEMP_IGNORE_CHANCE_ROLL)) { if (!roll_chance_i(e.event.event_chance)) 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(); if (Unit* tempInvoker = GetLastInvoker()) TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: Invoker: {} {}", tempInvoker->GetName(), tempInvoker->GetGUID()); ObjectVector targets; GetTargets(targets, e, Coalesce(unit, gob)); switch (e.GetActionType()) { case SMART_ACTION_TALK: { Creature* talker = e.target.type == 0 ? me : nullptr; Unit* talkTarget = nullptr; for (WorldObject* target : targets) { if (Unit* targetUnit = target->ToUnit()) { if (targetUnit->IsPet()) // Prevented sending text to pets. continue; if (targetUnit->IsPlayer() || e.action.talk.useTalkTarget) { talker = me; talkTarget = targetUnit; } else talker = targetUnit->ToCreature(); break; } } if (!talkTarget) talkTarget = GetLastInvoker(); if (!talker) break; mTalkerEntry = talker->GetEntry(); mLastTextID = e.action.talk.textGroupID; mTextTimer = e.action.talk.duration; mUseTextTimer = true; uint32 duration = sCreatureTextMgr->SendChat(talker, uint8(e.action.talk.textGroupID), talkTarget); mTimedActionWaitEvent = CreateTimedActionListWaitEventFor(e, GameTime::Now() + Milliseconds(duration)); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_TALK: talker: {} {}, textGuid: {}", talker->GetName(), talker->GetGUID(), Object::GetGUID(talkTarget)); break; } case SMART_ACTION_SIMPLE_TALK: { uint32 duration = 0; for (WorldObject* target : targets) { if (Creature* creatureTarget = target->ToCreature()) duration = std::max(sCreatureTextMgr->SendChat(creatureTarget, uint8(e.action.simpleTalk.textGroupID), Object::ToPlayer(GetLastInvoker())), duration); else if (Player* playerTarget = target->ToPlayer(); playerTarget && me) duration = std::max(sCreatureTextMgr->SendChat(me, uint8(e.action.simpleTalk.textGroupID), Object::ToPlayer(GetLastInvoker()), CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_NORMAL, 0, SoundKitPlayType::Normal, TEAM_OTHER, false, playerTarget), duration); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SIMPLE_TALK: talker: {} {}, textGroupId: {}", target->GetName(), target->GetGUID(), uint8(e.action.simpleTalk.textGroupID)); } mTimedActionWaitEvent = CreateTimedActionListWaitEventFor(e, GameTime::Now() + Milliseconds(duration)); break; } case SMART_ACTION_PLAY_EMOTE: { for (WorldObject* target : targets) { if (Unit* unitTarget = target->ToUnit()) { unitTarget->HandleEmoteCommand(static_cast(e.action.emote.emote)); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_PLAY_EMOTE: target: {} {}, emote: {}", target->GetName(), target->GetGUID(), e.action.emote.emote); } } break; } case SMART_ACTION_SOUND: { for (WorldObject* target : targets) { 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, e.action.sound.keyBroadcastTextId); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SOUND: target: {} {}, sound: {}, onlyself: {}", target->GetName(), target->GetGUID(), e.action.sound.sound, e.action.sound.onlySelf); } break; } case SMART_ACTION_SET_FACTION: { for (WorldObject* target : targets) { if (Creature* creatureTarget = target->ToCreature()) { if (e.action.faction.factionID) { creatureTarget->SetFaction(e.action.faction.factionID); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SET_FACTION: Creature {} set faction to {}", target->GetGUID(), e.action.faction.factionID); } else { creatureTarget->RestoreFaction(); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SET_FACTION: Creature {} set faction to {}", target->GetGUID(), creatureTarget->GetFaction()); } } } break; } case SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: { for (WorldObject* target : targets) { Creature* creatureTarget = target->ToCreature(); if (!creatureTarget) 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); creatureTarget->SetDisplayId(model->CreatureDisplayID); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: Creature {} set displayid to {}", target->GetGUID(), model->CreatureDisplayID); } } //if no param1, then use value from param2 (modelId) else { creatureTarget->SetDisplayId(e.action.morphOrMount.model); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: Creature {} set displayid to {}", target->GetGUID(), e.action.morphOrMount.model); } } else { creatureTarget->DeMorph(); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: Creature {} demorphs.", target->GetGUID()); } } break; } case SMART_ACTION_FAIL_QUEST: { for (WorldObject* target : targets) { if (Player* playerTarget = target->ToPlayer()) { playerTarget->FailQuest(e.action.quest.quest); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_FAIL_QUEST: Player {} fails quest {}", target->GetGUID(), 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)) { player->PlayerTalkClass->SendQuestGiverQuestDetails(q, me->GetGUID(), true, false); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_OFFER_QUEST: Player {} - offering quest {}", player->GetGUID(), e.action.questOffer.questID); } } else { player->AddQuestAndCheckCompletion(q, nullptr); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_OFFER_QUEST: Player {} - quest {} added", player->GetGUID(), e.action.questOffer.questID); } } } } break; } case SMART_ACTION_SET_REACT_STATE: { for (WorldObject* target : targets) if (Creature* creatureTarget = target->ToCreature()) creatureTarget->SetReactState(ReactStates(e.action.react.state)); break; } case SMART_ACTION_RANDOM_EMOTE: { std::array> emotes; auto emotesEnd = std::ranges::remove_copy(e.action.randomEmote.emotes, emotes.begin(), 0u).out; for (WorldObject* target : targets) { if (Unit* unitTarget = target->ToUnit()) { Emote emote = static_cast(Trinity::Containers::SelectRandomContainerElement(Trinity::IteratorPair(emotes.begin(), emotesEnd))); unitTarget->HandleEmoteCommand(emote); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_RANDOM_EMOTE: Creature {} handle random emote {}", target->GetGUID(), emote); } } break; } case SMART_ACTION_THREAT_ALL_PCT: { if (!me) break; for (auto* ref : me->GetThreatManager().GetModifiableThreatList()) { ref->ModifyThreatByPercent(std::max(-100,int32(e.action.threatPCT.threatINC) - int32(e.action.threatPCT.threatDEC))); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_THREAT_ALL_PCT: Creature {} modify threat for unit {}, value {}", me->GetGUID(), ref->GetVictim()->GetGUID(), 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 (Unit* unitTarget = target->ToUnit()) { me->GetThreatManager().ModifyThreatByPercent(unitTarget, std::max(-100, int32(e.action.threatPCT.threatINC) - int32(e.action.threatPCT.threatDEC))); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_THREAT_SINGLE_PCT: Creature {} modify threat for unit {}, value {}", me->GetGUID(), target->GetGUID(), int32(e.action.threatPCT.threatINC) - int32(e.action.threatPCT.threatDEC)); } } break; } case SMART_ACTION_CAST: { if (targets.empty()) break; if (e.action.cast.targetsLimit > 0 && targets.size() > e.action.cast.targetsLimit) Trinity::Containers::RandomResize(targets, e.action.cast.targetsLimit); bool failedSpellCast = false, successfulSpellCast = false; CastSpellExtraArgs args; if (e.action.cast.castFlags & SMARTCAST_TRIGGERED) { if (e.action.cast.triggerFlags) args.TriggerFlags = TriggerCastFlags(e.action.cast.triggerFlags); else args.TriggerFlags = TRIGGERED_FULL_MASK; } std::shared_ptr> waitEvent = CreateTimedActionListWaitEventFor>(e, targets.size()); for (WorldObject* target : targets) { if (e.action.cast.castFlags & SMARTCAST_AURA_NOT_PRESENT && (!target->IsUnit() || target->ToUnit()->HasAura(e.action.cast.spell))) { TC_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()); continue; } if (waitEvent) { args.SetScriptResult(Scripting::v2::ActionResult::GetResultSetter({ waitEvent, &waitEvent->Results.emplace_back() })); args.SetScriptWaitsForSpellHit((e.action.cast.castFlags & SMARTCAST_WAIT_FOR_HIT) != 0); } SpellCastResult result = SPELL_FAILED_BAD_TARGETS; if (me) { if (e.action.cast.castFlags & SMARTCAST_INTERRUPT_PREVIOUS) me->InterruptNonMeleeSpells(false); result = me->CastSpell(target, e.action.cast.spell, args); } else if (go) result = go->CastSpell(target, e.action.cast.spell, args); bool spellCastFailed = result != SPELL_CAST_OK && result != SPELL_FAILED_SPELL_IN_PROGRESS; if (me && 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. ENSURE_AI(SmartAI, me->AI())->SetCombatMove(spellCastFailed, true); } if (spellCastFailed) failedSpellCast = true; else successfulSpellCast = true; TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_CAST:: {} casts spell {} on target {} with castflags {}", me ? me->GetGUID() : go->GetGUID(), e.action.cast.spell, target->GetGUID(), e.action.cast.castFlags); } if (waitEvent && !waitEvent->Results.empty()) mTimedActionWaitEvent = std::move(waitEvent); // 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) Trinity::Containers::RandomResize(targets, e.action.cast.targetsLimit); std::shared_ptr> waitEvent = CreateTimedActionListWaitEventFor>(e, targets.size()); CastSpellExtraArgs args; if (e.action.cast.castFlags & SMARTCAST_TRIGGERED) { if (e.action.cast.triggerFlags) args.TriggerFlags = TriggerCastFlags(e.action.cast.triggerFlags); else args.TriggerFlags = TRIGGERED_FULL_MASK; } for (WorldObject* target : targets) { if (e.action.cast.castFlags & SMARTCAST_AURA_NOT_PRESENT && (!target->IsUnit() || target->ToUnit()->HasAura(e.action.cast.spell))) continue; if (waitEvent) { args.SetScriptResult(Scripting::v2::ActionResult::GetResultSetter({ waitEvent, &waitEvent->Results.emplace_back() })); args.SetScriptWaitsForSpellHit((e.action.cast.castFlags & SMARTCAST_WAIT_FOR_HIT) != 0); } if (e.action.cast.castFlags & SMARTCAST_INTERRUPT_PREVIOUS && target->IsUnit()) target->ToUnit()->InterruptNonMeleeSpells(false); target->CastSpell(target, e.action.cast.spell, args); } if (waitEvent && !waitEvent->Results.empty()) mTimedActionWaitEvent = std::move(waitEvent); break; } case SMART_ACTION_INVOKER_CAST: { Unit* tempLastInvoker = GetLastInvoker(unit); if (!tempLastInvoker) break; if (targets.empty()) break; if (e.action.cast.targetsLimit) Trinity::Containers::RandomResize(targets, e.action.cast.targetsLimit); CastSpellExtraArgs args; if (e.action.cast.castFlags & SMARTCAST_TRIGGERED) { if (e.action.cast.triggerFlags) args.TriggerFlags = TriggerCastFlags(e.action.cast.triggerFlags); else args.TriggerFlags = TRIGGERED_FULL_MASK; } std::shared_ptr> waitEvent = CreateTimedActionListWaitEventFor>(e, targets.size()); for (WorldObject* target : targets) { if (e.action.cast.castFlags & SMARTCAST_AURA_NOT_PRESENT && (!target->IsUnit() || target->ToUnit()->HasAura(e.action.cast.spell))) { TC_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()); continue; } if (e.action.cast.castFlags & SMARTCAST_INTERRUPT_PREVIOUS) tempLastInvoker->InterruptNonMeleeSpells(false); if (waitEvent) { args.SetScriptResult(Scripting::v2::ActionResult::GetResultSetter({ waitEvent, &waitEvent->Results.emplace_back() })); args.SetScriptWaitsForSpellHit((e.action.cast.castFlags & SMARTCAST_WAIT_FOR_HIT) != 0); } tempLastInvoker->CastSpell(target, e.action.cast.spell, args); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_INVOKER_CAST: Invoker {} casts spell {} on target {} with castflags {}", tempLastInvoker->GetGUID(), e.action.cast.spell, target->GetGUID(), e.action.cast.castFlags); } if (waitEvent && !waitEvent->Results.empty()) mTimedActionWaitEvent = std::move(waitEvent); break; } case SMART_ACTION_ACTIVATE_GOBJECT: { for (WorldObject* target : targets) { if (GameObject* gameObject = target->ToGameObject()) { // Activate gameObject->SetLootState(GO_READY); gameObject->UseDoorOrButton(0, false, unit); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_ACTIVATE_GOBJECT. Gameobject {} activated", target->GetGUID()); } } break; } case SMART_ACTION_RESET_GOBJECT: { for (WorldObject* target : targets) { if (GameObject* gameObject = target->ToGameObject()) { gameObject->ResetDoorOrButton(); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_RESET_GOBJECT. Gameobject {} reset", target->GetGUID()); } } break; } case SMART_ACTION_SET_EMOTE_STATE: { for (WorldObject* target : targets) { if (Unit* unitTarget = target->ToUnit()) { unitTarget->SetEmoteState(Emote(e.action.emote.emote)); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SET_EMOTE_STATE. Unit {} set emotestate to {}", target->GetGUID(), e.action.emote.emote); } } break; } case SMART_ACTION_AUTO_ATTACK: { me->SetCanMelee(e.action.autoAttack.attack != 0); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_AUTO_ATTACK: Creature: {} bool on = {}", me->GetGUID(), e.action.autoAttack.attack); break; } case SMART_ACTION_ALLOW_COMBAT_MOVEMENT: { if (!IsSmart()) break; bool move = e.action.combatMove.move != 0; ENSURE_AI(SmartAI, me->AI())->SetCombatMove(move); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_ALLOW_COMBAT_MOVEMENT: Creature {} bool on = {}", me->GetGUID(), e.action.combatMove.move); break; } case SMART_ACTION_SET_EVENT_PHASE: { if (!GetBaseObject()) break; SetPhase(e.action.setEventPhase.phase); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SET_EVENT_PHASE: Creature {} set event phase {}", GetBaseObject()->GetGUID(), e.action.setEventPhase.phase); break; } case SMART_ACTION_INC_EVENT_PHASE: { if (!GetBaseObject()) break; IncPhase(e.action.incEventPhase.inc); DecPhase(e.action.incEventPhase.dec); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_INC_EVENT_PHASE: Creature {} inc event phase by {}, " "decrease by {}", GetBaseObject()->GetGUID(), e.action.incEventPhase.inc, e.action.incEventPhase.dec); break; } case SMART_ACTION_EVADE: { if (!me) break; // Reset home position to respawn position if specified in the parameters if (e.action.evade.toRespawnPosition == 0) { float homeX, homeY, homeZ, homeO; me->GetRespawnPosition(homeX, homeY, homeZ, &homeO); me->SetHomePosition(homeX, homeY, homeZ, homeO); } me->AI()->EnterEvadeMode(); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_EVADE: Creature {} EnterEvadeMode", me->GetGUID()); break; } case SMART_ACTION_FLEE_FOR_ASSIST: { if (!me) break; me->DoFleeToGetAssistance(); if (e.action.fleeAssist.withEmote) { Trinity::BroadcastTextBuilder builder(me, CHAT_MSG_MONSTER_EMOTE, BROADCAST_TEXT_FLEE_FOR_ASSIST, me->GetGender()); CreatureTextMgr::SendChatPacket(me, builder, CHAT_MSG_MONSTER_EMOTE); } TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_FLEE_FOR_ASSIST: Creature {} DoFleeToGetAssistance", me->GetGUID()); break; } case SMART_ACTION_COMBAT_STOP: { if (!me) break; me->CombatStop(true); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_COMBAT_STOP: {} CombatStop", me->GetGUID()); break; } case SMART_ACTION_REMOVEAURASFROMSPELL: { for (WorldObject* target : targets) { Unit* unitTarget = target->ToUnit(); if (!unitTarget) continue; if (e.action.removeAura.spell) { ObjectGuid casterGUID; if (e.action.removeAura.onlyOwnedAuras) { if (!me) break; casterGUID = me->GetGUID(); } if (e.action.removeAura.charges) { if (Aura* aur = unitTarget->GetAura(e.action.removeAura.spell, casterGUID)) aur->ModCharges(-static_cast(e.action.removeAura.charges), AURA_REMOVE_BY_EXPIRE); } else unitTarget->RemoveAurasDueToSpell(e.action.removeAura.spell, casterGUID); } else unitTarget->RemoveAllAuras(); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_REMOVEAURASFROMSPELL: Unit {}, spell {}", target->GetGUID(), e.action.removeAura.spell); } break; } case SMART_ACTION_FOLLOW: { if (!IsSmart()) break; if (targets.empty()) { ENSURE_AI(SmartAI, me->AI())->StopFollow(false); break; } for (WorldObject* target : targets) { if (Unit* unitTarget = target->ToUnit()) { float angle = e.action.follow.angle > 6 ? (e.action.follow.angle * M_PI / 180.0f) : e.action.follow.angle; ENSURE_AI(SmartAI, me->AI())->SetFollow(unitTarget, float(e.action.follow.dist) + 0.1f, angle, e.action.follow.credit, e.action.follow.entry, e.action.follow.creditType); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_FOLLOW: Creature {} following target {}", me->GetGUID(), target->GetGUID()); break; } } break; } case SMART_ACTION_RANDOM_PHASE: { if (!GetBaseObject()) break; std::array> phases; auto phasesEnd = std::ranges::remove_copy(e.action.randomPhase.phases, phases.begin(), 0u).out; uint32 phase = Trinity::Containers::SelectRandomContainerElement(Trinity::IteratorPair(phases.begin(), phasesEnd)); SetPhase(phase); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_RANDOM_PHASE: Creature {} sets event phase to {}", GetBaseObject()->GetGUID(), phase); break; } case SMART_ACTION_RANDOM_PHASE_RANGE: { if (!GetBaseObject()) break; uint32 phase = urand(e.action.randomPhaseRange.phaseMin, e.action.randomPhaseRange.phaseMax); SetPhase(phase); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_RANDOM_PHASE_RANGE: Creature {} sets event phase to {}", GetBaseObject()->GetGUID(), phase); break; } case SMART_ACTION_CALL_KILLEDMONSTER: { if (e.target.type == SMART_TARGET_NONE || e.target.type == SMART_TARGET_SELF) // Loot recipient and his group members { if (!me) break; for (ObjectGuid tapperGuid : me->GetTapList()) { if (Player* tapper = ObjectAccessor::GetPlayer(*me, tapperGuid)) { tapper->KilledMonsterCredit(e.action.killedMonster.creature, me->GetGUID()); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_CALL_KILLEDMONSTER: Player {}, Killcredit: {}", tapper->GetGUID(), e.action.killedMonster.creature); } } } else // Specific target type { for (WorldObject* target : targets) { if (Player* playerTarget = target->ToPlayer()) { playerTarget->KilledMonsterCredit(e.action.killedMonster.creature); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_CALL_KILLEDMONSTER: Player {}, Killcredit: {}", target->GetGUID(), e.action.killedMonster.creature); } else if (Unit* unitTarget = target->ToUnit()) // Special handling for vehicles if (Vehicle* vehicle = unitTarget->GetVehicleKit()) for (std::pair& seat : vehicle->Seats) if (Player* player = ObjectAccessor::GetPlayer(*target, seat.second.Passenger.Guid)) player->KilledMonsterCredit(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) { TC_LOG_ERROR("sql.sql", "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); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_SET_INST_DATA: SetData 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)); TC_LOG_DEBUG("scripts.ai", "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: // Static analysis break; } break; } case SMART_ACTION_SET_INST_DATA64: { WorldObject* obj = GetBaseObject(); if (!obj) obj = unit; if (!obj) break; InstanceScript* instance = obj->GetInstanceScript(); if (!instance) { TC_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()); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_SET_INST_DATA64: Field: {}, data: {}", e.action.setInstanceData64.field, targets.front()->GetGUID()); break; } case SMART_ACTION_UPDATE_TEMPLATE: { for (WorldObject* target : targets) if (Creature* creatureTarget = target->ToCreature()) creatureTarget->UpdateEntry(e.action.updateTemplate.creature, nullptr, e.action.updateTemplate.updateLevel != 0); break; } case SMART_ACTION_DIE: { if (me && !me->isDead()) { me->KillSelf(); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_DIE: Creature {}", me->GetGUID()); } break; } case SMART_ACTION_SET_IN_COMBAT_WITH_ZONE: { if (me && me->IsAIEnabled()) { me->AI()->DoZoneInCombat(); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_SET_IN_COMBAT_WITH_ZONE: Creature {}", me->GetGUID()); } break; } case SMART_ACTION_CALL_FOR_HELP: { if (me) { me->CallForHelp(float(e.action.callHelp.range)); if (e.action.callHelp.withEmote) { Trinity::BroadcastTextBuilder builder(me, CHAT_MSG_MONSTER_EMOTE, BROADCAST_TEXT_CALL_FOR_HELP, me->GetGender()); sCreatureTextMgr->SendChatPacket(me, builder, CHAT_MSG_MONSTER_EMOTE); } TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_CALL_FOR_HELP: Creature {}", me->GetGUID()); } break; } case SMART_ACTION_SET_SHEATH: { if (me) { me->SetSheath(SheathState(e.action.setSheath.sheath)); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_SET_SHEATH: Creature {}, State: {}", me->GetGUID(), e.action.setSheath.sheath); } break; } case SMART_ACTION_FORCE_DESPAWN: { // there should be at least a world update tick before despawn, to avoid breaking linked actions Milliseconds despawnDelay(e.action.forceDespawn.delay); if (despawnDelay <= 0ms) despawnDelay = 1ms; Seconds forceRespawnTimer(e.action.forceDespawn.forceRespawnTimer); for (WorldObject* target : targets) { if (Creature* creature = target->ToCreature()) creature->DespawnOrUnsummon(despawnDelay, forceRespawnTimer); else if (GameObject* goTarget = target->ToGameObject()) goTarget->DespawnOrUnsummon(despawnDelay, forceRespawnTimer); } break; } case SMART_ACTION_SET_INGAME_PHASE_ID: { for (WorldObject* target : targets) { if (e.action.ingamePhaseId.apply == 1) PhasingHandler::AddPhase(target, e.action.ingamePhaseId.id, true); else PhasingHandler::RemovePhase(target, e.action.ingamePhaseId.id, true); } break; } case SMART_ACTION_SET_INGAME_PHASE_GROUP: { for (WorldObject* target : targets) { if (e.action.ingamePhaseGroup.apply == 1) PhasingHandler::AddPhaseGroup(target, e.action.ingamePhaseGroup.groupId, true); else PhasingHandler::RemovePhaseGroup(target, e.action.ingamePhaseGroup.groupId, true); } break; } case SMART_ACTION_MOUNT_TO_ENTRY_OR_MODEL: { for (WorldObject* target : targets) { Unit* unitTarget = target->ToUnit(); if (!unitTarget) 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)) unitTarget->Mount(ObjectMgr::ChooseDisplayId(cInfo)->CreatureDisplayID); } else unitTarget->Mount(e.action.morphOrMount.model); } else unitTarget->Dismount(); } break; } case SMART_ACTION_SET_INVINCIBILITY_HP_LEVEL: { for (WorldObject* target : targets) { if (Creature* creatureTarget = target->ToCreature()) { SmartAI* ai = CAST_AI(SmartAI, creatureTarget->AI()); if (!ai) continue; if (e.action.invincHP.percent) ai->SetInvincibilityHpLevel(creatureTarget->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(); if (IsSmart(cTarget, true)) ENSURE_AI(SmartAI, ai)->SetData(e.action.setData.field, e.action.setData.data, me); else ai->SetData(e.action.setData.field, e.action.setData.data); } else if (GameObject* oTarget = target->ToGameObject()) { GameObjectAI* ai = oTarget->AI(); if (IsSmart(oTarget, true)) ENSURE_AI(SmartGameObjectAI, ai)->SetData(e.action.setData.field, e.action.setData.data, me); else ai->SetData(e.action.setData.field, e.action.setData.data); } } break; } case SMART_ACTION_ATTACK_STOP: { for (WorldObject* target : targets) if (Unit* unitTarget = target->ToUnit()) unitTarget->AttackStop(); break; } case SMART_ACTION_MOVE_OFFSET: { std::shared_ptr> waitEvent = CreateTimedActionListWaitEventFor>(e, targets.size()); for (WorldObject* target : targets) { Creature* creatureTarget = target->ToCreature(); if (!creatureTarget) continue; if (!(e.event.event_flags & SMART_EVENT_FLAG_WHILE_CHARMED) && creatureTarget->IsCharmed()) continue; Position pos = target->GetPosition(); // Use forward/backward/left/right cartesian plane movement float x, y, z, o; o = pos.GetOrientation(); x = pos.GetPositionX() + (std::cos(o - (M_PI / 2))*e.target.x) + (std::cos(o)*e.target.y); y = pos.GetPositionY() + (std::sin(o - (M_PI / 2))*e.target.x) + (std::sin(o)*e.target.y); z = pos.GetPositionZ() + e.target.z; Optional> scriptResult; if (waitEvent) scriptResult = Scripting::v2::ActionResult::GetResultSetter({ waitEvent, &waitEvent->Results.emplace_back() }); creatureTarget->GetMotionMaster()->MovePoint(e.action.moveOffset.PointId, x, y, z, true, {}, {}, MovementWalkRunSpeedSelectionMode::Default, {}, std::move(scriptResult)); } if (waitEvent && !waitEvent->Results.empty()) mTimedActionWaitEvent = std::move(waitEvent); break; } case SMART_ACTION_SET_VISIBILITY: { for (WorldObject* target : targets) if (Unit* unitTarget = target->ToUnit()) unitTarget->SetVisible(e.action.visibility.state ? true : false); break; } case SMART_ACTION_SET_ACTIVE: { for (WorldObject* target : targets) target->setActive(e.action.active.state ? true : false); break; } case SMART_ACTION_ATTACK_START: { if (!me) break; std::erase_if(targets, [](WorldObject const* target) { return !target->IsUnit(); }); if (targets.empty()) break; // attack random target if (Unit * target = Trinity::Containers::SelectRandomContainerElement(targets)->ToUnit()) me->AI()->AttackStart(target); break; } case SMART_ACTION_SUMMON_CREATURE: { EnumFlag flags(static_cast(e.action.summonCreature.flags)); bool preferUnit = flags.HasFlag(SmartActionSummonCreatureFlags::PreferUnit); bool attackInvoker = flags.HasFlag(SmartActionSummonCreatureFlags::AttackInvoker); WorldObject* summoner = preferUnit ? unit : GetBaseObjectOrUnitInvoker(unit); if (!summoner) break; ObjectGuid privateObjectOwner; if (flags.HasFlag(SmartActionSummonCreatureFlags::PersonalSpawn)) privateObjectOwner = summoner->IsPrivateObject() ? summoner->GetPrivateObjectOwner() : summoner->GetGUID(); uint32 spawnsCount = std::max(e.action.summonCreature.count, 1u); if (e.action.summonCreature.storedTargetId) ClearTargetList(e.action.summonCreature.storedTargetId); SummonPropertiesEntry const* summonProperties = [&]() -> SummonPropertiesEntry const* { if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(e.action.summonCreature.createdBySpell, DIFFICULTY_NONE)) for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects()) if (spellEffectInfo.IsEffect(SPELL_EFFECT_SUMMON)) if (SummonPropertiesEntry const* summonProps = sSummonPropertiesStore.LookupEntry(spellEffectInfo.MiscValueB)) return summonProps; return nullptr; }(); auto DoSummon = [&](float x, float y, float z, float o, Unit* attackTarget) { for (uint32 counter = 0; counter < spawnsCount; counter++) { if (TempSummon* summon = summoner->GetMap()->SummonCreature(e.action.summonCreature.creature, { x, y, z, o }, summonProperties, Milliseconds(e.action.summonCreature.duration), summoner, e.action.summonCreature.createdBySpell, 0, privateObjectOwner)) { summon->SetTempSummonType((TempSummonType)e.action.summonCreature.type); if (e.action.summonCreature.storedTargetId) AddToStoredTargetList({ summon }, e.action.summonCreature.storedTargetId); if (attackInvoker && attackTarget) summon->AI()->AttackStart(attackTarget); } } }; 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; DoSummon(x, y, z, o, target->ToUnit()); } if (e.GetTargetType() != SMART_TARGET_POSITION) break; DoSummon(e.target.x, e.target.y, e.target.z, e.target.o, unit); break; } case SMART_ACTION_SUMMON_GO: { WorldObject* summoner = GetBaseObjectOrUnitInvoker(unit); if (!summoner) break; if (e.action.summonGO.storedTargetId) ClearTargetList(e.action.summonGO.storedTargetId); for (WorldObject* target : targets) { Position pos = target->GetPositionWithOffset(Position(e.target.x, e.target.y, e.target.z, e.target.o)); QuaternionData rot = QuaternionData::fromEulerAnglesZYX(pos.GetOrientation(), 0.f, 0.f); GameObject* summon = summoner->SummonGameObject(e.action.summonGO.entry, pos, rot, Seconds(e.action.summonGO.despawnTime), GOSummonType(e.action.summonGO.summonType)); if (e.action.summonGO.storedTargetId && summon) AddToStoredTargetList({ summon }, e.action.summonGO.storedTargetId); } if (e.GetTargetType() != SMART_TARGET_POSITION) break; QuaternionData rot = QuaternionData::fromEulerAnglesZYX(e.target.o, 0.f, 0.f); GameObject* summon = summoner->SummonGameObject(e.action.summonGO.entry, Position(e.target.x, e.target.y, e.target.z, e.target.o), rot, Seconds(e.action.summonGO.despawnTime), GOSummonType(e.action.summonGO.summonType)); if (e.action.summonGO.storedTargetId && summon) AddToStoredTargetList({ summon }, e.action.summonGO.storedTargetId); break; } case SMART_ACTION_KILL_UNIT: { for (WorldObject* target : targets) if (Unit* unitTarget = target->ToUnit()) unitTarget->KillSelf(); break; } case SMART_ACTION_ADD_ITEM: { for (WorldObject* target : targets) if (Player* playerTarget = target->ToPlayer()) playerTarget->AddItem(e.action.item.entry, e.action.item.count); break; } case SMART_ACTION_REMOVE_ITEM: { for (WorldObject* target : targets) if (Player* playerTarget = target->ToPlayer()) playerTarget->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 (Player* playerTarget = target->ToPlayer()) playerTarget->TeleportTo(e.action.teleport.mapID, e.target.x, e.target.y, e.target.z, e.target.o); else if (Creature* creatureTarget = target->ToCreature()) creatureTarget->NearTeleportTo(e.target.x, e.target.y, e.target.z, e.target.o); } break; } case SMART_ACTION_SET_DISABLE_GRAVITY: { for (WorldObject* target : targets) if (Creature* creatureTarget = target->ToCreature()) creatureTarget->SetFloating(e.action.setDisableGravity.disable != 0); break; } case SMART_ACTION_SET_RUN: { if (!IsSmart()) break; ENSURE_AI(SmartAI, me->AI())->SetRun(e.action.setRun.run != 0); break; } case SMART_ACTION_SET_COUNTER: { if (!targets.empty()) { for (WorldObject* target : targets) { if (Creature* creatureTarget = target->ToCreature()) { if (SmartAI* ai = CAST_AI(SmartAI, creatureTarget->AI())) ai->GetScript()->StoreCounter(e.action.setCounter.counterId, e.action.setCounter.value, e.action.setCounter.reset); else TC_LOG_ERROR("sql.sql", "SmartScript: Action target for SMART_ACTION_SET_COUNTER is not using SmartAI, skipping"); } else if (GameObject* gameObject = target->ToGameObject()) { if (SmartGameObjectAI* ai = CAST_AI(SmartGameObjectAI, gameObject->AI())) ai->GetScript()->StoreCounter(e.action.setCounter.counterId, e.action.setCounter.value, e.action.setCounter.reset); else TC_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); break; } case SMART_ACTION_WP_START: { if (!IsSmart()) break; uint32 entry = e.action.wpStart.pathID; bool repeat = e.action.wpStart.repeat != 0; for (WorldObject* target : targets) { if (target->IsPlayer()) { StoreTargetList(targets, SMART_ESCORT_TARGETS); break; } } std::shared_ptr> waitEvent = CreateTimedActionListWaitEventFor(e); Optional> scriptResult; if (waitEvent) scriptResult = Scripting::v2::ActionResult::GetResultSetter(waitEvent); ENSURE_AI(SmartAI, me->AI())->StartPath(entry, repeat, unit, 0, std::move(scriptResult)); mTimedActionWaitEvent = std::move(waitEvent); uint32 quest = e.action.wpStart.quest; uint32 DespawnTime = e.action.wpStart.despawnTime; ENSURE_AI(SmartAI, me->AI())->SetEscortQuest(quest); ENSURE_AI(SmartAI, me->AI())->SetDespawnTime(DespawnTime); break; } case SMART_ACTION_WP_PAUSE: { if (!IsSmart()) break; uint32 delay = e.action.wpPause.delay; ENSURE_AI(SmartAI, me->AI())->PausePath(delay, true); break; } case SMART_ACTION_WP_STOP: { if (!IsSmart()) break; uint32 DespawnTime = e.action.wpStop.despawnTime; uint32 quest = e.action.wpStop.quest; bool fail = e.action.wpStop.fail != 0; ENSURE_AI(SmartAI, me->AI())->StopPath(DespawnTime, quest, fail); break; } case SMART_ACTION_WP_RESUME: { if (!IsSmart()) break; // Set the timer to 1 ms so the path will be resumed on next update loop if (ENSURE_AI(SmartAI, me->AI())->CanResumePath()) ENSURE_AI(SmartAI, me->AI())->SetWPPauseTimer(1); break; } case SMART_ACTION_SET_ORIENTATION: { if (!me) break; if (e.GetTargetType() == SMART_TARGET_SELF) me->SetFacingTo((me->GetTransport() ? me->GetTransportHomePosition() : me->GetHomePosition()).GetOrientation()); else if (e.GetTargetType() == SMART_TARGET_POSITION) me->SetFacingTo(e.target.o); else if (!targets.empty()) me->SetFacingToObject(targets.front()); break; } case SMART_ACTION_PLAYMOVIE: { for (WorldObject* target : targets) if (Player* playerTarget = target->ToPlayer()) playerTarget->SendMovieStart(e.action.movie.entry); break; } case SMART_ACTION_MOVE_TO_POS: { if (!IsSmart()) break; WorldObject* target = nullptr; // we want to move to random element if (!targets.empty()) target = Trinity::Containers::SelectRandomContainerElement(targets); std::shared_ptr> waitEvent = CreateTimedActionListWaitEventFor(e); Optional> scriptResult; if (waitEvent) scriptResult = Scripting::v2::ActionResult::GetResultSetter(waitEvent); if (target) { float x, y, z; target->GetPosition(x, y, z); if (e.action.moveToPos.ContactDistance > 0) target->GetContactPoint(me, x, y, z, e.action.moveToPos.ContactDistance); me->GetMotionMaster()->MovePoint(e.action.moveToPos.pointId, x + e.target.x, y + e.target.y, z + e.target.z, e.action.moveToPos.disablePathfinding == 0); mTimedActionWaitEvent = std::move(waitEvent); } if (e.GetTargetType() != SMART_TARGET_POSITION) break; Position dest(e.target.x, e.target.y, e.target.z); if (e.action.moveToPos.transport) if (TransportBase* trans = me->GetDirectTransport()) trans->CalculatePassengerPosition(dest.m_positionX, dest.m_positionY, dest.m_positionZ); me->GetMotionMaster()->MovePoint(e.action.moveToPos.pointId, dest, e.action.moveToPos.disablePathfinding == 0, {}, {}, MovementWalkRunSpeedSelectionMode::Default, {}, std::move(scriptResult)); mTimedActionWaitEvent = std::move(waitEvent); break; } case SMART_ACTION_ENABLE_TEMP_GOBJ: { for (WorldObject* target : targets) { if (target->IsCreature()) TC_LOG_WARN("sql.sql", "Invalid creature target '{}' (entry {}, spawnId {}) specified for SMART_ACTION_ENABLE_TEMP_GOBJ", target->GetName(), target->GetEntry(), target->ToCreature()->GetSpawnId()); else if (GameObject* gameObject = target->ToGameObject()) { if (gameObject->isSpawnedByDefault()) TC_LOG_WARN("sql.sql", "Invalid gameobject target '{}' (entry {}, spawnId {}) for SMART_ACTION_ENABLE_TEMP_GOBJ - the object is spawned by default", target->GetName(), target->GetEntry(), gameObject->GetSpawnId()); else gameObject->SetRespawnTime(e.action.enableTempGO.duration); } } break; } case SMART_ACTION_CLOSE_GOSSIP: { for (WorldObject* target : targets) if (Player* playerTarget = target->ToPlayer()) playerTarget->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) { TC_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->Items), std::end(eInfo->Items), std::begin(slot)); } else { slot[0].ItemId = e.action.equip.slot1; slot[1].ItemId = e.action.equip.slot2; slot[2].ItemId = e.action.equip.slot3; } for (uint32 i = 0; i < MAX_EQUIPMENT_ITEMS; ++i) if (!e.action.equip.mask || (e.action.equip.mask & (1 << i))) npc->SetVirtualItem(i, slot[i].ItemId, slot[i].AppearanceModId, slot[i].ItemVisual); } } break; } case SMART_ACTION_CREATE_TIMED_EVENT: { SmartScriptHolder& ev = mStoredEvents.emplace_back(); ev.event_id = e.action.timeEvent.id; ev.event.type = (SMART_EVENT)SMART_EVENT_UPDATE; ev.event.event_chance = e.action.timeEvent.chance; if (!ev.event.event_chance) ev.event.event_chance = 100; ev.event.minMaxRepeat.min = e.action.timeEvent.min; ev.event.minMaxRepeat.max = e.action.timeEvent.max; ev.event.minMaxRepeat.repeatMin = e.action.timeEvent.repeatMin; ev.event.minMaxRepeat.repeatMax = e.action.timeEvent.repeatMax; ev.event.event_flags = 0; if (!ev.event.minMaxRepeat.repeatMin && !ev.event.minMaxRepeat.repeatMax) ev.event.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; ev.action.type = (SMART_ACTION)SMART_ACTION_TRIGGER_TIMED_EVENT; ev.action.timeEvent.id = e.action.timeEvent.id; ev.target = e.target; InitTimer(ev); break; } case SMART_ACTION_TRIGGER_TIMED_EVENT: { ProcessEventsFor((SMART_EVENT)SMART_EVENT_TIMED_EVENT_TRIGGERED, nullptr, e.action.timeEvent.id); // 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_CALL_SCRIPT_RESET: { SetPhase(0); 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 (ENSURE_AI(SmartAI, creature->AI())->CanCombatMove()) creature->StartDefaultCombatMovement(creature->GetVictim(), attackDistance, attackAngle); } break; } case SMART_ACTION_CALL_TIMED_ACTIONLIST: { if (e.GetTargetType() == SMART_TARGET_NONE) { TC_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)) ENSURE_AI(SmartAI, creature->AI())->SetTimedActionList(e, e.action.timedActionList.id, GetLastInvoker()); } else if (GameObject* goTarget = target->ToGameObject()) { if (IsSmart(goTarget)) ENSURE_AI(SmartGameObjectAI, goTarget->AI())->SetTimedActionList(e, e.action.timedActionList.id, GetLastInvoker()); } else if (AreaTrigger* areaTriggerTarget = target->ToAreaTrigger()) if (SmartAreaTriggerAI* atSAI = CAST_AI(SmartAreaTriggerAI, areaTriggerTarget->AI())) atSAI->SetTimedActionList(e, e.action.timedActionList.id, GetLastInvoker()); } break; } case SMART_ACTION_SET_NPC_FLAG: { for (WorldObject* target : targets) if (Creature* creatureTarget = target->ToCreature()) creatureTarget->ReplaceAllNpcFlags(NPCFlags(e.action.flag.flag)); break; } case SMART_ACTION_ADD_NPC_FLAG: { for (WorldObject* target : targets) if (Creature* creatureTarget = target->ToCreature()) creatureTarget->SetNpcFlag(NPCFlags(e.action.flag.flag)); break; } case SMART_ACTION_REMOVE_NPC_FLAG: { for (WorldObject* target : targets) if (Creature* creatureTarget = target->ToCreature()) creatureTarget->RemoveNpcFlag(NPCFlags(e.action.flag.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, SMART_ACTION_NONE, 0, 0, 0, 0, 0, 0, 0, (SMARTAI_TARGETS)e.action.crossCast.targetType, e.action.crossCast.targetParam1, e.action.crossCast.targetParam2, e.action.crossCast.targetParam3, e.action.crossCast.targetParam4, e.action.param_string, 0), unit); std::shared_ptr> waitEvent = CreateTimedActionListWaitEventFor>(e, casters.size()* targets.size()); CastSpellExtraArgs args; if (e.action.crossCast.castFlags & SMARTCAST_TRIGGERED) args.TriggerFlags = TRIGGERED_FULL_MASK; for (WorldObject* caster : casters) { Unit* casterUnit = caster->ToUnit(); if (!casterUnit) continue; bool interruptedSpell = false; for (WorldObject* target : targets) { if (e.action.crossCast.castFlags & SMARTCAST_AURA_NOT_PRESENT && (!target->IsUnit() || target->ToUnit()->HasAura(e.action.crossCast.spell))) { TC_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()); continue; } if (!interruptedSpell && e.action.crossCast.castFlags & SMARTCAST_INTERRUPT_PREVIOUS) { casterUnit->InterruptNonMeleeSpells(false); interruptedSpell = true; } if (waitEvent) { args.SetScriptResult(Scripting::v2::ActionResult::GetResultSetter({ waitEvent, &waitEvent->Results.emplace_back() })); args.SetScriptWaitsForSpellHit((e.action.crossCast.castFlags & SMARTCAST_WAIT_FOR_HIT) != 0); } casterUnit->CastSpell(target, e.action.crossCast.spell, args); } } if (waitEvent && !waitEvent->Results.empty()) mTimedActionWaitEvent = std::move(waitEvent); break; } case SMART_ACTION_CALL_RANDOM_TIMED_ACTIONLIST: { std::array> actionLists; auto actionListsEnd = std::ranges::remove_copy(e.action.randTimedActionList.actionLists, actionLists.begin(), 0u).out; uint32 id = Trinity::Containers::SelectRandomContainerElement(Trinity::IteratorPair(actionLists.begin(), actionListsEnd)); if (e.GetTargetType() == SMART_TARGET_NONE) { TC_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)) ENSURE_AI(SmartAI, creature->AI())->SetTimedActionList(e, id, GetLastInvoker()); } else if (GameObject* goTarget = target->ToGameObject()) { if (IsSmart(goTarget)) ENSURE_AI(SmartGameObjectAI, goTarget->AI())->SetTimedActionList(e, id, GetLastInvoker()); } else if (AreaTrigger* areaTriggerTarget = target->ToAreaTrigger()) if (SmartAreaTriggerAI* atSAI = CAST_AI(SmartAreaTriggerAI, areaTriggerTarget->AI())) atSAI->SetTimedActionList(e, id, GetLastInvoker()); } break; } case SMART_ACTION_CALL_RANDOM_RANGE_TIMED_ACTIONLIST: { uint32 id = urand(e.action.randRangeTimedActionList.idMin, e.action.randRangeTimedActionList.idMax); if (e.GetTargetType() == SMART_TARGET_NONE) { TC_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)) ENSURE_AI(SmartAI, creature->AI())->SetTimedActionList(e, id, GetLastInvoker()); } else if (GameObject* goTarget = target->ToGameObject()) { if (IsSmart(goTarget)) ENSURE_AI(SmartGameObjectAI, goTarget->AI())->SetTimedActionList(e, id, GetLastInvoker()); } else if (AreaTrigger* areaTriggerTarget = target->ToAreaTrigger()) if (SmartAreaTriggerAI* atSAI = CAST_AI(SmartAreaTriggerAI, areaTriggerTarget->AI())) atSAI->SetTimedActionList(e, id, GetLastInvoker()); } break; } case SMART_ACTION_ACTIVATE_TAXI: { std::shared_ptr> waitEvent = CreateTimedActionListWaitEventFor>(e, targets.size()); for (WorldObject* target : targets) { if (Player* playerTarget = target->ToPlayer()) { Optional> scriptResult; if (waitEvent) scriptResult = Scripting::v2::ActionResult::GetResultSetter({ waitEvent, &waitEvent->Results.emplace_back() }); if (!playerTarget->ActivateTaxiPathTo(e.action.taxi.id, 0, {}, scriptResult)) if (scriptResult) scriptResult->SetResult(MovementStopReason::Interrupted); } } if (waitEvent && !waitEvent->Results.empty()) mTimedActionWaitEvent = std::move(waitEvent); break; } case SMART_ACTION_RANDOM_MOVE: { bool foundTarget = false; for (WorldObject* target : targets) { if (Creature* creatureTarget = target->ToCreature()) { foundTarget = true; if (e.action.moveRandom.distance) creatureTarget->GetMotionMaster()->MoveRandom(float(e.action.moveRandom.distance)); else creatureTarget->GetMotionMaster()->MoveIdle(); } } if (!foundTarget && me && me->IsCreature()) { 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 (Unit* unitTarget = target->ToUnit()) { switch (e.action.setunitByte.type) { case 0: unitTarget->SetStandState(UnitStandStateType(e.action.setunitByte.byte1)); break; case 1: // pet talent points break; case 2: unitTarget->SetVisFlag(UnitVisFlags(e.action.setunitByte.byte1)); break; case 3: unitTarget->SetAnimTier(AnimTier(e.action.setunitByte.byte1)); break; } } break; } case SMART_ACTION_REMOVE_UNIT_FIELD_BYTES_1: { for (WorldObject* target : targets) if (Unit* unitTarget = target->ToUnit()) { switch (e.action.setunitByte.type) { case 0: unitTarget->SetStandState(UNIT_STAND_STATE_STAND); break; case 1: // pet talent points break; case 2: unitTarget->RemoveVisFlag(UnitVisFlags(e.action.setunitByte.byte1)); break; case 3: unitTarget->SetAnimTier(AnimTier::Ground); break; } } break; } case SMART_ACTION_INTERRUPT_SPELL: { for (WorldObject* target : targets) if (Unit* unitTarget = target->ToUnit()) unitTarget->InterruptNonMeleeSpells(e.action.interruptSpellCasting.withDelayed != 0, e.action.interruptSpellCasting.spell_id, e.action.interruptSpellCasting.withInstant != 0); break; } case SMART_ACTION_JUMP_TO_POS: { WorldObject* target = nullptr; if (!targets.empty()) target = Trinity::Containers::SelectRandomContainerElement(targets); Position pos(e.target.x, e.target.y, e.target.z); if (target) { float x, y, z; target->GetPosition(x, y, z); if (e.action.jump.ContactDistance > 0) target->GetContactPoint(me, x, y, z, e.action.jump.ContactDistance); pos = Position(x + e.target.x, y + e.target.y, z + e.target.z); } else if (e.GetTargetType() != SMART_TARGET_POSITION) break; std::shared_ptr> waitEvent = CreateTimedActionListWaitEventFor(e); Optional> actionResultSetter; if (waitEvent) actionResultSetter = Scripting::v2::ActionResult::GetResultSetter(waitEvent); if (e.action.jump.Gravity || e.action.jump.UseDefaultGravity) { float gravity = e.action.jump.UseDefaultGravity ? Movement::gravity : e.action.jump.Gravity; me->GetMotionMaster()->MoveJumpWithGravity(pos, float(e.action.jump.SpeedXY), gravity, e.action.jump.PointId, {}, false, nullptr, nullptr, std::move(actionResultSetter)); } else me->GetMotionMaster()->MoveJump(pos, float(e.action.jump.SpeedXY), float(e.action.jump.SpeedZ), e.action.jump.PointId, {}, false, nullptr, nullptr, std::move(actionResultSetter)); mTimedActionWaitEvent = std::move(waitEvent); break; } case SMART_ACTION_GO_SET_LOOT_STATE: { for (WorldObject* target : targets) if (GameObject* gameObject = target->ToGameObject()) gameObject->SetLootState((LootState)e.action.setGoLootState.state); break; } case SMART_ACTION_GO_SET_GO_STATE: { for (WorldObject* target : targets) if (GameObject* gameObject = target->ToGameObject()) gameObject->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 (Creature* creatureTarget = target->ToCreature()) { if (SmartAI* ai = CAST_AI(SmartAI, creatureTarget->AI())) ai->GetScript()->StoreTargetList(ObjectVector(*storedTargets), e.action.sendTargetToTarget.id); // store a copy of target list else TC_LOG_ERROR("sql.sql", "SmartScript: Action target for SMART_ACTION_SEND_TARGET_TO_TARGET is not using SmartAI, skipping"); } else if (GameObject* gameObject = target->ToGameObject()) { if (SmartGameObjectAI* ai = CAST_AI(SmartGameObjectAI, gameObject->AI())) ai->GetScript()->StoreTargetList(ObjectVector(*storedTargets), e.action.sendTargetToTarget.id); // store a copy of target list else TC_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() || !IsSmart()) break; TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SEND_GOSSIP_MENU: gossipMenuId {}, gossipNpcTextId {}", e.action.sendGossipMenu.gossipMenuId, e.action.sendGossipMenu.gossipNpcTextId); // override default gossip if (me) ENSURE_AI(SmartAI, me->AI())->SetGossipReturn(true); else if (go) ENSURE_AI(SmartGameObjectAI, go->AI())->SetGossipReturn(true); for (WorldObject* target : targets) { if (Player* player = target->ToPlayer()) { if (e.action.sendGossipMenu.gossipMenuId) player->PrepareGossipMenu(GetBaseObject(), e.action.sendGossipMenu.gossipMenuId, true); else player->PlayerTalkClass->ClearMenus(); uint32 gossipNpcTextId = e.action.sendGossipMenu.gossipNpcTextId; if (!gossipNpcTextId) gossipNpcTextId = player->GetGossipTextId(e.action.sendGossipMenu.gossipMenuId, GetBaseObject()); player->PlayerTalkClass->SendGossipMenu(gossipNpcTextId, GetBaseObject()->GetGUID()); } } break; } case SMART_ACTION_SET_HOME_POS: { for (WorldObject* target : targets) { if (Creature* creatureTarget = target->ToCreature()) { if (e.GetTargetType() == SMART_TARGET_SELF) creatureTarget->SetHomePosition(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), me->GetOrientation()); else if (e.GetTargetType() == SMART_TARGET_POSITION) creatureTarget->SetHomePosition(e.target.x, e.target.y, e.target.z, e.target.o); else if (e.GetTargetType() == SMART_TARGET_CREATURE_RANGE || e.GetTargetType() == SMART_TARGET_CREATURE_GUID || e.GetTargetType() == SMART_TARGET_CREATURE_DISTANCE || e.GetTargetType() == SMART_TARGET_GAMEOBJECT_RANGE || e.GetTargetType() == SMART_TARGET_GAMEOBJECT_GUID || e.GetTargetType() == SMART_TARGET_GAMEOBJECT_DISTANCE || e.GetTargetType() == SMART_TARGET_CLOSEST_CREATURE || e.GetTargetType() == SMART_TARGET_CLOSEST_GAMEOBJECT || e.GetTargetType() == SMART_TARGET_OWNER_OR_SUMMONER || e.GetTargetType() == SMART_TARGET_ACTION_INVOKER || e.GetTargetType() == SMART_TARGET_CLOSEST_ENEMY || e.GetTargetType() == SMART_TARGET_CLOSEST_FRIENDLY || e.GetTargetType() == SMART_TARGET_CLOSEST_UNSPAWNED_GAMEOBJECT) { creatureTarget->SetHomePosition(target->GetPositionX(), target->GetPositionY(), target->GetPositionZ(), target->GetOrientation()); } else TC_LOG_ERROR("sql.sql", "SmartScript: Action target for SMART_ACTION_SET_HOME_POS is invalid, skipping"); } } break; } case SMART_ACTION_SET_HEALTH_REGEN: { for (WorldObject* target : targets) if (Creature* creatureTarget = target->ToCreature()) creatureTarget->SetRegenerateHealth(e.action.setHealthRegen.regenHealth != 0); break; } case SMART_ACTION_SET_ROOT: { for (WorldObject* target : targets) if (Creature* creatureTarget = target->ToCreature()) creatureTarget->SetSessile(e.action.setRoot.root != 0); break; } case SMART_ACTION_SUMMON_CREATURE_GROUP: { std::list summonList; GetBaseObject()->SummonCreatureGroup(e.action.creatureGroup.group, &summonList); for (TempSummon* summon : summonList) if (unit && e.action.creatureGroup.attackInvoker) summon->AI()->AttackStart(unit); if (e.action.creatureGroup.storedTargetId) StoreTargetList({ summonList.begin(), summonList.end() }, e.action.creatureGroup.storedTargetId); break; } case SMART_ACTION_SET_POWER: { for (WorldObject* target : targets) if (Unit* unitTarget = target->ToUnit()) unitTarget->SetPower(Powers(e.action.power.powerType), e.action.power.newPower); break; } case SMART_ACTION_ADD_POWER: { for (WorldObject* target : targets) if (Unit* unitTarget = target->ToUnit()) unitTarget->ModifyPower(Powers(e.action.power.powerType), e.action.power.newPower); break; } case SMART_ACTION_REMOVE_POWER: { for (WorldObject* target : targets) if (Unit* unitTarget = target->ToUnit()) unitTarget->ModifyPower(Powers(e.action.power.powerType), -int32(e.action.power.newPower)); break; } case SMART_ACTION_GAME_EVENT_STOP: { uint32 eventId = e.action.gameEventStop.id; if (!sGameEventMgr->IsActiveEvent(eventId)) { TC_LOG_ERROR("sql.sql", "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)) { TC_LOG_ERROR("sql.sql", "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(); std::pair closest = { 0, 0 }; std::shared_ptr> waitEvent = CreateTimedActionListWaitEventFor>(e, targets.size()); for (WorldObject* target : targets) { if (Creature* creature = target->ToCreature()) { if (IsSmart(creature)) { for (uint32 pathId : e.action.closestWaypointFromList.wps) { WaypointPath const* path = sWaypointMgr->GetPath(pathId); if (!path || path->Nodes.empty()) continue; for (WaypointNode const& waypoint : path->Nodes) { float distanceToThisNode = creature->GetDistance(waypoint.X, waypoint.Y, waypoint.Z); if (distanceToThisNode < distanceToClosest) { distanceToClosest = distanceToThisNode; closest.first = pathId; closest.second = waypoint.Id; } } } if (closest.first != 0) { Optional> actionResultSetter; if (waitEvent) actionResultSetter = Scripting::v2::ActionResult::GetResultSetter({ waitEvent, &waitEvent->Results.emplace_back() }); ENSURE_AI(SmartAI, creature->AI())->StartPath(closest.first, true, nullptr, closest.second, std::move(actionResultSetter)); } } } } if (waitEvent && !waitEvent->Results.empty()) mTimedActionWaitEvent = std::move(waitEvent); break; } case SMART_ACTION_RANDOM_SOUND: { std::array> sounds; auto soundsEnd = std::ranges::remove_copy(e.action.randomSound.sounds, sounds.begin(), 0u).out; bool onlySelf = e.action.randomSound.onlySelf != 0; for (WorldObject* const target : targets) { uint32 sound = Trinity::Containers::SelectRandomContainerElement(Trinity::IteratorPair(sounds.begin(), soundsEnd)); if (e.action.randomSound.distance == 1) target->PlayDistanceSound(sound, onlySelf ? target->ToPlayer() : nullptr); else target->PlayDirectSound(sound, onlySelf ? target->ToPlayer() : nullptr); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_RANDOM_SOUND: target: {} ({}), sound: {}, onlyself: {}", target->GetName(), target->GetGUID(), sound, onlySelf ? "true" : "false"); } break; } case SMART_ACTION_SET_CORPSE_DELAY: { for (WorldObject* const target : targets) if (Creature* creatureTarget = target->ToCreature()) creatureTarget->SetCorpseDelay(e.action.corpseDelay.timer, !e.action.corpseDelay.includeDecayRatio); break; } case SMART_ACTION_SPAWN_SPAWNGROUP: { if (e.action.groupSpawn.minDelay == 0 && e.action.groupSpawn.maxDelay == 0) { bool const ignoreRespawn = ((e.action.groupSpawn.spawnflags & SMARTAI_SPAWN_FLAGS::SMARTAI_SPAWN_FLAG_IGNORE_RESPAWN) != 0); bool const force = ((e.action.groupSpawn.spawnflags & SMARTAI_SPAWN_FLAGS::SMARTAI_SPAWN_FLAG_FORCE_SPAWN) != 0); // Instant spawn GetBaseObject()->GetMap()->SpawnGroupSpawn(e.action.groupSpawn.groupId, ignoreRespawn, force); } else { // Delayed spawn (use values from parameter to schedule event to call us back SmartScriptHolder& ev = mStoredEvents.emplace_back(); ev.event_id = e.event_id; ev.event.type = (SMART_EVENT)SMART_EVENT_UPDATE; ev.event.event_chance = 100; ev.event.minMaxRepeat.min = e.action.groupSpawn.minDelay; ev.event.minMaxRepeat.max = e.action.groupSpawn.maxDelay; ev.event.minMaxRepeat.repeatMin = 0; ev.event.minMaxRepeat.repeatMax = 0; ev.event.event_flags = 0; ev.event.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; ev.action.type = (SMART_ACTION)SMART_ACTION_SPAWN_SPAWNGROUP; ev.action.groupSpawn.groupId = e.action.groupSpawn.groupId; ev.action.groupSpawn.minDelay = 0; ev.action.groupSpawn.maxDelay = 0; ev.action.groupSpawn.spawnflags = e.action.groupSpawn.spawnflags; ev.action.timeEvent.id = e.action.timeEvent.id; ev.target = e.target; InitTimer(ev); } break; } case SMART_ACTION_DESPAWN_SPAWNGROUP: { if (e.action.groupSpawn.minDelay == 0 && e.action.groupSpawn.maxDelay == 0) { bool const deleteRespawnTimes = ((e.action.groupSpawn.spawnflags & SMARTAI_SPAWN_FLAGS::SMARTAI_SPAWN_FLAG_NOSAVE_RESPAWN) != 0); // Instant spawn GetBaseObject()->GetMap()->SpawnGroupDespawn(e.action.groupSpawn.groupId, deleteRespawnTimes); } else { // Delayed spawn (use values from parameter to schedule event to call us back SmartScriptHolder& ev = mStoredEvents.emplace_back(); ev.event_id = e.event_id; ev.event.type = (SMART_EVENT)SMART_EVENT_UPDATE; ev.event.event_chance = 100; ev.event.minMaxRepeat.min = e.action.groupSpawn.minDelay; ev.event.minMaxRepeat.max = e.action.groupSpawn.maxDelay; ev.event.minMaxRepeat.repeatMin = 0; ev.event.minMaxRepeat.repeatMax = 0; ev.event.event_flags = 0; ev.event.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; ev.action.type = (SMART_ACTION)SMART_ACTION_DESPAWN_SPAWNGROUP; ev.action.groupSpawn.groupId = e.action.groupSpawn.groupId; ev.action.groupSpawn.minDelay = 0; ev.action.groupSpawn.maxDelay = 0; ev.action.groupSpawn.spawnflags = e.action.groupSpawn.spawnflags; ev.action.timeEvent.id = e.action.timeEvent.id; ev.target = e.target; InitTimer(ev); } break; } case SMART_ACTION_DISABLE_EVADE: { if (!IsSmart()) break; ENSURE_AI(SmartAI, me->AI())->SetEvadeDisabled(e.action.disableEvade.disable != 0); break; } case SMART_ACTION_ADD_THREAT: { if (!me->CanHaveThreatList()) break; for (WorldObject* const target : targets) if (Unit* unitTarget = target->ToUnit()) me->GetThreatManager().AddThreat(unitTarget, float(e.action.threat.threatINC) - float(e.action.threat.threatDEC), nullptr, true, true); break; } case SMART_ACTION_LOAD_EQUIPMENT: { for (WorldObject* const target : targets) if (Creature* creatureTarget = target->ToCreature()) creatureTarget->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_PAUSE_MOVEMENT: { for (WorldObject* const target : targets) if (Unit* unitTarget = target->ToUnit()) unitTarget->PauseMovement(e.action.pauseMovement.pauseTimer, e.action.pauseMovement.movementSlot, e.action.pauseMovement.force); break; } case SMART_ACTION_RESPAWN_BY_SPAWNID: { Map* map = nullptr; if (WorldObject* obj = GetBaseObject()) map = obj->GetMap(); else if (!targets.empty()) map = targets.front()->GetMap(); if (map) map->Respawn(SpawnObjectType(e.action.respawnData.spawnType), e.action.respawnData.spawnId); else TC_LOG_ERROR("sql.sql", "SmartScript::ProcessAction: Entry {} SourceType {}, Event {} - tries to respawn by spawnId but does not provide a map", e.entryOrGuid, e.GetScriptType(), e.event_id); break; } case SMART_ACTION_PLAY_ANIMKIT: { for (WorldObject* const target : targets) { if (Creature* creatureTarget = target->ToCreature()) { if (e.action.animKit.type == 0) creatureTarget->PlayOneShotAnimKitId(e.action.animKit.animKit); else if (e.action.animKit.type == 1) creatureTarget->SetAIAnimKitId(e.action.animKit.animKit); else if (e.action.animKit.type == 2) creatureTarget->SetMeleeAnimKitId(e.action.animKit.animKit); else if (e.action.animKit.type == 3) creatureTarget->SetMovementAnimKitId(e.action.animKit.animKit); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_PLAY_ANIMKIT: target: {} ({}), AnimKit: {}, Type: {}", target->GetName(), target->GetGUID(), e.action.animKit.animKit, e.action.animKit.type); } else if (GameObject* gameObject = target->ToGameObject()) { switch (e.action.animKit.type) { case 0: gameObject->SetAnimKitId(e.action.animKit.animKit, true); break; case 1: gameObject->SetAnimKitId(e.action.animKit.animKit, false); break; default: break; } TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_PLAY_ANIMKIT: target: {} ({}), AnimKit: {}, Type: {}", target->GetName(), target->GetGUID(), e.action.animKit.animKit, e.action.animKit.type); } } break; } case SMART_ACTION_SCENE_PLAY: { for (WorldObject* const target : targets) if (Player* playerTarget = target->ToPlayer()) playerTarget->GetSceneMgr().PlayScene(e.action.scene.sceneId); break; } case SMART_ACTION_SCENE_CANCEL: { for (WorldObject* const target : targets) if (Player* playerTarget = target->ToPlayer()) playerTarget->GetSceneMgr().CancelSceneBySceneId(e.action.scene.sceneId); break; } case SMART_ACTION_PLAY_CINEMATIC: { for (WorldObject* target : targets) if (Player* playerTarget = target->ToPlayer()) playerTarget->SendCinematicStart(e.action.cinematic.entry); 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* const target : targets) if (Creature* creatureTarget = target->ToCreature()) creatureTarget->SetSpeed(UnitMoveType(e.action.movementSpeed.movementType), speed); break; } case SMART_ACTION_PLAY_SPELL_VISUAL_KIT: { for (WorldObject* const target : targets) { if (Unit* unitTarget = target->ToUnit()) { unitTarget->SendPlaySpellVisualKit(e.action.spellVisualKit.spellVisualKitId, e.action.spellVisualKit.kitType, e.action.spellVisualKit.duration); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_PLAY_SPELL_VISUAL_KIT: target: {} ({}), SpellVisualKit: {}", target->GetName(), target->GetGUID(), e.action.spellVisualKit.spellVisualKitId); } } break; } case SMART_ACTION_OVERRIDE_LIGHT: { if (WorldObject* obj = GetBaseObject()) { obj->GetMap()->SetZoneOverrideLight(e.action.overrideLight.zoneId, e.action.overrideLight.areaLightId, e.action.overrideLight.overrideLightId, Milliseconds(e.action.overrideLight.transitionMilliseconds)); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_OVERRIDE_LIGHT: {} sets zone override light (zoneId: {}, areaLightId: {}, overrideLightId: {}, transitionMilliseconds: {})", obj->GetGUID(), e.action.overrideLight.zoneId, e.action.overrideLight.areaLightId, e.action.overrideLight.overrideLightId, e.action.overrideLight.transitionMilliseconds); } break; } case SMART_ACTION_OVERRIDE_WEATHER: { if (WorldObject* obj = GetBaseObject()) { obj->GetMap()->SetZoneWeather(e.action.overrideWeather.zoneId, (WeatherState)e.action.overrideWeather.weatherId, float(e.action.overrideWeather.intensity)); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_OVERRIDE_WEATHER: {} sets zone weather (zoneId: {}, weatherId: {}, intensity: {})", obj->GetGUID(), e.action.overrideWeather.zoneId, e.action.overrideWeather.weatherId, e.action.overrideWeather.intensity); } break; } case SMART_ACTION_SET_HOVER: { for (WorldObject* target : targets) if (Unit* unitTarget = target->ToUnit()) unitTarget->SetHover(e.action.setHover.enable != 0); 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_CREATE_CONVERSATION: { WorldObject* baseObject = GetBaseObject(); for (WorldObject* const target : targets) { if (Player* playerTarget = target->ToPlayer()) { Conversation* conversation = Conversation::CreateConversation(e.action.conversation.id, playerTarget, *playerTarget, playerTarget->GetGUID(), nullptr); if (!conversation) TC_LOG_WARN("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_CREATE_CONVERSATION: id {}, baseObject {}, target {} - failed to create conversation", e.action.conversation.id, !baseObject ? "" : baseObject->GetName().c_str(), playerTarget->GetName()); } } break; } case SMART_ACTION_SET_IMMUNE_PC: { for (WorldObject* target : targets) { if (Unit* unitTarget = target->ToUnit()) { if (e.action.setImmunePC.immunePC) unitTarget->SetUnitFlag(UNIT_FLAG_IMMUNE_TO_PC); else unitTarget->RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_PC); } } break; } case SMART_ACTION_SET_IMMUNE_NPC: { for (WorldObject* target : targets) { if (Unit* unitTarget = target->ToUnit()) { if (e.action.setImmuneNPC.immuneNPC) unitTarget->SetUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC); else unitTarget->RemoveUnitFlag(UNIT_FLAG_IMMUNE_TO_NPC); } } break; } case SMART_ACTION_SET_UNINTERACTIBLE: { for (WorldObject* target : targets) if (Unit* unitTarget = target->ToUnit()) unitTarget->SetUninteractible(e.action.setUninteractible.uninteractible != 0); break; } case SMART_ACTION_ACTIVATE_GAMEOBJECT: { for (WorldObject* target : targets) { if (GameObject* targetGo = target->ToGameObject()) { targetGo->ActivateObject(GameObjectActions(e.action.activateGameObject.gameObjectAction), e.action.activateGameObject.param, GetBaseObject()); } } break; } case SMART_ACTION_ADD_TO_STORED_TARGET_LIST: { if (!targets.empty()) AddToStoredTargetList(targets, e.action.addToStoredTargets.id); else { WorldObject* baseObject = GetBaseObject(); TC_LOG_WARN("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_ADD_TO_STORED_TARGET_LIST: var {}, baseObject {}, event {} - tried to add no targets to stored target list", e.action.addToStoredTargets.id, !baseObject ? "" : baseObject->GetName().c_str(), e.event_id); } break; } case SMART_ACTION_BECOME_PERSONAL_CLONE_FOR_PLAYER: { WorldObject* baseObject = GetBaseObject(); auto doCreatePersonalClone = [&](Position const& position, Player* privateObjectOwner) { if (Creature* summon = GetBaseObject()->SummonPersonalClone(position, TempSummonType(e.action.becomePersonalClone.type), Milliseconds(e.action.becomePersonalClone.duration), 0, 0, privateObjectOwner)) if (IsSmart(summon)) ENSURE_AI(SmartAI, summon->AI())->SetTimedActionList(e, e.entryOrGuid, privateObjectOwner, e.event_id + 1); }; // if target is position then targets container was empty if (e.GetTargetType() != SMART_TARGET_POSITION) { for (WorldObject* target : targets) if (Player* playerTarget = Object::ToPlayer(target)) doCreatePersonalClone(baseObject->GetPosition(), playerTarget); } else { if (Player* invoker = Object::ToPlayer(GetLastInvoker())) doCreatePersonalClone({ e.target.x, e.target.y, e.target.z, e.target.o }, invoker); } // action list will continue on personal clones std::erase_if(mTimedActionList, [e](SmartScriptHolder const& script) { return script.event_id > e.event_id; }); break; } case SMART_ACTION_TRIGGER_GAME_EVENT: { WorldObject* sourceObject = GetBaseObjectOrUnitInvoker(unit); for (WorldObject* target : targets) { if (e.action.triggerGameEvent.useSaiTargetAsGameEventSource) GameEvents::Trigger(e.action.triggerGameEvent.eventId, target, sourceObject); else GameEvents::Trigger(e.action.triggerGameEvent.eventId, sourceObject, target); } break; } case SMART_ACTION_DO_ACTION: { for (WorldObject* target : targets) { if (Unit* unitTarget = Object::ToUnit(target)) { if (unitTarget->GetAI()) unitTarget->GetAI()->DoAction(e.action.doAction.actionId); } else if (GameObject* goTarget = Object::ToGameObject(target)) { if (goTarget->AI()) goTarget->AI()->DoAction(e.action.doAction.actionId); } } break; } case SMART_ACTION_COMPLETE_QUEST: { uint32 questId = e.action.quest.quest; Quest const* quest = sObjectMgr->GetQuestTemplate(questId); if (!quest) break; for (WorldObject* target : targets) { Player* player = Object::ToPlayer(target); if (!player) continue; QuestStatus questStatus = player->GetQuestStatus(questId); if (questStatus == QUEST_STATUS_REWARDED) continue; if (quest->HasFlag(QUEST_FLAGS_COMPLETION_EVENT) || quest->HasFlag(QUEST_FLAGS_COMPLETION_AREA_TRIGGER)) { if (questStatus == QUEST_STATUS_INCOMPLETE) player->AreaExploredOrEventHappens(questId); } else if (quest->HasFlag(QUEST_FLAGS_TRACKING_EVENT)) // Check if the quest is used as a serverside flag player->CompleteQuest(questId); } break; } case SMART_ACTION_CREDIT_QUEST_OBJECTIVE_TALK_TO: { if (!me) break; for (WorldObject* target : targets) { Player* player = Object::ToPlayer(target); if (!player) continue; player->TalkedToCreature(me->GetEntry(), me->GetGUID()); } break; } case SMART_ACTION_DESTROY_CONVERSATION: { auto work = [&](Conversation* conversation) { if (conversation->GetEntry() != e.action.destroyConversation.id) return; if (conversation->IsPrivateObject()) { if (!e.action.destroyConversation.isPrivate || !advstd::ranges::contains(targets, conversation->GetPrivateObjectOwner(), [](WorldObject const* target) { return target->GetGUID(); })) return; } else if (e.action.destroyConversation.isPrivate) return; conversation->Remove(); }; Trinity::ConversationWorker worker(PhasingHandler::GetAlwaysVisiblePhaseShift(), work); Cell::VisitGridObjects(GetBaseObject(), worker, float(e.action.destroyConversation.range)); break; } case SMART_ACTION_ENTER_VEHICLE: { if (!me) break; for (WorldObject* target : targets) { if (Unit* unitTarget = target->ToUnit()) { me->EnterVehicle(unitTarget, (uint8)e.action.enterVehicle.seatId); break; } } break; } case SMART_ACTION_BOARD_PASSENGER: { if (!me) break; for (WorldObject* target : targets) { if (Unit* unitTarget = target->ToUnit()) { unitTarget->EnterVehicle(me, (uint8)e.action.enterVehicle.seatId); break; } } break; } case SMART_ACTION_EXIT_VEHICLE: { for (WorldObject* target : targets) { if (Unit* unitTarget = target->ToUnit()) { unitTarget->ExitVehicle(); } } break; } default: TC_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) { SmartScriptHolder& linked = SmartAIMgr::FindLinkedEvent(mEvents, e.link); if (linked) ProcessEvent(linked, unit, var0, var1, bvar, spell, gob, varString); else TC_LOG_DEBUG("sql.sql", "SmartScript::ProcessAction: Entry {} SourceType {}, Event {}, Link Event {} not found or invalid, 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, std::string_view varString) { // We may want to execute action rarely and because of this if condition is not fulfilled the action will be rechecked in a long time if (sConditionMgr->IsObjectMeetingSmartEventConditions(e.entryOrGuid, e.event_id, e.source_type, unit, GetBaseObject())) { RecalcTimer(e, min, max); ProcessAction(e, unit, var0, var1, bvar, spell, gob, varString); } else RecalcTimer(e, std::min(min, 5000), std::min(min, 5000)); } SmartScriptHolder SmartScript::CreateSmartEvent(SMART_EVENT e, uint32 event_flags, uint32 event_param1, uint32 event_param2, uint32 event_param3, uint32 event_param4, uint32 event_param5, SMART_ACTION action, uint32 action_param1, uint32 action_param2, uint32 action_param3, uint32 action_param4, uint32 action_param5, uint32 action_param6, uint32 action_param7, SMARTAI_TARGETS t, uint32 target_param1, uint32 target_param2, uint32 target_param3, uint32 target_param4, std::string_view targetParamString, 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.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.action.raw.param7 = action_param7; 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.target.param_string = targetParamString; 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 (Unit* 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.hostilRandom.powerType) { if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::MaxThreat, 1, PowerUsersSelector(me, Powers(e.target.hostilRandom.powerType - 1), float(e.target.hostilRandom.maxDist), e.target.hostilRandom.playerOnly != 0))) targets.push_back(u); } else if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::MaxThreat, 1, float(e.target.hostilRandom.maxDist), e.target.hostilRandom.playerOnly != 0)) targets.push_back(u); } break; case SMART_TARGET_HOSTILE_LAST_AGGRO: if (me) { if (e.target.hostilRandom.powerType) { if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::MinThreat, 0, PowerUsersSelector(me, Powers(e.target.hostilRandom.powerType - 1), float(e.target.hostilRandom.maxDist), e.target.hostilRandom.playerOnly != 0))) targets.push_back(u); } else if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::MinThreat, 0, float(e.target.hostilRandom.maxDist), e.target.hostilRandom.playerOnly != 0)) targets.push_back(u); } break; case SMART_TARGET_HOSTILE_RANDOM: if (me) { if (e.target.hostilRandom.powerType) { if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::Random, 0, PowerUsersSelector(me, Powers(e.target.hostilRandom.powerType - 1), float(e.target.hostilRandom.maxDist), e.target.hostilRandom.playerOnly != 0))) targets.push_back(u); } else if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::Random, 0, float(e.target.hostilRandom.maxDist), e.target.hostilRandom.playerOnly != 0)) targets.push_back(u); } break; case SMART_TARGET_HOSTILE_RANDOM_NOT_TOP: if (me) { if (e.target.hostilRandom.powerType) { if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::Random, 1, PowerUsersSelector(me, Powers(e.target.hostilRandom.powerType - 1), float(e.target.hostilRandom.maxDist), e.target.hostilRandom.playerOnly != 0))) targets.push_back(u); } else if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::Random, 1, float(e.target.hostilRandom.maxDist), e.target.hostilRandom.playerOnly != 0)) targets.push_back(u); } break; case SMART_TARGET_FARTHEST: if (me) { if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::MaxDistance, 0, FarthestTargetSelector(me, float(e.target.farthest.maxDist), e.target.farthest.playerOnly != 0, e.target.farthest.isInLos != 0))) 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 && scriptTrigger->ToUnit() && 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 const& groupRef : group->GetMembers()) if (groupRef.GetSource()->IsInMap(player)) targets.push_back(groupRef.GetSource()); } // 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) { TC_LOG_ERROR("sql.sql", "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; } std::vector creatures; ref->GetCreatureListWithOptionsInGrid(creatures, static_cast(e.target.unitRange.maxDist), { .CreatureId = e.target.unitRange.creature ? Optional(e.target.unitRange.creature) : Optional(), .StringId = !e.target.param_string.empty() ? Optional(e.target.param_string) : Optional(), }); std::ranges::copy_if(creatures, std::back_inserter(targets), [&](Creature const* target) { return !ref->IsWithinDist(target, static_cast(e.target.unitRange.minDist)); }); if (e.target.unitRange.maxSize) Trinity::Containers::RandomResize(targets, e.target.unitRange.maxSize); break; } case SMART_TARGET_CREATURE_DISTANCE: { if (!baseObject) break; std::vector creatures; baseObject->GetCreatureListWithOptionsInGrid(creatures, static_cast(e.target.unitDistance.dist), { .CreatureId = e.target.unitDistance.creature ? Optional(e.target.unitDistance.creature) : Optional(), .StringId = !e.target.param_string.empty() ? Optional(e.target.param_string) : Optional(), }); targets = { creatures.begin(), creatures.end() }; if (e.target.unitDistance.maxSize) Trinity::Containers::RandomResize(targets, e.target.unitDistance.maxSize); break; } case SMART_TARGET_GAMEOBJECT_RANGE: { WorldObject* ref = baseObject; if (!ref) ref = scriptTrigger; if (!ref) { TC_LOG_ERROR("sql.sql", "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; } std::vector gameObjects; ref->GetGameObjectListWithOptionsInGrid(gameObjects, static_cast(e.target.goRange.maxDist), { .GameObjectId = e.target.goRange.entry ? Optional(e.target.goRange.entry) : Optional(), .StringId = !e.target.param_string.empty() ? Optional(e.target.param_string) : Optional(), }); std::ranges::copy_if(gameObjects, std::back_inserter(targets), [&](GameObject const* target) { return !ref->IsWithinDist(target, static_cast(e.target.goRange.minDist)); }); if (e.target.goRange.maxSize) Trinity::Containers::RandomResize(targets, e.target.goRange.maxSize); break; } case SMART_TARGET_GAMEOBJECT_DISTANCE: { if (!baseObject) break; std::vector gameObjects; baseObject->GetGameObjectListWithOptionsInGrid(gameObjects, static_cast(e.target.goDistance.dist), { .GameObjectId = e.target.goDistance.entry ? Optional(e.target.goDistance.entry) : Optional(), .StringId = !e.target.param_string.empty() ? Optional(e.target.param_string) : Optional(), }); targets = { gameObjects.begin(), gameObjects.end() }; if (e.target.goDistance.maxSize) Trinity::Containers::RandomResize(targets, e.target.goDistance.maxSize); break; } case SMART_TARGET_CREATURE_GUID: { if (!scriptTrigger && !baseObject) { TC_LOG_ERROR("sql.sql", "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; } if (Creature* target = FindCreatureNear(scriptTrigger ? scriptTrigger : baseObject, e.target.unitGUID.dbGuid)) if (!e.target.unitGUID.entry || target->GetEntry() == e.target.unitGUID.entry) targets.push_back(target); break; } case SMART_TARGET_GAMEOBJECT_GUID: { if (!scriptTrigger && !baseObject) { TC_LOG_ERROR("sql.sql", "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; } if (GameObject* target = FindGameObjectNear(scriptTrigger ? scriptTrigger : baseObject, e.target.goGUID.dbGuid)) if (!e.target.goGUID.entry || target->GetEntry() == e.target.goGUID.entry) targets.push_back(target); break; } case SMART_TARGET_PLAYER_RANGE: { if (!baseObject) break; std::vector players; baseObject->GetPlayerListInGrid(players, static_cast(e.target.playerRange.maxDist)); std::ranges::copy_if(players, std::back_inserter(targets), [&](Player const* target) { return !baseObject->IsWithinDist(target, static_cast(e.target.playerRange.minDist)); }); break; } case SMART_TARGET_PLAYER_DISTANCE: { if (!baseObject) break; std::vector players; baseObject->GetPlayerListInGrid(players, static_cast(e.target.playerDistance.dist)); targets = { players.begin(), players.end() }; break; } case SMART_TARGET_STORED: { WorldObject* ref = baseObject; if (!ref) ref = scriptTrigger; if (!ref) { TC_LOG_ERROR("sql.sql", "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) { TC_LOG_ERROR("sql.sql", "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 = ref->FindNearestCreatureWithOptions(float(e.target.unitClosest.dist ? e.target.unitClosest.dist : 100), { .CreatureId = e.target.unitClosest.entry, .StringId = !e.target.param_string.empty() ? Optional(e.target.param_string) : Optional(), .IsAlive = (FindCreatureAliveState)e.target.unitClosest.findCreatureAliveState }); if (target) targets.push_back(target); break; } case SMART_TARGET_CLOSEST_GAMEOBJECT: { WorldObject* ref = baseObject; if (!ref) ref = scriptTrigger; if (!ref) { TC_LOG_ERROR("sql.sql", "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 = ref->FindNearestGameObjectWithOptions(float(e.target.goClosest.dist ? e.target.goClosest.dist : 100), { .GameObjectId = e.target.goClosest.entry, .StringId = !e.target.param_string.empty() ? Optional(e.target.param_string) : Optional() }); if (target) targets.push_back(target); break; } case SMART_TARGET_CLOSEST_PLAYER: { WorldObject* ref = baseObject; if (!ref) ref = scriptTrigger; if (!ref) { TC_LOG_ERROR("sql.sql", "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: { if (me) { ObjectGuid charmerOrOwnerGuid = me->GetCharmerOrOwnerGUID(); if (!charmerOrOwnerGuid) if (TempSummon* tempSummon = me->ToTempSummon()) if (WorldObject* summoner = tempSummon->GetSummoner()) charmerOrOwnerGuid = summoner->GetGUID(); if (!charmerOrOwnerGuid) charmerOrOwnerGuid = me->GetCreatorGUID(); if (WorldObject* owner = ObjectAccessor::GetWorldObject(*me, charmerOrOwnerGuid)) targets.push_back(owner); } else if (go) { if (Unit* owner = ObjectAccessor::GetUnit(*go, go->GetOwnerGUID())) targets.push_back(owner); } // Get owner of owner if (e.target.owner.useCharmerOrOwner && !targets.empty()) { WorldObject* owner = targets.front(); targets.clear(); if (Unit* base = ObjectAccessor::GetUnit(*owner, owner->GetCharmerOrOwnerGUID())) targets.push_back(base); } break; } case SMART_TARGET_THREAT_LIST: { if (me && me->CanHaveThreatList()) for (auto* ref : me->GetThreatManager().GetUnsortedThreatList()) if (!e.target.threatList.maxDist || me->IsWithinCombatRange(ref->GetVictim(), float(e.target.threatList.maxDist))) targets.push_back(ref->GetVictim()); break; } case SMART_TARGET_CLOSEST_ENEMY: { if (me) if (Unit* target = me->SelectNearestTarget(e.target.closestAttackable.maxDist, e.target.closestAttackable.playerOnly != 0)) targets.push_back(target); break; } case SMART_TARGET_CLOSEST_FRIENDLY: { if (me) if (Unit* target = DoFindClosestFriendlyInRange(e.target.closestFriendly.maxDist, e.target.closestFriendly.playerOnly != 0)) targets.push_back(target); break; } case SMART_TARGET_LOOT_RECIPIENTS: { if (me) for (ObjectGuid tapperGuid : me->GetTapList()) if (Player* tapper = ObjectAccessor::GetPlayer(*me, tapperGuid)) targets.push_back(tapper); break; } case SMART_TARGET_VEHICLE_PASSENGER: { if (me && me->IsVehicle()) for (std::pair& seat : me->GetVehicleKit()->Seats) if (!e.target.vehicle.seatMask || (e.target.vehicle.seatMask & (1 << seat.first))) if (Unit* u = ObjectAccessor::GetUnit(*me, seat.second.Passenger.Guid)) targets.push_back(u); break; } case SMART_TARGET_CLOSEST_UNSPAWNED_GAMEOBJECT: { if (GameObject* target = baseObject->FindNearestUnspawnedGameObject(e.target.goClosest.entry, float(e.target.goClosest.dist ? e.target.goClosest.dist : 100))) targets.push_back(target); break; } case SMART_TARGET_POSITION: case SMART_TARGET_NONE: default: break; } } void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, uint32 var1, bool bvar, SpellInfo const* spell, GameObject* gob, std::string_view varString) { 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) && me && me->IsCreature() && me->ToCreature()->IsCharmed()) 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->IsInEvadeMode() || !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_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_RANGE: { if (!me || !me->IsEngaged() || !me->GetVictim()) return; if (me->IsInRange(me->GetVictim(), (float)e.event.minMaxRepeat.min, (float)e.event.minMaxRepeat.max)) ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax, me->GetVictim()); else // make it predictable RecalcTimer(e, 500, 500); 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_IS_CC: { if (!me || !me->IsEngaged()) return; std::vector creatures; DoFindFriendlyCC(creatures, float(e.event.friendlyCC.radius)); if (creatures.empty()) { // 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, Trinity::Containers::SelectRandomContainerElement(creatures)); break; } case SMART_EVENT_FRIENDLY_MISSING_BUFF: { 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, Trinity::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->EnsureVictim()->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; } case SMART_EVENT_QUEST_ACCEPTED: case SMART_EVENT_QUEST_COMPLETION: case SMART_EVENT_QUEST_REWARDED: case SMART_EVENT_QUEST_FAIL: { ProcessAction(e, unit); break; } case SMART_EVENT_QUEST_OBJ_COMPLETION: { if (var0 == (e.event.questObjective.id)) ProcessAction(e, unit); break; } //no params case SMART_EVENT_AGGRO: case SMART_EVENT_DEATH: case SMART_EVENT_EVADE: case SMART_EVENT_REACHED_HOME: case SMART_EVENT_RESET: case SMART_EVENT_CORPSE_REMOVED: case SMART_EVENT_AI_INIT: case SMART_EVENT_TRANSPORT_ADDPLAYER: case SMART_EVENT_TRANSPORT_REMOVE_PLAYER: case SMART_EVENT_AREATRIGGER_ENTER: case SMART_EVENT_JUST_SUMMONED: case SMART_EVENT_JUST_CREATED: case SMART_EVENT_FOLLOW_COMPLETED: case SMART_EVENT_ON_SPELLCLICK: case SMART_EVENT_ON_DESPAWN: case SMART_EVENT_SEND_EVENT_TRIGGER: case SMART_EVENT_AREATRIGGER_EXIT: 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: // OnGossipHello only filter set, skip action if OnReportUse if (var0) return; break; case 2: // OnReportUse only filter set, skip action if OnGossipHello if (!var0) return; break; default: // Ignore any other value break; } ProcessAction(e, unit, var0, var1, bvar, spell, gob); break; case SMART_EVENT_RECEIVE_EMOTE: if (e.event.emote.emote == var0) { RecalcTimer(e, e.event.emote.cooldownMin, e.event.emote.cooldownMax); ProcessAction(e, unit); } break; case SMART_EVENT_KILL: { if (!me || !unit) return; if (e.event.kill.playerOnly && unit->GetTypeId() != TYPEID_PLAYER) 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, gob); } break; } case SMART_EVENT_ON_SPELL_CAST: case SMART_EVENT_ON_SPELL_FAILED: case SMART_EVENT_ON_SPELL_START: { if (!spell) return; if (spell->Id != e.event.spellCast.spell) return; RecalcTimer(e, e.event.spellCast.cooldownMin, e.event.spellCast.cooldownMax); ProcessAction(e, nullptr, 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->GetTypeId() != TYPEID_PLAYER) 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->GetTypeId() != TYPEID_PLAYER) 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: { if (!unit->IsCreature()) 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 != 0xFFFFFFFF && var1 != e.event.movementInform.id)) return; ProcessAction(e, unit, var0, var1); break; } case SMART_EVENT_TRANSPORT_RELOCATE: { if (e.event.transportRelocate.pointID && var0 != e.event.transportRelocate.pointID) return; ProcessAction(e, unit, var0); break; } case SMART_EVENT_WAYPOINT_REACHED: case SMART_EVENT_WAYPOINT_RESUMED: case SMART_EVENT_WAYPOINT_PAUSED: case SMART_EVENT_WAYPOINT_STOPPED: case SMART_EVENT_WAYPOINT_ENDED: { if (!me || (e.event.waypoint.pointID != 0xFFFFFFFF && var0 != e.event.waypoint.pointID) || (e.event.waypoint.pathID && var1 != 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_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); 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: { TC_LOG_DEBUG("scripts.ai", "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_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_LOOT_STATE_CHANGED: { if (e.event.goLootStateChanged.lootState != 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->IsInEvadeMode()) 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); auto unitTargetItr = std::ranges::find_if(targets, [this, &e](WorldObject* target) { Unit* unit = target->ToUnit(); if (unit && me->IsFriendlyTo(unit) && unit->IsAlive() && unit->IsInCombat()) { uint32 healthPct = uint32(unit->GetHealthPct()); if (e.event.friendlyHealthPct.minHpPct <= healthPct && healthPct <= e.event.friendlyHealthPct.maxHpPct) return true; } return false; }); if (unitTargetItr != targets.end()) unitTarget = (*unitTargetItr)->ToUnit(); break; } case SMART_TARGET_ACTION_INVOKER: unitTarget = DoSelectLowestHpPercentFriendly((float)e.event.friendlyHealthPct.radius, e.event.friendlyHealthPct.minHpPct, e.event.friendlyHealthPct.maxHpPct); 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; Creature* creature = nullptr; if (e.event.distance.guid != 0) { creature = FindCreatureNear(me, e.event.distance.guid); if (!creature) return; if (!me->IsInRange(creature, 0, static_cast(e.event.distance.dist))) return; } else if (e.event.distance.entry != 0) { std::list list; me->GetCreatureListWithEntryInGrid(list, e.event.distance.entry, static_cast(e.event.distance.dist)); if (!list.empty()) creature = list.front(); } if (creature) ProcessTimedAction(e, e.event.distance.repeat, e.event.distance.repeat, creature); break; } case SMART_EVENT_DISTANCE_GAMEOBJECT: { if (!me) return; GameObject* gameobject = nullptr; if (e.event.distance.guid != 0) { gameobject = FindGameObjectNear(me, e.event.distance.guid); if (!gameobject) return; if (!me->IsInRange(gameobject, 0, static_cast(e.event.distance.dist))) return; } else if (e.event.distance.entry != 0) { std::list list; me->GetGameObjectListWithEntryInGrid(list, e.event.distance.entry, static_cast(e.event.distance.dist)); if (!list.empty()) gameobject = list.front(); } if (gameobject) ProcessTimedAction(e, e.event.distance.repeat, e.event.distance.repeat, nullptr, 0, 0, false, nullptr, gameobject); 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_SCENE_START: case SMART_EVENT_SCENE_CANCEL: case SMART_EVENT_SCENE_COMPLETE: { ProcessAction(e, unit); break; } case SMART_EVENT_SCENE_TRIGGER: { if (e.event.param_string != varString) return; ProcessAction(e, unit, var0, 0, false, nullptr, nullptr, varString); break; } default: TC_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_UPDATE: case SMART_EVENT_UPDATE_IC: case SMART_EVENT_UPDATE_OOC: 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; default: e.active = true; break; } } void SmartScript::RecalcTimer(SmartScriptHolder& e, uint32 min, uint32 max) { // min/max was checked at loading! e.timer = urand(min, 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.GetScriptType() == SMART_SCRIPT_TYPE_TIMED_ACTIONLIST && mTimedActionWaitEvent && !mTimedActionWaitEvent->IsReady()) return; if (e.timer < diff) { // delay spell cast event 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 stunned or rooted if (e.GetActionType() == SMART_ACTION_FLEE_FOR_ASSIST) { if (me && me->HasUnitState(UNIT_STATE_ROOT | UNIT_STATE_LOST_CONTROL)) { e.timer = 1; return; } } e.active = true;//activate events with cooldown switch (e.GetEventType())//process ONLY timed events { case SMART_EVENT_UPDATE: case SMART_EVENT_UPDATE_OOC: case SMART_EVENT_UPDATE_IC: case SMART_EVENT_HEALTH_PCT: case SMART_EVENT_MANA_PCT: case SMART_EVENT_RANGE: case SMART_EVENT_VICTIM_CASTING: 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_FRIENDLY_HEALTH_PCT: case SMART_EVENT_DISTANCE_CREATURE: case SMART_EVENT_DISTANCE_GAMEOBJECT: { if (e.GetScriptType() == SMART_SCRIPT_TYPE_TIMED_ACTIONLIST) { Unit* invoker = nullptr; if (me && !mTimedActionListInvoker.IsEmpty()) invoker = ObjectAccessor::GetUnit(*me, mTimedActionListInvoker); ProcessEvent(e, invoker); e.enableTimed = false;//disable event if it is in an ActionList and was processed once for (SmartScriptHolder& scriptholder : mTimedActionList) { //find the first event which is not the current one and enable it if (scriptholder.event_id > e.event_id) { scriptholder.enableTimed = true; break; } } } else ProcessEvent(e); 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()) { mEvents.insert(mEvents.end(), std::move_iterator(mInstallEvents.begin()), std::move_iterator(mInstallEvents.end())); mInstallEvents.clear(); } } void SmartScript::RemoveStoredEvent(uint32 id) { if (!mStoredEvents.empty()) { for (auto i = mStoredEvents.begin(); i != mStoredEvents.end(); ++i) { if (i->event_id == id) { mStoredEvents.erase(i); return; } } } } WorldObject* SmartScript::GetBaseObject() const { WorldObject* obj = nullptr; if (me) obj = me; else if (go) obj = go; else if (areaTrigger) obj = areaTrigger; else if (player) obj = player; return obj; } WorldObject* SmartScript::GetBaseObjectOrUnitInvoker(Unit* invoker) { return Coalesce(GetBaseObject(), invoker); } void SmartScript::OnUpdate(uint32 const diff) { if ((mScriptType == SMART_SCRIPT_TYPE_CREATURE || mScriptType == SMART_SCRIPT_TYPE_GAMEOBJECT || mScriptType == SMART_SCRIPT_TYPE_AREATRIGGER_ENTITY || mScriptType == SMART_SCRIPT_TYPE_AREATRIGGER_ENTITY_CUSTOM) && !GetBaseObject()) return; // Don't run any action while evading if (me && me->IsInEvadeMode()) { // Check if the timed action list finished and clear it if so. // This is required by SMART_ACTION_CALL_TIMED_ACTIONLIST failing if mTimedActionList is not empty. if (!mTimedActionList.empty()) { bool needCleanup = true; for (SmartScriptHolder& scriptholder : mTimedActionList) { if (scriptholder.enableTimed) needCleanup = false; } if (needCleanup) mTimedActionList.clear(); } return; } InstallEvents();//before UpdateTimers if (mEventSortingRequired) { SortEvents(mEvents); mEventSortingRequired = false; } for (SmartScriptHolder& mEvent : mEvents) UpdateTimer(mEvent, 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 (size_t i = 0; i < mTimedActionList.size(); ++i) { SmartScriptHolder& scriptHolder = mTimedActionList[i]; if (scriptHolder.enableTimed) { UpdateTimer(scriptHolder, diff); needCleanup = false; } } isProcessingTimedActionList = false; } if (needCleanup) mTimedActionList.clear(); if (!mRemIDs.empty()) { for (auto i : mRemIDs) RemoveStoredEvent(i); 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 = 1; // 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, AreaTriggerEntry const* at, SceneTemplate const* scene, Quest const* quest, uint32 event) { if (e.empty()) { if (obj) TC_LOG_DEBUG("scripts.ai", "SmartScript: EventMap for Entry {} is empty but is using SmartScript.", obj->GetEntry()); if (at) TC_LOG_DEBUG("scripts.ai", "SmartScript: EventMap for AreaTrigger {} is empty but is using SmartScript.", at->ID); if (scene) TC_LOG_DEBUG("scripts.ai", "SmartScript: EventMap for SceneId {} is empty but is using SmartScript.", scene->SceneId); if (quest) TC_LOG_DEBUG("scripts.ai", "SmartScript: EventMap for Quest {} is empty but is using SmartScript.", quest->GetQuestId()); if (event) TC_LOG_DEBUG("scripts.ai", "SmartScript: EventMap for Event {} is empty but is using SmartScript.", event); return; } for (SmartScriptHolder& scriptholder : std::move(e)) { #ifndef TRINITY_DEBUG if (scriptholder.event.event_flags & SMART_EVENT_FLAG_DEBUG_ONLY) continue; #endif if (obj && !scriptholder.Difficulties.empty()) { bool foundValidDifficulty = false; for (Difficulty difficulty : scriptholder.Difficulties) { if (difficulty == obj->GetMap()->GetDifficultyID()) { foundValidDifficulty = true; break; } } if (!foundValidDifficulty) continue; } mAllEventFlags |= scriptholder.event.event_flags; mEvents.push_back(std::move(scriptholder)); } } void SmartScript::GetScript() { SmartAIEventList e; // We must use script type to avoid ambiguities switch (mScriptType) { case SMART_SCRIPT_TYPE_CREATURE: e = sSmartScriptMgr->GetScript(-((int32)me->GetSpawnId()), mScriptType); if (e.empty()) e = sSmartScriptMgr->GetScript((int32)me->GetEntry(), mScriptType); FillScript(std::move(e), me, nullptr, nullptr, nullptr, 0); break; case SMART_SCRIPT_TYPE_GAMEOBJECT: e = sSmartScriptMgr->GetScript(-((int32)go->GetSpawnId()), mScriptType); if (e.empty()) e = sSmartScriptMgr->GetScript((int32)go->GetEntry(), mScriptType); FillScript(std::move(e), go, nullptr, nullptr, nullptr, 0); break; case SMART_SCRIPT_TYPE_AREATRIGGER_ENTITY: case SMART_SCRIPT_TYPE_AREATRIGGER_ENTITY_CUSTOM: e = sSmartScriptMgr->GetScript((int32)areaTrigger->GetEntry(), mScriptType); FillScript(std::move(e), areaTrigger, nullptr, nullptr, nullptr, 0); break; case SMART_SCRIPT_TYPE_AREATRIGGER: e = sSmartScriptMgr->GetScript((int32)trigger->ID, mScriptType); FillScript(std::move(e), nullptr, trigger, nullptr, nullptr, 0); break; case SMART_SCRIPT_TYPE_SCENE: e = sSmartScriptMgr->GetScript(sceneTemplate->SceneId, mScriptType); FillScript(std::move(e), nullptr, nullptr, sceneTemplate, nullptr, 0); break; case SMART_SCRIPT_TYPE_QUEST: e = sSmartScriptMgr->GetScript(quest->GetQuestId(), mScriptType); FillScript(std::move(e), nullptr, nullptr, nullptr, quest, 0); break; case SMART_SCRIPT_TYPE_EVENT: e = sSmartScriptMgr->GetScript((int32)event, mScriptType); FillScript(std::move(e), nullptr, nullptr, nullptr, nullptr, event); break; default: break; } } void SmartScript::OnInitialize(WorldObject* obj, AreaTriggerEntry const* at, SceneTemplate const* scene, Quest const* qst, uint32 evnt) { if (at) { mScriptType = SMART_SCRIPT_TYPE_AREATRIGGER; trigger = at; player = obj->ToPlayer(); if (!player) { TC_LOG_ERROR("misc", "SmartScript::OnInitialize: source is AreaTrigger with id {}, missing trigger player", trigger->ID); return; } TC_LOG_DEBUG("scripts.ai", "SmartScript::OnInitialize: source is AreaTrigger with id {}, triggered by player {}", trigger->ID, player->GetGUID()); } else if (scene) { mScriptType = SMART_SCRIPT_TYPE_SCENE; sceneTemplate = scene; player = obj->ToPlayer(); if (!player) { TC_LOG_ERROR("misc", "SmartScript::OnInitialize: source is Scene with id {}, missing trigger player", scene->SceneId); return; } TC_LOG_DEBUG("scripts.ai", "SmartScript::OnInitialize: source is Scene with id {}, triggered by player {}", scene->SceneId, player->GetGUID()); } else if (qst) { mScriptType = SMART_SCRIPT_TYPE_QUEST; quest = qst; player = obj->ToPlayer(); if (!player) { TC_LOG_ERROR("misc", "SmartScript::OnInitialize: source is Quest with id {}, missing trigger player", qst->GetQuestId()); return; } TC_LOG_DEBUG("scripts.ai", "SmartScript::OnInitialize: source is Quest with id {}, triggered by player {}", qst->GetQuestId(), player->GetGUID()); } else if (evnt) { mScriptType = SMART_SCRIPT_TYPE_EVENT; event = evnt; if (obj->IsPlayer()) { player = obj->ToPlayer(); TC_LOG_DEBUG("scripts.ai", "SmartScript::OnInitialize: source is Event {}, triggered by player {}", event, player->GetGUID()); } else if (obj->IsCreature()) { me = obj->ToCreature(); TC_LOG_DEBUG("scripts.ai", "SmartScript::OnInitialize: source is Event {}, triggered by creature {}", event, me->GetEntry()); } else if (obj->IsGameObject()) { go = obj->ToGameObject(); TC_LOG_DEBUG("scripts.ai", "SmartScript::OnInitialize: source is Event {}, triggered by gameobject {}", event, go->GetEntry()); } else { TC_LOG_ERROR("misc", "SmartScript::OnInitialize: source is Event {}, missing trigger WorldObject", event); return; } } else if (obj) // Handle object based scripts { switch (obj->GetTypeId()) { case TYPEID_UNIT: mScriptType = SMART_SCRIPT_TYPE_CREATURE; me = obj->ToCreature(); TC_LOG_DEBUG("scripts.ai", "SmartScript::OnInitialize: source is Creature {}", me->GetEntry()); break; case TYPEID_GAMEOBJECT: mScriptType = SMART_SCRIPT_TYPE_GAMEOBJECT; go = obj->ToGameObject(); TC_LOG_DEBUG("scripts.ai", "SmartScript::OnInitialize: source is GameObject {}", go->GetEntry()); break; case TYPEID_AREATRIGGER: areaTrigger = obj->ToAreaTrigger(); mScriptType = areaTrigger->IsCustom() ? SMART_SCRIPT_TYPE_AREATRIGGER_ENTITY_CUSTOM : SMART_SCRIPT_TYPE_AREATRIGGER_ENTITY; TC_LOG_DEBUG("scripts.ai", "SmartScript::OnInitialize: source is AreaTrigger {}, IsCustom {}", areaTrigger->GetEntry(), uint32(areaTrigger->IsCustom())); break; default: TC_LOG_ERROR("misc", "SmartScript::OnInitialize: Unhandled TypeID !WARNING!"); return; } } else { TC_LOG_ERROR("misc", "SmartScript::OnInitialize: !WARNING! Initialized objects are NULL."); 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); mCounterList.clear(); } void SmartScript::OnMoveInLineOfSight(Unit* who) { if (!me) return; ProcessEventsFor(me->IsEngaged() ? SMART_EVENT_IC_LOS : SMART_EVENT_OOC_LOS, who); } // SmartScript end Unit* SmartScript::DoSelectLowestHpFriendly(float range, uint32 MinHPDiff) const { if (!me) return nullptr; Unit* unit = nullptr; Trinity::MostHPMissingInRange u_check(me, range, MinHPDiff); Trinity::UnitLastSearcher searcher(me, unit, u_check); Cell::VisitGridObjects(me, searcher, range); return unit; } Unit* SmartScript::DoSelectLowestHpPercentFriendly(float range, uint32 minHpPct, uint32 maxHpPct) const { if (!me) return nullptr; Unit* unit = nullptr; Trinity::MostHPPercentMissingInRange u_check(me, range, minHpPct, maxHpPct); Trinity::UnitLastSearcher searcher(me, unit, u_check); Cell::VisitGridObjects(me, searcher, range); return unit; } void SmartScript::DoFindFriendlyCC(std::vector& creatures, float range) const { if (!me) return; Trinity::FriendlyCCedInRange u_check(me, range); Trinity::CreatureListSearcher searcher(me, creatures, u_check); Cell::VisitGridObjects(me, searcher, range); } void SmartScript::DoFindFriendlyMissingBuff(std::vector& creatures, float range, uint32 spellid) const { if (!me) return; Trinity::FriendlyMissingBuffInRange u_check(me, range, spellid); Trinity::CreatureListSearcher searcher(me, creatures, u_check); Cell::VisitGridObjects(me, searcher, range); } Unit* SmartScript::DoFindClosestFriendlyInRange(float range, bool playerOnly) const { if (!me) return nullptr; Unit* unit = nullptr; Trinity::AnyFriendlyUnitInObjectRangeCheck u_check(me, me, range, playerOnly); Trinity::UnitLastSearcher searcher(me, unit, u_check); Cell::VisitAllObjects(me, searcher, range); return unit; } void SmartScript::SetTimedActionList(SmartScriptHolder& e, uint32 entry, Unit* invoker, uint32 startFromEventId) { //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) { TC_LOG_ERROR("scripts.ai", "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.GetActionType() == SMART_ACTION_CALL_TIMED_ACTIONLIST && !e.action.timedActionList.allowOverride && !mTimedActionList.empty()) return; mTimedActionList.clear(); mTimedActionList = sSmartScriptMgr->GetScript(entry, SMART_SCRIPT_TYPE_TIMED_ACTIONLIST); if (mTimedActionList.empty()) return; Trinity::Containers::EraseIf(mTimedActionList, [startFromEventId](SmartScriptHolder const& script) { return script.event_id < startFromEventId; }); mTimedActionListInvoker = invoker ? invoker->GetGUID() : ObjectGuid::Empty; 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)); } } Unit* SmartScript::GetLastInvoker(Unit* invoker) const { // Look for invoker only on map of base object... Prevents multithreaded crashes if (WorldObject* baseObject = GetBaseObject()) return ObjectAccessor::GetUnit(*baseObject, mLastInvoker); // used for area triggers invoker cast else if (invoker) return ObjectAccessor::GetUnit(*invoker, mLastInvoker); return nullptr; } 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) { mEventPhase = p; } bool SmartScript::IsInPhase(uint32 p) const { if (mEventPhase == 0) return false; return ((1 << (mEventPhase - 1)) & p) != 0; }