/* * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "SmartAI.h" #include "AreaTrigger.h" #include "ConditionMgr.h" #include "Creature.h" #include "CreatureGroups.h" #include "DB2Structure.h" #include "GameObject.h" #include "Group.h" #include "Log.h" #include "MotionMaster.h" #include "ObjectAccessor.h" #include "PetDefines.h" #include "Player.h" #include "ScriptMgr.h" #include "Vehicle.h" #include "WaypointManager.h" SmartAI::SmartAI(Creature* creature, uint32 scriptId) : CreatureAI(creature, scriptId), _charmed(false), _followCreditType(0), _followArrivedTimer(0), _followCredit(0), _followArrivedEntry(0), _followDistance(0.f), _followAngle(0.f), _escortState(SMART_ESCORT_NONE), _escortNPCFlags(0), _escortInvokerCheckTimer(1000), _currentWaypointNodeId(0), _waypointReached(false), _waypointPauseTimer(0), _waypointPauseForced(false), _repeatWaypointPath(false), _OOCReached(false), _waypointPathEnded(false), _run(true), _evadeDisabled(false), _canCombatMove(true), _invincibilityHPLevel(0), _despawnTime(0), _despawnState(0), _vehicleConditionsTimer(0), _gossipReturn(false), _escortQuestId(0) { _vehicleConditions = sConditionMgr->HasConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, creature->GetEntry()); } bool SmartAI::IsAIControlled() const { return !_charmed; } void SmartAI::StartPath(uint32 pathId/* = 0*/, bool repeat/* = false*/, Unit* invoker/* = nullptr*/, uint32 nodeId/* = 0*/, Optional>&& scriptResult/* = {}*/) { if (HasEscortState(SMART_ESCORT_ESCORTING)) StopPath(); if (!pathId) return; WaypointPath const* path = LoadPath(pathId); if (!path) return; _currentWaypointNodeId = nodeId; _waypointPathEnded = false; _repeatWaypointPath = repeat; // Do not use AddEscortState, removing everything from previous _escortState = SMART_ESCORT_ESCORTING; if (invoker && invoker->GetTypeId() == TYPEID_PLAYER) { _escortNPCFlags = me->GetNpcFlags(); me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_NONE); } me->GetMotionMaster()->MovePath(pathId, _repeatWaypointPath, {}, {}, MovementWalkRunSpeedSelectionMode::Default, {}, {}, {}, {}, true, std::move(scriptResult)); } WaypointPath const* SmartAI::LoadPath(uint32 entry) { if (HasEscortState(SMART_ESCORT_ESCORTING)) return nullptr; WaypointPath const* path = sWaypointMgr->GetPath(entry); if (!path || path->Nodes.empty()) { GetScript()->SetPathId(0); return nullptr; } GetScript()->SetPathId(entry); return path; } void SmartAI::PausePath(uint32 delay, bool forced) { if (!HasEscortState(SMART_ESCORT_ESCORTING)) { me->PauseMovement(delay, MOTION_SLOT_DEFAULT, forced); if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) { std::pair waypointInfo = me->GetCurrentWaypointInfo(); GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_PAUSED, nullptr, waypointInfo.first, waypointInfo.second); } return; } if (HasEscortState(SMART_ESCORT_PAUSED)) { TC_LOG_ERROR("scripts.ai.sai", "SmartAI::PausePath: Creature wanted to pause waypoint (current waypoint: {}) movement while already paused, ignoring. ({})", _currentWaypointNodeId, me->GetGUID().ToString()); return; } _waypointPauseTimer = delay; if (forced) { _waypointPauseForced = forced; SetRun(_run); me->PauseMovement(); me->SetHomePosition(me->GetPosition()); } else _waypointReached = false; AddEscortState(SMART_ESCORT_PAUSED); GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_PAUSED, nullptr, _currentWaypointNodeId, GetScript()->GetPathId()); } bool SmartAI::CanResumePath() { if (!HasEscortState(SMART_ESCORT_ESCORTING)) { // The whole resume logic doesn't support this case return false; } return HasEscortState(SMART_ESCORT_PAUSED); } void SmartAI::StopPath(uint32 DespawnTime, uint32 quest, bool fail) { if (!HasEscortState(SMART_ESCORT_ESCORTING)) { std::pair waypointInfo = { 0, 0 }; if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) waypointInfo = me->GetCurrentWaypointInfo(); if (_despawnState != 2) SetDespawnTime(DespawnTime); me->GetMotionMaster()->MoveIdle(); if (waypointInfo.second) GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_STOPPED, nullptr, waypointInfo.first, waypointInfo.second); if (!fail) { if (waypointInfo.second) GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_ENDED, nullptr, waypointInfo.first, waypointInfo.second); if (_despawnState == 1) StartDespawn(); } return; } if (quest) _escortQuestId = quest; if (_despawnState != 2) SetDespawnTime(DespawnTime); me->GetMotionMaster()->MoveIdle(); GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_STOPPED, nullptr, _currentWaypointNodeId, GetScript()->GetPathId()); EndPath(fail); } void SmartAI::EndPath(bool fail) { RemoveEscortState(SMART_ESCORT_ESCORTING | SMART_ESCORT_PAUSED | SMART_ESCORT_RETURNING); _waypointPauseTimer = 0; if (_escortNPCFlags) { me->ReplaceAllNpcFlags((NPCFlags)_escortNPCFlags); _escortNPCFlags = 0; } ObjectVector const* targets = GetScript()->GetStoredTargetVector(SMART_ESCORT_TARGETS, *me); if (targets && _escortQuestId) { if (targets->size() == 1 && targets->front()->IsPlayer()) { Player* player = targets->front()->ToPlayer(); if (!fail && player->IsAtGroupRewardDistance(me) && !player->HasCorpse()) player->GroupEventHappens(_escortQuestId, me); if (fail) player->FailQuest(_escortQuestId); if (Group* group = player->GetGroup()) { for (GroupReference const& groupRef : group->GetMembers()) { Player* groupGuy = groupRef.GetSource(); if (!groupGuy->IsInMap(player)) continue; if (!fail && groupGuy->IsAtGroupRewardDistance(me) && !groupGuy->HasCorpse()) groupGuy->AreaExploredOrEventHappens(_escortQuestId); else if (fail) groupGuy->FailQuest(_escortQuestId); } } } else { for (WorldObject* target : *targets) { if (Player* player = target->ToPlayer()) { if (!fail && player->IsAtGroupRewardDistance(me) && !player->HasCorpse()) player->AreaExploredOrEventHappens(_escortQuestId); else if (fail) player->FailQuest(_escortQuestId); } } } } // End Path events should be only processed if it was SUCCESSFUL stop or stop called by SMART_ACTION_WAYPOINT_STOP if (fail) return; uint32 pathid = GetScript()->GetPathId(); GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_ENDED, nullptr, _currentWaypointNodeId, pathid); if (_repeatWaypointPath) { if (IsAIControlled()) StartPath(GetScript()->GetPathId(), _repeatWaypointPath); } else if (pathid == GetScript()->GetPathId()) // if it's not the same pathid, our script wants to start another path; don't override it GetScript()->SetPathId(0); if (_despawnState == 1) StartDespawn(); } void SmartAI::ResumePath() { GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_RESUMED, nullptr, _currentWaypointNodeId, GetScript()->GetPathId()); RemoveEscortState(SMART_ESCORT_PAUSED); _waypointPauseForced = false; _waypointReached = false; _waypointPauseTimer = 0; SetRun(_run); me->ResumeMovement(); } void SmartAI::ReturnToLastOOCPos() { if (!IsAIControlled()) return; me->SetWalk(false); me->GetMotionMaster()->MovePoint(SMART_ESCORT_LAST_OOC_POINT, me->GetHomePosition()); } void SmartAI::UpdateAI(uint32 diff) { if (!me->IsAlive()) { if (IsEngaged()) EngagementOver(); return; } CheckConditions(diff); UpdateVictim(); GetScript()->OnUpdate(diff); UpdatePath(diff); UpdateFollow(diff); UpdateDespawn(diff); } bool SmartAI::IsEscortInvokerInRange() { if (ObjectVector const* targets = GetScript()->GetStoredTargetVector(SMART_ESCORT_TARGETS, *me)) { float checkDist = me->GetInstanceScript() ? SMART_ESCORT_MAX_PLAYER_DIST * 2 : SMART_ESCORT_MAX_PLAYER_DIST; if (targets->size() == 1 && targets->front()->IsPlayer()) { Player* player = targets->front()->ToPlayer(); if (me->GetDistance(player) <= checkDist) return true; if (Group* group = player->GetGroup()) { for (GroupReference const& groupRef : group->GetMembers()) { Player* groupGuy = groupRef.GetSource(); if (groupGuy->IsInMap(player) && me->GetDistance(groupGuy) <= checkDist) return true; } } } else { for (WorldObject* target : *targets) if (target->IsPlayer() && me->GetDistance(target) <= checkDist) return true; } // no valid target found return false; } // no player invoker was stored, just ignore range check return true; } void SmartAI::WaypointReached(uint32 nodeId, uint32 pathId) { if (!HasEscortState(SMART_ESCORT_ESCORTING)) { GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_REACHED, nullptr, nodeId, pathId); return; } _currentWaypointNodeId = nodeId; GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_REACHED, nullptr, _currentWaypointNodeId, pathId); if (_waypointPauseTimer && !_waypointPauseForced) { _waypointReached = true; me->PauseMovement(); me->SetHomePosition(me->GetPosition()); } else if (HasEscortState(SMART_ESCORT_ESCORTING) && me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) { WaypointPath const* path = sWaypointMgr->GetPath(pathId); if (path && _currentWaypointNodeId == path->Nodes.back().Id) _waypointPathEnded = true; else SetRun(_run); } } ///@todo move escort related logic void SmartAI::WaypointPathEnded(uint32 nodeId, uint32 pathId) { if (!HasEscortState(SMART_ESCORT_ESCORTING)) { GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_ENDED, nullptr, nodeId, pathId); return; } } void SmartAI::MovementInform(uint32 type, uint32 id) { if (type == POINT_MOTION_TYPE && id == SMART_ESCORT_LAST_OOC_POINT) me->ClearUnitState(UNIT_STATE_EVADE); GetScript()->ProcessEventsFor(SMART_EVENT_MOVEMENTINFORM, nullptr, type, id); if (!HasEscortState(SMART_ESCORT_ESCORTING)) return; if (type == POINT_MOTION_TYPE && id == SMART_ESCORT_LAST_OOC_POINT) _OOCReached = true; } void SmartAI::EnterEvadeMode(EvadeReason /*why*/) { if (_evadeDisabled) { GetScript()->ProcessEventsFor(SMART_EVENT_EVADE); return; } if (!IsAIControlled()) { me->AttackStop(); return; } if (!_EnterEvadeMode()) return; me->AddUnitState(UNIT_STATE_EVADE); GetScript()->ProcessEventsFor(SMART_EVENT_EVADE); // must be after _EnterEvadeMode (spells, auras, ...) SetRun(_run); if (Unit* owner = me->GetCharmerOrOwner()) { me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); me->ClearUnitState(UNIT_STATE_EVADE); } else if (HasEscortState(SMART_ESCORT_ESCORTING)) { AddEscortState(SMART_ESCORT_RETURNING); ReturnToLastOOCPos(); } else if (Unit* target = !_followGUID.IsEmpty() ? ObjectAccessor::GetUnit(*me, _followGUID) : nullptr) { me->GetMotionMaster()->MoveFollow(target, _followDistance, _followAngle); // evade is not cleared in MoveFollow, so we can't keep it me->ClearUnitState(UNIT_STATE_EVADE); } else me->GetMotionMaster()->MoveTargetedHome(); if (!me->HasUnitState(UNIT_STATE_EVADE)) GetScript()->OnReset(); } void SmartAI::MoveInLineOfSight(Unit* who) { if (!who) return; GetScript()->OnMoveInLineOfSight(who); if (!IsAIControlled()) return; if (HasEscortState(SMART_ESCORT_ESCORTING) && AssistPlayerInCombatAgainst(who)) return; CreatureAI::MoveInLineOfSight(who); } bool SmartAI::AssistPlayerInCombatAgainst(Unit* who) { if (me->HasReactState(REACT_PASSIVE) || !IsAIControlled()) return false; if (!who || !who->GetVictim()) return false; // experimental (unknown) flag not present if (!(me->GetCreatureDifficulty()->TypeFlags & CREATURE_TYPE_FLAG_CAN_ASSIST)) return false; // not a player if (!who->EnsureVictim()->GetCharmerOrOwnerPlayerOrPlayerItself()) return false; if (!who->isInAccessiblePlaceFor(me)) return false; if (!CanAIAttack(who)) return false; // we cannot attack in evade mode if (me->IsInEvadeMode()) return false; // or if enemy is in evade mode if (who->GetTypeId() == TYPEID_UNIT && who->ToCreature()->IsInEvadeMode()) return false; if (!me->IsValidAssistTarget(who->GetVictim())) return false; // too far away and no free sight if (me->IsWithinDistInMap(who, SMART_MAX_AID_DIST) && me->IsWithinLOSInMap(who)) { me->EngageWithTarget(who); return true; } return false; } void SmartAI::InitializeAI() { GetScript()->OnInitialize(me); _despawnTime = 0; _despawnState = 0; _escortState = SMART_ESCORT_NONE; _followGUID.Clear(); // do not reset follower on Reset(), we need it after combat evade _followDistance = 0; _followAngle = 0; _followCredit = 0; _followArrivedTimer = 1000; _followArrivedEntry = 0; _followCreditType = 0; } void SmartAI::JustAppeared() { CreatureAI::JustAppeared(); if (me->isDead()) return; GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN); GetScript()->OnReset(); } void SmartAI::JustReachedHome() { GetScript()->OnReset(); GetScript()->ProcessEventsFor(SMART_EVENT_REACHED_HOME); CreatureGroup* formation = me->GetFormation(); if (!formation || formation->GetLeader() == me || !formation->IsFormed()) { if (me->GetMotionMaster()->GetCurrentMovementGeneratorType(MOTION_SLOT_DEFAULT) != WAYPOINT_MOTION_TYPE) { if (me->GetWaypointPathId()) me->GetMotionMaster()->MovePath(me->GetWaypointPathId(), true); } me->ResumeMovement(); } else if (formation->IsFormed()) me->GetMotionMaster()->MoveIdle(); // wait the order of leader } void SmartAI::JustEngagedWith(Unit* enemy) { if (IsAIControlled()) me->InterruptNonMeleeSpells(false); // must be before ProcessEvents GetScript()->ProcessEventsFor(SMART_EVENT_AGGRO, enemy); } void SmartAI::JustDied(Unit* killer) { if (HasEscortState(SMART_ESCORT_ESCORTING)) EndPath(true); GetScript()->ProcessEventsFor(SMART_EVENT_DEATH, killer); } void SmartAI::KilledUnit(Unit* victim) { GetScript()->ProcessEventsFor(SMART_EVENT_KILL, victim); } void SmartAI::JustSummoned(Creature* creature) { GetScript()->ProcessEventsFor(SMART_EVENT_SUMMONED_UNIT, creature); } void SmartAI::SummonedCreatureDies(Creature* summon, Unit* /*killer*/) { GetScript()->ProcessEventsFor(SMART_EVENT_SUMMONED_UNIT_DIES, summon); } void SmartAI::AttackStart(Unit* who) { // dont allow charmed npcs to act on their own if (!IsAIControlled()) { if (who) me->Attack(who, true); return; } if (who && me->Attack(who, true)) { me->GetMotionMaster()->Clear(MOTION_PRIORITY_NORMAL); me->PauseMovement(); if (_canCombatMove) { SetRun(_run); me->StartDefaultCombatMovement(who); } } } void SmartAI::SpellHit(WorldObject* caster, SpellInfo const* spellInfo) { GetScript()->ProcessEventsFor(SMART_EVENT_SPELLHIT, caster->ToUnit(), 0, 0, false, spellInfo, caster->ToGameObject()); } void SmartAI::SpellHitTarget(WorldObject* target, SpellInfo const* spellInfo) { GetScript()->ProcessEventsFor(SMART_EVENT_SPELLHIT_TARGET, target->ToUnit(), 0, 0, false, spellInfo, target->ToGameObject()); } void SmartAI::OnSpellCast(SpellInfo const* spellInfo) { GetScript()->ProcessEventsFor(SMART_EVENT_ON_SPELL_CAST, nullptr, 0, 0, false, spellInfo); } void SmartAI::OnSpellFailed(SpellInfo const* spellInfo) { GetScript()->ProcessEventsFor(SMART_EVENT_ON_SPELL_FAILED, nullptr, 0, 0, false, spellInfo); } void SmartAI::OnSpellStart(SpellInfo const* spellInfo) { GetScript()->ProcessEventsFor(SMART_EVENT_ON_SPELL_START, nullptr, 0, 0, false, spellInfo); } void SmartAI::DamageTaken(Unit* doneBy, uint32& damage, DamageEffectType /*damageType*/, SpellInfo const* /*spellInfo = nullptr*/) { GetScript()->ProcessEventsFor(SMART_EVENT_DAMAGED, doneBy, damage); if (!IsAIControlled()) // don't allow players to use unkillable units return; if (_invincibilityHPLevel && (damage >= me->GetHealth() - _invincibilityHPLevel)) damage = me->GetHealth() - _invincibilityHPLevel; // damage should not be nullified, because of player damage req. } void SmartAI::HealReceived(Unit* doneBy, uint32& addhealth) { GetScript()->ProcessEventsFor(SMART_EVENT_RECEIVE_HEAL, doneBy, addhealth); } void SmartAI::ReceiveEmote(Player* player, uint32 textEmote) { GetScript()->ProcessEventsFor(SMART_EVENT_RECEIVE_EMOTE, player, textEmote); } void SmartAI::IsSummonedBy(WorldObject* summoner) { GetScript()->ProcessEventsFor(SMART_EVENT_JUST_SUMMONED, summoner->ToUnit(), 0, 0, false, nullptr, summoner->ToGameObject()); } void SmartAI::DamageDealt(Unit* doneTo, uint32& damage, DamageEffectType /*damagetype*/) { GetScript()->ProcessEventsFor(SMART_EVENT_DAMAGED_TARGET, doneTo, damage); } void SmartAI::SummonedCreatureDespawn(Creature* unit) { GetScript()->ProcessEventsFor(SMART_EVENT_SUMMON_DESPAWNED, unit, unit->GetEntry()); } void SmartAI::CorpseRemoved(uint32& respawnDelay) { GetScript()->ProcessEventsFor(SMART_EVENT_CORPSE_REMOVED, nullptr, respawnDelay); } void SmartAI::OnDespawn() { GetScript()->ProcessEventsFor(SMART_EVENT_ON_DESPAWN); } void SmartAI::PassengerBoarded(Unit* who, int8 seatId, bool apply) { GetScript()->ProcessEventsFor(apply ? SMART_EVENT_PASSENGER_BOARDED : SMART_EVENT_PASSENGER_REMOVED, who, uint32(seatId), 0, apply); } void SmartAI::OnCharmed(bool isNew) { bool const charmed = me->IsCharmed(); if (charmed) // do this before we change charmed state, as charmed state might prevent these things from processing { if (HasEscortState(SMART_ESCORT_ESCORTING | SMART_ESCORT_PAUSED | SMART_ESCORT_RETURNING)) EndPath(true); } _charmed = charmed; if (charmed && !me->isPossessed() && !me->IsVehicle()) me->GetMotionMaster()->MoveFollow(me->GetCharmer(), PET_FOLLOW_DIST, me->GetFollowAngle()); if (!charmed && !me->IsInEvadeMode()) { if (_repeatWaypointPath) StartPath(GetScript()->GetPathId(), true); else me->SetWalk(!_run); if (!me->LastCharmerGUID.IsEmpty()) { if (!me->HasReactState(REACT_PASSIVE)) if (Unit* lastCharmer = ObjectAccessor::GetUnit(*me, me->LastCharmerGUID)) me->EngageWithTarget(lastCharmer); me->LastCharmerGUID.Clear(); if (!me->IsInCombat()) EnterEvadeMode(EvadeReason::NoHostiles); } } GetScript()->ProcessEventsFor(SMART_EVENT_CHARMED, nullptr, 0, 0, charmed); if (!GetScript()->HasAnyEventWithFlag(SMART_EVENT_FLAG_WHILE_CHARMED)) // we can change AI if there are no events with this flag UnitAI::OnCharmed(isNew); } void SmartAI::DoAction(int32 param) { GetScript()->ProcessEventsFor(SMART_EVENT_ACTION_DONE, nullptr, param); } uint32 SmartAI::GetData(uint32 /*id*/) const { return 0; } void SmartAI::SetData(uint32 id, uint32 value, Unit* invoker) { GetScript()->ProcessEventsFor(SMART_EVENT_DATA_SET, invoker, id, value); } void SmartAI::SetGUID(ObjectGuid const& /*guid*/, int32 /*id*/) { } ObjectGuid SmartAI::GetGUID(int32 /*id*/) const { return ObjectGuid::Empty; } void SmartAI::SetRun(bool run) { me->SetWalk(!run); _run = run; } void SmartAI::SetEvadeDisabled(bool disable) { _evadeDisabled = disable; } bool SmartAI::OnGossipHello(Player* player) { _gossipReturn = false; GetScript()->ProcessEventsFor(SMART_EVENT_GOSSIP_HELLO, player); return _gossipReturn; } bool SmartAI::OnGossipSelect(Player* player, uint32 menuId, uint32 gossipListId) { _gossipReturn = false; GetScript()->ProcessEventsFor(SMART_EVENT_GOSSIP_SELECT, player, menuId, gossipListId); return _gossipReturn; } bool SmartAI::OnGossipSelectCode(Player* /*player*/, uint32 /*menuId*/, uint32 /*gossipListId*/, char const* /*code*/) { return false; } void SmartAI::OnQuestAccept(Player* player, Quest const* quest) { GetScript()->ProcessEventsFor(SMART_EVENT_ACCEPTED_QUEST, player, quest->GetQuestId()); } void SmartAI::OnQuestReward(Player* player, Quest const* quest, LootItemType /*type*/, uint32 opt) { GetScript()->ProcessEventsFor(SMART_EVENT_REWARD_QUEST, player, quest->GetQuestId(), opt); } void SmartAI::SetCombatMove(bool on, bool stopMoving) { if (_canCombatMove == on) return; _canCombatMove = on; if (!IsAIControlled()) return; if (me->IsEngaged()) { if (on) { if (!me->HasReactState(REACT_PASSIVE) && me->GetVictim() && !me->GetMotionMaster()->HasMovementGenerator([](MovementGenerator const* movement) -> bool { return movement->GetMovementGeneratorType() == CHASE_MOTION_TYPE && movement->Mode == MOTION_MODE_DEFAULT && movement->Priority == MOTION_PRIORITY_NORMAL; })) { SetRun(_run); me->StartDefaultCombatMovement(me->GetVictim()); } } else if (MovementGenerator* movement = me->GetMotionMaster()->GetMovementGenerator([](MovementGenerator const* a) -> bool { return a->GetMovementGeneratorType() == CHASE_MOTION_TYPE && a->Mode == MOTION_MODE_DEFAULT && a->Priority == MOTION_PRIORITY_NORMAL; })) { me->GetMotionMaster()->Remove(movement); if (stopMoving) me->StopMoving(); } } } void SmartAI::SetFollow(Unit* target, float dist, float angle, uint32 credit, uint32 end, uint32 creditType) { if (!target) { StopFollow(false); return; } _followGUID = target->GetGUID(); _followDistance = dist; _followAngle = angle; _followArrivedTimer = 1000; _followCredit = credit; _followArrivedEntry = end; _followCreditType = creditType; SetRun(_run); me->GetMotionMaster()->MoveFollow(target, _followDistance, _followAngle); } void SmartAI::StopFollow(bool complete) { _followGUID.Clear(); _followDistance = 0; _followAngle = 0; uint32 followCredit = std::exchange(_followCredit, 0); _followArrivedTimer = 1000; _followArrivedEntry = 0; _followCreditType = 0; me->GetMotionMaster()->Clear(); me->GetMotionMaster()->MoveIdle(); if (!complete) return; Player* player = ObjectAccessor::GetPlayer(*me, _followGUID); if (player) { if (!_followCreditType) player->RewardPlayerAndGroupAtEvent(followCredit, me); else player->GroupEventHappens(followCredit, me); } SetDespawnTime(5000); StartDespawn(); GetScript()->ProcessEventsFor(SMART_EVENT_FOLLOW_COMPLETED, player); } void SmartAI::SetTimedActionList(SmartScriptHolder& e, uint32 entry, Unit* invoker, uint32 startFromEventId) { GetScript()->SetTimedActionList(e, entry, invoker, startFromEventId); } void SmartAI::OnGameEvent(bool start, uint16 eventId) { GetScript()->ProcessEventsFor(start ? SMART_EVENT_GAME_EVENT_START : SMART_EVENT_GAME_EVENT_END, nullptr, eventId); } void SmartAI::OnSpellClick(Unit* clicker, bool spellClickHandled) { if (!spellClickHandled) return; GetScript()->ProcessEventsFor(SMART_EVENT_ON_SPELLCLICK, clicker); } void SmartAI::CheckConditions(uint32 diff) { if (!_vehicleConditions) return; if (_vehicleConditionsTimer <= diff) { if (Vehicle* vehicleKit = me->GetVehicleKit()) { for (std::pair& seat : vehicleKit->Seats) if (Unit* passenger = ObjectAccessor::GetUnit(*me, seat.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 } } } } _vehicleConditionsTimer = 1000; } else _vehicleConditionsTimer -= diff; } void SmartAI::UpdatePath(uint32 diff) { if (!HasEscortState(SMART_ESCORT_ESCORTING)) return; if (_escortInvokerCheckTimer < diff) { if (!IsEscortInvokerInRange()) { StopPath(0, _escortQuestId, 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(); return; } _escortInvokerCheckTimer = 1000; } else _escortInvokerCheckTimer -= diff; // handle pause if (HasEscortState(SMART_ESCORT_PAUSED) && (_waypointReached || _waypointPauseForced)) { // Resume only if there was a pause timer set if (_waypointPauseTimer && !me->IsInCombat() && !HasEscortState(SMART_ESCORT_RETURNING)) { if (_waypointPauseTimer <= diff) ResumePath(); else _waypointPauseTimer -= diff; } } else if (_waypointPathEnded) // end path { _waypointPathEnded = false; StopPath(); return; } if (HasEscortState(SMART_ESCORT_RETURNING)) { if (_OOCReached) // reached OOC WP { _OOCReached = false; RemoveEscortState(SMART_ESCORT_RETURNING); if (!HasEscortState(SMART_ESCORT_PAUSED)) ResumePath(); } } } void SmartAI::UpdateFollow(uint32 diff) { if (!_followGUID.IsEmpty()) { if (_followArrivedTimer < diff) { if (me->FindNearestCreature(_followArrivedEntry, INTERACTION_DISTANCE, true)) { StopFollow(true); return; } _followArrivedTimer = 1000; } else _followArrivedTimer -= diff; } } void SmartAI::UpdateDespawn(uint32 diff) { if (_despawnState <= 1 || _despawnState > 3) return; if (_despawnTime < diff) { if (_despawnState == 2) { me->SetVisible(false); _despawnTime = 5000; _despawnState++; } else me->DespawnOrUnsummon(); } else _despawnTime -= diff; } void SmartGameObjectAI::UpdateAI(uint32 diff) { GetScript()->OnUpdate(diff); } void SmartGameObjectAI::InitializeAI() { GetScript()->OnInitialize(me); // do not call respawn event if go is not spawned if (me->isSpawned()) GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN); //Reset(); } void SmartGameObjectAI::Reset() { GetScript()->OnReset(); } // Called when a player opens a gossip dialog with the gameobject. bool SmartGameObjectAI::OnGossipHello(Player* player) { _gossipReturn = false; GetScript()->ProcessEventsFor(SMART_EVENT_GOSSIP_HELLO, player, 0, 0, false, nullptr, me); return _gossipReturn; } // Called when a player selects a gossip item in the gameobject's gossip menu. bool SmartGameObjectAI::OnGossipSelect(Player* player, uint32 sender, uint32 action) { _gossipReturn = false; GetScript()->ProcessEventsFor(SMART_EVENT_GOSSIP_SELECT, player, sender, action, false, nullptr, me); return _gossipReturn; } // Called when a player selects a gossip with a code in the gameobject's gossip menu. bool SmartGameObjectAI::OnGossipSelectCode(Player* /*player*/, uint32 /*menuId*/, uint32 /*gossipListId*/, char const* /*code*/) { return false; } // Called when a player accepts a quest from the gameobject. void SmartGameObjectAI::OnQuestAccept(Player* player, Quest const* quest) { GetScript()->ProcessEventsFor(SMART_EVENT_ACCEPTED_QUEST, player, quest->GetQuestId(), 0, false, nullptr, me); } // Called when a player selects a quest reward. void SmartGameObjectAI::OnQuestReward(Player* player, Quest const* quest, LootItemType /*type*/, uint32 opt) { GetScript()->ProcessEventsFor(SMART_EVENT_REWARD_QUEST, player, quest->GetQuestId(), opt, false, nullptr, me); } bool SmartGameObjectAI::OnReportUse(Player* player) { _gossipReturn = false; GetScript()->ProcessEventsFor(SMART_EVENT_GOSSIP_HELLO, player, 1, 0, false, nullptr, me); return _gossipReturn; } // Called when the gameobject is destroyed (destructible buildings only). void SmartGameObjectAI::Destroyed(WorldObject* attacker, uint32 eventId) { GetScript()->ProcessEventsFor(SMART_EVENT_DEATH, attacker ? attacker->ToUnit() : nullptr, eventId, 0, false, nullptr, me); } void SmartGameObjectAI::SetData(uint32 id, uint32 value, Unit* invoker) { GetScript()->ProcessEventsFor(SMART_EVENT_DATA_SET, invoker, id, value); } void SmartGameObjectAI::SetTimedActionList(SmartScriptHolder& e, uint32 entry, Unit* invoker) { GetScript()->SetTimedActionList(e, entry, invoker); } void SmartGameObjectAI::OnGameEvent(bool start, uint16 eventId) { GetScript()->ProcessEventsFor(start ? SMART_EVENT_GAME_EVENT_START : SMART_EVENT_GAME_EVENT_END, nullptr, eventId); } void SmartGameObjectAI::OnLootStateChanged(uint32 state, Unit* unit) { GetScript()->ProcessEventsFor(SMART_EVENT_GO_LOOT_STATE_CHANGED, unit, state); } void SmartGameObjectAI::EventInform(uint32 eventId) { GetScript()->ProcessEventsFor(SMART_EVENT_GO_EVENT_INFORM, nullptr, eventId); } void SmartGameObjectAI::SpellHit(WorldObject* caster, SpellInfo const* spellInfo) { GetScript()->ProcessEventsFor(SMART_EVENT_SPELLHIT, caster->ToUnit(), 0, 0, false, spellInfo); } void SmartGameObjectAI::JustSummoned(Creature* creature) { GetScript()->ProcessEventsFor(SMART_EVENT_SUMMONED_UNIT, creature); } void SmartGameObjectAI::SummonedCreatureDies(Creature* summon, Unit* /*killer*/) { GetScript()->ProcessEventsFor(SMART_EVENT_SUMMONED_UNIT_DIES, summon); } void SmartGameObjectAI::SummonedCreatureDespawn(Creature* unit) { GetScript()->ProcessEventsFor(SMART_EVENT_SUMMON_DESPAWNED, unit, unit->GetEntry()); } class SmartTrigger : public AreaTriggerScript { public: SmartTrigger() : AreaTriggerScript("SmartTrigger") { } bool OnTrigger(Player* player, AreaTriggerEntry const* trigger) override { TC_LOG_DEBUG("scripts.ai", "AreaTrigger {} enter is using SmartTrigger script", trigger->ID); SmartScript script; script.OnInitialize(player, trigger); script.ProcessEventsFor(SMART_EVENT_AREATRIGGER_ENTER, player); return true; } bool OnExit(Player* player, AreaTriggerEntry const* trigger) override { TC_LOG_DEBUG("scripts.ai", "AreaTrigger {} exit is using SmartTrigger script", trigger->ID); SmartScript script; script.OnInitialize(player, trigger); script.ProcessEventsFor(SMART_EVENT_AREATRIGGER_EXIT, player); return true; } }; void SmartAreaTriggerAI::OnInitialize() { GetScript()->OnInitialize(at); } void SmartAreaTriggerAI::OnUpdate(uint32 diff) { GetScript()->OnUpdate(diff); } void SmartAreaTriggerAI::OnUnitEnter(Unit* unit) { GetScript()->ProcessEventsFor(SMART_EVENT_AREATRIGGER_ENTER, unit); } void SmartAreaTriggerAI::OnUnitExit(Unit* unit, AreaTriggerExitReason /*reason*/) { GetScript()->ProcessEventsFor(SMART_EVENT_AREATRIGGER_EXIT, unit); } void SmartAreaTriggerAI::SetTimedActionList(SmartScriptHolder& e, uint32 entry, Unit* invoker) { GetScript()->SetTimedActionList(e, entry, invoker); } class SmartAreaTriggerEntityScript : public AreaTriggerEntityScript { public: SmartAreaTriggerEntityScript() : AreaTriggerEntityScript("SmartAreaTriggerAI") { } AreaTriggerAI* GetAI(AreaTrigger* areaTrigger) const override { return new SmartAreaTriggerAI(areaTrigger); } }; class SmartScene : public SceneScript { public: SmartScene() : SceneScript("SmartScene") { } void OnSceneStart(Player* player, uint32 /*sceneInstanceID*/, SceneTemplate const* sceneTemplate) override { SmartScript smartScript; smartScript.OnInitialize(player, nullptr, sceneTemplate); smartScript.ProcessEventsFor(SMART_EVENT_SCENE_START, player); } void OnSceneTriggerEvent(Player* player, uint32 /*sceneInstanceID*/, SceneTemplate const* sceneTemplate, std::string const& triggerName) override { SmartScript smartScript; smartScript.OnInitialize(player, nullptr, sceneTemplate); smartScript.ProcessEventsFor(SMART_EVENT_SCENE_TRIGGER, player, 0, 0, false, nullptr, nullptr, triggerName); } void OnSceneCancel(Player* player, uint32 /*sceneInstanceID*/, SceneTemplate const* sceneTemplate) override { SmartScript smartScript; smartScript.OnInitialize(player, nullptr, sceneTemplate); smartScript.ProcessEventsFor(SMART_EVENT_SCENE_CANCEL, player); } void OnSceneComplete(Player* player, uint32 /*sceneInstanceID*/, SceneTemplate const* sceneTemplate) override { SmartScript smartScript; smartScript.OnInitialize(player, nullptr, sceneTemplate); smartScript.ProcessEventsFor(SMART_EVENT_SCENE_COMPLETE, player); } }; class SmartQuest : public QuestScript { public: SmartQuest() : QuestScript("SmartQuest") { } // Called when a quest status change void OnQuestStatusChange(Player* player, Quest const* quest, QuestStatus /*oldStatus*/, QuestStatus newStatus) override { SmartScript smartScript; smartScript.OnInitialize(player, nullptr, nullptr, quest); switch (newStatus) { case QUEST_STATUS_INCOMPLETE: smartScript.ProcessEventsFor(SMART_EVENT_QUEST_ACCEPTED, player); break; case QUEST_STATUS_COMPLETE: smartScript.ProcessEventsFor(SMART_EVENT_QUEST_COMPLETION, player); break; case QUEST_STATUS_FAILED: smartScript.ProcessEventsFor(SMART_EVENT_QUEST_FAIL, player); break; case QUEST_STATUS_REWARDED: smartScript.ProcessEventsFor(SMART_EVENT_QUEST_REWARDED, player); break; case QUEST_STATUS_NONE: default: break; } } // Called when a quest objective data change void OnQuestObjectiveChange(Player* player, Quest const* quest, QuestObjective const& objective, int32 /*oldAmount*/, int32 /*newAmount*/) override { uint16 slot = player->FindQuestSlot(quest->GetQuestId()); if (slot < MAX_QUEST_LOG_SIZE && player->IsQuestObjectiveComplete(slot, quest, objective)) { SmartScript smartScript; smartScript.OnInitialize(player, nullptr, nullptr, quest); smartScript.ProcessEventsFor(SMART_EVENT_QUEST_OBJ_COMPLETION, player, objective.ID); } } }; class SmartEventTrigger : public EventScript { public: SmartEventTrigger() : EventScript("SmartEventTrigger") { } void OnTrigger(WorldObject* object, WorldObject* invoker, uint32 eventId) override { TC_LOG_DEBUG("scripts.ai", "Event {} is using SmartEventTrigger script", eventId); SmartScript script; // Set invoker as BaseObject if there isn't target for GameEvents::Trigger script.OnInitialize(Coalesce(object, invoker), nullptr, nullptr, nullptr, eventId); script.ProcessEventsFor(SMART_EVENT_SEND_EVENT_TRIGGER, invoker->ToUnit(), 0, 0, false, nullptr, invoker->ToGameObject()); } }; void AddSC_SmartScripts() { new SmartTrigger(); new SmartAreaTriggerEntityScript(); new SmartScene(); new SmartQuest(); new SmartEventTrigger(); }