/* * 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 "Creature.h" #include "CreatureTextMgr.h" #include "CreatureTextMgrImpl.h" #include "DB2Stores.h" #include "GameEventMgr.h" #include "GameObject.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 "ObjectAccessor.h" #include "ObjectMgr.h" #include "PhasingHandler.h" #include "Random.h" #include "SmartAI.h" #include "SpellAuras.h" #include "SpellMgr.h" #include "TemporarySummon.h" #include "Vehicle.h" #include "WaypointDefines.h" SmartScript::SmartScript() { go = nullptr; me = nullptr; trigger = nullptr; areaTrigger = nullptr; sceneTemplate = nullptr; quest = nullptr; mEventPhase = 0; mPathId = 0; mTextTimer = 0; mLastTextID = 0; mUseTextTimer = false; mTalkerEntry = 0; mTemplate = SMARTAI_TEMPLATE_BASIC; mScriptType = SMART_SCRIPT_TYPE_CREATURE; isProcessingTimedActionList = false; } SmartScript::~SmartScript() { } bool SmartScript::IsSmart(Creature* c /*= nullptr*/) { bool smart = true; if (c && c->GetAIName() != "SmartAI") smart = false; if (!me || me->GetAIName() != "SmartAI") smart = false; if (!smart) TC_LOG_ERROR("sql.sql", "SmartScript: Action target Creature (GUID: " UI64FMTD " Entry: %u) is not using SmartAI, action called by Creature (GUID: " UI64FMTD " Entry: %u) skipped to prevent crash.", uint64(c ? c->GetSpawnId() : UI64LIT(0)), c ? c->GetEntry() : 0, uint64(me ? me->GetSpawnId() : UI64LIT(0)), me ? me->GetEntry() : 0); return smart; } bool SmartScript::IsSmartGO(GameObject* g /*= nullptr*/) { bool smart = true; if (g && g->GetAIName() != "SmartGameObjectAI") smart = false; if (!go || go->GetAIName() != "SmartGameObjectAI") smart = false; if (!smart) TC_LOG_ERROR("sql.sql", "SmartScript: Action target GameObject (GUID: " UI64FMTD " Entry: %u) is not using SmartGameObjectAI, action called by GameObject (GUID: " UI64FMTD " Entry: %u) skipped to prevent crash.", uint64(g ? g->GetSpawnId() : UI64LIT(0)), g ? g->GetEntry() : 0, uint64(go ? go->GetSpawnId() : UI64LIT(0)), go ? go->GetEntry() : 0); return smart; } 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 (SmartAIEventList::iterator i = mEvents.begin(); i != mEvents.end(); ++i) { if (!((*i).event.event_flags & SMART_EVENT_FLAG_DONT_RESET)) { InitTimer((*i)); (*i).runOnce = false; } } ProcessEventsFor(SMART_EVENT_RESET); mLastInvoker.Clear(); mCounterList.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; } } if (!goOrigGUID.IsEmpty()) { if (GameObject* o = ObjectAccessor::GetGameObject(*lookupRoot, goOrigGUID)) { me = nullptr; go = o; areaTrigger = 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 const& varString) { for (SmartAIEventList::iterator i = mEvents.begin(); i != mEvents.end(); ++i) { SMART_EVENT eventType = SMART_EVENT(i->GetEventType()); if (eventType == SMART_EVENT_LINK)//special handling continue; if (eventType == e) if (sConditionMgr->IsObjectMeetingSmartEventConditions(i->entryOrGuid, i->event_id, i->source_type, unit, GetBaseObject())) ProcessEvent(*i, unit, var0, var1, bvar, spell, gob, varString); } } void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, uint32 var1, bool bvar, SpellInfo const* spell, GameObject* gob, std::string const& varString) { // calc random if (e.GetEventType() != SMART_EVENT_LINK && e.event.event_chance < 100 && e.event.event_chance) { if (!roll_chance_i(e.event.event_chance)) return; } e.runOnce = true;//used for repeat check if (unit) mLastInvoker = unit->GetGUID(); if (Unit* tempInvoker = GetLastInvoker()) TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: Invoker: %s (%s)", tempInvoker->GetName().c_str(), tempInvoker->GetGUID().ToString().c_str()); ObjectVector targets; GetTargets(targets, e, unit); switch (e.GetActionType()) { case SMART_ACTION_TALK: { Creature* talker = e.target.type == 0 ? me : nullptr; Unit* talkTarget = nullptr; for (WorldObject* target : targets) { if (IsCreature(target) && !target->ToCreature()->IsPet()) // Prevented sending text to pets. { if (e.action.talk.useTalkTarget) { talker = me; talkTarget = target->ToCreature(); } else talker = target->ToCreature(); break; } else if (IsPlayer(target)) { talker = me; talkTarget = target->ToPlayer(); break; } } if (!talkTarget) talkTarget = GetLastInvoker(); if (!talker) break; mTalkerEntry = talker->GetEntry(); mLastTextID = e.action.talk.textGroupID; mTextTimer = e.action.talk.duration; mUseTextTimer = true; sCreatureTextMgr->SendChat(talker, uint8(e.action.talk.textGroupID), talkTarget); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_TALK: talker: %s (%s), textGuid: %s", talker->GetName().c_str(), talker->GetGUID().ToString().c_str(), talkTarget ? talkTarget->GetGUID().ToString().c_str() : "Empty"); break; } case SMART_ACTION_SIMPLE_TALK: { for (WorldObject* target : targets) { if (IsCreature(target)) sCreatureTextMgr->SendChat(target->ToCreature(), uint8(e.action.talk.textGroupID), IsPlayer(GetLastInvoker()) ? GetLastInvoker() : nullptr); else if (IsPlayer(target) && me) { Unit* templastInvoker = GetLastInvoker(); sCreatureTextMgr->SendChat(me, uint8(e.action.talk.textGroupID), IsPlayer(templastInvoker) ? templastInvoker : nullptr, CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_NORMAL, 0, TEAM_OTHER, false, target->ToPlayer()); } TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SIMPLE_TALK: talker: %s (%s), textGroupId: %u", target->GetName().c_str(), target->GetGUID().ToString().c_str(), uint8(e.action.talk.textGroupID)); } break; } case SMART_ACTION_PLAY_EMOTE: { for (WorldObject* target : targets) { if (IsUnit(target)) { target->ToUnit()->HandleEmoteCommand(e.action.emote.emote); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_PLAY_EMOTE: target: %s (%s), emote: %u", target->GetName().c_str(), target->GetGUID().ToString().c_str(), e.action.emote.emote); } } break; } case SMART_ACTION_SOUND: { for (WorldObject* target : targets) { if (IsUnit(target)) { if (e.action.sound.distance == 1) target->PlayDistanceSound(e.action.sound.sound, e.action.sound.onlySelf ? target->ToPlayer() : nullptr); else target->PlayDirectSound(e.action.sound.sound, e.action.sound.onlySelf ? target->ToPlayer() : nullptr, e.action.sound.keyBroadcastTextId); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SOUND: target: %s (%s), sound: %u, onlyself: %u", target->GetName().c_str(), target->GetGUID().ToString().c_str(), e.action.sound.sound, e.action.sound.onlySelf); } } break; } case SMART_ACTION_SET_FACTION: { for (WorldObject* target : targets) { if (IsCreature(target)) { if (e.action.faction.factionID) { target->ToCreature()->SetFaction(e.action.faction.factionID); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SET_FACTION: Creature entry %u, %s set faction to %u", target->GetEntry(), target->GetGUID().ToString().c_str(), e.action.faction.factionID); } else { if (CreatureTemplate const* ci = sObjectMgr->GetCreatureTemplate(target->ToCreature()->GetEntry())) { if (target->ToCreature()->GetFaction() != ci->faction) { target->ToCreature()->SetFaction(ci->faction); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SET_FACTION: Creature entry %u, %s set faction to %u", target->GetEntry(), target->GetGUID().ToString().c_str(), ci->faction); } } } } } break; } case SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: { for (WorldObject* target : targets) { if (!IsCreature(target)) continue; if (e.action.morphOrMount.creature || e.action.morphOrMount.model) { //set model based on entry from creature_template if (e.action.morphOrMount.creature) { if (CreatureTemplate const* ci = sObjectMgr->GetCreatureTemplate(e.action.morphOrMount.creature)) { CreatureModel const* model = ObjectMgr::ChooseDisplayId(ci); target->ToCreature()->SetDisplayId(model->CreatureDisplayID, model->DisplayScale); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: Creature entry %u, %s set displayid to %u", target->GetEntry(), target->GetGUID().ToString().c_str(), model->CreatureDisplayID); } } //if no param1, then use value from param2 (modelId) else { target->ToCreature()->SetDisplayId(e.action.morphOrMount.model); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: Creature entry %u, %s set displayid to %u", target->GetEntry(), target->GetGUID().ToString().c_str(), e.action.morphOrMount.model); } } else { target->ToCreature()->DeMorph(); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: Creature entry %u, %s demorphs.", target->GetEntry(), target->GetGUID().ToString().c_str()); } } break; } case SMART_ACTION_FAIL_QUEST: { for (WorldObject* target : targets) { if (IsPlayer(target)) { target->ToPlayer()->FailQuest(e.action.quest.quest); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_FAIL_QUEST: Player %s fails quest %u", target->GetGUID().ToString().c_str(), e.action.quest.quest); } } break; } case SMART_ACTION_OFFER_QUEST: { for (WorldObject* target : targets) { if (Player* player = target->ToPlayer()) { if (Quest const* q = sObjectMgr->GetQuestTemplate(e.action.questOffer.questID)) { if (me && e.action.questOffer.directAdd == 0) { if (player->CanTakeQuest(q, true)) { if (WorldSession* session = player->GetSession()) { PlayerMenu menu(session); menu.SendQuestGiverQuestDetails(q, me->GetGUID(), true, false); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_OFFER_QUEST: Player %s - offering quest %u", player->GetGUID().ToString().c_str(), e.action.questOffer.questID); } } } else { player->AddQuestAndCheckCompletion(q, nullptr); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_OFFER_QUEST: Player %s - quest %u added", player->GetGUID().ToString().c_str(), e.action.questOffer.questID); } } } } break; } case SMART_ACTION_SET_REACT_STATE: { for (WorldObject* target : targets) { if (!IsCreature(target)) continue; target->ToCreature()->SetReactState(ReactStates(e.action.react.state)); } break; } case SMART_ACTION_RANDOM_EMOTE: { std::vector emotes; std::copy_if(std::begin(e.action.randomEmote.emotes), std::end(e.action.randomEmote.emotes), std::back_inserter(emotes), [](uint32 emote) { return emote != 0; }); for (WorldObject* target : targets) { if (IsUnit(target)) { uint32 emote = Trinity::Containers::SelectRandomContainerElement(emotes); target->ToUnit()->HandleEmoteCommand(emote); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_RANDOM_EMOTE: Creature %s handle random emote %u", target->GetGUID().ToString().c_str(), 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 %s modify threat for %s, value %i", me->GetGUID().ToString().c_str(), ref->GetVictim()->GetGUID().ToString().c_str(), int32(e.action.threatPCT.threatINC)-int32(e.action.threatPCT.threatDEC)); } break; } case SMART_ACTION_THREAT_SINGLE_PCT: { if (!me) break; for (WorldObject* target : targets) { if (IsUnit(target)) { me->GetThreatManager().ModifyThreatByPercent(target->ToUnit(), 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 %s modify threat for %s, value %i", me->GetGUID().ToString().c_str(), target->GetGUID().ToString().c_str(), int32(e.action.threatPCT.threatINC) - int32(e.action.threatPCT.threatDEC)); } } break; } case SMART_ACTION_CALL_AREAEXPLOREDOREVENTHAPPENS: { for (WorldObject* target : targets) { // Special handling for vehicles if (IsUnit(target)) if (Vehicle* vehicle = target->ToUnit()->GetVehicleKit()) for (SeatMap::iterator it = vehicle->Seats.begin(); it != vehicle->Seats.end(); ++it) if (Player* player = ObjectAccessor::GetPlayer(*target, it->second.Passenger.Guid)) player->AreaExploredOrEventHappens(e.action.quest.quest); if (IsPlayer(target)) { target->ToPlayer()->AreaExploredOrEventHappens(e.action.quest.quest); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_CALL_AREAEXPLOREDOREVENTHAPPENS: %s credited quest %u", target->GetGUID().ToString().c_str(), e.action.quest.quest); } } 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); for (WorldObject* target : targets) { // may be nullptr if (go) go->CastSpell(target->ToUnit(), e.action.cast.spell); if (!IsUnit(target)) continue; if (!(e.action.cast.castFlags & SMARTCAST_AURA_NOT_PRESENT) || !target->ToUnit()->HasAura(e.action.cast.spell)) { TriggerCastFlags triggerFlag = TRIGGERED_NONE; if (e.action.cast.castFlags & SMARTCAST_TRIGGERED) { if (e.action.cast.triggerFlags) triggerFlag = TriggerCastFlags(e.action.cast.triggerFlags); else triggerFlag = TRIGGERED_FULL_MASK; } if (me) { if (e.action.cast.castFlags & SMARTCAST_INTERRUPT_PREVIOUS) me->InterruptNonMeleeSpells(false); if (e.action.cast.castFlags & SMARTCAST_COMBAT_MOVE) { // If cast flag SMARTCAST_COMBAT_MOVE is set combat movement will not be allowed // unless target is outside spell range, out of mana, or LOS. bool allowMove = false; SpellInfo const* spellInfo = sSpellMgr->AssertSpellInfo(e.action.cast.spell, me->GetMap()->GetDifficultyID()); std::vector costs = spellInfo->CalcPowerCost(me, spellInfo->GetSchoolMask()); bool hasPower = true; for (SpellPowerCost const& cost : costs) { if (cost.Power == POWER_HEALTH) { if (me->GetHealth() <= uint32(cost.Amount)) { hasPower = false; break; } } else { if (me->GetPower(cost.Power) < cost.Amount) { hasPower = false; break; } } } if (me->GetDistance(target) > spellInfo->GetMaxRange(true) || me->GetDistance(target) < spellInfo->GetMinRange(true) || !me->IsWithinLOSInMap(target) || !hasPower) allowMove = true; ENSURE_AI(SmartAI, me->AI())->SetCombatMove(allowMove); } me->CastSpell(target->ToUnit(), e.action.cast.spell, triggerFlag); } else if (go) go->CastSpell(target->ToUnit(), e.action.cast.spell, triggerFlag); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_CAST:: %s casts spell %u on target %s with castflags %u", (me ? me->GetGUID() : go->GetGUID()).ToString().c_str(), e.action.cast.spell, target->GetGUID().ToString().c_str(), e.action.cast.castFlags); } else TC_LOG_DEBUG("scripts.ai", "Spell %u not cast because it has flag SMARTCAST_AURA_NOT_PRESENT and the target (%s) already has the aura", e.action.cast.spell, target->GetGUID().ToString().c_str()); } break; } case SMART_ACTION_INVOKER_CAST: { Unit* tempLastInvoker = GetLastInvoker(unit); if (!tempLastInvoker) break; if (targets.empty()) break; if (e.action.cast.targetsLimit > 0 && targets.size() > e.action.cast.targetsLimit) Trinity::Containers::RandomResize(targets, e.action.cast.targetsLimit); for (WorldObject* target : targets) { if (!IsUnit(target)) continue; if (!(e.action.cast.castFlags & SMARTCAST_AURA_NOT_PRESENT) || !target->ToUnit()->HasAura(e.action.cast.spell)) { if (e.action.cast.castFlags & SMARTCAST_INTERRUPT_PREVIOUS) tempLastInvoker->InterruptNonMeleeSpells(false); TriggerCastFlags triggerFlag = TRIGGERED_NONE; if (e.action.cast.castFlags & SMARTCAST_TRIGGERED) { if (e.action.cast.triggerFlags) triggerFlag = TriggerCastFlags(e.action.cast.triggerFlags); else triggerFlag = TRIGGERED_FULL_MASK; } tempLastInvoker->CastSpell(target->ToUnit(), e.action.cast.spell, triggerFlag); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_INVOKER_CAST: Invoker %s casts spell %u on target %s with castflags %u", tempLastInvoker->GetGUID().ToString().c_str(), e.action.cast.spell, target->GetGUID().ToString().c_str(), e.action.cast.castFlags); } else TC_LOG_DEBUG("scripts.ai", "Spell %u not cast because it has flag SMARTCAST_AURA_NOT_PRESENT and the target (%s) already has the aura", e.action.cast.spell, target->GetGUID().ToString().c_str()); } break; } case SMART_ACTION_ADD_AURA: { for (WorldObject* target : targets) { if (IsUnit(target)) { target->ToUnit()->AddAura(e.action.cast.spell, target->ToUnit()); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_ADD_AURA: Adding aura %u to %s", e.action.cast.spell, target->GetGUID().ToString().c_str()); } } break; } case SMART_ACTION_ACTIVATE_GOBJECT: { for (WorldObject* target : targets) { if (IsGameObject(target)) { // Activate target->ToGameObject()->SetLootState(GO_READY); target->ToGameObject()->UseDoorOrButton(0, false, unit); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_ACTIVATE_GOBJECT. %s (entry: %u) activated", target->GetGUID().ToString().c_str(), target->GetEntry()); } } break; } case SMART_ACTION_RESET_GOBJECT: { for (WorldObject* target : targets) { if (IsGameObject(target)) { target->ToGameObject()->ResetDoorOrButton(); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_RESET_GOBJECT. %s (entry: %u) reset", target->GetGUID().ToString().c_str(), target->GetEntry()); } } break; } case SMART_ACTION_SET_EMOTE_STATE: { for (WorldObject* target : targets) { if (IsUnit(target)) { target->ToUnit()->SetEmoteState(Emote(e.action.emote.emote)); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SET_EMOTE_STATE. %s set emotestate to %u", target->GetGUID().ToString().c_str(), e.action.emote.emote); } } break; } case SMART_ACTION_SET_UNIT_FLAG: { for (WorldObject* target : targets) { if (IsUnit(target)) { if (!e.action.unitFlag.type) { target->ToUnit()->AddUnitFlag(UnitFlags(e.action.unitFlag.flag)); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SET_UNIT_FLAG. %s added flag %u to UNIT_FIELD_FLAGS", target->GetGUID().ToString().c_str(), e.action.unitFlag.flag); } else { target->ToUnit()->AddUnitFlag2(UnitFlags2(e.action.unitFlag.flag)); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SET_UNIT_FLAG. %s added flag %u to UNIT_FIELD_FLAGS_2", target->GetGUID().ToString().c_str(), e.action.unitFlag.flag); } } } break; } case SMART_ACTION_REMOVE_UNIT_FLAG: { for (WorldObject* target : targets) { if (IsUnit(target)) { if (!e.action.unitFlag.type) { target->ToUnit()->RemoveUnitFlag(UnitFlags(e.action.unitFlag.flag)); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_REMOVE_UNIT_FLAG. %s removed flag %u to UNIT_FIELD_FLAGS", target->GetGUID().ToString().c_str(), e.action.unitFlag.flag); } else { target->ToUnit()->RemoveUnitFlag2(UnitFlags2(e.action.unitFlag.flag)); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_REMOVE_UNIT_FLAG. %s removed flag %u to UNIT_FIELD_FLAGS_2", target->GetGUID().ToString().c_str(), e.action.unitFlag.flag); } } } break; } case SMART_ACTION_AUTO_ATTACK: { if (!IsSmart()) break; ENSURE_AI(SmartAI, me->AI())->SetAutoAttack(e.action.autoAttack.attack != 0); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_AUTO_ATTACK: %s bool on = %u", me->GetGUID().ToString().c_str(), 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: %s bool on = %u", me->GetGUID().ToString().c_str(), 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: %s set event phase %u", GetBaseObject()->GetGUID().ToString().c_str(), 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: %s inc event phase by %u, " "decrease by %u", GetBaseObject()->GetGUID().ToString().c_str(), e.action.incEventPhase.inc, e.action.incEventPhase.dec); break; } case SMART_ACTION_EVADE: { if (!me) break; me->AI()->EnterEvadeMode(); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_EVADE: %s EnterEvadeMode", me->GetGUID().ToString().c_str()); 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: %s DoFleeToGetAssistance", me->GetGUID().ToString().c_str()); break; } case SMART_ACTION_CALL_GROUPEVENTHAPPENS: { if (!unit) break; // If invoker was pet or charm Player* playerCharmed = unit->GetCharmerOrOwnerPlayerOrPlayerItself(); if (playerCharmed && GetBaseObject()) { playerCharmed->GroupEventHappens(e.action.quest.quest, GetBaseObject()); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_CALL_GROUPEVENTHAPPENS: %s, group credit for quest %u", unit->GetGUID().ToString().c_str(), e.action.quest.quest); } // Special handling for vehicles if (Vehicle* vehicle = unit->GetVehicleKit()) for (SeatMap::iterator it = vehicle->Seats.begin(); it != vehicle->Seats.end(); ++it) if (Player* player = ObjectAccessor::GetPlayer(*unit, it->second.Passenger.Guid)) player->GroupEventHappens(e.action.quest.quest, GetBaseObject()); break; } case SMART_ACTION_COMBAT_STOP: { if (!me) break; me->CombatStop(true); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_COMBAT_STOP: %s CombatStop", me->GetGUID().ToString().c_str()); break; } case SMART_ACTION_REMOVEAURASFROMSPELL: { for (WorldObject* target : targets) { if (!IsUnit(target)) continue; if (e.action.removeAura.spell) { if (e.action.removeAura.charges) { if (Aura* aur = target->ToUnit()->GetAura(e.action.removeAura.spell)) aur->ModCharges(-static_cast(e.action.removeAura.charges), AURA_REMOVE_BY_EXPIRE); } else target->ToUnit()->RemoveAurasDueToSpell(e.action.removeAura.spell); } else target->ToUnit()->RemoveAllAuras(); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_REMOVEAURASFROMSPELL: %s, spell %u", target->GetGUID().ToString().c_str(), 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 (IsUnit(target)) { 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(target->ToUnit(), 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: %s following target %s", me->GetGUID().ToString().c_str(), target->GetGUID().ToString().c_str()); break; } } break; } case SMART_ACTION_RANDOM_PHASE: { if (!GetBaseObject()) break; std::vector phases; std::copy_if(std::begin(e.action.randomPhase.phases), std::end(e.action.randomPhase.phases), std::back_inserter(phases), [](uint32 phase) { return phase != 0; }); uint32 phase = Trinity::Containers::SelectRandomContainerElement(phases); SetPhase(phase); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_RANDOM_PHASE: %s sets event phase to %u", GetBaseObject()->GetGUID().ToString().c_str(), 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: %s sets event phase to %u", GetBaseObject()->GetGUID().ToString().c_str(), 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; if (Player* player = me->GetLootRecipient()) { player->RewardPlayerAndGroupAtEvent(e.action.killedMonster.creature, player); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_CALL_KILLEDMONSTER: %s, Killcredit: %u", player->GetGUID().ToString().c_str(), e.action.killedMonster.creature); } } else // Specific target type { for (WorldObject* target : targets) { if (IsPlayer(target)) { target->ToPlayer()->KilledMonsterCredit(e.action.killedMonster.creature); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_CALL_KILLEDMONSTER: %s, Killcredit: %u", target->GetGUID().ToString().c_str(), e.action.killedMonster.creature); } else if (IsUnit(target)) // Special handling for vehicles if (Vehicle* vehicle = target->ToUnit()->GetVehicleKit()) for (SeatMap::iterator seatItr = vehicle->Seats.begin(); seatItr != vehicle->Seats.end(); ++seatItr) if (Player* player = ObjectAccessor::GetPlayer(*target, seatItr->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 %u attempt to set instance data without instance script. EntryOrGuid " SI64FMTD "", 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: %u, data: %u", 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: %u, State: %u (%s)", 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 %u attempt to set instance data without instance script. EntryOrGuid " SI64FMTD "", 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: %u, data: %s", e.action.setInstanceData64.field, targets.front()->GetGUID().ToString().c_str()); break; } case SMART_ACTION_UPDATE_TEMPLATE: { for (WorldObject* target : targets) if (IsCreature(target)) target->ToCreature()->UpdateEntry(e.action.updateTemplate.creature, target->ToCreature()->GetCreatureData(), e.action.updateTemplate.updateLevel != 0); break; } case SMART_ACTION_DIE: { if (me && !me->isDead()) { me->KillSelf(); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_DIE: %s", me->GetGUID().ToString().c_str()); } break; } case SMART_ACTION_SET_IN_COMBAT_WITH_ZONE: { for (WorldObject* target : targets) { if (IsCreature(target)) { target->ToCreature()->SetInCombatWithZone(); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_SET_IN_COMBAT_WITH_ZONE: Creature %s, target: %s", me->GetGUID().ToString().c_str(), target->GetGUID().ToString().c_str()); } } break; } case SMART_ACTION_CALL_FOR_HELP: { for (WorldObject* target : targets) { if (IsCreature(target)) { target->ToCreature()->CallForHelp(float(e.action.callHelp.range)); if (e.action.callHelp.withEmote) { 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 %s, target: %s", me->GetGUID().ToString().c_str(), target->GetGUID().ToString().c_str()); } } 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: %s, State: %u", me->GetGUID().ToString().c_str(), 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 int32 const respawnDelay = std::max(e.action.forceDespawn.delay, 1); for (WorldObject* target : targets) { if (Creature* creature = target->ToCreature()) { if (SmartAI* smartAI = CAST_AI(SmartAI, creature->AI())) { smartAI->SetDespawnTime(respawnDelay); smartAI->StartDespawn(); } else creature->DespawnOrUnsummon(respawnDelay); } else if (GameObject* goTarget = target->ToGameObject()) goTarget->SetRespawnTime(respawnDelay); } 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) { if (!IsUnit(target)) continue; if (e.action.morphOrMount.creature || e.action.morphOrMount.model) { if (e.action.morphOrMount.creature > 0) { if (CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(e.action.morphOrMount.creature)) target->ToUnit()->Mount(ObjectMgr::ChooseDisplayId(cInfo)->CreatureDisplayID); } else target->ToUnit()->Mount(e.action.morphOrMount.model); } else target->ToUnit()->Dismount(); } break; } case SMART_ACTION_SET_INVINCIBILITY_HP_LEVEL: { for (WorldObject* target : targets) { if (IsCreature(target)) { SmartAI* ai = CAST_AI(SmartAI, target->ToCreature()->AI()); if (!ai) continue; if (e.action.invincHP.percent) ai->SetInvincibilityHpLevel(target->ToCreature()->CountPctFromMaxHealth(e.action.invincHP.percent)); else ai->SetInvincibilityHpLevel(e.action.invincHP.minHP); } } break; } case SMART_ACTION_SET_DATA: { for (WorldObject* target : targets) { if (IsCreature(target)) target->ToCreature()->AI()->SetData(e.action.setData.field, e.action.setData.data); else if (IsGameObject(target)) target->ToGameObject()->AI()->SetData(e.action.setData.field, e.action.setData.data); } break; } case SMART_ACTION_MOVE_OFFSET: { for (WorldObject* target : targets) { if (!IsCreature(target)) continue; if (!(e.event.event_flags & SMART_EVENT_FLAG_WHILE_CHARMED) && IsCharmedCreature(target)) 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; target->ToCreature()->GetMotionMaster()->MovePoint(SMART_RANDOM_POINT, x, y, z); } break; } case SMART_ACTION_SET_VISIBILITY: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->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; 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); WorldObject* summoner = preferUnit ? unit : GetBaseObjectOrUnit(unit); if (!summoner) break; ObjectGuid privateObjectOwner; if (flags.HasFlag(SmartActionSummonCreatureFlags::PersonalSpawn)) privateObjectOwner = summoner->IsPrivateObject() ? summoner->GetPrivateObjectOwner() : summoner->GetGUID(); float x, y, z, o; for (WorldObject* target : targets) { target->GetPosition(x, y, z, o); x += e.target.x; y += e.target.y; z += e.target.z; o += e.target.o; if (Creature* summon = summoner->SummonCreature(e.action.summonCreature.creature, x, y, z, o, (TempSummonType)e.action.summonCreature.type, e.action.summonCreature.duration, privateObjectOwner)) if (e.action.summonCreature.attackInvoker) summon->AI()->AttackStart(target->ToUnit()); } if (e.GetTargetType() != SMART_TARGET_POSITION) break; if (Creature* summon = summoner->SummonCreature(e.action.summonCreature.creature, e.target.x, e.target.y, e.target.z, e.target.o, (TempSummonType)e.action.summonCreature.type, e.action.summonCreature.duration, privateObjectOwner)) if (unit && e.action.summonCreature.attackInvoker) summon->AI()->AttackStart(unit); break; } case SMART_ACTION_SUMMON_GO: { WorldObject* summoner = GetBaseObjectOrUnit(unit); if (!summoner) break; for (WorldObject* target : targets) { Position pos = target->GetPositionWithOffset(Position(e.target.x, e.target.y, e.target.z, e.target.o)); summoner->SummonGameObject(e.action.summonGO.entry, pos, QuaternionData::fromEulerAnglesZYX(pos.GetOrientation(), 0.f, 0.f), e.action.summonGO.despawnTime); } if (e.GetTargetType() != SMART_TARGET_POSITION) break; summoner->SummonGameObject(e.action.summonGO.entry, Position(e.target.x, e.target.y, e.target.z, e.target.o), QuaternionData::fromEulerAnglesZYX(e.target.o, 0.f, 0.f), e.action.summonGO.despawnTime); break; } case SMART_ACTION_KILL_UNIT: { for (WorldObject* target : targets) { if (!IsUnit(target)) continue; target->ToUnit()->KillSelf(); } break; } case SMART_ACTION_INSTALL_AI_TEMPLATE: { InstallTemplate(e); break; } case SMART_ACTION_ADD_ITEM: { for (WorldObject* target : targets) { if (!IsPlayer(target)) continue; target->ToPlayer()->AddItem(e.action.item.entry, e.action.item.count); } break; } case SMART_ACTION_REMOVE_ITEM: { for (WorldObject* target : targets) { if (!IsPlayer(target)) continue; target->ToPlayer()->DestroyItemCount(e.action.item.entry, e.action.item.count, true); } break; } case SMART_ACTION_STORE_TARGET_LIST: { StoreTargetList(targets, e.action.storeTargets.id); break; } case SMART_ACTION_TELEPORT: { for (WorldObject* target : targets) { if (IsPlayer(target)) target->ToPlayer()->TeleportTo(e.action.teleport.mapID, e.target.x, e.target.y, e.target.z, e.target.o); else if (IsCreature(target)) target->ToCreature()->NearTeleportTo(e.target.x, e.target.y, e.target.z, e.target.o); } break; } case SMART_ACTION_SET_DISABLE_GRAVITY: { if (!IsSmart()) break; ENSURE_AI(SmartAI, me->AI())->SetDisableGravity(e.action.setDisableGravity.disable != 0); break; } case SMART_ACTION_SET_CAN_FLY: { if (!IsSmart()) break; ENSURE_AI(SmartAI, me->AI())->SetCanFly(e.action.setFly.fly != 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_SWIM: { if (!IsSmart()) break; ENSURE_AI(SmartAI, me->AI())->SetSwim(e.action.setSwim.swim != 0); break; } case SMART_ACTION_SET_COUNTER: { if (!targets.empty()) { for (WorldObject* target : targets) { if (IsCreature(target)) { if (SmartAI* ai = CAST_AI(SmartAI, target->ToCreature()->AI())) ai->GetScript()->StoreCounter(e.action.setCounter.counterId, e.action.setCounter.value, e.action.setCounter.reset); else TC_LOG_ERROR("sql.sql", "SmartScript: Action target for SMART_ACTION_SET_COUNTER is not using SmartAI, skipping"); } else if (IsGameObject(target)) { if (SmartGameObjectAI* ai = CAST_AI(SmartGameObjectAI, target->ToGameObject()->AI())) ai->GetScript()->StoreCounter(e.action.setCounter.counterId, e.action.setCounter.value, e.action.setCounter.reset); 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; bool run = e.action.wpStart.run != 0; uint32 entry = e.action.wpStart.pathID; bool repeat = e.action.wpStart.repeat != 0; for (WorldObject* target : targets) { if (IsPlayer(target)) { StoreTargetList(targets, SMART_ESCORT_TARGETS); break; } } me->SetReactState((ReactStates)e.action.wpStart.reactState); ENSURE_AI(SmartAI, me->AI())->StartPath(run, entry, repeat, unit); 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, e.GetEventType() == SMART_EVENT_WAYPOINT_REACHED ? false : 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; ENSURE_AI(SmartAI, me->AI())->SetWPPauseTimer(0); 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 (!IsPlayer(target)) continue; target->ToPlayer()->SendMovieStart(e.action.movie.entry); } break; } case SMART_ACTION_MOVE_TO_POS: { if (!IsSmart()) break; WorldObject* target = nullptr; /*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)*/ { // we want to move to random element if (!targets.empty()) target = Trinity::Containers::SelectRandomContainerElement(targets); } if (!target) { 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); } else { 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); } break; } case SMART_ACTION_RESPAWN_TARGET: { for (WorldObject* target : targets) { if (IsCreature(target)) target->ToCreature()->Respawn(); else if (IsGameObject(target)) { // do not modify respawndelay of already spawned gameobjects if (target->ToGameObject()->isSpawnedByDefault()) target->ToGameObject()->Respawn(); else target->ToGameObject()->SetRespawnTime(e.action.RespawnTarget.goRespawnTime); } } break; } case SMART_ACTION_CLOSE_GOSSIP: { for (WorldObject* target : targets) if (IsPlayer(target)) target->ToPlayer()->PlayerTalkClass->SendCloseGossip(); break; } case SMART_ACTION_EQUIP: { for (WorldObject* target : targets) { if (Creature* npc = target->ToCreature()) { std::array slot; if (int8 equipId = static_cast(e.action.equip.entry)) { EquipmentInfo const* eInfo = sObjectMgr->GetEquipmentInfo(npc->GetEntry(), equipId); if (!eInfo) { TC_LOG_ERROR("sql.sql", "SmartScript: SMART_ACTION_EQUIP uses non-existent equipment info id %u for creature %u", 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(0, slot[i].ItemId, slot[i].AppearanceModId, slot[i].ItemVisual); } } break; } case SMART_ACTION_CREATE_TIMED_EVENT: { SmartEvent ne = SmartEvent(); ne.type = (SMART_EVENT)SMART_EVENT_UPDATE; ne.event_chance = e.action.timeEvent.chance; if (!ne.event_chance) ne.event_chance = 100; ne.minMaxRepeat.min = e.action.timeEvent.min; ne.minMaxRepeat.max = e.action.timeEvent.max; ne.minMaxRepeat.repeatMin = e.action.timeEvent.repeatMin; ne.minMaxRepeat.repeatMax = e.action.timeEvent.repeatMax; ne.event_flags = 0; if (!ne.minMaxRepeat.repeatMin && !ne.minMaxRepeat.repeatMax) ne.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; SmartAction ac = SmartAction(); ac.type = (SMART_ACTION)SMART_ACTION_TRIGGER_TIMED_EVENT; ac.timeEvent.id = e.action.timeEvent.id; SmartScriptHolder ev = SmartScriptHolder(); ev.event = ne; ev.event_id = e.action.timeEvent.id; ev.target = e.target; ev.action = ac; InitTimer(ev); mStoredEvents.push_back(ev); break; } case SMART_ACTION_TRIGGER_TIMED_EVENT: ProcessEventsFor((SMART_EVENT)SMART_EVENT_TIMED_EVENT_TRIGGERED, nullptr, e.action.timeEvent.id); // remove this event if not repeatable if (e.event.event_flags & SMART_EVENT_FLAG_NOT_REPEATABLE) mRemIDs.push_back(e.action.timeEvent.id); break; case SMART_ACTION_REMOVE_TIMED_EVENT: mRemIDs.push_back(e.action.timeEvent.id); break; case SMART_ACTION_OVERRIDE_SCRIPT_BASE_OBJECT: { for (WorldObject* target : targets) { if (IsCreature(target)) { if (!meOrigGUID && me) meOrigGUID = me->GetGUID(); if (!goOrigGUID && go) goOrigGUID = go->GetGUID(); go = nullptr; me = target->ToCreature(); break; } else if (IsGameObject(target)) { if (!meOrigGUID && me) meOrigGUID = me->GetGUID(); if (!goOrigGUID && go) goOrigGUID = go->GetGUID(); go = target->ToGameObject(); me = nullptr; break; } } break; } case SMART_ACTION_RESET_SCRIPT_BASE_OBJECT: ResetBaseObject(); 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->GetMotionMaster()->MoveChase(creature->GetVictim(), attackDistance, attackAngle); break; } case SMART_ACTION_CALL_TIMED_ACTIONLIST: { if (e.GetTargetType() == SMART_TARGET_NONE) { TC_LOG_ERROR("sql.sql", "SmartScript: Entry " SI64FMTD " SourceType %u Event %u Action %u 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())->SetScript9(e, e.action.timedActionList.id, GetLastInvoker()); } else if (GameObject* goTarget = target->ToGameObject()) { if (IsSmartGO(goTarget)) ENSURE_AI(SmartGameObjectAI, goTarget->AI())->SetScript9(e, e.action.timedActionList.id, GetLastInvoker()); } else if (AreaTrigger* areaTriggerTarget = target->ToAreaTrigger()) if (SmartAreaTriggerAI* atSAI = CAST_AI(SmartAreaTriggerAI, areaTriggerTarget->AI())) atSAI->SetScript9(e, e.action.timedActionList.id, GetLastInvoker()); } break; } case SMART_ACTION_SET_NPC_FLAG: { for (WorldObject* target : targets) if (IsCreature(target)) target->ToUnit()->SetNpcFlags(NPCFlags(e.action.unitFlag.flag)); break; } case SMART_ACTION_ADD_NPC_FLAG: { for (WorldObject* target : targets) if (IsCreature(target)) target->ToUnit()->AddNpcFlag(NPCFlags(e.action.unitFlag.flag)); break; } case SMART_ACTION_REMOVE_NPC_FLAG: { for (WorldObject* target : targets) if (IsCreature(target)) target->ToUnit()->RemoveNpcFlag(NPCFlags(e.action.unitFlag.flag)); break; } case SMART_ACTION_CROSS_CAST: { if (targets.empty()) break; ObjectVector casters; GetTargets(casters, CreateSmartEvent(SMART_EVENT_UPDATE_IC, 0, 0, 0, 0, 0, 0, SMART_ACTION_NONE, 0, 0, 0, 0, 0, 0, (SMARTAI_TARGETS)e.action.crossCast.targetType, e.action.crossCast.targetParam1, e.action.crossCast.targetParam2, e.action.crossCast.targetParam3, 0), unit); for (WorldObject* caster : casters) { if (!IsUnit(caster)) continue; Unit* casterUnit = caster->ToUnit(); bool interruptedSpell = false; for (WorldObject* target : targets) { if (!IsUnit(target)) continue; if (!(e.action.crossCast.castFlags & SMARTCAST_AURA_NOT_PRESENT) || !target->ToUnit()->HasAura(e.action.crossCast.spell)) { if (!interruptedSpell && e.action.crossCast.castFlags & SMARTCAST_INTERRUPT_PREVIOUS) { casterUnit->InterruptNonMeleeSpells(false); interruptedSpell = true; } casterUnit->CastSpell(target->ToUnit(), e.action.crossCast.spell, (e.action.crossCast.castFlags & SMARTCAST_TRIGGERED) != 0); } else TC_LOG_DEBUG("scripts.ai", "Spell %u not cast because it has flag SMARTCAST_AURA_NOT_PRESENT and the target (%s) already has the aura", e.action.crossCast.spell, target->GetGUID().ToString().c_str()); } } break; } case SMART_ACTION_CALL_RANDOM_TIMED_ACTIONLIST: { std::vector actionLists; std::copy_if(std::begin(e.action.randTimedActionList.actionLists), std::end(e.action.randTimedActionList.actionLists), std::back_inserter(actionLists), [](uint32 actionList) { return actionList != 0; }); uint32 id = Trinity::Containers::SelectRandomContainerElement(actionLists); if (e.GetTargetType() == SMART_TARGET_NONE) { TC_LOG_ERROR("sql.sql", "SmartScript: Entry " SI64FMTD " SourceType %u Event %u Action %u 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())->SetScript9(e, id, GetLastInvoker()); } else if (GameObject* goTarget = target->ToGameObject()) { if (IsSmartGO(goTarget)) ENSURE_AI(SmartGameObjectAI, goTarget->AI())->SetScript9(e, id, GetLastInvoker()); } else if (AreaTrigger* areaTriggerTarget = target->ToAreaTrigger()) if (SmartAreaTriggerAI* atSAI = CAST_AI(SmartAreaTriggerAI, areaTriggerTarget->AI())) atSAI->SetScript9(e, id, GetLastInvoker()); } break; } case SMART_ACTION_CALL_RANDOM_RANGE_TIMED_ACTIONLIST: { uint32 id = urand(e.action.randTimedActionList.actionLists[0], e.action.randTimedActionList.actionLists[1]); if (e.GetTargetType() == SMART_TARGET_NONE) { TC_LOG_ERROR("sql.sql", "SmartScript: Entry " SI64FMTD " SourceType %u Event %u Action %u 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())->SetScript9(e, id, GetLastInvoker()); } else if (GameObject* goTarget = target->ToGameObject()) { if (IsSmartGO(goTarget)) ENSURE_AI(SmartGameObjectAI, goTarget->AI())->SetScript9(e, id, GetLastInvoker()); } else if (AreaTrigger* areaTriggerTarget = target->ToAreaTrigger()) if (SmartAreaTriggerAI* atSAI = CAST_AI(SmartAreaTriggerAI, areaTriggerTarget->AI())) atSAI->SetScript9(e, id, GetLastInvoker()); } break; } case SMART_ACTION_ACTIVATE_TAXI: { for (WorldObject* target : targets) if (IsPlayer(target)) target->ToPlayer()->ActivateTaxiPathTo(e.action.taxi.id); break; } case SMART_ACTION_RANDOM_MOVE: { bool foundTarget = false; for (WorldObject* target : targets) { if (IsCreature((target))) { foundTarget = true; if (e.action.moveRandom.distance) target->ToCreature()->GetMotionMaster()->MoveRandom(float(e.action.moveRandom.distance)); else target->ToCreature()->GetMotionMaster()->MoveIdle(); } } if (!foundTarget && me && IsCreature(me)) { if (e.action.moveRandom.distance) me->GetMotionMaster()->MoveRandom(float(e.action.moveRandom.distance)); else me->GetMotionMaster()->MoveIdle(); } break; } case SMART_ACTION_SET_UNIT_FIELD_BYTES_1: { for (WorldObject* target : targets) if (IsUnit(target)) { switch (e.action.setunitByte.type) { case 0: target->ToUnit()->SetStandState(UnitStandStateType(e.action.setunitByte.byte1)); break; case 1: // pet talent points break; case 2: target->ToUnit()->AddVisFlags(UnitVisFlags(e.action.setunitByte.byte1)); break; case 3: // this is totally wrong to maintain compatibility with existing scripts // TODO: fix with animtier overhaul target->ToUnit()->SetAnimTier(UnitBytes1_Flags(target->ToUnit()->m_unitData->AnimTier | e.action.setunitByte.byte1), false); break; } } break; } case SMART_ACTION_REMOVE_UNIT_FIELD_BYTES_1: { for (WorldObject* target : targets) if (IsUnit(target)) { switch (e.action.setunitByte.type) { case 0: target->ToUnit()->SetStandState(UNIT_STAND_STATE_STAND); break; case 1: // pet talent points break; case 2: target->ToUnit()->RemoveVisFlags(UnitVisFlags(e.action.setunitByte.byte1)); break; case 3: target->ToUnit()->SetAnimTier(UnitBytes1_Flags(target->ToUnit()->m_unitData->AnimTier & ~e.action.setunitByte.byte1), false); break; } } break; } case SMART_ACTION_INTERRUPT_SPELL: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->InterruptNonMeleeSpells(e.action.interruptSpellCasting.withDelayed != 0, e.action.interruptSpellCasting.spell_id, e.action.interruptSpellCasting.withInstant != 0); break; } case SMART_ACTION_SEND_GO_CUSTOM_ANIM: { for (WorldObject* target : targets) if (IsGameObject(target)) target->ToGameObject()->SendCustomAnim(e.action.sendGoCustomAnim.anim); break; } case SMART_ACTION_SET_DYNAMIC_FLAG: { for (WorldObject* target : targets) target->SetDynamicFlags(e.action.unitFlag.flag); break; } case SMART_ACTION_ADD_DYNAMIC_FLAG: { for (WorldObject* target : targets) target->AddDynamicFlag(e.action.unitFlag.flag); break; } case SMART_ACTION_REMOVE_DYNAMIC_FLAG: { for (WorldObject* target : targets) target->RemoveDynamicFlag(e.action.unitFlag.flag); break; } case SMART_ACTION_JUMP_TO_POS: { for (WorldObject* target : targets) if (Creature* creature = target->ToCreature()) creature->GetMotionMaster()->MoveJump(e.target.x, e.target.y, e.target.z, 0.0f, float(e.action.jump.speedxy), float(e.action.jump.speedz)); // @todo add optional jump orientation support? break; } case SMART_ACTION_GO_SET_LOOT_STATE: { for (WorldObject* target : targets) if (IsGameObject(target)) target->ToGameObject()->SetLootState((LootState)e.action.setGoLootState.state); break; } case SMART_ACTION_GO_SET_GO_STATE: { for (WorldObject* target : targets) if (IsGameObject(target)) target->ToGameObject()->SetGoState((GOState)e.action.goState.state); break; } case SMART_ACTION_SEND_TARGET_TO_TARGET: { WorldObject* ref = GetBaseObject(); if (!ref) ref = unit; if (!ref) break; ObjectVector const* storedTargets = GetStoredTargetVector(e.action.sendTargetToTarget.id, *ref); if (!storedTargets) break; for (WorldObject* target : targets) { if (IsCreature(target)) { if (SmartAI* ai = CAST_AI(SmartAI, target->ToCreature()->AI())) ai->GetScript()->StoreTargetList(ObjectVector(*storedTargets), e.action.sendTargetToTarget.id); // store a copy of target list else TC_LOG_ERROR("sql.sql", "SmartScript: Action target for SMART_ACTION_SEND_TARGET_TO_TARGET is not using SmartAI, skipping"); } else if (IsGameObject(target)) { if (SmartGameObjectAI* ai = CAST_AI(SmartGameObjectAI, target->ToGameObject()->AI())) ai->GetScript()->StoreTargetList(ObjectVector(*storedTargets), e.action.sendTargetToTarget.id); // store a copy of target list else 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 %d, gossipNpcTextId %d", 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(); player->PlayerTalkClass->SendGossipMenu(e.action.sendGossipMenu.gossipNpcTextId, GetBaseObject()->GetGUID()); } } break; } case SMART_ACTION_SET_HOME_POS: { for (WorldObject* target : targets) { if (IsCreature(target)) { if (e.GetTargetType() == SMART_TARGET_SELF) target->ToCreature()->SetHomePosition(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), me->GetOrientation()); else if (e.GetTargetType() == SMART_TARGET_POSITION) target->ToCreature()->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) { target->ToCreature()->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 (IsCreature(target)) target->ToCreature()->SetRegenerateHealth(e.action.setHealthRegen.regenHealth != 0); break; } case SMART_ACTION_SET_ROOT: { for (WorldObject* target : targets) if (IsCreature(target)) target->ToCreature()->SetControlled(e.action.setRoot.root != 0, UNIT_STATE_ROOT); break; } case SMART_ACTION_SET_GO_FLAG: { for (WorldObject* target : targets) if (IsGameObject(target)) target->ToGameObject()->SetFlags(GameObjectFlags(e.action.goFlag.flag)); break; } case SMART_ACTION_ADD_GO_FLAG: { for (WorldObject* target : targets) if (IsGameObject(target)) target->ToGameObject()->AddFlag(GameObjectFlags(e.action.goFlag.flag)); break; } case SMART_ACTION_REMOVE_GO_FLAG: { for (WorldObject* target : targets) if (IsGameObject(target)) target->ToGameObject()->RemoveFlag(GameObjectFlags(e.action.goFlag.flag)); 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); break; } case SMART_ACTION_SET_POWER: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->SetPower(Powers(e.action.power.powerType), e.action.power.newPower); break; } case SMART_ACTION_ADD_POWER: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->SetPower(Powers(e.action.power.powerType), target->ToUnit()->GetPower(Powers(e.action.power.powerType)) + e.action.power.newPower); break; } case SMART_ACTION_REMOVE_POWER: { for (WorldObject* target : targets) if (IsUnit(target)) target->ToUnit()->SetPower(Powers(e.action.power.powerType), target->ToUnit()->GetPower(Powers(e.action.power.powerType)) - e.action.power.newPower); break; } case SMART_ACTION_GAME_EVENT_STOP: { uint32 eventId = e.action.gameEventStop.id; if (!sGameEventMgr->IsActiveEvent(eventId)) { TC_LOG_ERROR("sql.sql", "SmartScript::ProcessAction: At case SMART_ACTION_GAME_EVENT_STOP, inactive event (id: %u)", 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: %u)", eventId); break; } sGameEventMgr->StartEvent(eventId, true); break; } case SMART_ACTION_START_CLOSEST_WAYPOINT: { std::vector waypoints; std::copy_if(std::begin(e.action.closestWaypointFromList.wps), std::end(e.action.closestWaypointFromList.wps), std::back_inserter(waypoints), [](uint32 wp) { return wp != 0; }); float distanceToClosest = std::numeric_limits::max(); std::pair closest = { 0, 0 }; for (WorldObject* target : targets) { if (Creature* creature = target->ToCreature()) { if (IsSmart(creature)) { for (uint32 pathId : waypoints) { WaypointPath const* path = sSmartWaypointMgr->GetPath(pathId); if (!path || path->nodes.empty()) continue; for (auto itr = path->nodes.begin(); itr != path->nodes.end(); ++itr) { WaypointNode const waypoint = *itr; float distamceToThisNode = creature->GetDistance(waypoint.x, waypoint.y, waypoint.z); if (distamceToThisNode < distanceToClosest) { distanceToClosest = distamceToThisNode; closest.first = pathId; closest.second = waypoint.id; } } } if (closest.first != 0) ENSURE_AI(SmartAI, creature->AI())->StartPath(false, closest.first, true, nullptr, closest.second); } } } break; } case SMART_ACTION_RANDOM_SOUND: { std::vector sounds; std::copy_if(std::begin(e.action.randomSound.sounds), std::end(e.action.randomSound.sounds), std::back_inserter(sounds), [](uint32 sound) { return sound != 0; }); bool onlySelf = e.action.randomSound.onlySelf != 0; for (WorldObject* const target : targets) { if (IsUnit(target)) { uint32 sound = Trinity::Containers::SelectRandomContainerElement(sounds); 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: %s (%s), sound: %u, onlyself: %s", target->GetName().c_str(), target->GetGUID().ToString().c_str(), sound, onlySelf ? "true" : "false"); } } break; } /* fallthrough */ case SMART_ACTION_SET_CORPSE_DELAY: { for (WorldObject* const target : targets) { if (IsCreature(target)) target->ToCreature()->SetCorpseDelay(e.action.corpseDelay.timer); } break; } case SMART_ACTION_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 SmartEvent ne = SmartEvent(); ne.type = (SMART_EVENT)SMART_EVENT_UPDATE; ne.event_chance = 100; ne.minMaxRepeat.min = e.action.groupSpawn.minDelay; ne.minMaxRepeat.max = e.action.groupSpawn.maxDelay; ne.minMaxRepeat.repeatMin = 0; ne.minMaxRepeat.repeatMax = 0; ne.event_flags = 0; ne.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; SmartAction ac = SmartAction(); ac.type = (SMART_ACTION)SMART_ACTION_SPAWN_SPAWNGROUP; ac.groupSpawn.groupId = e.action.groupSpawn.groupId; ac.groupSpawn.minDelay = 0; ac.groupSpawn.maxDelay = 0; ac.groupSpawn.spawnflags = e.action.groupSpawn.spawnflags; ac.timeEvent.id = e.action.timeEvent.id; SmartScriptHolder ev = SmartScriptHolder(); ev.event = ne; ev.event_id = e.event_id; ev.target = e.target; ev.action = ac; InitTimer(ev); mStoredEvents.push_back(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 SmartEvent ne = SmartEvent(); ne.type = (SMART_EVENT)SMART_EVENT_UPDATE; ne.event_chance = 100; ne.minMaxRepeat.min = e.action.groupSpawn.minDelay; ne.minMaxRepeat.max = e.action.groupSpawn.maxDelay; ne.minMaxRepeat.repeatMin = 0; ne.minMaxRepeat.repeatMax = 0; ne.event_flags = 0; ne.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; SmartAction ac = SmartAction(); ac.type = (SMART_ACTION)SMART_ACTION_DESPAWN_SPAWNGROUP; ac.groupSpawn.groupId = e.action.groupSpawn.groupId; ac.groupSpawn.minDelay = 0; ac.groupSpawn.maxDelay = 0; ac.groupSpawn.spawnflags = e.action.groupSpawn.spawnflags; ac.timeEvent.id = e.action.timeEvent.id; SmartScriptHolder ev = SmartScriptHolder(); ev.event = ne; ev.event_id = e.event_id; ev.target = e.target; ev.action = ac; InitTimer(ev); mStoredEvents.push_back(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_REMOVE_AURAS_BY_TYPE: // can be used to exit vehicle for example { for (WorldObject* const target : targets) if (IsUnit(target)) target->ToUnit()->RemoveAurasByType((AuraType)e.action.auraType.type); break; } case SMART_ACTION_SET_SIGHT_DIST: { for (WorldObject* const target : targets) if (IsCreature(target)) target->ToCreature()->m_SightDistance = e.action.sightDistance.dist; break; } case SMART_ACTION_FLEE: { for (WorldObject* const target : targets) if (IsCreature(target)) target->ToCreature()->GetMotionMaster()->MoveFleeing(me, e.action.flee.fleeTime); break; } case SMART_ACTION_ADD_THREAT: { if (!me->CanHaveThreatList()) break; for (WorldObject* const target : targets) if (IsUnit(target)) me->GetThreatManager().AddThreat(target->ToUnit(), float(e.action.threatPCT.threatINC) - float(e.action.threatPCT.threatDEC), nullptr, true, true); break; } case SMART_ACTION_LOAD_EQUIPMENT: { for (WorldObject* const target : targets) if (IsCreature(target)) target->ToCreature()->LoadEquipment(e.action.loadEquipment.id, e.action.loadEquipment.force != 0); break; } case SMART_ACTION_TRIGGER_RANDOM_TIMED_EVENT: { uint32 eventId = urand(e.action.randomTimedEvent.minId, e.action.randomTimedEvent.maxId); ProcessEventsFor((SMART_EVENT)SMART_EVENT_TIMED_EVENT_TRIGGERED, nullptr, eventId); break; } case SMART_ACTION_REMOVE_ALL_GAMEOBJECTS: { for (WorldObject* const target : targets) if (IsUnit(target)) target->ToUnit()->RemoveAllGameObjects(); break; } case SMART_ACTION_STOP_MOTION: { for (WorldObject* const target : targets) { if (IsUnit(target)) { if (e.action.stopMotion.stopMovement) target->ToUnit()->StopMoving(); if (e.action.stopMotion.movementExpired) target->ToUnit()->GetMotionMaster()->MovementExpired(); } } break; } case SMART_ACTION_PLAY_ANIMKIT: { for (WorldObject* const target : targets) { if (IsCreature(target)) { if (e.action.animKit.type == 0) target->ToCreature()->PlayOneShotAnimKitId(e.action.animKit.animKit); else if (e.action.animKit.type == 1) target->ToCreature()->SetAIAnimKitId(e.action.animKit.animKit); else if (e.action.animKit.type == 2) target->ToCreature()->SetMeleeAnimKitId(e.action.animKit.animKit); else if (e.action.animKit.type == 3) target->ToCreature()->SetMovementAnimKitId(e.action.animKit.animKit); else { TC_LOG_ERROR("sql.sql", "SmartScript: Invalid type for SMART_ACTION_PLAY_ANIMKIT, skipping"); break; } TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_PLAY_ANIMKIT: target: %s (%s), AnimKit: %u, Type: %u", target->GetName().c_str(), target->GetGUID().ToString().c_str(), 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_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 (IsCreature(target)) me->SetSpeed(UnitMoveType(e.action.movementSpeed.movementType), speed); break; } case SMART_ACTION_PLAY_SPELL_VISUAL_KIT: { for (WorldObject* const target : targets) { if (IsUnit(target)) { target->ToUnit()->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: %s (%s), SpellVisualKit: %u", target->GetName().c_str(), target->GetGUID().ToString().c_str(), e.action.spellVisualKit.spellVisualKitId); } } 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 %u, baseObject %s, target %s - failed to create conversation", e.action.conversation.id, !baseObject ? "" : baseObject->GetName().c_str(), playerTarget->GetName().c_str()); } } break; } default: TC_LOG_ERROR("sql.sql", "SmartScript::ProcessAction: Entry " SI64FMTD " SourceType %u, Event %u, Unhandled Action type %u", 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 " SI64FMTD " SourceType %u, Event %u, Link Event %u 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 const& 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)); } void SmartScript::InstallTemplate(SmartScriptHolder const& e) { if (!GetBaseObject()) return; if (mTemplate != SMARTAI_TEMPLATE_BASIC) { TC_LOG_ERROR("sql.sql", "SmartScript::InstallTemplate: Entry " SI64FMTD " SourceType %u AI Template can not be set more then once, skipped.", e.entryOrGuid, e.GetScriptType()); return; } mTemplate = (SMARTAI_TEMPLATE)e.action.installTtemplate.id; switch ((SMARTAI_TEMPLATE)e.action.installTtemplate.id) { case SMARTAI_TEMPLATE_CASTER: { AddEvent(SMART_EVENT_UPDATE_IC, 0, 0, 0, e.action.installTtemplate.param2, e.action.installTtemplate.param3, 0, SMART_ACTION_CAST, e.action.installTtemplate.param1, e.target.raw.param1, 0, 0, 0, 0, SMART_TARGET_VICTIM, 0, 0, 0, 1); AddEvent(SMART_EVENT_RANGE, 0, e.action.installTtemplate.param4, 300, 0, 0, 0, SMART_ACTION_ALLOW_COMBAT_MOVEMENT, 1, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 1); AddEvent(SMART_EVENT_RANGE, 0, 0, e.action.installTtemplate.param4>10?e.action.installTtemplate.param4-10:0, 0, 0, 0, SMART_ACTION_ALLOW_COMBAT_MOVEMENT, 0, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 1); AddEvent(SMART_EVENT_MANA_PCT, 0, e.action.installTtemplate.param5-15>100?100:e.action.installTtemplate.param5+15, 100, 1000, 1000, 0, SMART_ACTION_SET_EVENT_PHASE, 1, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0); AddEvent(SMART_EVENT_MANA_PCT, 0, 0, e.action.installTtemplate.param5, 1000, 1000, 0, SMART_ACTION_SET_EVENT_PHASE, 0, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0); AddEvent(SMART_EVENT_MANA_PCT, 0, 0, e.action.installTtemplate.param5, 1000, 1000, 0, SMART_ACTION_ALLOW_COMBAT_MOVEMENT, 1, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0); break; } case SMARTAI_TEMPLATE_TURRET: { AddEvent(SMART_EVENT_UPDATE_IC, 0, 0, 0, e.action.installTtemplate.param2, e.action.installTtemplate.param3, 0, SMART_ACTION_CAST, e.action.installTtemplate.param1, e.target.raw.param1, 0, 0, 0, 0, SMART_TARGET_VICTIM, 0, 0, 0, 0); AddEvent(SMART_EVENT_JUST_CREATED, 0, 0, 0, 0, 0, 0, SMART_ACTION_ALLOW_COMBAT_MOVEMENT, 0, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0); break; } case SMARTAI_TEMPLATE_CAGED_NPC_PART: { if (!me) return; //store cage as id1 AddEvent(SMART_EVENT_DATA_SET, 0, 0, 0, 0, 0, 0, SMART_ACTION_STORE_TARGET_LIST, 1, 0, 0, 0, 0, 0, SMART_TARGET_CLOSEST_GAMEOBJECT, e.action.installTtemplate.param1, 10, 0, 0); //reset(close) cage on hostage(me) respawn AddEvent(SMART_EVENT_UPDATE, SMART_EVENT_FLAG_NOT_REPEATABLE, 0, 0, 0, 0, 0, SMART_ACTION_RESET_GOBJECT, 0, 0, 0, 0, 0, 0, SMART_TARGET_GAMEOBJECT_DISTANCE, e.action.installTtemplate.param1, 5, 0, 0); AddEvent(SMART_EVENT_DATA_SET, 0, 0, 0, 0, 0, 0, SMART_ACTION_SET_RUN, e.action.installTtemplate.param3, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0); AddEvent(SMART_EVENT_DATA_SET, 0, 0, 0, 0, 0, 0, SMART_ACTION_SET_EVENT_PHASE, 1, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0); AddEvent(SMART_EVENT_UPDATE, SMART_EVENT_FLAG_NOT_REPEATABLE, 1000, 1000, 0, 0, 0, SMART_ACTION_MOVE_OFFSET, 0, 0, 0, 0, 0, 0, SMART_TARGET_SELF, 0, e.action.installTtemplate.param4, 0, 1); //phase 1: give quest credit on movepoint reached AddEvent(SMART_EVENT_MOVEMENTINFORM, 0, POINT_MOTION_TYPE, SMART_RANDOM_POINT, 0, 0, 0, SMART_ACTION_SET_DATA, 0, 0, 0, 0, 0, 0, SMART_TARGET_STORED, 1, 0, 0, 1); //phase 1: despawn after time on movepoint reached AddEvent(SMART_EVENT_MOVEMENTINFORM, 0, POINT_MOTION_TYPE, SMART_RANDOM_POINT, 0, 0, 0, SMART_ACTION_FORCE_DESPAWN, e.action.installTtemplate.param2, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 1); if (sCreatureTextMgr->TextExist(me->GetEntry(), (uint8)e.action.installTtemplate.param5)) AddEvent(SMART_EVENT_MOVEMENTINFORM, 0, POINT_MOTION_TYPE, SMART_RANDOM_POINT, 0, 0, 0, SMART_ACTION_TALK, e.action.installTtemplate.param5, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 1); break; } case SMARTAI_TEMPLATE_CAGED_GO_PART: { if (!go) return; //store hostage as id1 AddEvent(SMART_EVENT_GO_LOOT_STATE_CHANGED, 0, GO_ACTIVATED, 0, 0, 0, 0, SMART_ACTION_STORE_TARGET_LIST, 1, 0, 0, 0, 0, 0, SMART_TARGET_CLOSEST_CREATURE, e.action.installTtemplate.param1, 10, 0, 0); //store invoker as id2 AddEvent(SMART_EVENT_GO_LOOT_STATE_CHANGED, 0, GO_ACTIVATED, 0, 0, 0, 0, SMART_ACTION_STORE_TARGET_LIST, 2, 0, 0, 0, 0, 0, SMART_TARGET_NONE, 0, 0, 0, 0); //signal hostage AddEvent(SMART_EVENT_GO_LOOT_STATE_CHANGED, 0, GO_ACTIVATED, 0, 0, 0, 0, SMART_ACTION_SET_DATA, 0, 0, 0, 0, 0, 0, SMART_TARGET_STORED, 1, 0, 0, 0); //when hostage raeched end point, give credit to invoker if (e.action.installTtemplate.param2) AddEvent(SMART_EVENT_DATA_SET, 0, 0, 0, 0, 0, 0, SMART_ACTION_CALL_KILLEDMONSTER, e.action.installTtemplate.param1, 0, 0, 0, 0, 0, SMART_TARGET_STORED, 2, 0, 0, 0); else AddEvent(SMART_EVENT_GO_LOOT_STATE_CHANGED, 0, GO_ACTIVATED, 0, 0, 0, 0, SMART_ACTION_CALL_KILLEDMONSTER, e.action.installTtemplate.param1, 0, 0, 0, 0, 0, SMART_TARGET_STORED, 2, 0, 0, 0); break; } case SMARTAI_TEMPLATE_BASIC: default: return; } } void SmartScript::AddEvent(SMART_EVENT e, uint32 event_flags, uint32 event_param1, uint32 event_param2, uint32 event_param3, uint32 event_param4, uint32 event_param5, SMART_ACTION action, uint32 action_param1, uint32 action_param2, uint32 action_param3, uint32 action_param4, uint32 action_param5, uint32 action_param6, SMARTAI_TARGETS t, uint32 target_param1, uint32 target_param2, uint32 target_param3, uint32 phaseMask) { mInstallEvents.push_back(CreateSmartEvent(e, event_flags, event_param1, event_param2, event_param3, event_param4, event_param5, action, action_param1, action_param2, action_param3, action_param4, action_param5, action_param6, t, target_param1, target_param2, target_param3, phaseMask)); } SmartScriptHolder SmartScript::CreateSmartEvent(SMART_EVENT e, uint32 event_flags, uint32 event_param1, uint32 event_param2, uint32 event_param3, uint32 event_param4, uint32 event_param5, SMART_ACTION action, uint32 action_param1, uint32 action_param2, uint32 action_param3, uint32 action_param4, uint32 action_param5, uint32 action_param6, SMARTAI_TARGETS t, uint32 target_param1, uint32 target_param2, uint32 target_param3, uint32 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.target.type = t; script.target.raw.param1 = target_param1; script.target.raw.param2 = target_param2; script.target.raw.param3 = target_param3; script.source_type = SMART_SCRIPT_TYPE_CREATURE; InitTimer(script); return script; } void SmartScript::GetTargets(ObjectVector& targets, SmartScriptHolder const& e, Unit* invoker /*= nullptr*/) const { Unit* 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(SELECT_TARGET_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(SELECT_TARGET_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(SELECT_TARGET_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(SELECT_TARGET_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(SELECT_TARGET_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(SELECT_TARGET_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(SELECT_TARGET_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(SELECT_TARGET_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(SELECT_TARGET_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->GetVehicle() && scriptTrigger->GetVehicle()->GetBase()) targets.push_back(scriptTrigger->GetVehicle()->GetBase()); break; case SMART_TARGET_INVOKER_PARTY: if (scriptTrigger) { if (Player* player = scriptTrigger->ToPlayer()) { if (Group* group = player->GetGroup()) { for (GroupReference* groupRef = group->GetFirstMember(); groupRef != nullptr; groupRef = groupRef->next()) if (Player* member = groupRef->GetSource()) if (member->IsInMap(player)) targets.push_back(member); } // 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: { ObjectVector units; GetWorldObjectsInDist(units, static_cast(e.target.unitRange.maxDist)); for (WorldObject* unit : units) { if (!IsCreature(unit)) continue; if (me && me->GetGUID() == unit->GetGUID()) continue; if ((!e.target.unitRange.creature || unit->ToCreature()->GetEntry() == e.target.unitRange.creature) && baseObject->IsInRange(unit, float(e.target.unitRange.minDist), float(e.target.unitRange.maxDist))) targets.push_back(unit); } break; } case SMART_TARGET_CREATURE_DISTANCE: { ObjectVector units; GetWorldObjectsInDist(units, static_cast(e.target.unitDistance.dist)); for (WorldObject* unit : units) { if (!IsCreature(unit)) continue; if (me && me->GetGUID() == unit->GetGUID()) continue; if (!e.target.unitDistance.creature || unit->ToCreature()->GetEntry() == e.target.unitDistance.creature) targets.push_back(unit); } break; } case SMART_TARGET_GAMEOBJECT_DISTANCE: { ObjectVector units; GetWorldObjectsInDist(units, static_cast(e.target.goDistance.dist)); for (WorldObject* unit : units) { if (!IsGameObject(unit)) continue; if (go && go->GetGUID() == unit->GetGUID()) continue; if (!e.target.goDistance.entry || unit->ToGameObject()->GetEntry() == e.target.goDistance.entry) targets.push_back(unit); } break; } case SMART_TARGET_GAMEOBJECT_RANGE: { ObjectVector units; GetWorldObjectsInDist(units, static_cast(e.target.goRange.maxDist)); for (WorldObject* unit : units) { if (!IsGameObject(unit)) continue; if (go && go->GetGUID() == unit->GetGUID()) continue; if ((!e.target.goRange.entry && unit->ToGameObject()->GetEntry() == e.target.goRange.entry) && baseObject->IsInRange(unit, float(e.target.goRange.minDist), float(e.target.goRange.maxDist))) targets.push_back(unit); } break; } case SMART_TARGET_CREATURE_GUID: { if (!scriptTrigger && !baseObject) { TC_LOG_ERROR("sql.sql", "SMART_TARGET_CREATURE_GUID can not be used without invoker"); 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 can not be used without invoker"); 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: { ObjectVector units; GetWorldObjectsInDist(units, static_cast(e.target.playerRange.maxDist)); if (!units.empty() && baseObject) for (WorldObject* unit : units) if (IsPlayer(unit) && baseObject->IsInRange(unit, float(e.target.playerRange.minDist), float(e.target.playerRange.maxDist))) targets.push_back(unit); break; } case SMART_TARGET_PLAYER_DISTANCE: { ObjectVector units; GetWorldObjectsInDist(units, static_cast(e.target.playerDistance.dist)); for (WorldObject* unit : units) if (IsPlayer(unit)) targets.push_back(unit); break; } case SMART_TARGET_STORED: { WorldObject* ref = GetBaseObject(); if (!ref) ref = scriptTrigger; if (ref) if (ObjectVector const* stored = GetStoredTargetVector(e.target.stored.id, *ref)) targets.assign(stored->begin(), stored->end()); break; } case SMART_TARGET_CLOSEST_CREATURE: { if (Creature* target = baseObject->FindNearestCreature(e.target.closest.entry, float(e.target.closest.dist ? e.target.closest.dist : 100), !e.target.closest.dead)) targets.push_back(target); break; } case SMART_TARGET_CLOSEST_GAMEOBJECT: { if (GameObject* target = baseObject->FindNearestGameObject(e.target.closest.entry, float(e.target.closest.dist ? e.target.closest.dist : 100))) targets.push_back(target); break; } case SMART_TARGET_CLOSEST_PLAYER: { if (WorldObject* obj = GetBaseObject()) if (Player* target = obj->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 (Unit* summoner = tempSummon->GetSummoner()) charmerOrOwnerGuid = summoner->GetGUID(); if (!charmerOrOwnerGuid) charmerOrOwnerGuid = me->GetCreatorGUID(); if (Unit* owner = ObjectAccessor::GetUnit(*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()) { Unit* owner = targets.front()->ToUnit(); 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.hostilRandom.maxDist || me->IsWithinCombatRange(ref->GetVictim(), float(e.target.hostilRandom.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) { if (Group* lootGroup = me->GetLootRecipientGroup()) { for (GroupReference* it = lootGroup->GetFirstMember(); it != nullptr; it = it->next()) if (Player* recipient = it->GetSource()) if (recipient->IsInMap(me)) targets.push_back(recipient); } else { if (Player* recipient = me->GetLootRecipient()) targets.push_back(recipient); } } break; } case SMART_TARGET_VEHICLE_ACCESSORY: { if (me && me->IsVehicle()) { if (Unit* target = me->GetVehicleKit()->GetPassenger(e.target.vehicle.seat)) targets.push_back(target); } break; } case SMART_TARGET_POSITION: case SMART_TARGET_NONE: default: break; } } void SmartScript::GetWorldObjectsInDist(ObjectVector& targets, float dist) const { WorldObject* obj = GetBaseObject(); if (!obj) return; Trinity::AllWorldObjectsInRange u_check(obj, dist); Trinity::WorldObjectListSearcher searcher(obj, targets, u_check); Cell::VisitAllObjects(obj, searcher, dist); } void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, uint32 var1, bool bvar, SpellInfo const* spell, GameObject* gob, std::string const& 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) && IsCharmedCreature(me)) return; switch (e.GetEventType()) { case SMART_EVENT_LINK://special handling ProcessAction(e, unit, var0, var1, bvar, spell, gob); break; //called from Update tick case SMART_EVENT_UPDATE: ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); break; case SMART_EVENT_UPDATE_OOC: if (me && me->IsEngaged()) return; ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); break; case SMART_EVENT_UPDATE_IC: if (!me || !me->IsEngaged()) return; ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); break; case SMART_EVENT_HEALT_PCT: { if (!me || !me->IsEngaged() || !me->GetMaxHealth()) return; uint32 perc = (uint32)me->GetHealthPct(); if (perc > e.event.minMaxRepeat.max || perc < e.event.minMaxRepeat.min) return; ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); break; } case SMART_EVENT_TARGET_HEALTH_PCT: { if (!me || !me->IsEngaged() || !me->GetVictim() || !me->EnsureVictim()->GetMaxHealth()) return; uint32 perc = (uint32)me->EnsureVictim()->GetHealthPct(); if (perc > e.event.minMaxRepeat.max || perc < e.event.minMaxRepeat.min) return; ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax, me->GetVictim()); break; } case SMART_EVENT_MANA_PCT: { if (!me || !me->IsEngaged() || !me->GetMaxPower(POWER_MANA)) return; uint32 perc = uint32(me->GetPowerPct(POWER_MANA)); if (perc > e.event.minMaxRepeat.max || perc < e.event.minMaxRepeat.min) return; ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); break; } case SMART_EVENT_TARGET_MANA_PCT: { if (!me || !me->IsEngaged() || !me->GetVictim() || !me->EnsureVictim()->GetMaxPower(POWER_MANA)) return; uint32 perc = uint32(me->EnsureVictim()->GetPowerPct(POWER_MANA)); if (perc > e.event.minMaxRepeat.max || perc < e.event.minMaxRepeat.min) return; ProcessTimedAction(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax, me->GetVictim()); break; } case SMART_EVENT_RANGE: { if (!me || !me->IsEngaged() || !me->GetVictim()) return; if (me->IsInRange(me->GetVictim(), (float)e.event.minMaxRepeat.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_HEALTH: { if (!me || !me->IsEngaged()) return; Unit* target = DoSelectLowestHpFriendly((float)e.event.friendlyHealth.radius, e.event.friendlyHealth.hpDeficit); if (!target || !target->IsInCombat()) { // if there are at least two same npcs, they will perform the same action immediately even if this is useless... RecalcTimer(e, 1000, 3000); return; } ProcessTimedAction(e, e.event.friendlyHealth.repeatMin, e.event.friendlyHealth.repeatMax, target); break; } case SMART_EVENT_FRIENDLY_IS_CC: { if (!me || !me->IsEngaged()) return; std::vector creatures; DoFindFriendlyCC(creatures, float(e.event.friendlyCC.radius)); if (creatures.empty()) { // 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); 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_COPLETETION: { 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_CHARMED_TARGET: case SMART_EVENT_CORPSE_REMOVED: case SMART_EVENT_AI_INIT: case SMART_EVENT_TRANSPORT_ADDPLAYER: case SMART_EVENT_TRANSPORT_REMOVE_PLAYER: case SMART_EVENT_JUST_SUMMONED: case SMART_EVENT_RESET: case SMART_EVENT_JUST_CREATED: case SMART_EVENT_FOLLOW_COMPLETED: case SMART_EVENT_ON_SPELLCLICK: ProcessAction(e, unit, var0, var1, bvar, spell, gob); break; case SMART_EVENT_GOSSIP_HELLO: if (e.event.gossipHello.noReportUse && var0) return; ProcessAction(e, unit, var0, var1, bvar, spell, gob); break; case SMART_EVENT_IS_BEHIND_TARGET: { if (!me) return; if (Unit* victim = me->GetVictim()) { if (!victim->HasInArc(static_cast(M_PI), me)) ProcessTimedAction(e, e.event.behindTarget.cooldownMin, e.event.behindTarget.cooldownMax, victim); } 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); } 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)) { //if friendly event&&who is not hostile OR hostile event&&who is hostile if ((e.event.los.noHostile && !me->IsHostileTo(unit)) || (!e.event.los.noHostile && 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)) { //if friendly event&&who is not hostile OR hostile event&&who is hostile if ((e.event.los.noHostile && !me->IsHostileTo(unit)) || (!e.event.los.noHostile && 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: { if (!IsCreature(unit)) return; if (e.event.summoned.creature && unit->GetEntry() != e.event.summoned.creature) return; RecalcTimer(e, e.event.summoned.cooldownMin, e.event.summoned.cooldownMax); ProcessAction(e, unit); break; } case SMART_EVENT_RECEIVE_HEAL: case SMART_EVENT_DAMAGED: case SMART_EVENT_DAMAGED_TARGET: { if (var0 > e.event.minMaxRepeat.max || var0 < e.event.minMaxRepeat.min) return; RecalcTimer(e, e.event.minMaxRepeat.repeatMin, e.event.minMaxRepeat.repeatMax); ProcessAction(e, unit); break; } case SMART_EVENT_MOVEMENTINFORM: { if ((e.event.movementInform.type && var0 != e.event.movementInform.type) || (e.event.movementInform.id && var1 != e.event.movementInform.id)) return; ProcessAction(e, unit, var0, var1); break; } case SMART_EVENT_TRANSPORT_RELOCATE: case SMART_EVENT_WAYPOINT_START: { if (e.event.waypoint.pathID && var0 != e.event.waypoint.pathID) 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 && 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; ProcessAction(e, unit, var0); break; } case SMART_EVENT_TRANSPORT_ADDCREATURE: { if (e.event.transportAddCreature.creature && var0 != e.event.transportAddCreature.creature) return; ProcessAction(e, unit, var0); break; } case SMART_EVENT_AREATRIGGER_ONTRIGGER: { if (e.event.areatrigger.id && var0 != e.event.areatrigger.id) return; ProcessAction(e, unit, var0); break; } case SMART_EVENT_TEXT_OVER: { if (var0 != e.event.textOver.textGroupID || (e.event.textOver.creatureEntry && e.event.textOver.creatureEntry != var1)) return; ProcessAction(e, unit, var0); break; } case SMART_EVENT_DATA_SET: { if (e.event.dataSet.id != var0 || e.event.dataSet.value != var1) return; RecalcTimer(e, e.event.dataSet.cooldownMin, e.event.dataSet.cooldownMax); ProcessAction(e, unit, var0, var1); 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 %u action %u", var0, var1);//little help for scripters if (e.event.gossip.sender != var0 || e.event.gossip.action != var1) return; ProcessAction(e, unit, var0, var1); break; } case SMART_EVENT_EVENT_PHASE_CHANGE: { if (!IsInPhase(e.event.eventPhaseChange.phasemask)) return; ProcessAction(e, GetLastInvoker()); 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->IsEngaged()) return; ObjectVector targets; 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: GetTargets(targets, e); break; default: return; } Unit* unitTarget = nullptr; for (WorldObject* target : targets) { if (IsUnit(target) && me->IsFriendlyTo(target->ToUnit()) && target->ToUnit()->IsAlive() && target->ToUnit()->IsInCombat()) { uint32 healthPct = uint32(target->ToUnit()->GetHealthPct()); if (healthPct > e.event.friendlyHealthPct.maxHpPct || healthPct < e.event.friendlyHealthPct.minHpPct) continue; unitTarget = target->ToUnit(); break; } } 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 %u", 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.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)) { e.timer = 1; 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_HEALT_PCT: case SMART_EVENT_TARGET_HEALTH_PCT: case SMART_EVENT_MANA_PCT: case SMART_EVENT_TARGET_MANA_PCT: case SMART_EVENT_RANGE: case SMART_EVENT_VICTIM_CASTING: case SMART_EVENT_FRIENDLY_HEALTH: case SMART_EVENT_FRIENDLY_IS_CC: case SMART_EVENT_FRIENDLY_MISSING_BUFF: case SMART_EVENT_HAS_AURA: case SMART_EVENT_TARGET_BUFFED: case SMART_EVENT_IS_BEHIND_TARGET: case SMART_EVENT_FRIENDLY_HEALTH_PCT: case SMART_EVENT_DISTANCE_CREATURE: case SMART_EVENT_DISTANCE_GAMEOBJECT: { ProcessEvent(e); if (e.GetScriptType() == SMART_SCRIPT_TYPE_TIMED_ACTIONLIST) { e.enableTimed = false;//disable event if it is in an ActionList and was processed once for (SmartAIEventList::iterator i = mTimedActionList.begin(); i != mTimedActionList.end(); ++i) { //find the first event which is not the current one and enable it if (i->event_id > e.event_id) { i->enableTimed = true; break; } } } break; } } } else e.timer -= diff; } bool SmartScript::CheckTimer(SmartScriptHolder const& e) const { return e.active; } void SmartScript::InstallEvents() { if (!mInstallEvents.empty()) { for (SmartAIEventList::iterator i = mInstallEvents.begin(); i != mInstallEvents.end(); ++i) mEvents.push_back(*i);//must be before UpdateTimers mInstallEvents.clear(); } } void SmartScript::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; return obj; } WorldObject* SmartScript::GetBaseObjectOrUnit(Unit* unit) { WorldObject* summoner = GetBaseObject(); if (!summoner && unit) return unit; return summoner; } bool SmartScript::IsUnit(WorldObject* obj) { return obj && (obj->GetTypeId() == TYPEID_UNIT || obj->GetTypeId() == TYPEID_PLAYER); } bool SmartScript::IsPlayer(WorldObject* obj) { return obj && obj->GetTypeId() == TYPEID_PLAYER; } bool SmartScript::IsCreature(WorldObject* obj) { return obj && obj->GetTypeId() == TYPEID_UNIT; } bool SmartScript::IsCharmedCreature(WorldObject* obj) { if (!obj) return false; if (Creature* creatureObj = obj->ToCreature()) return creatureObj->IsCharmed(); return false; } bool SmartScript::IsGameObject(WorldObject* obj) { return obj && obj->GetTypeId() == TYPEID_GAMEOBJECT; } 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_SERVERSIDE) && !GetBaseObject()) return; InstallEvents();//before UpdateTimers for (SmartAIEventList::iterator i = mEvents.begin(); i != mEvents.end(); ++i) UpdateTimer(*i, diff); if (!mStoredEvents.empty()) { SmartAIEventStoredList::iterator i, icurr; for (i = mStoredEvents.begin(); i != mStoredEvents.end();) { icurr = i++; UpdateTimer(*icurr, diff); } } bool needCleanup = true; if (!mTimedActionList.empty()) { isProcessingTimedActionList = true; for (SmartAIEventList::iterator i = mTimedActionList.begin(); i != mTimedActionList.end(); ++i) { if ((*i).enableTimed) { UpdateTimer(*i, diff); needCleanup = false; } } isProcessingTimedActionList = false; } if (needCleanup) mTimedActionList.clear(); if (!mRemIDs.empty()) { for (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::FillScript(SmartAIEventList e, WorldObject* obj, AreaTriggerEntry const* at, SceneTemplate const* scene, Quest const* quest) { if (e.empty()) { if (obj) TC_LOG_DEBUG("scripts.ai", "SmartScript: EventMap for Entry %u is empty but is using SmartScript.", obj->GetEntry()); if (at) TC_LOG_DEBUG("scripts.ai", "SmartScript: EventMap for AreaTrigger %u is empty but is using SmartScript.", at->ID); if (scene) TC_LOG_DEBUG("scripts.ai", "SmartScript: EventMap for SceneId %u is empty but is using SmartScript.", scene->SceneId); if (quest) TC_LOG_DEBUG("scripts.ai", "SmartScript: EventMap for Quest %u is empty but is using SmartScript.", quest->GetQuestId()); return; } for (SmartAIEventList::iterator i = e.begin(); i != e.end(); ++i) { #ifndef TRINITY_DEBUG if ((*i).event.event_flags & SMART_EVENT_FLAG_DEBUG_ONLY) continue; #endif if ((*i).event.event_flags & SMART_EVENT_FLAG_DIFFICULTY_ALL)//if has instance flag add only if in it { if (obj && obj->GetMap()->IsDungeon()) { // TODO: fix it for new maps and difficulties switch (obj->GetMap()->GetDifficultyID()) { case DIFFICULTY_NORMAL: case DIFFICULTY_10_N: if (i->event.event_flags & SMART_EVENT_FLAG_DIFFICULTY_0) mEvents.emplace_back(std::move(*i)); break; case DIFFICULTY_HEROIC: case DIFFICULTY_25_N: if (i->event.event_flags & SMART_EVENT_FLAG_DIFFICULTY_1) mEvents.emplace_back(std::move(*i)); break; case DIFFICULTY_10_HC: if (i->event.event_flags & SMART_EVENT_FLAG_DIFFICULTY_2) mEvents.emplace_back(std::move(*i)); break; case DIFFICULTY_25_HC: if (i->event.event_flags & SMART_EVENT_FLAG_DIFFICULTY_3) mEvents.emplace_back(std::move(*i)); break; default: break; } } continue; } mEvents.push_back((*i));//NOTE: 'world(0)' events still get processed in ANY instance mode } } void SmartScript::GetScript() { SmartAIEventList e; if (me) { e = sSmartScriptMgr->GetScript(-((int32)me->GetSpawnId()), mScriptType); if (e.empty()) e = sSmartScriptMgr->GetScript((int32)me->GetEntry(), mScriptType); FillScript(std::move(e), me, nullptr, nullptr, nullptr); } else if (go) { 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); } else if (trigger) { e = sSmartScriptMgr->GetScript((int32)trigger->ID, mScriptType); FillScript(std::move(e), nullptr, trigger, nullptr, nullptr); } else if (areaTrigger) { e = sSmartScriptMgr->GetScript((int32)areaTrigger->GetEntry(), mScriptType); FillScript(std::move(e), areaTrigger, nullptr, nullptr, nullptr); } else if (sceneTemplate) { e = sSmartScriptMgr->GetScript(sceneTemplate->SceneId, mScriptType); FillScript(std::move(e), nullptr, nullptr, sceneTemplate, nullptr); } else if (quest) { e = sSmartScriptMgr->GetScript(quest->GetQuestId(), mScriptType); FillScript(std::move(e), nullptr, nullptr, nullptr, quest); } } void SmartScript::OnInitialize(WorldObject* obj, AreaTriggerEntry const* at, SceneTemplate const* scene, Quest const* qst) { 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 %u", me->GetEntry()); break; case TYPEID_GAMEOBJECT: mScriptType = SMART_SCRIPT_TYPE_GAMEOBJECT; go = obj->ToGameObject(); TC_LOG_DEBUG("scripts.ai", "SmartScript::OnInitialize: source is GameObject %u", go->GetEntry()); break; case TYPEID_AREATRIGGER: areaTrigger = obj->ToAreaTrigger(); mScriptType = areaTrigger->IsServerSide() ? SMART_SCRIPT_TYPE_AREATRIGGER_ENTITY_SERVERSIDE : SMART_SCRIPT_TYPE_AREATRIGGER_ENTITY; TC_LOG_DEBUG("scripts.ai", "SmartScript::OnInitialize: source is AreaTrigger %u, IsServerSide %u", areaTrigger->GetEntry(), uint32(areaTrigger->IsServerSide())); break; default: TC_LOG_ERROR("misc", "SmartScript::OnInitialize: Unhandled TypeID !WARNING!"); return; } } else if (at) { mScriptType = SMART_SCRIPT_TYPE_AREATRIGGER; trigger = at; TC_LOG_DEBUG("scripts.ai", "SmartScript::OnInitialize: source is AreaTrigger %u", trigger->ID); } else if (scene) { mScriptType = SMART_SCRIPT_TYPE_SCENE; sceneTemplate = scene; TC_LOG_DEBUG("scripts.ai", "SmartScript::OnInitialize: source is Scene with id %u", scene->SceneId); } else if (qst) { mScriptType = SMART_SCRIPT_TYPE_QUEST; quest = qst; TC_LOG_DEBUG("scripts.ai", "SmartScript::OnInitialize: source is Quest with id %u", qst->GetQuestId()); } else { TC_LOG_ERROR("misc", "SmartScript::OnInitialize: !WARNING! Initialized objects are NULL."); return; } GetScript();//load copy of script for (SmartAIEventList::iterator i = mEvents.begin(); i != mEvents.end(); ++i) InitTimer((*i));//calculate timers for first time use ProcessEventsFor(SMART_EVENT_AI_INIT); InstallEvents(); ProcessEventsFor(SMART_EVENT_JUST_CREATED); } void SmartScript::OnMoveInLineOfSight(Unit* who) { if (!me) return; ProcessEventsFor(me->IsEngaged() ? SMART_EVENT_IC_LOS : SMART_EVENT_OOC_LOS, who); } // 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; } 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::SetScript9(SmartScriptHolder& e, uint32 entry) { //do NOT clear mTimedActionList if it's being iterated because it will invalidate the iterator and delete // any SmartScriptHolder contained like the "e" parameter passed to this function if (isProcessingTimedActionList) { TC_LOG_ERROR("scripts.ai", "Entry " SI64FMTD " SourceType %u Event %u Action %u is trying to overwrite timed action list from a timed action, this is not allowed!.", e.entryOrGuid, e.GetScriptType(), e.GetEventType(), e.GetActionType()); return; } mTimedActionList.clear(); mTimedActionList = sSmartScriptMgr->GetScript(entry, SMART_SCRIPT_TYPE_TIMED_ACTIONLIST); if (mTimedActionList.empty()) return; for (SmartAIEventList::iterator i = mTimedActionList.begin(); i != mTimedActionList.end(); ++i) { i->enableTimed = i == mTimedActionList.begin();//enable processing only for the first action if (e.action.timedActionList.timerType == 0) i->event.type = SMART_EVENT_UPDATE_OOC; else if (e.action.timedActionList.timerType == 1) i->event.type = SMART_EVENT_UPDATE_IC; else if (e.action.timedActionList.timerType > 1) i->event.type = SMART_EVENT_UPDATE; InitTimer((*i)); } } 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) { uint32 oldPhase = mEventPhase; mEventPhase = p; if (oldPhase != mEventPhase) ProcessEventsFor(SMART_EVENT_EVENT_PHASE_CHANGE); } bool SmartScript::IsInPhase(uint32 p) const { if (mEventPhase == 0) return false; return ((1 << (mEventPhase - 1)) & p) != 0; }