/* * This file is part of the AzerothCore 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 "AreaTriggerScript.h" #include "CellImpl.h" #include "GridDefines.h" #include "GridNotifiers.h" #include "Group.h" #include "ObjectDefines.h" #include "ObjectMgr.h" #include "ScriptedCreature.h" #include "SpellMgr.h" #include "Vehicle.h" SmartAI::SmartAI(Creature* c) : CreatureAI(c) { mIsCharmed = false; // copy script to local (protection for table reload) mWayPoints = nullptr; mEscortState = SMART_ESCORT_NONE; mCurrentWPID = 0;//first wp id is 1 !! mWPReached = false; mOOCReached = false; mWPPauseTimer = 0; mLastWP = nullptr; mEscortNPCFlags = 0; mCanRepeatPath = false; mEvadeDisabled = false; mCanAutoAttack = true; mForcedPaused = false; mForcedMovement = FORCED_MOVEMENT_NONE; mEscortQuestID = 0; mDespawnTime = 0; mDespawnState = 0; mEscortInvokerCheckTimer = 1000; mFollowDist = 0; mFollowAngle = 0; mFollowCredit = 0; mFollowArrivedEntry = 0; mFollowCreditType = 0; mFollowArrivedAlive = 0; mFollowArrivedTimer = 0; mInvincibilityHpLevel = 0; mJustReset = false; mcanSpawn = true; _chaseOnInterrupt = false; aiDataSet.clear(); // Xinef: Vehicle conditions m_ConditionsTimer = 0; if (me->GetVehicleKit()) conditions = sConditionMgr->GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, me->GetEntry()); _currentRangeMode = false; _attackDistance = 0.f; } bool SmartAI::IsAIControlled() const { return !mIsCharmed; } void SmartAI::UpdateDespawn(const uint32 diff) { if (mDespawnState <= 1 || mDespawnState > 3) return; if (mDespawnTime < diff) { if (mDespawnState == 2) { me->SetVisible(false); mDespawnTime = 5000; mDespawnState++; } else me->DespawnOrUnsummon(); } else mDespawnTime -= diff; } WaypointData const* SmartAI::GetNextWayPoint() { if (!mWayPoints || mWayPoints->empty()) return nullptr; mCurrentWPID++; auto itr = mWayPoints->find(mCurrentWPID); if (itr != mWayPoints->end()) { mLastWP = &(*itr).second; if (mLastWP->id != mCurrentWPID) LOG_ERROR("scripts.ai.sai", "SmartAI::GetNextWayPoint: Got not expected waypoint id {}, expected {}", mLastWP->id, mCurrentWPID); return &(*itr).second; } return nullptr; } void SmartAI::GenerateWayPointArray(Movement::PointsArray* points) { if (!mWayPoints || mWayPoints->empty()) return; // Flying unit, just fill array if (me->m_movementInfo.HasMovementFlag((MovementFlags)(MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_DISABLE_GRAVITY))) { // xinef: first point in vector is unit real position points->clear(); points->push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ())); uint32 wpCounter = mCurrentWPID; auto itr = mWayPoints->find(wpCounter++); do { WaypointData const& wp = (*itr).second; points->push_back(G3D::Vector3(wp.x, wp.y, wp.z)); itr = mWayPoints->find(wpCounter++); } while (itr != mWayPoints->end()); } else { for (float size = 1.0f; size; size *= 0.5f) { std::vector pVector; // xinef: first point in vector is unit real position pVector.push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ())); uint32 wpCounter = mCurrentWPID; uint32 length = (mWayPoints->size() - mCurrentWPID) * size; uint32 cnt = 0; for (auto itr = mWayPoints->find(wpCounter); itr != mWayPoints->end() && cnt++ <= length; ++itr) { WaypointData const& wp = (*itr).second; pVector.push_back(G3D::Vector3(wp.x, wp.y, wp.z)); } if (pVector.size() > 2) // more than source + dest { G3D::Vector3 middle = (pVector[0] + pVector[pVector.size() - 1]) / 2.f; G3D::Vector3 offset; bool continueLoop = false; for (uint32 i = 1; i < pVector.size() - 1; ++i) { offset = middle - pVector[i]; if (std::fabs(offset.x) >= 0xFF || std::fabs(offset.y) >= 0xFF || std::fabs(offset.z) >= 0x7F) { // offset is too big, split points continueLoop = true; break; } } if (continueLoop) continue; } // everything ok *points = pVector; break; } } } void SmartAI::StartPath(ForcedMovement forcedMovement, uint32 path, bool repeat, Unit* invoker, PathSource pathSource) { if (HasEscortState(SMART_ESCORT_ESCORTING)) StopPath(); if (path) { if (!LoadPath(path, pathSource)) return; } if (!mWayPoints || mWayPoints->empty()) return; if (WaypointData const* wp = GetNextWayPoint()) { AddEscortState(SMART_ESCORT_ESCORTING); mCanRepeatPath = repeat; mForcedMovement = forcedMovement; if (invoker && invoker->IsPlayer()) { mEscortNPCFlags = me->GetNpcFlags(); me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_NONE); } Movement::PointsArray pathPoints; GenerateWayPointArray(&pathPoints); me->GetMotionMaster()->MoveSplinePath(&pathPoints, mForcedMovement); GetScript()->ProcessEventsFor(SMART_EVENT_ESCORT_START, nullptr, wp->id, GetScript()->GetPathId()); } } bool SmartAI::LoadPath(uint32 entry, PathSource pathSource) { if (HasEscortState(SMART_ESCORT_ESCORTING)) return false; switch (pathSource) { case PathSource::SMART_WAYPOINT_MGR: { mWayPoints = sSmartWaypointMgr->GetPath(entry); if (!mWayPoints) { GetScript()->SetPathId(0); return false; } break; } case PathSource::WAYPOINT_MGR: { mWayPoints = sWaypointMgr->GetPath(entry); if (!mWayPoints) { GetScript()->SetPathId(0); return false; } break; } } GetScript()->SetPathId(entry); return true; } void SmartAI::PausePath(uint32 delay, bool forced) { if (!HasEscortState(SMART_ESCORT_ESCORTING)) return; if (HasEscortState(SMART_ESCORT_PAUSED)) { LOG_ERROR("scripts.ai.sai", "SmartAI::StartPath: Creature entry {} wanted to pause waypoint movement while already paused, ignoring.", me->GetEntry()); return; } AddEscortState(SMART_ESCORT_PAUSED); mWPPauseTimer = delay; if (forced && !mWPReached) { mForcedPaused = forced; if (me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE) == ESCORT_MOTION_TYPE) me->GetMotionMaster()->MovementExpired(); me->StopMoving(); me->GetMotionMaster()->MoveIdle();//force stop auto waypoint = mWayPoints->find(mCurrentWPID); if (waypoint->second.orientation.has_value()) { me->SetFacingTo(*waypoint->second.orientation); } } GetScript()->ProcessEventsFor(SMART_EVENT_ESCORT_PAUSED, nullptr, mCurrentWPID, GetScript()->GetPathId()); } void SmartAI::StopPath(uint32 DespawnTime, uint32 quest, bool fail) { if (!HasEscortState(SMART_ESCORT_ESCORTING)) return; if (quest) mEscortQuestID = quest; SetDespawnTime(DespawnTime); if (me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE) == ESCORT_MOTION_TYPE) me->GetMotionMaster()->MovementExpired(); me->StopMoving(); me->GetMotionMaster()->MoveIdle(); GetScript()->ProcessEventsFor(SMART_EVENT_ESCORT_STOPPED, nullptr, mCurrentWPID, GetScript()->GetPathId()); EndPath(fail); } void SmartAI::EndPath(bool fail) { RemoveEscortState(SMART_ESCORT_ESCORTING | SMART_ESCORT_PAUSED | SMART_ESCORT_RETURNING); mWayPoints = nullptr; mLastWP = nullptr; mWPPauseTimer = 0; if (mEscortNPCFlags) { me->ReplaceAllNpcFlags(NPCFlags(mEscortNPCFlags)); mEscortNPCFlags = 0; } ObjectVector const* targets = GetScript()->GetStoredTargetVector(SMART_ESCORT_TARGETS, *me); if (targets && mEscortQuestID) { if (targets->size() == 1 && GetScript()->IsPlayer((*targets->begin()))) { Player* player = targets->front()->ToPlayer(); if (Group* group = player->GetGroup()) { for (GroupReference* groupRef = group->GetFirstMember(); groupRef != nullptr; groupRef = groupRef->next()) { Player* groupGuy = groupRef->GetSource(); if (!groupGuy || !player->IsInMap(groupGuy)) continue; if (!fail && groupGuy->IsAtGroupRewardDistance(me) && !groupGuy->HasCorpse()) groupGuy->AreaExploredOrEventHappens(mEscortQuestID); else if (fail && groupGuy->GetQuestStatus(mEscortQuestID) == QUEST_STATUS_INCOMPLETE) groupGuy->FailQuest(mEscortQuestID); } } else { if (!fail && player->IsAtGroupRewardDistance(me) && !player->HasCorpse()) player->GroupEventHappens(mEscortQuestID, me); else if (fail && player->GetQuestStatus(mEscortQuestID) == QUEST_STATUS_INCOMPLETE) player->FailQuest(mEscortQuestID); } } else { for (WorldObject* target : *targets) { if (GetScript()->IsPlayer(target)) { Player* player = target->ToPlayer(); if (!fail && player->IsAtGroupRewardDistance(me) && !player->HasCorpse()) player->AreaExploredOrEventHappens(mEscortQuestID); else if (fail && player->GetQuestStatus(mEscortQuestID) == QUEST_STATUS_INCOMPLETE) player->FailQuest(mEscortQuestID); } } } } // Xinef: if the escort failed - DO NOT PROCESS ANYTHING // Xinef: End Path events should be only processed if it was SUCCESSFUL stop or stop called by SMART_ACTION_WAYPOINT_STOP if (fail) { mCurrentWPID = 0; return; } GetScript()->ProcessEventsFor(SMART_EVENT_ESCORT_ENDED, nullptr, mCurrentWPID, GetScript()->GetPathId()); mCurrentWPID = 0; if (mCanRepeatPath) { if (IsAIControlled()) StartPath(mForcedMovement, GetScript()->GetPathId(), true); } else GetScript()->SetPathId(0); if (mDespawnState == 1) StartDespawn(); } void SmartAI::ResumePath() { if (mLastWP) { Movement::PointsArray pathPoints; GenerateWayPointArray(&pathPoints); me->GetMotionMaster()->MoveSplinePath(&pathPoints, mForcedMovement); } } void SmartAI::ReturnToLastOOCPos() { if (!IsAIControlled()) return; float x, y, z, o; me->GetHomePosition(x, y, z, o); me->GetMotionMaster()->MovePoint(SMART_ESCORT_LAST_OOC_POINT, x, y, z, FORCED_MOVEMENT_RUN); } void SmartAI::UpdatePath(const uint32 diff) { if (!HasEscortState(SMART_ESCORT_ESCORTING)) return; if (mEscortInvokerCheckTimer < diff) { // Xinef: Escort failed - no players in range // Xinef: Despawn immediately if (!IsEscortInvokerInRange()) { StopPath(0, mEscortQuestID, true); // Xinef: 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(1ms); return; } mEscortInvokerCheckTimer = 1000; } else mEscortInvokerCheckTimer -= diff; // handle pause if (HasEscortState(SMART_ESCORT_PAUSED)) { if (mWPPauseTimer < diff) { if (!me->IsInCombat() && !HasEscortState(SMART_ESCORT_RETURNING) && (mWPReached || mForcedPaused)) { GetScript()->ProcessEventsFor(SMART_EVENT_ESCORT_RESUMED, nullptr, mCurrentWPID, GetScript()->GetPathId()); RemoveEscortState(SMART_ESCORT_PAUSED); if (mForcedPaused)// if paused between 2 wps resend movement { mWPReached = false; mForcedPaused = false; ResumePath(); } mWPPauseTimer = 0; } } else mWPPauseTimer -= diff; } if (HasEscortState(SMART_ESCORT_RETURNING)) { if (mOOCReached)//reached OOC WP { mOOCReached = false; RemoveEscortState(SMART_ESCORT_RETURNING); if (!HasEscortState(SMART_ESCORT_PAUSED)) ResumePath(); } } if ((me->GetVictim() && me->IsInCombat()) || HasEscortState(SMART_ESCORT_PAUSED | SMART_ESCORT_RETURNING)) return; // handle next wp if (!me->HasUnitState(UNIT_STATE_NOT_MOVE) && me->movespline->Finalized())//reached WP { if (!mWPReached) { ResumePath(); return; } mWPReached = false; if (mCurrentWPID == GetWPCount()) EndPath(); else if (GetNextWayPoint()) { // xinef: if we have reached waypoint, and there is no working spline movement it means our splitted array has ended, make new one if (me->movespline->Finalized()) ResumePath(); } } } void SmartAI::CheckConditions(const uint32 diff) { Vehicle* vehicle = me->GetVehicleKit(); if (!vehicle) return; if (m_ConditionsTimer < diff) { if (!conditions.empty()) { for (SeatMap::iterator itr = vehicle->Seats.begin(); itr != vehicle->Seats.end(); ++itr) if (Unit* passenger = ObjectAccessor::GetUnit(*me, itr->second.Passenger.Guid)) { if (Player* player = passenger->ToPlayer()) { if (!sConditionMgr->IsObjectMeetToConditions(player, me, conditions)) { player->ExitVehicle(); return;//check other pessanger in next tick } } } } m_ConditionsTimer = 1000; } else m_ConditionsTimer -= diff; } void SmartAI::UpdateAI(uint32 diff) { bool hasVictim = UpdateVictim(); CheckConditions(diff); GetScript()->OnUpdate(diff); UpdatePath(diff); UpdateDespawn(diff); //TODO move to void if (mFollowGuid) { if (mFollowArrivedTimer < diff) { if (me->FindNearestCreature(mFollowArrivedEntry, INTERACTION_DISTANCE, mFollowArrivedAlive)) { StopFollow(true); return; } mFollowArrivedTimer = 1000; } else mFollowArrivedTimer -= diff; } if (!IsAIControlled()) { if (CharmInfo* charmInfo = me->GetCharmInfo()) { if (charmInfo->IsCommandAttack() && mCanAutoAttack) { DoMeleeAttackIfReady(); } } return; } if (!hasVictim) return; if (mCanAutoAttack) DoMeleeAttackIfReady(); } 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 && GetScript()->IsPlayer((*targets->begin()))) { Player* player = (*targets->begin())->ToPlayer(); 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 && me->IsInMap(groupGuy) && me->GetDistance(groupGuy) <= checkDist) return true; } } } else { for (WorldObject* target : *targets) { if (GetScript()->IsPlayer(target)) { if (me->GetDistance(target->ToPlayer()) <= checkDist) return true; } } } // Xinef: no valid target found return false; } // Xinef: no player invoker was stored, just ignore range check return true; } void SmartAI::MovepointReached(uint32 id) { // override the id, path can be resumed any time and counter will reset // mCurrentWPID holds proper id // xinef: both point movement and escort generator can enter this function if (id == SMART_ESCORT_LAST_OOC_POINT) { mOOCReached = true; return; } mWPReached = true; GetScript()->ProcessEventsFor(SMART_EVENT_ESCORT_REACHED, nullptr, mCurrentWPID); if (mLastWP) { me->SetPosition(mLastWP->x, mLastWP->y, mLastWP->z, me->GetOrientation()); me->SetHomePosition(me->GetPosition()); } if (HasEscortState(SMART_ESCORT_PAUSED)) { if (me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE) == ESCORT_MOTION_TYPE) me->GetMotionMaster()->MovementExpired(); me->StopMovingOnCurrentPos(); me->GetMotionMaster()->MoveIdle(); } // Xinef: Can be unset in ProcessEvents else if (HasEscortState(SMART_ESCORT_ESCORTING) && me->GetMotionMaster()->GetCurrentMovementGeneratorType() == ESCORT_MOTION_TYPE) { mWPReached = false; if (mCurrentWPID == GetWPCount()) EndPath(); else if (GetNextWayPoint()) { // xinef: if we have reached waypoint, and there is no working spline movement it means our splitted array has ended, make new one if (me->movespline->Finalized()) ResumePath(); } } } void SmartAI::MovementInform(uint32 MovementType, uint32 Data) { if (MovementType == POINT_MOTION_TYPE && Data == SMART_ESCORT_LAST_OOC_POINT) me->ClearUnitState(UNIT_STATE_EVADE); if (MovementType == WAYPOINT_MOTION_TYPE) GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_REACHED, nullptr, Data); // data now corresponds to columns GetScript()->ProcessEventsFor(SMART_EVENT_MOVEMENTINFORM, nullptr, MovementType, Data); if (!HasEscortState(SMART_ESCORT_ESCORTING)) return; if (MovementType == ESCORT_MOTION_TYPE || (MovementType == POINT_MOTION_TYPE && Data == SMART_ESCORT_LAST_OOC_POINT)) MovepointReached(Data); } void SmartAI::EnterEvadeMode(EvadeReason /*why*/) { if (mEvadeDisabled) { GetScript()->ProcessEventsFor(SMART_EVENT_EVADE); return; } if (me->GetCharmerGUID().IsPlayer() || me->HasUnitFlag(UNIT_FLAG_POSSESSED)) { me->AttackStop(); me->RemoveUnitFlag(UNIT_FLAG_IN_COMBAT); 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 if (HasEscortState(SMART_ESCORT_ESCORTING)) { AddEscortState(SMART_ESCORT_RETURNING); ReturnToLastOOCPos(); } else if (mFollowGuid) { if (Unit* target = ObjectAccessor::GetUnit(*me, mFollowGuid)) me->GetMotionMaster()->MoveFollow(target, mFollowDist, mFollowAngle); me->ClearUnitState(UNIT_STATE_EVADE); // xinef: do not forget to reset scripts as we wont call reached home GetScript()->OnReset(); } else { me->GetMotionMaster()->MoveTargetedHome(); // xinef: do not forget to reset scripts as we wont call reached home if (!me->HasUnitState(UNIT_STATE_EVADE)) GetScript()->OnReset(); } } void SmartAI::MoveInLineOfSight(Unit* who) { if (!who) return; GetScript()->OnMoveInLineOfSight(who); if (!IsAIControlled()) return; if (me->GetVictim()) return; if (me->HasReactState(REACT_PASSIVE) || AssistPlayerInCombatAgainst(who)) return; if (me->HasReactState(REACT_AGGRESSIVE) && me->CanStartAttack(who)) { if (me->HasUnitState(UNIT_STATE_DISTRACTED)) { me->ClearUnitState(UNIT_STATE_DISTRACTED); me->GetMotionMaster()->Clear(); } AttackStart(who); } } bool SmartAI::CanAIAttack(Unit const* /*who*/) const { return !(me->GetReactState() == REACT_PASSIVE); } bool SmartAI::AssistPlayerInCombatAgainst(Unit* who) { if (!IsAIControlled()) return false; // Xinef: if unit has no victim, or victim is player controlled thing if (!who->GetVictim() || who->GetCharmerOrOwnerOrOwnGUID().IsPlayer()) return false; //experimental (unknown) flag not present if (!(me->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_CAN_ASSIST)) return false; // Xinef: victim of unit has to be a player controlled unit if (!who->GetVictim()->GetCharmerOrOwnerOrOwnGUID().IsPlayer()) return false; // Xinef: Check if victim can be assisted 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)) { AttackStart(who); return true; } return false; } void SmartAI::JustRespawned() { mDespawnTime = 0; mDespawnState = 0; mEscortState = SMART_ESCORT_NONE; me->SetVisible(true); if (me->GetFaction() != me->GetCreatureTemplate()->faction) me->RestoreFaction(); mJustReset = true; JustReachedHome(); GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN); mFollowGuid.Clear();//do not reset follower on Reset(), we need it after combat evade mFollowDist = 0; mFollowAngle = 0; mFollowCredit = 0; mFollowArrivedTimer = 1000; mFollowArrivedEntry = 0; mFollowCreditType = 0; mFollowArrivedAlive = true; aiDataSet.clear(); } void SmartAI::JustReachedHome() { GetScript()->OnReset(); if (!mJustReset) { GetScript()->ProcessEventsFor(SMART_EVENT_REACHED_HOME); if (!UpdateVictim() && me->GetMotionMaster()->GetCurrentMovementGeneratorType() == IDLE_MOTION_TYPE && me->GetWaypointPath()) me->GetMotionMaster()->MoveWaypoint(me->GetWaypointPath(), true); } mJustReset = false; } void SmartAI::JustEngagedWith(Unit* enemy) { // Xinef: Interrupt channeled spells if (IsAIControlled()) me->InterruptSpell(CURRENT_CHANNELED_SPELL, true, true); GetScript()->ProcessEventsFor(SMART_EVENT_AGGRO, enemy); } void SmartAI::JustDied(Unit* killer) { GetScript()->ProcessEventsFor(SMART_EVENT_DEATH, killer); if (HasEscortState(SMART_ESCORT_ESCORTING)) EndPath(true); } void SmartAI::KilledUnit(Unit* victim) { GetScript()->ProcessEventsFor(SMART_EVENT_KILL, victim); } void SmartAI::JustSummoned(Creature* creature) { GetScript()->ProcessEventsFor(SMART_EVENT_SUMMONED_UNIT, creature); GetScript()->AddCreatureSummon(creature->GetGUID()); } void SmartAI::SummonedCreatureDies(Creature* summon, Unit* /*killer*/) { GetScript()->ProcessEventsFor(SMART_EVENT_SUMMONED_UNIT_DIES, summon); } void SmartAI::SummonedCreatureEvade(Creature* summon) { GetScript()->ProcessEventsFor(SMART_EVENT_SUMMONED_UNIT_EVADE, summon); } void SmartAI::AttackStart(Unit* who) { // xinef: dont allow charmed npcs to act on their own if (me->HasUnitFlag(UNIT_FLAG_POSSESSED)) { if (who) me->Attack(who, mCanAutoAttack); return; } if (who && me->Attack(who, me->IsWithinMeleeRange(who))) { if (!me->HasUnitState(UNIT_STATE_NO_COMBAT_MOVEMENT)) { MovementGeneratorType type = me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE); if (type == ESCORT_MOTION_TYPE || type == POINT_MOTION_TYPE) { me->GetMotionMaster()->MovementExpired(); me->StopMoving(); me->GetMotionMaster()->Clear(false); } me->GetMotionMaster()->MoveChase(who); } } } void SmartAI::SpellHit(Unit* unit, SpellInfo const* spellInfo) { GetScript()->ProcessEventsFor(SMART_EVENT_SPELLHIT, unit, 0, 0, false, spellInfo); } void SmartAI::SpellHitTarget(Unit* target, SpellInfo const* spellInfo) { GetScript()->ProcessEventsFor(SMART_EVENT_SPELLHIT_TARGET, target, 0, 0, false, spellInfo); } void SmartAI::DamageTaken(Unit* doneBy, uint32& damage, DamageEffectType damagetype, SpellSchoolMask /*damageSchoolMask*/) { if (doneBy) GetScript()->ProcessEventsFor(SMART_EVENT_DAMAGED, doneBy, damage); if (!IsAIControlled()) // don't allow players to use unkillable units return; // Xinef: skip nodamage type (eg. instakill effect) if (damagetype != NODAMAGE && mInvincibilityHpLevel && (damage >= me->GetHealth() - mInvincibilityHpLevel)) damage = me->GetHealth() - mInvincibilityHpLevel; // 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*/, SpellSchoolMask /*damageSchoolMask*/) { GetScript()->ProcessEventsFor(SMART_EVENT_DAMAGED_TARGET, doneTo, damage); } void SmartAI::SummonedCreatureDespawn(Creature* unit) { GetScript()->ProcessEventsFor(SMART_EVENT_SUMMON_DESPAWNED, unit); GetScript()->RemoveCreatureSummon(unit->GetGUID()); } void SmartAI::CorpseRemoved(uint32& respawnDelay) { GetScript()->ProcessEventsFor(SMART_EVENT_CORPSE_REMOVED, nullptr, respawnDelay); // xinef: end escort upon corpse remove, safe check in case of immediate despawn if (IsEscorted()) EndPath(true); } 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::InitializeAI() { GetScript()->OnInitialize(me); if (!me->isDead()) { mJustReset = true; JustReachedHome(); GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN); } } void SmartAI::OnCharmed(bool /* apply */) { 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); } mIsCharmed = charmed; if (!charmed && !me->IsInEvadeMode()) { if (mCanRepeatPath) StartPath(mForcedMovement, GetScript()->GetPathId(), true); if (Unit* charmer = me->GetCharmer()) AttackStart(charmer); } GetScript()->ProcessEventsFor(SMART_EVENT_CHARMED, nullptr, 0, 0, charmed); } void SmartAI::DoAction(int32 param) { GetScript()->ProcessEventsFor(SMART_EVENT_ACTION_DONE, nullptr, param); } uint32 SmartAI::GetData(uint32 id) const { auto const& itr = aiDataSet.find(id); if (itr != aiDataSet.end()) return itr->second; return 0; } void SmartAI::SetData(uint32 id, uint32 value, WorldObject* invoker) { Unit* unit = nullptr; GameObject* gob = nullptr; if (invoker) { unit = invoker->ToUnit(); if (!unit) gob = invoker->ToGameObject(); } aiDataSet[id] = value; GetScript()->ProcessEventsFor(SMART_EVENT_DATA_SET, unit, id, value, false, nullptr, gob); } void SmartAI::SetGUID(ObjectGuid const& /*guid*/, int32 /*id*/) { } ObjectGuid SmartAI::GetGUID(int32 /*id*/) const { return ObjectGuid::Empty; } void SmartAI::SetFly(bool fly) { // xinef: set proper flag! //me->SetDisableGravity(fly); me->SetCanFly(fly); } void SmartAI::SetSwim(bool swim) { me->SetSwim(swim); } void SmartAI::SetEvadeDisabled(bool disable) { mEvadeDisabled = disable; } void SmartAI::sGossipHello(Player* player) { GetScript()->ProcessEventsFor(SMART_EVENT_GOSSIP_HELLO, player); } void SmartAI::sGossipSelect(Player* player, uint32 sender, uint32 action) { GetScript()->ProcessEventsFor(SMART_EVENT_GOSSIP_SELECT, player, sender, action); } void SmartAI::sGossipSelectCode(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/, const char* /*code*/) { } void SmartAI::sQuestAccept(Player* player, Quest const* quest) { GetScript()->ProcessEventsFor(SMART_EVENT_ACCEPTED_QUEST, player, quest->GetQuestId()); } void SmartAI::sQuestReward(Player* player, Quest const* quest, uint32 opt) { GetScript()->ProcessEventsFor(SMART_EVENT_REWARD_QUEST, player, quest->GetQuestId(), opt); } void SmartAI::SetCombatMovement(bool on, bool stopOrStartMovement) { if (on) me->ClearUnitState(UNIT_STATE_NO_COMBAT_MOVEMENT); else me->AddUnitState(UNIT_STATE_NO_COMBAT_MOVEMENT); if (!IsAIControlled() || HasEscortState(SMART_ESCORT_ESCORTING)) return; if (stopOrStartMovement && me->GetVictim()) // Only change current movement while in combat { if (!me->IsCrowdControlled()) { if (on) me->GetMotionMaster()->MoveChase(me->GetVictim()); else if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE) me->StopMoving(); } } } void SmartAI::SetCurrentRangeMode(bool on, float range) { _currentRangeMode = on; _attackDistance = range; if (Unit* victim = me->GetVictim()) me->GetMotionMaster()->MoveChase(victim, _attackDistance); } void SmartAI::DistanceYourself(float range) { Unit* victim = me->GetVictim(); if (!victim || !victim->IsWithinMeleeRange(me)) return; float combatReach = me->GetMeleeRange(victim); float distance = DISTANCING_CONSTANT + std::max(combatReach * 1.5f, combatReach + range); me->GetMotionMaster()->DistanceYourself(distance); _pendingDistancing = distance; } void SmartAI::SetFollow(Unit* target, float dist, float angle, uint32 credit, uint32 end, uint32 creditType, bool aliveState) { if (!target) { StopFollow(false); return; } mFollowGuid = target->GetGUID(); mFollowDist = dist; mFollowAngle = angle; mFollowArrivedTimer = 1000; mFollowCredit = credit; mFollowArrivedEntry = end; mFollowArrivedAlive = !aliveState; // negate - 0 is alive mFollowCreditType = creditType; me->GetMotionMaster()->MoveFollow(target, mFollowDist, mFollowAngle); } void SmartAI::StopFollow(bool complete) { mFollowGuid.Clear(); mFollowDist = 0; mFollowAngle = 0; mFollowCredit = 0; mFollowArrivedTimer = 1000; mFollowArrivedEntry = 0; mFollowCreditType = 0; me->GetMotionMaster()->Clear(false); me->StopMoving(); me->GetMotionMaster()->MoveIdle(); if (!complete) return; Player* player = ObjectAccessor::GetPlayer(*me, mFollowGuid); if (player) { if (!mFollowCreditType) player->RewardPlayerAndGroupAtEvent(mFollowCredit, me); else player->GroupEventHappens(mFollowCredit, me); } SetDespawnTime(5000); StartDespawn(); GetScript()->ProcessEventsFor(SMART_EVENT_FOLLOW_COMPLETED, player); } void SmartAI::SetScript9(SmartScriptHolder& e, uint32 entry, WorldObject* invoker) { if (invoker) GetScript()->mLastInvoker = invoker->GetGUID(); GetScript()->SetScript9(e, entry); } void SmartAI::sOnGameEvent(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& /*result*/) { // Xinef: i dont think this is necessery (can be made as event parameter) //if (!result) // return; GetScript()->ProcessEventsFor(SMART_EVENT_ON_SPELLCLICK, clicker); } void SmartAI::PathEndReached(uint32 /*pathId*/) { GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_ENDED, nullptr, 0, me->GetWaypointPath()); me->LoadPath(0); } void SmartAI::DistancingEnded() { SetCurrentRangeMode(true, _pendingDistancing); _pendingDistancing = 0.f; } void SmartGameObjectAI::SummonedCreatureDies(Creature* summon, Unit* /*killer*/) { GetScript()->ProcessEventsFor(SMART_EVENT_SUMMONED_UNIT_DIES, summon); } void SmartGameObjectAI::SummonedCreatureEvade(Creature* summon) { GetScript()->ProcessEventsFor(SMART_EVENT_SUMMONED_UNIT_EVADE, summon); } void SmartGameObjectAI::UpdateAI(uint32 diff) { GetScript()->OnUpdate(diff); } void SmartGameObjectAI::InitializeAI() { GetScript()->OnInitialize(me); // Xinef: do not call respawn event if go is not spawned if (me->isSpawned()) GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN); //Reset(); } void SmartGameObjectAI::Reset() { // Xinef: 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 reportUse) { LOG_DEBUG("sql.sql", "SmartGameObjectAI::GossipHello"); GetScript()->ProcessEventsFor(SMART_EVENT_GOSSIP_HELLO, player, (uint32)reportUse, 0, false, nullptr, me); return false; } // Called when a player selects a gossip item in the gameobject's gossip menu. bool SmartGameObjectAI::GossipSelect(Player* player, uint32 sender, uint32 action) { GetScript()->ProcessEventsFor(SMART_EVENT_GOSSIP_SELECT, player, sender, action, false, nullptr, me); return false; } // Called when a player selects a gossip with a code in the gameobject's gossip menu. bool SmartGameObjectAI::GossipSelectCode(Player* /*player*/, uint32 /*sender*/, uint32 /*action*/, const char* /*code*/) { return false; } // Called when a player accepts a quest from the gameobject. bool SmartGameObjectAI::QuestAccept(Player* player, Quest const* quest) { GetScript()->ProcessEventsFor(SMART_EVENT_ACCEPTED_QUEST, player, quest->GetQuestId(), 0, false, nullptr, me); return false; } // Called when a player selects a quest reward. bool SmartGameObjectAI::QuestReward(Player* player, Quest const* quest, uint32 opt) { GetScript()->ProcessEventsFor(SMART_EVENT_REWARD_QUEST, player, quest->GetQuestId(), opt, false, nullptr, me); return false; } // Called when the gameobject is destroyed (destructible buildings only). void SmartGameObjectAI::Destroyed(Player* player, uint32 eventId) { GetScript()->ProcessEventsFor(SMART_EVENT_DEATH, player, eventId, 0, false, nullptr, me); } void SmartGameObjectAI::SetData(uint32 id, uint32 value, WorldObject* invoker) { Unit* unit = nullptr; GameObject* gob = nullptr; if (invoker) { unit = invoker->ToUnit(); if (!unit) gob = invoker->ToGameObject(); } GetScript()->ProcessEventsFor(SMART_EVENT_DATA_SET, unit, id, value, false, nullptr, gob); } void SmartGameObjectAI::SetScript9(SmartScriptHolder& e, uint32 entry, WorldObject* invoker) { if (invoker) GetScript()->mLastInvoker = invoker->GetGUID(); GetScript()->SetScript9(e, entry); } void SmartGameObjectAI::OnGameEvent(bool start, uint16 eventId) { GetScript()->ProcessEventsFor(start ? SMART_EVENT_GAME_EVENT_START : SMART_EVENT_GAME_EVENT_END, nullptr, eventId); } void SmartGameObjectAI::OnStateChanged(uint32 state, Unit* unit) { GetScript()->ProcessEventsFor(SMART_EVENT_GO_STATE_CHANGED, unit, state); } void SmartGameObjectAI::EventInform(uint32 eventId) { GetScript()->ProcessEventsFor(SMART_EVENT_GO_EVENT_INFORM, nullptr, eventId); } void SmartGameObjectAI::SpellHit(Unit* unit, SpellInfo const* spellInfo) { GetScript()->ProcessEventsFor(SMART_EVENT_SPELLHIT, unit, 0, 0, false, spellInfo); } void SmartGameObjectAI::JustSummoned(Creature* creature) { GetScript()->ProcessEventsFor(SMART_EVENT_SUMMONED_UNIT, creature); } 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, AreaTrigger const* trigger) override { if (!player->IsAlive()) return false; LOG_DEBUG("sql.sql", "AreaTrigger {} is using SmartTrigger script", trigger->entry); SmartScript script; script.OnInitialize(nullptr, trigger); script.ProcessEventsFor(SMART_EVENT_AREATRIGGER_ONTRIGGER, player, trigger->entry); return true; } }; void AddSC_SmartScripts() { new SmartTrigger(); }