mirror of
https://github.com/araxiaonline/TrinityCore.git
synced 2026-06-13 11:43:18 -04:00
* Added personalSpawn for SMART_ACTION_SUMMON_CREATURE * Added flags for smart action summon creature, including prefer unit and personal spawn. When preferUnit flag is chosen only the unit will be chosen, even if nullptr
3959 lines
159 KiB
C++
3959 lines
159 KiB
C++
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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;
|
|
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<uint32> 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<int32>(-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<int32>(-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<SpellPowerCost> 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<int32>(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<uint32> 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<EncounterState>(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<int32>(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<SmartActionSummonCreatureFlags> flags(static_cast<SmartActionSummonCreatureFlags>(e.action.summonCreature.flags));
|
|
bool preferUnit = flags.HasFlag(SmartActionSummonCreatureFlags::PreferUnit);
|
|
WorldObject* summoner = preferUnit ? unit : GetBaseObjectOrUnit(unit);
|
|
if (!summoner)
|
|
break;
|
|
|
|
bool personalSpawn = flags.HasFlag(SmartActionSummonCreatureFlags::PersonalSpawn);
|
|
|
|
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, personalSpawn))
|
|
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, personalSpawn))
|
|
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<EquipmentItem, MAX_EQUIPMENT_ITEMS> slot;
|
|
if (int8 equipId = static_cast<int8>(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<uint32> 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<TempSummon*> 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<uint32> 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<float>::max();
|
|
std::pair<uint32, uint32> 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<uint32> 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:
|
|
{
|
|
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;
|
|
}
|
|
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<uint32>(min, 5000), std::min<uint32>(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<float>(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<float>(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<float>(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<float>(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<float>(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<float>(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<Trinity::AllWorldObjectsInRange> 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<Creature*> 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<Creature*> 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;
|
|
}
|
|
//no params
|
|
case SMART_EVENT_AGGRO:
|
|
case SMART_EVENT_DEATH:
|
|
case SMART_EVENT_EVADE:
|
|
case SMART_EVENT_REACHED_HOME:
|
|
case SMART_EVENT_CHARMED_TARGET:
|
|
case SMART_EVENT_CORPSE_REMOVED:
|
|
case SMART_EVENT_AI_INIT:
|
|
case SMART_EVENT_TRANSPORT_ADDPLAYER:
|
|
case SMART_EVENT_TRANSPORT_REMOVE_PLAYER:
|
|
case SMART_EVENT_QUEST_ACCEPTED:
|
|
case SMART_EVENT_QUEST_OBJ_COPLETETION:
|
|
case SMART_EVENT_QUEST_COMPLETION:
|
|
case SMART_EVENT_QUEST_REWARDED:
|
|
case SMART_EVENT_QUEST_FAIL:
|
|
case SMART_EVENT_JUST_SUMMONED:
|
|
case SMART_EVENT_RESET:
|
|
case SMART_EVENT_JUST_CREATED:
|
|
case SMART_EVENT_FOLLOW_COMPLETED:
|
|
case SMART_EVENT_ON_SPELLCLICK:
|
|
ProcessAction(e, unit, var0, var1, bvar, spell, gob);
|
|
break;
|
|
case SMART_EVENT_GOSSIP_HELLO:
|
|
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<float>(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)))
|
|
{
|
|
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)))
|
|
{
|
|
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<float>(e.event.distance.dist)))
|
|
return;
|
|
}
|
|
else if (e.event.distance.entry != 0)
|
|
{
|
|
std::list<Creature*> list;
|
|
me->GetCreatureListWithEntryInGrid(list, e.event.distance.entry, static_cast<float>(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<float>(e.event.distance.dist)))
|
|
return;
|
|
}
|
|
else if (e.event.distance.entry != 0)
|
|
{
|
|
std::list<GameObject*> list;
|
|
me->GetGameObjectListWithEntryInGrid(list, e.event.distance.entry, static_cast<float>(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)
|
|
{
|
|
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);
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
else if (trigger)
|
|
{
|
|
e = sSmartScriptMgr->GetScript((int32)trigger->ID, mScriptType);
|
|
FillScript(std::move(e), nullptr, trigger, nullptr);
|
|
}
|
|
else if (areaTrigger)
|
|
{
|
|
e = sSmartScriptMgr->GetScript((int32)areaTrigger->GetEntry(), mScriptType);
|
|
FillScript(std::move(e), areaTrigger, nullptr, nullptr);
|
|
}
|
|
else if (sceneTemplate)
|
|
{
|
|
e = sSmartScriptMgr->GetScript(sceneTemplate->SceneId, mScriptType);
|
|
FillScript(std::move(e), nullptr, nullptr, sceneTemplate);
|
|
}
|
|
}
|
|
|
|
void SmartScript::OnInitialize(WorldObject* obj, AreaTriggerEntry const* at, SceneTemplate const* scene)
|
|
{
|
|
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
|
|
{
|
|
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<Trinity::MostHPMissingInRange> searcher(me, unit, u_check);
|
|
Cell::VisitGridObjects(me, searcher, range);
|
|
return unit;
|
|
}
|
|
|
|
void SmartScript::DoFindFriendlyCC(std::vector<Creature*>& creatures, float range) const
|
|
{
|
|
if (!me)
|
|
return;
|
|
|
|
Trinity::FriendlyCCedInRange u_check(me, range);
|
|
Trinity::CreatureListSearcher<Trinity::FriendlyCCedInRange> searcher(me, creatures, u_check);
|
|
Cell::VisitGridObjects(me, searcher, range);
|
|
}
|
|
|
|
void SmartScript::DoFindFriendlyMissingBuff(std::vector<Creature*>& creatures, float range, uint32 spellid) const
|
|
{
|
|
if (!me)
|
|
return;
|
|
|
|
Trinity::FriendlyMissingBuffInRange u_check(me, range, spellid);
|
|
Trinity::CreatureListSearcher<Trinity::FriendlyMissingBuffInRange> 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<Trinity::AnyFriendlyUnitInObjectRangeCheck> 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<uint32>(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;
|
|
}
|