diff options
21 files changed, 831 insertions, 252 deletions
diff --git a/src/server/game/AI/CoreAI/CombatAI.cpp b/src/server/game/AI/CoreAI/CombatAI.cpp index f4a557d25b3..4846bc54154 100644 --- a/src/server/game/AI/CoreAI/CombatAI.cpp +++ b/src/server/game/AI/CoreAI/CombatAI.cpp @@ -302,24 +302,27 @@ void VehicleAI::LoadConditions() void VehicleAI::CheckConditions(uint32 diff) { - if (m_ConditionsTimer < diff) + if (!m_HasConditions) + return; + + if (m_ConditionsTimer <= diff) { - if (m_HasConditions) + if (Vehicle* vehicleKit = me->GetVehicleKit()) { - if (Vehicle* vehicleKit = me->GetVehicleKit()) - for (SeatMap::iterator itr = vehicleKit->Seats.begin(); itr != vehicleKit->Seats.end(); ++itr) - if (Unit* passenger = ObjectAccessor::GetUnit(*me, itr->second.Passenger.Guid)) + for (SeatMap::iterator itr = vehicleKit->Seats.begin(); itr != vehicleKit->Seats.end(); ++itr) + if (Unit* passenger = ObjectAccessor::GetUnit(*me, itr->second.Passenger.Guid)) + { + if (Player* player = passenger->ToPlayer()) { - if (Player* player = passenger->ToPlayer()) + if (!sConditionMgr->IsObjectMeetingNotGroupedConditions(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, me->GetEntry(), player, me)) { - if (!sConditionMgr->IsObjectMeetingNotGroupedConditions(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, me->GetEntry(), player, me)) - { - player->ExitVehicle(); - return; // check other pessanger in next tick - } + player->ExitVehicle(); + return; // check other pessanger in next tick } } + } } + m_ConditionsTimer = VEHICLE_CONDITION_CHECK_TIME; } else diff --git a/src/server/game/AI/CoreAI/GameObjectAI.h b/src/server/game/AI/CoreAI/GameObjectAI.h index b4e7362a8db..7e149a89962 100644 --- a/src/server/game/AI/CoreAI/GameObjectAI.h +++ b/src/server/game/AI/CoreAI/GameObjectAI.h @@ -47,7 +47,7 @@ class TC_GAME_API GameObjectAI static int Permissible(GameObject const* go); - virtual bool GossipHello(Player* /*player*/) { return false; } + virtual bool GossipHello(Player* /*player*/, bool /*reportUse*/) { return false; } virtual bool GossipSelect(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/) { return false; } virtual bool GossipSelectCode(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/, char const* /*code*/) { return false; } virtual bool QuestAccept(Player* /*player*/, Quest const* /*quest*/) { return false; } @@ -61,6 +61,7 @@ class TC_GAME_API GameObjectAI virtual void OnGameEvent(bool /*start*/, uint16 /*eventId*/) { } virtual void OnStateChanged(uint32 /*state*/, Unit* /*unit*/) { } virtual void EventInform(uint32 /*eventId*/) { } + virtual void SpellHit(Unit* /*unit*/, const SpellInfo* /*spellInfo*/) { } }; class TC_GAME_API NullGameObjectAI : public GameObjectAI diff --git a/src/server/game/AI/CoreAI/UnitAI.cpp b/src/server/game/AI/CoreAI/UnitAI.cpp index e4b426b3827..69d4cc4c65d 100644 --- a/src/server/game/AI/CoreAI/UnitAI.cpp +++ b/src/server/game/AI/CoreAI/UnitAI.cpp @@ -348,3 +348,40 @@ bool NonTankTargetSelector::operator()(Unit const* target) const return target != _source->GetVictim(); } + +bool PowerUsersSelector::operator()(Unit const* target) const +{ + if (!_me || !target) + return false; + + if (target->getPowerType() != _power) + return false; + + if (_playerOnly && target->GetTypeId() != TYPEID_PLAYER) + return false; + + if (_dist > 0.0f && !_me->IsWithinCombatRange(target, _dist)) + return false; + + if (_dist < 0.0f && _me->IsWithinCombatRange(target, -_dist)) + return false; + + return true; +} + +bool FarthestTargetSelector::operator()(Unit const* target) const +{ + if (!_me || !target) + return false; + + if (_playerOnly && target->GetTypeId() != TYPEID_PLAYER) + return false; + + if (_dist > 0.0f && !_me->IsWithinCombatRange(target, _dist)) + return false; + + if (_inLos && !_me->IsWithinLOSInMap(target)) + return false; + + return true; +} diff --git a/src/server/game/AI/CoreAI/UnitAI.h b/src/server/game/AI/CoreAI/UnitAI.h index d56c5b346a7..6a6c3aa5ea9 100644 --- a/src/server/game/AI/CoreAI/UnitAI.h +++ b/src/server/game/AI/CoreAI/UnitAI.h @@ -115,6 +115,33 @@ struct TC_GAME_API NonTankTargetSelector : public std::unary_function<Unit*, boo bool _playerOnly; }; +// Simple selector for units using mana +struct TC_GAME_API PowerUsersSelector +{ + public: + PowerUsersSelector(Unit const* unit, Powers power, float dist, bool playerOnly) : _me(unit), _power(power), _dist(dist), _playerOnly(playerOnly) { } + bool operator()(Unit const* target) const; + + private: + Unit const* _me; + Powers const _power; + float const _dist; + bool const _playerOnly; +}; + +struct TC_GAME_API FarthestTargetSelector +{ + public: + FarthestTargetSelector(Unit const* unit, float dist, bool playerOnly, bool inLos) : _me(unit), _dist(dist), _playerOnly(playerOnly), _inLos(inLos) {} + bool operator()(Unit const* target) const; + + private: + const Unit* _me; + float _dist; + bool _playerOnly; + bool _inLos; +}; + class TC_GAME_API UnitAI { protected: diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index ed034cd3336..798c1d1d813 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -25,6 +25,7 @@ #include "Group.h" #include "SmartAI.h" #include "ScriptMgr.h" +#include "Vehicle.h" SmartAI::SmartAI(Creature* c) : CreatureAI(c) { @@ -36,6 +37,7 @@ SmartAI::SmartAI(Creature* c) : CreatureAI(c) mCurrentWPID = 0;//first wp id is 1 !! mWPReached = false; mWPPauseTimer = 0; + mEscortNPCFlags = 0; mLastWP = nullptr; mCanRepeatPath = false; @@ -69,6 +71,8 @@ SmartAI::SmartAI(Creature* c) : CreatureAI(c) mInvincibilityHpLevel = 0; mJustReset = false; + mConditionsTimer = 0; + mHasConditions = sConditionMgr->HasConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, c->GetEntry()); } bool SmartAI::IsAIControlled() const @@ -76,7 +80,7 @@ bool SmartAI::IsAIControlled() const return !mIsCharmed; } -void SmartAI::UpdateDespawn(const uint32 diff) +void SmartAI::UpdateDespawn(uint32 diff) { if (mDespawnState <= 1 || mDespawnState > 3) return; @@ -113,28 +117,39 @@ WayPoint* SmartAI::GetNextWayPoint() return nullptr; } -void SmartAI::StartPath(bool run, uint32 path, bool repeat, Unit* /*invoker*/) +void SmartAI::StartPath(bool run, uint32 path, bool repeat, Unit* invoker) { if (me->IsInCombat())// no wp movement in combat { TC_LOG_ERROR("misc", "SmartAI::StartPath: Creature entry %u wanted to start waypoint movement while in combat, ignoring.", me->GetEntry()); return; } + if (HasEscortState(SMART_ESCORT_ESCORTING)) StopPath(); + if (path) + { if (!LoadPath(path)) return; + } + if (!mWayPoints || mWayPoints->empty()) return; - AddEscortState(SMART_ESCORT_ESCORTING); - mCanRepeatPath = repeat; - - SetRun(run); - if (WayPoint* wp = GetNextWayPoint()) { + AddEscortState(SMART_ESCORT_ESCORTING); + mCanRepeatPath = repeat; + + SetRun(run); + + if (invoker && invoker->GetTypeId() == TYPEID_PLAYER) + { + mEscortNPCFlags = me->GetUInt32Value(UNIT_NPC_FLAGS); + me->SetUInt32Value(UNIT_NPC_FLAGS, 0); + } + mLastOOCPos = me->GetPosition(); me->GetMotionMaster()->MovePoint(wp->id, wp->x, wp->y, wp->z); GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_START, nullptr, wp->id, GetScript()->GetPathId()); @@ -204,6 +219,12 @@ void SmartAI::EndPath(bool fail) mWPPauseTimer = 0; mLastWP = nullptr; + if (mEscortNPCFlags) + { + me->SetUInt32Value(UNIT_NPC_FLAGS, mEscortNPCFlags); + mEscortNPCFlags = 0; + } + if (mCanRepeatPath) { if (IsAIControlled()) @@ -280,12 +301,21 @@ void SmartAI::UpdatePath(const uint32 diff) return; if (mEscortInvokerCheckTimer < diff) { + // Escort failed, no players in range if (!IsEscortInvokerInRange()) { - StopPath(mDespawnTime, mEscortQuestID, true); + StopPath(0, mEscortQuestID, true); + + // allow to properly hook out of range despawn action, which in most cases should perform the same operation as dying + GetScript()->ProcessEventsFor(SMART_EVENT_DEATH, me); + me->DespawnOrUnsummon(1); + return; } mEscortInvokerCheckTimer = 1000; - } else mEscortInvokerCheckTimer -= diff; + } + else + mEscortInvokerCheckTimer -= diff; + // handle pause if (HasEscortState(SMART_ESCORT_PAUSED)) { @@ -304,11 +334,13 @@ void SmartAI::UpdatePath(const uint32 diff) if (mLastWPIDReached == SMART_ESCORT_LAST_OOC_POINT) mWPReached = true; } + mWPPauseTimer = 0; - } else { - mWPPauseTimer -= diff; } + else + mWPPauseTimer -= diff; } + if (HasEscortState(SMART_ESCORT_RETURNING)) { if (mWPReached)//reached OOC WP @@ -319,8 +351,10 @@ void SmartAI::UpdatePath(const uint32 diff) mWPReached = false; } } + if ((!me->HasReactState(REACT_PASSIVE) && me->IsInCombat()) || HasEscortState(SMART_ESCORT_PAUSED | SMART_ESCORT_RETURNING)) return; + // handle next wp if (mWPReached)//reached WP { @@ -339,6 +373,7 @@ void SmartAI::UpdatePath(const uint32 diff) void SmartAI::UpdateAI(uint32 diff) { + CheckConditions(diff); GetScript()->OnUpdate(diff); UpdatePath(diff); UpdateDespawn(diff); @@ -350,7 +385,7 @@ void SmartAI::UpdateAI(uint32 diff) { if (me->FindNearestCreature(mFollowArrivedEntry, INTERACTION_DISTANCE, true)) { - StopFollow(); + StopFollow(true); return; } @@ -375,18 +410,19 @@ bool SmartAI::IsEscortInvokerInRange() ObjectList* targets = GetScript()->GetTargetList(SMART_ESCORT_TARGETS); if (targets) { + float checkDist = me->GetInstanceScript() ? SMART_ESCORT_MAX_PLAYER_DIST * 2 : SMART_ESCORT_MAX_PLAYER_DIST; if (targets->size() == 1 && GetScript()->IsPlayer((*targets->begin()))) { Player* player = (*targets->begin())->ToPlayer(); - if (me->GetDistance(player) <= SMART_ESCORT_MAX_PLAYER_DIST) - return true; + if (me->GetDistance(player) <= checkDist) + return true; if (Group* group = player->GetGroup()) { for (GroupReference* groupRef = group->GetFirstMember(); groupRef != nullptr; groupRef = groupRef->next()) { Player* groupGuy = groupRef->GetSource(); - if (groupGuy->IsInMap(player) && me->GetDistance(groupGuy) <= SMART_ESCORT_MAX_PLAYER_DIST) + if (groupGuy->IsInMap(player) && me->GetDistance(groupGuy) <= checkDist) return true; } } @@ -397,13 +433,18 @@ bool SmartAI::IsEscortInvokerInRange() { if (GetScript()->IsPlayer((*iter))) { - if (me->GetDistance((*iter)->ToPlayer()) <= SMART_ESCORT_MAX_PLAYER_DIST) + if (me->GetDistance((*iter)->ToPlayer()) <= checkDist) return true; } } } + + // no valid target found + return false; } - return true;//escort targets were not set, ignore range check + + // no player invoker was stored, just ignore range check + return true; } void SmartAI::MovepointReached(uint32 id) @@ -434,8 +475,15 @@ void SmartAI::EnterEvadeMode(EvadeReason /*why*/) return; } + if (!IsAIControlled()) + { + me->AttackStop(); + return; + } + if (!_EnterEvadeMode()) return; + me->AddUnitState(UNIT_STATE_EVADE); GetScript()->ProcessEventsFor(SMART_EVENT_EVADE);//must be after aura clear so we can cast spells from db @@ -502,7 +550,7 @@ bool SmartAI::AssistPlayerInCombatAgainst(Unit* who) return false; //never attack friendly - if (me->IsFriendlyTo(who)) + if (!me->IsValidAssistTarget(who->GetVictim())) return false; //too far away and no free sight? @@ -605,6 +653,14 @@ void SmartAI::JustSummoned(Creature* creature) void SmartAI::AttackStart(Unit* who) { + // dont allow charmed npcs to act on their own + if (!IsAIControlled()) + { + if (who && mCanAutoAttack) + me->Attack(who, true); + return; + } + if (who && me->Attack(who, me->IsWithinMeleeRange(who))) { if (mCanCombatMove) @@ -672,9 +728,11 @@ void SmartAI::InitializeAI() { GetScript()->OnInitialize(me); if (!me->isDead()) - mJustReset = true; - JustReachedHome(); - GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN); + { + mJustReset = true; + JustReachedHome(); + GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN); + } } void SmartAI::OnCharmed(bool apply) @@ -729,11 +787,16 @@ void SmartAI::SetRun(bool run) mRun = run; } -void SmartAI::SetFly(bool fly) +void SmartAI::SetDisableGravity(bool fly) { me->SetDisableGravity(fly); } +void SmartAI::SetCanFly(bool fly) +{ + me->SetCanFly(fly); +} + void SmartAI::SetSwim(bool swim) { me->SetSwim(swim); @@ -807,13 +870,13 @@ void SmartAI::SetFollow(Unit* target, float dist, float angle, uint32 credit, ui { if (!target) { - StopFollow(); + StopFollow(false); return; } mFollowGuid = target->GetGUID(); - mFollowDist = dist >= 0.0f ? dist : PET_FOLLOW_DIST; - mFollowAngle = angle >= 0.0f ? angle : me->GetFollowAngle(); + mFollowDist = dist; + mFollowAngle = angle; mFollowArrivedTimer = 1000; mFollowCredit = credit; mFollowArrivedEntry = end; @@ -822,16 +885,8 @@ void SmartAI::SetFollow(Unit* target, float dist, float angle, uint32 credit, ui me->GetMotionMaster()->MoveFollow(target, mFollowDist, mFollowAngle); } -void SmartAI::StopFollow() +void SmartAI::StopFollow(bool complete) { - if (Player* player = ObjectAccessor::GetPlayer(*me, mFollowGuid)) - { - if (!mFollowCreditType) - player->RewardPlayerAndGroupAtEvent(mFollowCredit, me); - else - player->GroupEventHappens(mFollowCredit, me); - } - mFollowGuid.Clear(); mFollowDist = 0; mFollowAngle = 0; @@ -839,12 +894,25 @@ void SmartAI::StopFollow() mFollowArrivedTimer = 1000; mFollowArrivedEntry = 0; mFollowCreditType = 0; - SetDespawnTime(5000); me->StopMoving(); me->GetMotionMaster()->MoveIdle(); + + if (!complete) + return; + + if (Player* player = ObjectAccessor::GetPlayer(*me, mFollowGuid)) + { + if (!mFollowCreditType) + player->RewardPlayerAndGroupAtEvent(mFollowCredit, me); + else + player->GroupEventHappens(mFollowCredit, me); + } + + SetDespawnTime(5000); StartDespawn(); GetScript()->ProcessEventsFor(SMART_EVENT_FOLLOW_COMPLETED); } + void SmartAI::SetScript9(SmartScriptHolder& e, uint32 entry, Unit* invoker) { if (invoker) @@ -865,6 +933,35 @@ void SmartAI::OnSpellClick(Unit* clicker, bool& result) GetScript()->ProcessEventsFor(SMART_EVENT_ON_SPELLCLICK, clicker); } +void SmartAI::CheckConditions(uint32 diff) +{ + if (!mHasConditions) + return; + + if (mConditionsTimer <= diff) + { + if (Vehicle* vehicleKit = me->GetVehicleKit()) + { + for (SeatMap::iterator itr = vehicleKit->Seats.begin(); itr != vehicleKit->Seats.end(); ++itr) + if (Unit* passenger = ObjectAccessor::GetUnit(*me, itr->second.Passenger.Guid)) + { + if (Player* player = passenger->ToPlayer()) + { + if (!sConditionMgr->IsObjectMeetingNotGroupedConditions(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, me->GetEntry(), player, me)) + { + player->ExitVehicle(); + return; // check other pessanger in next tick + } + } + } + } + + mConditionsTimer = 1000; + } + else + mConditionsTimer -= diff; +} + int SmartGameObjectAI::Permissible(const GameObject* g) { if (g->GetAIName() == "SmartGameObjectAI") @@ -880,20 +977,26 @@ void SmartGameObjectAI::UpdateAI(uint32 diff) void SmartGameObjectAI::InitializeAI() { GetScript()->OnInitialize(go); - GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN); + + // do not call respawn event if go is not spawned + if (go->isSpawned()) + GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN); //Reset(); } void SmartGameObjectAI::Reset() { + // call respawn event on reset + GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN); + GetScript()->OnReset(); } // Called when a player opens a gossip dialog with the gameobject. -bool SmartGameObjectAI::GossipHello(Player* player) +bool SmartGameObjectAI::GossipHello(Player* player, bool reportUse) { TC_LOG_DEBUG("scripts.ai", "SmartGameObjectAI::GossipHello"); - GetScript()->ProcessEventsFor(SMART_EVENT_GOSSIP_HELLO, player, 0, 0, false, nullptr, go); + GetScript()->ProcessEventsFor(SMART_EVENT_GOSSIP_HELLO, player, uint32(reportUse), 0, false, nullptr, go); return false; } @@ -957,6 +1060,11 @@ void SmartGameObjectAI::EventInform(uint32 eventId) GetScript()->ProcessEventsFor(SMART_EVENT_GO_EVENT_INFORM, nullptr, eventId); } +void SmartGameObjectAI::SpellHit(Unit* unit, const SpellInfo* spellInfo) +{ + GetScript()->ProcessEventsFor(SMART_EVENT_SPELLHIT, unit, 0, 0, false, spellInfo); +} + class SmartTrigger : public AreaTriggerScript { public: diff --git a/src/server/game/AI/SmartScripts/SmartAI.h b/src/server/game/AI/SmartScripts/SmartAI.h index a6a6191ac0b..204821a2bf3 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.h +++ b/src/server/game/AI/SmartScripts/SmartAI.h @@ -38,7 +38,7 @@ enum SmartEscortState enum SmartEscortVars { - SMART_ESCORT_MAX_PLAYER_DIST = 50, + SMART_ESCORT_MAX_PLAYER_DIST = 60, SMART_MAX_AID_DIST = SMART_ESCORT_MAX_PLAYER_DIST / 2 }; @@ -66,7 +66,7 @@ class TC_GAME_API SmartAI : public CreatureAI void SetCombatMove(bool on); bool CanCombatMove() { return mCanCombatMove; } void SetFollow(Unit* target, float dist = 0.0f, float angle = 0.0f, uint32 credit = 0, uint32 end = 0, uint32 creditType = 0); - void StopFollow(); + void StopFollow(bool complete); void SetScript9(SmartScriptHolder& e, uint32 entry, Unit* invoker); SmartScript* GetScript() { return &mScript; } @@ -168,7 +168,9 @@ class TC_GAME_API SmartAI : public CreatureAI // Makes the creature run/walk void SetRun(bool run = true); - void SetFly(bool fly = true); + void SetCanFly(bool fly = true); + + void SetDisableGravity(bool disable = true); void SetSwim(bool swim = true); @@ -196,6 +198,8 @@ class TC_GAME_API SmartAI : public CreatureAI void OnSpellClick(Unit* clicker, bool& result) override; + void SetWPPauseTimer(uint32 time) { mWPPauseTimer = time; } + private: bool mIsCharmed; uint32 mFollowCreditType; @@ -215,6 +219,7 @@ class TC_GAME_API SmartAI : public CreatureAI uint32 mLastWPIDReached; bool mWPReached; uint32 mWPPauseTimer; + uint32 mEscortNPCFlags; WayPoint* mLastWP; Position mLastOOCPos;//set on enter combat uint32 GetWPCount() const { return mWayPoints ? uint32(mWayPoints->size()) : 0; } @@ -229,9 +234,14 @@ class TC_GAME_API SmartAI : public CreatureAI uint32 mDespawnTime; uint32 mDespawnState; - void UpdateDespawn(const uint32 diff); + void UpdateDespawn(uint32 diff); uint32 mEscortInvokerCheckTimer; bool mJustReset; + + // Vehicle conditions + void CheckConditions(uint32 diff); + bool mHasConditions; + uint32 mConditionsTimer; }; class TC_GAME_API SmartGameObjectAI : public GameObjectAI @@ -246,7 +256,7 @@ class TC_GAME_API SmartGameObjectAI : public GameObjectAI SmartScript* GetScript() { return &mScript; } static int Permissible(const GameObject* g); - bool GossipHello(Player* player) override; + bool GossipHello(Player* player, bool reportUse) override; bool GossipSelect(Player* player, uint32 sender, uint32 action) override; bool GossipSelectCode(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/, const char* /*code*/) override; bool QuestAccept(Player* player, Quest const* quest) override; @@ -257,6 +267,7 @@ class TC_GAME_API SmartGameObjectAI : public GameObjectAI void OnGameEvent(bool start, uint16 eventId) override; void OnStateChanged(uint32 state, Unit* unit) override; void EventInform(uint32 eventId) override; + void SpellHit(Unit* unit, const SpellInfo* spellInfo) override; private: SmartScript mScript; diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index a6f84695876..8bb526f9373 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -114,8 +114,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u case SMART_ACTION_TALK: { ObjectList* targets = GetTargets(e, unit); - Creature* talker = me; - Player* targetPlayer = nullptr; + Creature* talker = e.target.type == 0 ? me : nullptr; Unit* talkTarget = nullptr; if (targets) @@ -125,14 +124,18 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (IsCreature(*itr) && !(*itr)->ToCreature()->IsPet()) // Prevented sending text to pets. { if (e.action.talk.useTalkTarget) + { + talker = me; talkTarget = (*itr)->ToCreature(); + } else talker = (*itr)->ToCreature(); break; } else if (IsPlayer(*itr)) { - targetPlayer = (*itr)->ToPlayer(); + talker = me; + talkTarget = (*itr)->ToPlayer(); break; } } @@ -140,18 +143,15 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u delete targets; } + if (!talkTarget) + talkTarget = GetLastInvoker(); + if (!talker) break; mTalkerEntry = talker->GetEntry(); mLastTextID = e.action.talk.textGroupID; mTextTimer = e.action.talk.duration; - - if (IsPlayer(GetLastInvoker())) // used for $vars in texts and whisper target - talkTarget = GetLastInvoker(); - else if (targetPlayer) - talkTarget = targetPlayer; - mUseTextTimer = true; sCreatureTextMgr->SendChat(talker, uint8(e.action.talk.textGroupID), talkTarget); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_TALK: talker: %s (GuidLow: %u), textGuid: %u", @@ -482,8 +482,17 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (!targets) break; + if (e.action.cast.targetsLimit > 0 && targets->size() > e.action.cast.targetsLimit) + Trinity::Containers::RandomResizeList(*targets, e.action.cast.targetsLimit); + for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) { + if (go) + { + // may be nullptr + go->CastSpell((*itr)->ToUnit(), e.action.cast.spell); + } + if (!IsUnit(*itr)) continue; @@ -546,6 +555,9 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (!targets) break; + if (e.action.cast.targetsLimit > 0 && targets->size() > e.action.cast.targetsLimit) + Trinity::Containers::RandomResizeList(*targets, e.action.cast.targetsLimit); + for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) { if (!IsUnit(*itr)) @@ -768,7 +780,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u me->DoFleeToGetAssistance(); - if (e.action.flee.withEmote) + if (e.action.fleeAssist.withEmote) { Trinity::BroadcastTextBuilder builder(me, CHAT_MSG_MONSTER_EMOTE, BROADCAST_TEXT_FLEE_FOR_ASSIST); sCreatureTextMgr->SendChatPacket(me, builder, CHAT_MSG_MONSTER_EMOTE); @@ -781,9 +793,11 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (!unit) break; - if (IsPlayer(unit) && GetBaseObject()) + // If invoker was pet or charm + Player* player = unit->GetCharmerOrOwnerPlayerOrPlayerItself(); + if (player && GetBaseObject()) { - unit->ToPlayer()->GroupEventHappens(e.action.quest.quest, GetBaseObject()); + player->GroupEventHappens(e.action.quest.quest, GetBaseObject()); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_CALL_GROUPEVENTHAPPENS: Player %u, group credit for quest %u", unit->GetGUID().GetCounter(), e.action.quest.quest); } @@ -843,7 +857,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u ObjectList* targets = GetTargets(e, unit); if (!targets) { - ENSURE_AI(SmartAI, me->AI())->StopFollow(); + ENSURE_AI(SmartAI, me->AI())->StopFollow(false); break; } @@ -851,7 +865,8 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u { if (IsUnit(*itr)) { - ENSURE_AI(SmartAI, me->AI())->SetFollow((*itr)->ToUnit(), (float)e.action.follow.dist, (float)e.action.follow.angle, e.action.follow.credit, e.action.follow.entry, e.action.follow.creditType); + 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((*itr)->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: Creature %u following target %u", me->GetGUID().GetCounter(), (*itr)->GetGUID().GetCounter()); break; @@ -1028,25 +1043,39 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u } case SMART_ACTION_SET_IN_COMBAT_WITH_ZONE: { - if (me) - { - me->SetInCombatWithZone(); - TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_SET_IN_COMBAT_WITH_ZONE: Creature %u", me->GetGUID().GetCounter()); - } + ObjectList* targets = GetTargets(e, unit); + if (!targets) + break; + + for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) + if (IsCreature(*itr)) + { + (*itr)->ToCreature()->SetInCombatWithZone(); + TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_SET_IN_COMBAT_WITH_ZONE: Creature %u, target: %u", me->GetGUID().GetCounter(), (*itr)->GetGUID().GetCounter()); + } + + delete targets; break; } case SMART_ACTION_CALL_FOR_HELP: { - if (me) - { - me->CallForHelp((float)e.action.callHelp.range); - if (e.action.callHelp.withEmote) + ObjectList* targets = GetTargets(e, unit); + if (!targets) + break; + + for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) + if (IsCreature(*itr)) { - Trinity::BroadcastTextBuilder builder(me, CHAT_MSG_MONSTER_EMOTE, BROADCAST_TEXT_CALL_FOR_HELP); - sCreatureTextMgr->SendChatPacket(me, builder, CHAT_MSG_MONSTER_EMOTE); + (*itr)->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); + sCreatureTextMgr->SendChatPacket(me, builder, CHAT_MSG_MONSTER_EMOTE); + } + TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_CALL_FOR_HELP: Creature %u, target: %u", me->GetGUID().GetCounter(), (*itr)->GetGUID().GetCounter()); } - TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_CALL_FOR_HELP: Creature %u", me->GetGUID().GetCounter()); - } + + delete targets; break; } case SMART_ACTION_SET_SHEATH: @@ -1136,19 +1165,26 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u } case SMART_ACTION_SET_INVINCIBILITY_HP_LEVEL: { - if (!me) + ObjectList* targets = GetTargets(e, unit); + if (!targets) break; - SmartAI* ai = CAST_AI(SmartAI, me->AI()); - - if (!ai) - break; + for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) + { + if (IsCreature(*itr)) + { + SmartAI* ai = CAST_AI(SmartAI, (*itr)->ToCreature()->AI()); + if (!ai) + continue; - if (e.action.invincHP.percent) - ai->SetInvincibilityHpLevel(me->CountPctFromMaxHealth(e.action.invincHP.percent)); - else - ai->SetInvincibilityHpLevel(e.action.invincHP.minHP); + if (e.action.invincHP.percent) + ai->SetInvincibilityHpLevel((*itr)->ToCreature()->CountPctFromMaxHealth(e.action.invincHP.percent)); + else + ai->SetInvincibilityHpLevel(e.action.invincHP.minHP); + } + } + delete targets; break; } case SMART_ACTION_SET_DATA: @@ -1198,15 +1234,27 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u } case SMART_ACTION_SET_VISIBILITY: { - if (me) - me->SetVisible(e.action.visibility.state != 0); + ObjectList* targets = GetTargets(e, unit); + if (!targets) + break; + + for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) + if (IsUnit(*itr)) + (*itr)->ToUnit()->SetVisible(e.action.visibility.state ? true : false); + + delete targets; break; } case SMART_ACTION_SET_ACTIVE: { - if (WorldObject* baseObj = GetBaseObject()) - baseObj->setActive(e.action.active.state != 0); + ObjectList* targets = GetTargets(e, unit); + if (!targets) + break; + for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) + (*itr)->setActive(e.action.active.state ? true : false); + + delete targets; break; } case SMART_ACTION_ATTACK_START: @@ -1218,14 +1266,9 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (!targets) break; - for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) - { - if (IsUnit(*itr)) - { - me->AI()->AttackStart((*itr)->ToUnit()); - break; - } - } + // attack random target + if (Unit* target = Trinity::Containers::SelectRandomContainerElement(*targets)->ToUnit()) + me->AI()->AttackStart(target); delete targets; break; @@ -1269,8 +1312,9 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u { for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) { - if (!IsUnit(*itr)) - continue; + // allow gameobjects to summon gameobjects + //if (!IsUnit(*itr)) + // continue; Position pos = (*itr)->GetPositionWithOffset(Position(e.target.x, e.target.y, e.target.z, e.target.o)); G3D::Quat rot = G3D::Matrix3::fromEulerAnglesZYX(pos.GetOrientation(), 0.f, 0.f); @@ -1366,12 +1410,20 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u delete targets; break; } - case SMART_ACTION_SET_FLY: + 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())->SetFly(e.action.setFly.fly != 0); + ENSURE_AI(SmartAI, me->AI())->SetCanFly(e.action.setFly.fly != 0); break; } case SMART_ACTION_SET_RUN: @@ -1427,8 +1479,25 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u bool run = e.action.wpStart.run != 0; uint32 entry = e.action.wpStart.pathID; bool repeat = e.action.wpStart.repeat != 0; + + // ensure that SMART_ESCORT_TARGETS contains at least one player reference + bool stored = false; ObjectList* targets = GetTargets(e, unit); - StoreTargetList(targets, SMART_ESCORT_TARGETS); + if (targets) + { + for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) + { + if (IsPlayer(*itr)) + { + stored = true; + StoreTargetList(targets, SMART_ESCORT_TARGETS); + break; + } + } + if (!stored) + delete targets; + } + me->SetReactState((ReactStates)e.action.wpStart.reactState); ENSURE_AI(SmartAI, me->AI())->StartPath(run, entry, repeat, unit); @@ -1463,7 +1532,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (!IsSmart()) break; - ENSURE_AI(SmartAI, me->AI())->ResumePath(); + ENSURE_AI(SmartAI, me->AI())->SetWPPauseTimer(0); break; } case SMART_ACTION_SET_ORIENTATION: @@ -1510,19 +1579,20 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u 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) + /*if (e.GetTargetType() == SMART_TARGET_CREATURE_RANGE || e.GetTargetType() == SMART_TARGET_CREATURE_GUID || + e.GetTargetType() == SMART_TARGET_CREATURE_DISTANCE || e.GetTargetType() == SMART_TARGET_GAMEOBJECT_RANGE || + e.GetTargetType() == SMART_TARGET_GAMEOBJECT_GUID || e.GetTargetType() == SMART_TARGET_GAMEOBJECT_DISTANCE || + e.GetTargetType() == SMART_TARGET_CLOSEST_CREATURE || e.GetTargetType() == SMART_TARGET_CLOSEST_GAMEOBJECT || + e.GetTargetType() == SMART_TARGET_OWNER_OR_SUMMONER || e.GetTargetType() == SMART_TARGET_ACTION_INVOKER || + e.GetTargetType() == SMART_TARGET_CLOSEST_ENEMY || e.GetTargetType() == SMART_TARGET_CLOSEST_FRIENDLY || + e.GetTargetType() == SMART_TARGET_SELF || e.GetTargetType() == SMART_TARGET_STORED)*/ { - ObjectList* targets = GetTargets(e, unit); - if (!targets) - break; - - target = targets->front(); - delete targets; + if (ObjectList* targets = GetTargets(e, unit)) + { + // we want to move to random element + target = Trinity::Containers::SelectRandomContainerElement(*targets); + delete targets; + } } if (!target) @@ -1540,7 +1610,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u 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, y, z, e.action.MoveToPos.disablePathfinding == 0); + 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; } @@ -1555,7 +1625,13 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (IsCreature(*itr)) (*itr)->ToCreature()->Respawn(); else if (IsGameObject(*itr)) - (*itr)->ToGameObject()->SetRespawnTime(e.action.RespawnTarget.goRespawnTime); + { + // do not modify respawndelay of already spawned gameobjects + if ((*itr)->ToGameObject()->isSpawnedByDefault()) + (*itr)->ToGameObject()->Respawn(); + else + (*itr)->ToGameObject()->SetRespawnTime(e.action.RespawnTarget.goRespawnTime); + } } delete targets; @@ -1649,6 +1725,10 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u } 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); @@ -2423,6 +2503,109 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u 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 + { + ObjectList* targets = GetTargets(e, unit); + if (!targets) + break; + + for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) + if (IsUnit(*itr)) + (*itr)->ToUnit()->RemoveAurasByType((AuraType)e.action.auraType.type); + + delete targets; + break; + } + case SMART_ACTION_SET_SIGHT_DIST: + { + ObjectList* targets = GetTargets(e, unit); + if (!targets) + break; + + for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) + if (IsCreature(*itr)) + (*itr)->ToCreature()->m_SightDistance = e.action.sightDistance.dist; + + delete targets; + break; + } + case SMART_ACTION_FLEE: + { + ObjectList* targets = GetTargets(e, unit); + if (!targets) + break; + + for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) + if (IsCreature(*itr)) + (*itr)->ToCreature()->GetMotionMaster()->MoveFleeing(me, e.action.flee.fleeTime); + + delete targets; + break; + } + case SMART_ACTION_ADD_THREAT: + { + ObjectList* targets = GetTargets(e, unit); + if (!targets) + break; + + for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) + if (IsUnit(*itr)) + me->AddThreat((*itr)->ToUnit(), (float)e.action.threatPCT.threatINC - (float)e.action.threatPCT.threatDEC); + + delete targets; + break; + } + case SMART_ACTION_LOAD_EQUIPMENT: + { + ObjectList* targets = GetTargets(e, unit); + if (!targets) + break; + + for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) + if (IsCreature(*itr)) + (*itr)->ToCreature()->LoadEquipment(e.action.loadEquipment.id, e.action.loadEquipment.force != 0); + + delete targets; + 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, NULL, eventId); + break; + } + + case SMART_ACTION_REMOVE_ALL_GAMEOBJECTS: + { + ObjectList* targets = GetTargets(e, unit); + if (!targets) + break; + + for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) + if (IsUnit(*itr)) + (*itr)->ToUnit()->RemoveAllGameObjects(); + + delete targets; + break; + } + case SMART_ACTION_STOP_MOTION: + { + ObjectList* targets = GetTargets(e, unit); + if (!targets) + break; + + for (ObjectList::const_iterator itr = targets->begin(); itr != targets->end(); ++itr) + if (IsUnit(*itr)) + { + if (e.action.stopMotion.stopMovement) + (*itr)->ToUnit()->StopMoving(); + if (e.action.stopMotion.movementExpired) + (*itr)->ToUnit()->GetMotionMaster()->MovementExpired(); + } + + delete targets; + break; + } default: TC_LOG_ERROR("sql.sql", "SmartScript::ProcessAction: Entry %d SourceType %u, Event %u, Unhandled Action type %u", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType()); break; @@ -2440,10 +2623,14 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u void SmartScript::ProcessTimedAction(SmartScriptHolder& e, uint32 const& min, uint32 const& max, Unit* unit, uint32 var0, uint32 var1, bool bvar, const SpellInfo* spell, GameObject* gob) { + // 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())) + { ProcessAction(e, unit, var0, var1, bvar, spell, gob); - - RecalcTimer(e, min, max); + RecalcTimer(e, min, max); + } + else + RecalcTimer(e, std::min<uint32>(min, 5000), std::min<uint32>(min, 5000)); } void SmartScript::InstallTemplate(SmartScriptHolder const& e) @@ -2579,23 +2766,58 @@ ObjectList* SmartScript::GetTargets(SmartScriptHolder const& e, Unit* invoker /* break; case SMART_TARGET_HOSTILE_SECOND_AGGRO: if (me) - if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_TOPAGGRO, 1)) + { + if (e.target.hostilRandom.powerType) + { + if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_TOPAGGRO, 1, PowerUsersSelector(me, Powers(e.target.hostilRandom.powerType - 1), (float)e.target.hostilRandom.maxDist, e.target.hostilRandom.playerOnly != 0))) + l->push_back(u); + } + else if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_TOPAGGRO, 1, (float)e.target.hostilRandom.maxDist, e.target.hostilRandom.playerOnly != 0)) l->push_back(u); + } break; case SMART_TARGET_HOSTILE_LAST_AGGRO: if (me) - if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_BOTTOMAGGRO, 0)) + { + if (e.target.hostilRandom.powerType) + { + if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_BOTTOMAGGRO, 0, PowerUsersSelector(me, Powers(e.target.hostilRandom.powerType - 1), (float)e.target.hostilRandom.maxDist, e.target.hostilRandom.playerOnly != 0))) + l->push_back(u); + } + else if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_BOTTOMAGGRO, 0, (float)e.target.hostilRandom.maxDist, e.target.hostilRandom.playerOnly != 0)) l->push_back(u); + } break; case SMART_TARGET_HOSTILE_RANDOM: if (me) - if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_RANDOM, 0)) + { + 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))) + l->push_back(u); + } + else if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_RANDOM, 0, (float)e.target.hostilRandom.maxDist, e.target.hostilRandom.playerOnly != 0)) l->push_back(u); + } break; case SMART_TARGET_HOSTILE_RANDOM_NOT_TOP: if (me) - if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_RANDOM, 1)) + { + 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))) + l->push_back(u); + } + else if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_RANDOM, 1, (float)e.target.hostilRandom.maxDist, e.target.hostilRandom.playerOnly != 0)) l->push_back(u); + } + break; + case SMART_TARGET_FARTHEST: + if (me) + { + if (Unit* u = me->AI()->SelectTarget(SELECT_TARGET_FARTHEST, 0, FarthestTargetSelector(me, (float)e.target.farthest.maxDist, e.target.farthest.playerOnly != 0, e.target.farthest.isInLos != 0))) + l->push_back(u); + } break; case SMART_TARGET_ACTION_INVOKER: if (scriptTrigger) @@ -2759,7 +2981,7 @@ ObjectList* SmartScript::GetTargets(SmartScriptHolder const& e, Unit* invoker /* l->assign(objectList->begin(), objectList->end()); } - return l; + break; } case SMART_TARGET_CLOSEST_CREATURE: { @@ -2775,8 +2997,8 @@ ObjectList* SmartScript::GetTargets(SmartScriptHolder const& e, Unit* invoker /* } case SMART_TARGET_CLOSEST_PLAYER: { - if (me) - if (Player* target = me->SelectNearestPlayer(float(e.target.playerDistance.dist))) + if (WorldObject* obj = GetBaseObject()) + if (Player* target = obj->SelectNearestPlayer(float(e.target.playerDistance.dist))) l->push_back(target); break; } @@ -2797,6 +3019,21 @@ ObjectList* SmartScript::GetTargets(SmartScriptHolder const& e, Unit* invoker /* if (Unit* owner = ObjectAccessor::GetUnit(*me, charmerOrOwnerGuid)) l->push_back(owner); } + else if (go) + { + if (Unit* owner = ObjectAccessor::GetUnit(*go, go->GetOwnerGUID())) + l->push_back(owner); + } + + // Get owner of owner + if (e.target.owner.useCharmerOrOwner && !l->empty()) + { + Unit* owner = l->front()->ToUnit(); + l->clear(); + + if (Unit* base = ObjectAccessor::GetUnit(*owner, owner->GetCharmerOrOwnerGUID())) + l->push_back(base); + } break; } case SMART_TARGET_THREAT_LIST: @@ -2806,7 +3043,8 @@ ObjectList* SmartScript::GetTargets(SmartScriptHolder const& e, Unit* invoker /* ThreatContainer::StorageType threatList = me->getThreatManager().getThreatList(); for (ThreatContainer::StorageType::const_iterator i = threatList.begin(); i != threatList.end(); ++i) if (Unit* temp = ObjectAccessor::GetUnit(*me, (*i)->getUnitGuid())) - l->push_back(temp); + if (e.target.hostilRandom.maxDist == 0 || me->IsWithinCombatRange(temp, (float)e.target.hostilRandom.maxDist)) + l->push_back(temp); } break; } @@ -2947,6 +3185,8 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui 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: @@ -2974,7 +3214,12 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui 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; } @@ -2986,8 +3231,12 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui std::list<Creature*> pList; DoFindFriendlyCC(pList, (float)e.event.friendlyCC.radius); if (pList.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, *pList.begin()); + } + ProcessTimedAction(e, e.event.friendlyCC.repeatMin, e.event.friendlyCC.repeatMax, Trinity::Containers::SelectRandomContainerElement(pList)); break; } case SMART_EVENT_FRIENDLY_MISSING_BUFF: @@ -2998,7 +3247,7 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui if (pList.empty()) return; - ProcessTimedAction(e, e.event.missingBuff.repeatMin, e.event.missingBuff.repeatMax, *pList.begin()); + ProcessTimedAction(e, e.event.missingBuff.repeatMin, e.event.missingBuff.repeatMax, Trinity::Containers::SelectRandomContainerElement(pList)); break; } case SMART_EVENT_HAS_AURA: @@ -3044,11 +3293,15 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui case SMART_EVENT_JUST_SUMMONED: case SMART_EVENT_RESET: case SMART_EVENT_JUST_CREATED: - case SMART_EVENT_GOSSIP_HELLO: 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) @@ -3191,6 +3444,13 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui break; } case SMART_EVENT_SUMMON_DESPAWNED: + { + if (e.event.summoned.creature && e.event.summoned.creature != var0) + return; + ProcessAction(e, unit, var0); + RecalcTimer(e, e.event.summoned.cooldownMin, e.event.summoned.cooldownMax); + break; + } case SMART_EVENT_INSTANCE_PLAYER_ENTER: { if (e.event.instancePlayerEnter.team && var0 != e.event.instancePlayerEnter.team) @@ -3405,8 +3665,10 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui break; } case SMART_EVENT_COUNTER_SET: - if (GetCounterId(e.event.counter.id) != 0 && GetCounterValue(e.event.counter.id) == e.event.counter.value) - ProcessTimedAction(e, e.event.counter.cooldownMin, e.event.counter.cooldownMax); + 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; default: TC_LOG_ERROR("sql.sql", "SmartScript::ProcessEvent: Unhandled Event type %u", e.GetEventType()); @@ -3472,7 +3734,7 @@ void SmartScript::UpdateTimer(SmartScriptHolder& e, uint32 const diff) // 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_STUNNED)) + if (me && me->HasUnitState(UNIT_STATE_ROOT | UNIT_STATE_LOST_CONTROL)) { e.timer = 1; return; @@ -3550,8 +3812,14 @@ void SmartScript::OnUpdate(uint32 const diff) UpdateTimer(*i, diff); if (!mStoredEvents.empty()) - for (SmartAIEventList::iterator i = mStoredEvents.begin(); i != mStoredEvents.end(); ++i) - UpdateTimer(*i, diff); + { + SmartAIEventStoredList::iterator i, icurr; + for (i = mStoredEvents.begin(); i != mStoredEvents.end();) + { + icurr = i++; + UpdateTimer(*icurr, diff); + } + } bool needCleanup = true; if (!mTimedActionList.empty()) @@ -3573,11 +3841,12 @@ void SmartScript::OnUpdate(uint32 const diff) if (!mRemIDs.empty()) { - for (std::list<uint32>::iterator i = mRemIDs.begin(); i != mRemIDs.end(); ++i) - { - RemoveStoredEvent((*i)); - } + for (auto i : mRemIDs) + RemoveStoredEvent(i); + + mRemIDs.clear(); } + if (mUseTextTimer && me) { if (mTextTimer < diff) @@ -3705,18 +3974,11 @@ Unit* SmartScript::DoSelectLowestHpFriendly(float range, uint32 MinHPDiff) if (!me) return nullptr; - CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY())); - Cell cell(p); - cell.SetNoCreate(); - Unit* unit = nullptr; Trinity::MostHPMissingInRange u_check(me, range, MinHPDiff); Trinity::UnitLastSearcher<Trinity::MostHPMissingInRange> searcher(me, unit, u_check); - - TypeContainerVisitor<Trinity::UnitLastSearcher<Trinity::MostHPMissingInRange>, GridTypeMapContainer > grid_unit_searcher(searcher); - - cell.Visit(p, grid_unit_searcher, *me->GetMap(), *me, range); + me->VisitNearbyObject(range, searcher); return unit; } @@ -3725,16 +3987,9 @@ void SmartScript::DoFindFriendlyCC(std::list<Creature*>& _list, float range) if (!me) return; - CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY())); - Cell cell(p); - cell.SetNoCreate(); - Trinity::FriendlyCCedInRange u_check(me, range); Trinity::CreatureListSearcher<Trinity::FriendlyCCedInRange> searcher(me, _list, u_check); - - TypeContainerVisitor<Trinity::CreatureListSearcher<Trinity::FriendlyCCedInRange>, GridTypeMapContainer > grid_creature_searcher(searcher); - - cell.Visit(p, grid_creature_searcher, *me->GetMap(), *me, range); + me->VisitNearbyObject(range, searcher); } void SmartScript::DoFindFriendlyMissingBuff(std::list<Creature*>& list, float range, uint32 spellid) @@ -3742,16 +3997,9 @@ void SmartScript::DoFindFriendlyMissingBuff(std::list<Creature*>& list, float ra if (!me) return; - CellCoord p(Trinity::ComputeCellCoord(me->GetPositionX(), me->GetPositionY())); - Cell cell(p); - cell.SetNoCreate(); - Trinity::FriendlyMissingBuffInRange u_check(me, range, spellid); Trinity::CreatureListSearcher<Trinity::FriendlyMissingBuffInRange> searcher(me, list, u_check); - - TypeContainerVisitor<Trinity::CreatureListSearcher<Trinity::FriendlyMissingBuffInRange>, GridTypeMapContainer > grid_creature_searcher(searcher); - - cell.Visit(p, grid_creature_searcher, *me->GetMap(), *me, range); + me->VisitNearbyObject(range, searcher); } Unit* SmartScript::DoFindClosestFriendlyInRange(float range, bool playerOnly) diff --git a/src/server/game/AI/SmartScripts/SmartScript.h b/src/server/game/AI/SmartScripts/SmartScript.h index 7113bda92cf..5b3221cf3ea 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.h +++ b/src/server/game/AI/SmartScripts/SmartScript.h @@ -158,29 +158,23 @@ class TC_GAME_API SmartScript void StoreCounter(uint32 id, uint32 value, uint32 reset) { - CounterMap::const_iterator itr = mCounterList.find(id); + CounterMap::iterator itr = mCounterList.find(id); if (itr != mCounterList.end()) { if (reset == 0) - value += GetCounterValue(id); - mCounterList.erase(id); + itr->second += value; + else + itr->second = value; } + else + mCounterList.insert(std::make_pair(id, value)); - mCounterList.insert(std::make_pair(id, value)); - ProcessEventsFor(SMART_EVENT_COUNTER_SET); - } - - uint32 GetCounterId(uint32 id) - { - CounterMap::iterator itr = mCounterList.find(id); - if (itr != mCounterList.end()) - return itr->first; - return 0; + ProcessEventsFor(SMART_EVENT_COUNTER_SET, nullptr, id); } - uint32 GetCounterValue(uint32 id) + uint32 GetCounterValue(uint32 id) const { - CounterMap::iterator itr = mCounterList.find(id); + CounterMap::const_iterator itr = mCounterList.find(id); if (itr != mCounterList.end()) return itr->second; return 0; @@ -250,23 +244,27 @@ class TC_GAME_API SmartScript CounterMap mCounterList; private: - void IncPhase(int32 p = 1) + void IncPhase(uint32 p) { - if (p >= 0) - mEventPhase += (uint32)p; - else - DecPhase(abs(p)); + // protect phase from overflowing + mEventPhase = std::min<uint32>(SMART_EVENT_PHASE_12, mEventPhase + p); } - void DecPhase(int32 p = 1) + void DecPhase(uint32 p) { - if (mEventPhase > (uint32)p) - mEventPhase -= (uint32)p; - else + if (p >= mEventPhase) mEventPhase = 0; + else + mEventPhase -= p; + } + + bool IsInPhase(uint32 p) const + { + if (mEventPhase == 0) + return false; + return ((1 << (mEventPhase - 1)) & p) != 0; } - bool IsInPhase(uint32 p) const { return ((1 << (mEventPhase - 1)) & p) != 0; } void SetPhase(uint32 p = 0) { mEventPhase = p; } SmartAIEventList mEvents; @@ -282,8 +280,8 @@ class TC_GAME_API SmartScript uint32 mEventPhase; uint32 mPathId; - SmartAIEventList mStoredEvents; - std::list<uint32>mRemIDs; + SmartAIEventStoredList mStoredEvents; + std::vector<uint32> mRemIDs; uint32 mTextTimer; uint32 mLastTextID; @@ -297,7 +295,7 @@ class TC_GAME_API SmartScript { if (!mStoredEvents.empty()) { - for (SmartAIEventList::iterator i = mStoredEvents.begin(); i != mStoredEvents.end(); ++i) + for (auto i = mStoredEvents.begin(); i != mStoredEvents.end(); ++i) { if (i->event_id == id) { diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp index 60d2169bdf4..a44537ea91a 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp @@ -224,7 +224,7 @@ void SmartAIMgr::LoadSmartAIFromDB() temp.event_id = fields[2].GetUInt16(); temp.link = fields[3].GetUInt16(); temp.event.type = (SMART_EVENT)fields[4].GetUInt8(); - temp.event.event_phase_mask = fields[5].GetUInt8(); + temp.event.event_phase_mask = fields[5].GetUInt16(); temp.event.event_chance = fields[6].GetUInt8(); temp.event.event_flags = fields[7].GetUInt16(); @@ -258,6 +258,50 @@ void SmartAIMgr::LoadSmartAIFromDB() if (!IsEventValid(temp)) continue; + // specific check for timed events + switch (temp.event.type) + { + 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_FRIENDLY_HEALTH: + case SMART_EVENT_FRIENDLY_HEALTH_PCT: + case SMART_EVENT_FRIENDLY_MISSING_BUFF: + case SMART_EVENT_HAS_AURA: + case SMART_EVENT_TARGET_BUFFED: + if (temp.event.minMaxRepeat.repeatMin == 0 && temp.event.minMaxRepeat.repeatMax == 0 && !(temp.event.event_flags & SMART_EVENT_FLAG_NOT_REPEATABLE) && temp.source_type != SMART_SCRIPT_TYPE_TIMED_ACTIONLIST) + { + temp.event.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; + TC_LOG_ERROR("sql.sql", "SmartAIMgr::LoadSmartAIFromDB: Entry %d SourceType %u, Event %u, Missing Repeat flag.", + temp.entryOrGuid, temp.GetScriptType(), temp.event_id); + } + break; + case SMART_EVENT_VICTIM_CASTING: + case SMART_EVENT_IS_BEHIND_TARGET: + if (temp.event.minMaxRepeat.min == 0 && temp.event.minMaxRepeat.max == 0 && !(temp.event.event_flags & SMART_EVENT_FLAG_NOT_REPEATABLE) && temp.source_type != SMART_SCRIPT_TYPE_TIMED_ACTIONLIST) + { + temp.event.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; + TC_LOG_ERROR("sql.sql", "SmartAIMgr::LoadSmartAIFromDB: Entry %d SourceType %u, Event %u, Missing Repeat flag.", + temp.entryOrGuid, temp.GetScriptType(), temp.event_id); + } + break; + case SMART_EVENT_FRIENDLY_IS_CC: + if (temp.event.friendlyCC.repeatMin == 0 && temp.event.friendlyCC.repeatMax == 0 && !(temp.event.event_flags & SMART_EVENT_FLAG_NOT_REPEATABLE) && temp.source_type != SMART_SCRIPT_TYPE_TIMED_ACTIONLIST) + { + temp.event.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; + TC_LOG_ERROR("sql.sql", "SmartAIMgr::LoadSmartAIFromDB: Entry %d SourceType %u, Event %u, Missing Repeat flag.", + temp.entryOrGuid, temp.GetScriptType(), temp.event_id); + } + break; + default: + break; + } + // creature entry / guid not found in storage, create empty event list for it and increase counters if (mEventMap[source_type].find(temp.entryOrGuid) == mEventMap[source_type].end()) { @@ -376,6 +420,7 @@ bool SmartAIMgr::IsTargetValid(SmartScriptHolder const& e) case SMART_TARGET_CLOSEST_FRIENDLY: case SMART_TARGET_STORED: case SMART_TARGET_LOOT_RECIPIENTS: + case SMART_TARGET_FARTHEST: break; default: TC_LOG_ERROR("sql.sql", "SmartAIMgr: Not handled target_type(%u), Entry %d SourceType %u Event %u Action %u, skipped.", e.GetTargetType(), e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType()); @@ -874,6 +919,12 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) } break; } + case SMART_ACTION_CROSS_CAST: + { + if (!IsSpellValid(e, e.action.crossCast.spell)) + return false; + break; + } case SMART_ACTION_ADD_AURA: case SMART_ACTION_INVOKER_CAST: if (!IsSpellValid(e, e.action.cast.spell)) @@ -1038,20 +1089,6 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) return false; } break; - case SMART_ACTION_SET_COUNTER: - if (e.action.setCounter.counterId == 0) - { - TC_LOG_ERROR("sql.sql", "SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses wrong counterId %u, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.action.setCounter.counterId); - return false; - } - - if (e.action.setCounter.value == 0) - { - TC_LOG_ERROR("sql.sql", "SmartAIMgr: Entry %d SourceType %u Event %u Action %u uses wrong value %u, skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.action.setCounter.value); - return false; - } - - break; case SMART_ACTION_INSTALL_AI_TEMPLATE: if (e.action.installTtemplate.id >= SMARTAI_TEMPLATE_END) { @@ -1176,6 +1213,15 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) } break; } + case SMART_ACTION_REMOVE_AURAS_BY_TYPE: + { + if (e.action.auraType.type >= TOTAL_AURAS) + { + TC_LOG_ERROR("sql.sql", "Entry %u SourceType %u Event %u Action %u uses invalid data type %u (value range 0-TOTAL_AURAS), skipped.", e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(), e.action.auraType.type); + return false; + } + break; + } case SMART_ACTION_START_CLOSEST_WAYPOINT: case SMART_ACTION_FOLLOW: case SMART_ACTION_SET_ORIENTATION: @@ -1200,7 +1246,8 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) case SMART_ACTION_SET_DATA: case SMART_ACTION_SET_VISIBILITY: case SMART_ACTION_WP_PAUSE: - case SMART_ACTION_SET_FLY: + case SMART_ACTION_SET_DISABLE_GRAVITY: + case SMART_ACTION_SET_CAN_FLY: case SMART_ACTION_SET_RUN: case SMART_ACTION_SET_SWIM: case SMART_ACTION_FORCE_DESPAWN: @@ -1222,7 +1269,6 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) case SMART_ACTION_SET_NPC_FLAG: case SMART_ACTION_ADD_NPC_FLAG: case SMART_ACTION_REMOVE_NPC_FLAG: - case SMART_ACTION_CROSS_CAST: case SMART_ACTION_CALL_RANDOM_TIMED_ACTIONLIST: case SMART_ACTION_RANDOM_MOVE: case SMART_ACTION_SET_UNIT_FIELD_BYTES_1: @@ -1247,6 +1293,13 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) case SMART_ACTION_MOVE_OFFSET: case SMART_ACTION_SET_CORPSE_DELAY: case SMART_ACTION_DISABLE_EVADE: + case SMART_ACTION_SET_SIGHT_DIST: + case SMART_ACTION_FLEE: + case SMART_ACTION_ADD_THREAT: + case SMART_ACTION_LOAD_EQUIPMENT: + case SMART_ACTION_TRIGGER_RANDOM_TIMED_EVENT: + case SMART_ACTION_SET_COUNTER: + case SMART_ACTION_REMOVE_ALL_GAMEOBJECTS: break; default: TC_LOG_ERROR("sql.sql", "SmartAIMgr: Not handled action_type(%u), event_type(%u), Entry %d SourceType %u Event %u, skipped.", e.GetActionType(), e.GetEventType(), e.entryOrGuid, e.GetScriptType(), e.event_id); diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.h b/src/server/game/AI/SmartScripts/SmartScriptMgr.h index 44201191f50..103407962d5 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.h +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.h @@ -65,9 +65,12 @@ enum SMART_EVENT_PHASE SMART_EVENT_PHASE_7 = 7, SMART_EVENT_PHASE_8 = 8, SMART_EVENT_PHASE_9 = 9, - SMART_EVENT_PHASE_MAX = 10, + SMART_EVENT_PHASE_10 = 10, + SMART_EVENT_PHASE_11 = 11, + SMART_EVENT_PHASE_12 = 12, + SMART_EVENT_PHASE_MAX = 13, - SMART_EVENT_PHASE_COUNT = 9 + SMART_EVENT_PHASE_COUNT = 12 }; enum SMART_EVENT_PHASE_BITS @@ -82,7 +85,12 @@ enum SMART_EVENT_PHASE_BITS SMART_EVENT_PHASE_7_BIT = 64, SMART_EVENT_PHASE_8_BIT = 128, SMART_EVENT_PHASE_9_BIT = 256, - SMART_EVENT_PHASE_ALL = SMART_EVENT_PHASE_1_BIT + SMART_EVENT_PHASE_2_BIT + SMART_EVENT_PHASE_3_BIT + SMART_EVENT_PHASE_4_BIT + SMART_EVENT_PHASE_5_BIT + SMART_EVENT_PHASE_6_BIT + SMART_EVENT_PHASE_7_BIT + SMART_EVENT_PHASE_8_BIT + SMART_EVENT_PHASE_9_BIT + SMART_EVENT_PHASE_10_BIT = 512, + SMART_EVENT_PHASE_11_BIT = 1024, + SMART_EVENT_PHASE_12_BIT = 2048, + SMART_EVENT_PHASE_ALL = SMART_EVENT_PHASE_1_BIT + SMART_EVENT_PHASE_2_BIT + SMART_EVENT_PHASE_3_BIT + SMART_EVENT_PHASE_4_BIT + SMART_EVENT_PHASE_5_BIT + + SMART_EVENT_PHASE_6_BIT + SMART_EVENT_PHASE_7_BIT + SMART_EVENT_PHASE_8_BIT + SMART_EVENT_PHASE_9_BIT + SMART_EVENT_PHASE_10_BIT + + SMART_EVENT_PHASE_11_BIT + SMART_EVENT_PHASE_12_BIT }; const uint32 SmartPhaseMask[SMART_EVENT_PHASE_COUNT][2] = @@ -95,7 +103,10 @@ const uint32 SmartPhaseMask[SMART_EVENT_PHASE_COUNT][2] = {SMART_EVENT_PHASE_6, SMART_EVENT_PHASE_6_BIT }, {SMART_EVENT_PHASE_7, SMART_EVENT_PHASE_7_BIT }, {SMART_EVENT_PHASE_8, SMART_EVENT_PHASE_8_BIT }, - {SMART_EVENT_PHASE_9, SMART_EVENT_PHASE_9_BIT } + {SMART_EVENT_PHASE_9, SMART_EVENT_PHASE_9_BIT }, + {SMART_EVENT_PHASE_10, SMART_EVENT_PHASE_10_BIT }, + {SMART_EVENT_PHASE_11, SMART_EVENT_PHASE_11_BIT }, + {SMART_EVENT_PHASE_12, SMART_EVENT_PHASE_12_BIT } }; enum SMART_EVENT @@ -164,7 +175,7 @@ enum SMART_EVENT SMART_EVENT_LINK = 61, // INTERNAL USAGE, no params, used to link together multiple events, does not use any extra resources to iterate event lists needlessly SMART_EVENT_GOSSIP_SELECT = 62, // menuID, actionID SMART_EVENT_JUST_CREATED = 63, // none - SMART_EVENT_GOSSIP_HELLO = 64, // none + SMART_EVENT_GOSSIP_HELLO = 64, // noReportUse (for GOs) SMART_EVENT_FOLLOW_COMPLETED = 65, // none SMART_EVENT_DUMMY_EFFECT = 66, // spellId, effectIndex SMART_EVENT_IS_BEHIND_TARGET = 67, // cooldownMin, CooldownMax @@ -360,6 +371,11 @@ struct SmartEvent struct { + uint32 noReportUse; + } gossipHello; + + struct + { uint32 sender; uint32 action; } gossip; @@ -499,7 +515,7 @@ enum SMART_ACTION SMART_ACTION_REMOVE_ITEM = 57, // itemID, count SMART_ACTION_INSTALL_AI_TEMPLATE = 58, // AITemplateID SMART_ACTION_SET_RUN = 59, // 0/1 - SMART_ACTION_SET_FLY = 60, // 0/1 + SMART_ACTION_SET_DISABLE_GRAVITY = 60, // 0/1 SMART_ACTION_SET_SWIM = 61, // 0/1 SMART_ACTION_TELEPORT = 62, // mapID, SMART_ACTION_SET_COUNTER = 63, // id, value, reset (0/1) @@ -558,8 +574,17 @@ enum SMART_ACTION SMART_ACTION_SET_CORPSE_DELAY = 116, // timer SMART_ACTION_DISABLE_EVADE = 117, // 0/1 (1 = disabled, 0 = enabled) SMART_ACTION_GO_SET_GO_STATE = 118, // state - - SMART_ACTION_END = 119 + SMART_ACTION_SET_CAN_FLY = 119, // 0/1 + SMART_ACTION_REMOVE_AURAS_BY_TYPE = 120, // type + SMART_ACTION_SET_SIGHT_DIST = 121, // sightDistance + SMART_ACTION_FLEE = 122, // fleeTime + SMART_ACTION_ADD_THREAT = 123, // +threat, -threat + SMART_ACTION_LOAD_EQUIPMENT = 124, // id + SMART_ACTION_TRIGGER_RANDOM_TIMED_EVENT = 125, // id min range, id max range + SMART_ACTION_REMOVE_ALL_GAMEOBJECTS = 126, + SMART_ACTION_STOP_MOTION = 127, // stopMoving, movementExpired + + SMART_ACTION_END = 128 }; struct SmartAction @@ -628,6 +653,7 @@ struct SmartAction uint32 spell; uint32 castFlags; uint32 triggerFlags; + uint32 targetsLimit; } cast; struct @@ -853,6 +879,11 @@ struct SmartAction struct { + uint32 disable; + } setDisableGravity; + + struct + { uint32 fly; } setFly; @@ -968,6 +999,11 @@ struct SmartAction struct { uint32 withEmote; + } fleeAssist; + + struct + { + uint32 fleeTime; } flee; struct @@ -1073,6 +1109,34 @@ struct SmartAction uint32 disable; } disableEvade; + struct + { + uint32 type; + } auraType; + + struct + { + uint32 dist; + } sightDistance; + + struct + { + uint32 id; + uint32 force; + } loadEquipment; + + struct + { + uint32 minId; + uint32 maxId; + } randomTimedEvent; + + struct + { + uint32 stopMovement; + uint32 movementExpired; + } stopMotion; + //! Note for any new future actions //! All parameters must have type uint32 @@ -1104,10 +1168,10 @@ enum SMARTAI_TARGETS SMART_TARGET_NONE = 0, // NONE, defaulting to invoket SMART_TARGET_SELF = 1, // Self cast SMART_TARGET_VICTIM = 2, // Our current target (ie: highest aggro) - SMART_TARGET_HOSTILE_SECOND_AGGRO = 3, // Second highest aggro - SMART_TARGET_HOSTILE_LAST_AGGRO = 4, // Dead last on aggro - SMART_TARGET_HOSTILE_RANDOM = 5, // Just any random target on our threat list - SMART_TARGET_HOSTILE_RANDOM_NOT_TOP = 6, // Any random target except top threat + SMART_TARGET_HOSTILE_SECOND_AGGRO = 3, // Second highest aggro, maxdist, playerOnly, powerType + 1 + SMART_TARGET_HOSTILE_LAST_AGGRO = 4, // Dead last on aggro, maxdist, playerOnly, powerType + 1 + SMART_TARGET_HOSTILE_RANDOM = 5, // Just any random target on our threat list, maxdist, playerOnly, powerType + 1 + SMART_TARGET_HOSTILE_RANDOM_NOT_TOP = 6, // Any random target except top threat, maxdist, playerOnly, powerType + 1 SMART_TARGET_ACTION_INVOKER = 7, // Unit who caused this Event to occur SMART_TARGET_POSITION = 8, // use xyz from event params SMART_TARGET_CREATURE_RANGE = 9, // CreatureEntry(0any), minDist, maxDist @@ -1124,13 +1188,14 @@ enum SMARTAI_TARGETS SMART_TARGET_CLOSEST_GAMEOBJECT = 20, // entry(0any), maxDist SMART_TARGET_CLOSEST_PLAYER = 21, // maxDist SMART_TARGET_ACTION_INVOKER_VEHICLE = 22, // Unit's vehicle who caused this Event to occur - SMART_TARGET_OWNER_OR_SUMMONER = 23, // Unit's owner or summoner - SMART_TARGET_THREAT_LIST = 24, // All units on creature's threat list + SMART_TARGET_OWNER_OR_SUMMONER = 23, // Unit's owner or summoner, Use Owner/Charmer of this unit + SMART_TARGET_THREAT_LIST = 24, // All units on creature's threat list, maxdist SMART_TARGET_CLOSEST_ENEMY = 25, // maxDist, playerOnly SMART_TARGET_CLOSEST_FRIENDLY = 26, // maxDist, playerOnly SMART_TARGET_LOOT_RECIPIENTS = 27, // all players that have tagged this creature (for kill credit) + SMART_TARGET_FARTHEST = 28, // maxDist, playerOnly, isInLos - SMART_TARGET_END = 28 + SMART_TARGET_END = 29 }; struct SmartTarget @@ -1152,6 +1217,20 @@ struct SmartTarget { struct { + uint32 maxDist; + uint32 playerOnly; + uint32 powerType; + } hostilRandom; + + struct + { + uint32 maxDist; + uint32 playerOnly; + uint32 isInLos; + } farthest; + + struct + { uint32 creature; uint32 minDist; uint32 maxDist; @@ -1230,6 +1309,11 @@ struct SmartTarget struct { + uint32 useCharmerOrOwner; + } owner; + + struct + { uint32 param1; uint32 param2; uint32 param3; @@ -1499,6 +1583,7 @@ class TC_GAME_API SmartWaypointMgr // all events for a single entry typedef std::vector<SmartScriptHolder> SmartAIEventList; +typedef std::vector<SmartScriptHolder> SmartAIEventStoredList; // all events for all entries / guids typedef std::unordered_map<int32, SmartAIEventList> SmartAIEventMap; diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index f44c8c5311c..5029d66c111 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -2085,17 +2085,6 @@ Unit* Creature::SelectNearestTargetInAttackDistance(float dist) const return target; } -Player* Creature::SelectNearestPlayer(float distance) const -{ - Player* target = nullptr; - - Trinity::NearestPlayerInObjectRangeCheck checker(this, distance); - Trinity::PlayerLastSearcher<Trinity::NearestPlayerInObjectRangeCheck> searcher(this, target, checker); - VisitNearbyObject(distance, searcher); - - return target; -} - void Creature::SendAIReaction(AiReaction reactionType) { WorldPacket data(SMSG_AI_REACTION, 12); diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index cc797966c46..b1ea4ac35fd 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -585,7 +585,6 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma Unit* SelectNearestTarget(float dist = 0, bool playerOnly = false) const; Unit* SelectNearestTargetInAttackDistance(float dist = 0) const; - Player* SelectNearestPlayer(float distance = 0) const; Unit* SelectNearestHostileUnitInAggroRange(bool useLOS = false) const; void DoFleeToGetAssistance(); diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index b7f4433b63e..3ef54a6187c 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -482,6 +482,10 @@ void GameObject::Update(uint32 diff) return; } + // Call AI Reset (required for example in SmartAI to clear one time events) + if (AI()) + AI()->Reset(); + // Respawn timer uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool<GameObject>(GetSpawnId()) : 0; if (poolid) @@ -1202,7 +1206,7 @@ void GameObject::Use(Unit* user) if (sScriptMgr->OnGossipHello(playerUser, this)) return; - if (AI()->GossipHello(playerUser)) + if (AI()->GossipHello(playerUser, false)) return; } diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index a8369728fe8..9e838809799 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -2055,7 +2055,7 @@ Creature* WorldObject::SummonTrigger(float x, float y, float z, float ang, uint3 TempSummonType summonType = (duration == 0) ? TEMPSUMMON_DEAD_DESPAWN : TEMPSUMMON_TIMED_DESPAWN; Creature* summon = SummonCreature(WORLD_TRIGGER, x, y, z, ang, summonType, duration); if (!summon) - return NULL; + return nullptr; //summon->SetName(GetName()); if (GetTypeId() == TYPEID_PLAYER || GetTypeId() == TYPEID_UNIT) @@ -2094,7 +2094,7 @@ void WorldObject::SummonCreatureGroup(uint8 group, std::list<TempSummon*>* list Creature* WorldObject::FindNearestCreature(uint32 entry, float range, bool alive) const { - Creature* creature = NULL; + Creature* creature = nullptr; Trinity::NearestCreatureEntryWithLiveStateInObjectRangeCheck checker(*this, entry, alive, range); Trinity::CreatureLastSearcher<Trinity::NearestCreatureEntryWithLiveStateInObjectRangeCheck> searcher(this, creature, checker); VisitNearbyObject(range, searcher); @@ -2103,7 +2103,7 @@ Creature* WorldObject::FindNearestCreature(uint32 entry, float range, bool alive GameObject* WorldObject::FindNearestGameObject(uint32 entry, float range) const { - GameObject* go = NULL; + GameObject* go = nullptr; Trinity::NearestGameObjectEntryInObjectRangeCheck checker(*this, entry, range); Trinity::GameObjectLastSearcher<Trinity::NearestGameObjectEntryInObjectRangeCheck> searcher(this, go, checker); VisitNearbyGridObject(range, searcher); @@ -2112,13 +2112,24 @@ GameObject* WorldObject::FindNearestGameObject(uint32 entry, float range) const GameObject* WorldObject::FindNearestGameObjectOfType(GameobjectTypes type, float range) const { - GameObject* go = NULL; + GameObject* go = nullptr; Trinity::NearestGameObjectTypeInObjectRangeCheck checker(*this, type, range); Trinity::GameObjectLastSearcher<Trinity::NearestGameObjectTypeInObjectRangeCheck> searcher(this, go, checker); VisitNearbyGridObject(range, searcher); return go; } +Player* WorldObject::SelectNearestPlayer(float distance) const +{ + Player* target = nullptr; + + Trinity::NearestPlayerInObjectRangeCheck checker(this, distance); + Trinity::PlayerLastSearcher<Trinity::NearestPlayerInObjectRangeCheck> searcher(this, target, checker); + VisitNearbyObject(distance, searcher); + + return target; +} + template <typename Container> void WorldObject::GetGameObjectListWithEntryInGrid(Container& gameObjectContainer, uint32 entry, float maxSearchRange /*= 250.0f*/) const { @@ -2203,7 +2214,7 @@ void WorldObject::GetNearPoint(WorldObject const* /*searcher*/, float &x, float void WorldObject::GetClosePoint(float &x, float &y, float &z, float size, float distance2d /*= 0*/, float angle /*= 0*/) const { // angle calculated from current orientation - GetNearPoint(NULL, x, y, z, size, distance2d, GetOrientation() + angle); + GetNearPoint(nullptr, x, y, z, size, distance2d, GetOrientation() + angle); } Position WorldObject::GetNearPosition(float dist, float angle) diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index 2dfb671353a..5726a755be3 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -558,6 +558,7 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation Creature* FindNearestCreature(uint32 entry, float range, bool alive = true) const; GameObject* FindNearestGameObject(uint32 entry, float range) const; GameObject* FindNearestGameObjectOfType(GameobjectTypes type, float range) const; + Player* SelectNearestPlayer(float distance) const; template <typename Container> void GetGameObjectListWithEntryInGrid(Container& gameObjectContainer, uint32 entry, float maxSearchRange = 250.0f) const; diff --git a/src/server/game/Handlers/SpellHandler.cpp b/src/server/game/Handlers/SpellHandler.cpp index 955bb114ec4..c42782141b2 100644 --- a/src/server/game/Handlers/SpellHandler.cpp +++ b/src/server/game/Handlers/SpellHandler.cpp @@ -313,7 +313,7 @@ void WorldSession::HandleGameobjectReportUse(WorldPacket& recvPacket) if (GameObject* go = GetPlayer()->GetGameObjectIfCanInteractWith(guid)) { - if (go->AI()->GossipHello(_player)) + if (go->AI()->GossipHello(_player, true)) return; _player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_USE_GAMEOBJECT, go->GetEntry()); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index ff418153773..90572f14a54 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -54,6 +54,7 @@ #include "Battlefield.h" #include "BattlefieldMgr.h" #include "TradeData.h" +#include "GameObjectAI.h" extern pEffect SpellEffects[TOTAL_SPELL_EFFECTS]; @@ -2738,6 +2739,9 @@ void Spell::DoAllEffectOnTarget(GOTargetInfo* target) if (effectMask & (1 << effectNumber)) HandleEffects(NULL, NULL, go, effectNumber, SPELL_EFFECT_HANDLE_HIT_TARGET); + if (go->AI()) + go->AI()->SpellHit(m_caster, m_spellInfo); + CallScriptOnHitHandlers(); CallScriptAfterHitHandlers(); } diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 0321d786312..debf220c133 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -1868,7 +1868,7 @@ void Spell::SendLoot(ObjectGuid guid, LootType loottype) if (sScriptMgr->OnGossipHello(player, gameObjTarget)) return; - if (gameObjTarget->AI()->GossipHello(player)) + if (gameObjTarget->AI()->GossipHello(player, false)) return; switch (gameObjTarget->GetGoType()) diff --git a/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_halion.cpp b/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_halion.cpp index 439a1b39251..c1045aaee74 100644 --- a/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_halion.cpp +++ b/src/server/scripts/Northrend/ChamberOfAspects/RubySanctum/boss_halion.cpp @@ -1364,7 +1364,7 @@ class go_twilight_portal : public GameObjectScript } } - bool GossipHello(Player* player) override + bool GossipHello(Player* player, bool /*reportUse*/) override { if (_spellId != 0) player->CastSpell(player, _spellId, true); diff --git a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_algalon_the_observer.cpp b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_algalon_the_observer.cpp index d12b1cd5cf2..e1cdb3477d1 100644 --- a/src/server/scripts/Northrend/Ulduar/Ulduar/boss_algalon_the_observer.cpp +++ b/src/server/scripts/Northrend/Ulduar/Ulduar/boss_algalon_the_observer.cpp @@ -980,7 +980,7 @@ class go_celestial_planetarium_access : public GameObjectScript { } - bool GossipHello(Player* player) override + bool GossipHello(Player* player, bool /*reportUse*/) override { if (go->HasFlag(GAMEOBJECT_FLAGS, GO_FLAG_IN_USE)) return true; diff --git a/src/server/scripts/World/go_scripts.cpp b/src/server/scripts/World/go_scripts.cpp index c584c32b524..810e0dd5500 100644 --- a/src/server/scripts/World/go_scripts.cpp +++ b/src/server/scripts/World/go_scripts.cpp @@ -889,7 +889,7 @@ class go_soulwell : public GameObjectScript /// _and_ CMSG_GAMEOBJECT_REPORT_USE, this GossipHello hook is called /// twice. The script's handling is fine as it won't remove two charges /// on the well. We have to find how to segregate REPORT_USE and USE. - bool GossipHello(Player* player) override + bool GossipHello(Player* player, bool /*reportUse*/) override { Unit* owner = go->GetOwner(); if (_stoneSpell == 0 || _stoneId == 0) |