summaryrefslogtreecommitdiff
path: root/src/game/AI/SmartScripts/SmartAI.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/game/AI/SmartScripts/SmartAI.cpp')
-rw-r--r--src/game/AI/SmartScripts/SmartAI.cpp1202
1 files changed, 1202 insertions, 0 deletions
diff --git a/src/game/AI/SmartScripts/SmartAI.cpp b/src/game/AI/SmartScripts/SmartAI.cpp
new file mode 100644
index 0000000000..04aefdec48
--- /dev/null
+++ b/src/game/AI/SmartScripts/SmartAI.cpp
@@ -0,0 +1,1202 @@
+/*
+ * Copyright (C)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "DatabaseEnv.h"
+#include "ObjectMgr.h"
+#include "ObjectDefines.h"
+#include "GridDefines.h"
+#include "GridNotifiers.h"
+#include "SpellMgr.h"
+#include "GridNotifiersImpl.h"
+#include "Cell.h"
+#include "CellImpl.h"
+#include "InstanceScript.h"
+#include "ScriptedCreature.h"
+#include "Group.h"
+#include "SmartAI.h"
+#include "ScriptMgr.h"
+#include "Vehicle.h"
+
+SmartAI::SmartAI(Creature* c) : CreatureAI(c)
+{
+ // copy script to local (protection for table reload)
+
+ mWayPoints = NULL;
+ mEscortState = SMART_ESCORT_NONE;
+ mCurrentWPID = 0;//first wp id is 1 !!
+ mWPReached = false;
+ mOOCReached = false;
+ mWPPauseTimer = 0;
+ mLastWP = NULL;
+ mEscortNPCFlags = 0;
+
+ mCanRepeatPath = false;
+
+ // spawn in run mode
+ // Xinef: spawn in run mode and set mRun to run... this overrides SetWalk EVERYWHERE, RETARDS
+ mRun = true;
+
+ mCanAutoAttack = true;
+ mCanCombatMove = true;
+
+ mForcedPaused = false;
+
+ mEscortQuestID = 0;
+
+ mDespawnTime = 0;
+ mDespawnState = 0;
+
+ mEscortInvokerCheckTimer = 1000;
+ mFollowGuid = 0;
+ mFollowDist = 0;
+ mFollowAngle = 0;
+ mFollowCredit = 0;
+ mFollowArrivedEntry = 0;
+ mFollowCreditType = 0;
+ mFollowArrivedAlive = 0;
+ mFollowArrivedTimer = 0;
+ mInvincibilityHpLevel = 0;
+
+ mJustReset = false;
+
+ // Xinef: Vehicle conditions
+ m_ConditionsTimer = 0;
+ if (me->GetVehicleKit())
+ conditions = sConditionMgr->GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, me->GetEntry());
+}
+
+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;
+}
+
+WayPoint* SmartAI::GetNextWayPoint()
+{
+ if (!mWayPoints || mWayPoints->empty())
+ return NULL;
+
+ mCurrentWPID++;
+ WPPath::const_iterator itr = mWayPoints->find(mCurrentWPID);
+ if (itr != mWayPoints->end())
+ {
+ mLastWP = (*itr).second;
+ if (mLastWP->id != mCurrentWPID)
+ sLog->outError("SmartAI::GetNextWayPoint: Got not expected waypoint id %u, expected %u", mLastWP->id, mCurrentWPID);
+
+ return (*itr).second;
+ }
+ return NULL;
+}
+
+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;
+ WPPath::const_iterator itr;
+ while ((itr = mWayPoints->find(wpCounter++)) != mWayPoints->end())
+ {
+ WayPoint* wp = (*itr).second;
+ points->push_back(G3D::Vector3(wp->x, wp->y, wp->z));
+ }
+ }
+ else
+ {
+ for (float size = 1.0f; size; size *= 0.5f)
+ {
+ std::vector<G3D::Vector3> pVector;
+ // xinef: first point in vector is unit real position
+ pVector.push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()));
+ uint32 length = (mWayPoints->size() - mCurrentWPID)*size;
+
+ uint32 cnt = 0;
+ uint32 wpCounter = mCurrentWPID;
+ WPPath::const_iterator itr;
+ while ((itr = mWayPoints->find(wpCounter++)) != mWayPoints->end() && cnt++ <= length)
+ {
+ WayPoint* 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 (fabs(offset.x) >= 0xFF || fabs(offset.y) >= 0xFF || fabs(offset.z) >= 0x7F)
+ {
+ // offset is too big, split points
+ continueLoop = true;
+ break;
+ }
+ }
+ if (continueLoop)
+ continue;
+ }
+ // everything ok
+ *points = pVector;
+ break;
+ }
+ }
+}
+
+void SmartAI::StartPath(bool run, uint32 path, bool repeat, Unit* invoker)
+{
+ if (me->IsInCombat())// no wp movement in combat
+ {
+ sLog->outError("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;
+
+ 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);
+ }
+
+ Movement::PointsArray pathPoints;
+ GenerateWayPointArray(&pathPoints);
+
+ me->GetMotionMaster()->MoveSplinePath(&pathPoints);
+ GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_START, NULL, wp->id, GetScript()->GetPathId());
+ }
+}
+
+bool SmartAI::LoadPath(uint32 entry)
+{
+ if (HasEscortState(SMART_ESCORT_ESCORTING))
+ return false;
+
+ mWayPoints = sSmartWaypointMgr->GetPath(entry);
+ if (!mWayPoints)
+ {
+ GetScript()->SetPathId(0);
+ return false;
+ }
+
+ GetScript()->SetPathId(entry);
+ return true;
+}
+
+void SmartAI::PausePath(uint32 delay, bool forced)
+{
+ if (!HasEscortState(SMART_ESCORT_ESCORTING))
+ return;
+
+ if (HasEscortState(SMART_ESCORT_PAUSED))
+ {
+ sLog->outError("SmartAI::StartPath: Creature entry %u wanted to pause waypoint movement while already paused, ignoring.", me->GetEntry());
+ return;
+ }
+
+ AddEscortState(SMART_ESCORT_PAUSED);
+ mWPPauseTimer = delay;
+ if (forced && !mWPReached)
+ {
+ mForcedPaused = forced;
+ SetRun(mRun);
+ if (me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE) == ESCORT_MOTION_TYPE)
+ me->GetMotionMaster()->MovementExpired();
+
+ me->StopMoving();
+ me->GetMotionMaster()->MoveIdle();//force stop
+ }
+ GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_PAUSED, NULL, 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_WAYPOINT_STOPPED, NULL, mCurrentWPID, GetScript()->GetPathId());
+ EndPath(fail);
+}
+
+void SmartAI::EndPath(bool fail)
+{
+ RemoveEscortState(SMART_ESCORT_ESCORTING | SMART_ESCORT_PAUSED | SMART_ESCORT_RETURNING);
+ mWayPoints = NULL;
+ mLastWP = NULL;
+ mWPPauseTimer = 0;
+
+ if (mEscortNPCFlags)
+ {
+ me->SetUInt32Value(UNIT_NPC_FLAGS, mEscortNPCFlags);
+ mEscortNPCFlags = 0;
+ }
+
+ ObjectList* targets = GetScript()->GetTargetList(SMART_ESCORT_TARGETS);
+ if (targets && mEscortQuestID)
+ {
+ if (targets->size() == 1 && GetScript()->IsPlayer((*targets->begin())))
+ {
+ Player* player = (*targets->begin())->ToPlayer();
+ if (Group* group = player->GetGroup())
+ {
+ for (GroupReference* groupRef = group->GetFirstMember(); groupRef != NULL; groupRef = groupRef->next())
+ {
+ Player* groupGuy = groupRef->GetSource();
+ if (!groupGuy || !player->IsInMap(groupGuy))
+ continue;
+
+ if (!fail && groupGuy->IsAtGroupRewardDistance(me) && !groupGuy->GetCorpse())
+ groupGuy->AreaExploredOrEventHappens(mEscortQuestID);
+ else if (fail && groupGuy->GetQuestStatus(mEscortQuestID) == QUEST_STATUS_INCOMPLETE)
+ groupGuy->FailQuest(mEscortQuestID);
+ }
+ }
+ else
+ {
+ if (!fail && player->IsAtGroupRewardDistance(me) && !player->GetCorpse())
+ player->GroupEventHappens(mEscortQuestID, me);
+ else if (fail && player->GetQuestStatus(mEscortQuestID) == QUEST_STATUS_INCOMPLETE)
+ player->FailQuest(mEscortQuestID);
+ }
+ }
+ else
+ {
+ for (ObjectList::iterator iter = targets->begin(); iter != targets->end(); ++iter)
+ {
+ if (GetScript()->IsPlayer((*iter)))
+ {
+ Player* player = (*iter)->ToPlayer();
+ if (!fail && player->IsAtGroupRewardDistance(me) && !player->GetCorpse())
+ player->AreaExploredOrEventHappens(mEscortQuestID);
+ else if (fail && player->GetQuestStatus(mEscortQuestID) == QUEST_STATUS_INCOMPLETE)
+ player->FailQuest(mEscortQuestID);
+ }
+ }
+ }
+ }
+
+ // Xinef: if the escort failed - DO NOT PROCESS ANYTHING, ITS RETARDED
+ // 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_WAYPOINT_ENDED, NULL, mCurrentWPID, GetScript()->GetPathId());
+ mCurrentWPID = 0;
+
+ if (mCanRepeatPath)
+ StartPath(mRun, GetScript()->GetPathId(), mCanRepeatPath);
+ else
+ GetScript()->SetPathId(0);
+
+ if (mDespawnState == 1)
+ StartDespawn();
+}
+
+void SmartAI::ResumePath()
+{
+ SetRun(mRun);
+
+ if (mLastWP)
+ {
+ Movement::PointsArray pathPoints;
+ GenerateWayPointArray(&pathPoints);
+
+ me->GetMotionMaster()->MoveSplinePath(&pathPoints);
+ }
+}
+
+void SmartAI::ReturnToLastOOCPos()
+{
+ me->SetWalk(false);
+ float x, y, z, o;
+ me->GetHomePosition(x, y, z, o);
+ me->GetMotionMaster()->MovePoint(SMART_ESCORT_LAST_OOC_POINT, x, y, z);
+}
+
+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(1);
+ 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_WAYPOINT_RESUMED, NULL, 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())
+ {
+ SetRun(mRun);
+ // 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)
+{
+ 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 (!UpdateVictim())
+ return;
+
+ if (mCanAutoAttack)
+ DoMeleeAttackIfReady();
+}
+
+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) <= checkDist)
+ return true;
+
+ if (Group* group = player->GetGroup())
+ {
+ for (GroupReference* groupRef = group->GetFirstMember(); groupRef != NULL; groupRef = groupRef->next())
+ {
+ Player* groupGuy = groupRef->GetSource();
+
+ if (groupGuy && me->IsInMap(groupGuy) && me->GetDistance(groupGuy) <= checkDist)
+ return true;
+ }
+ }
+ }
+ else
+ {
+ for (ObjectList::iterator iter = targets->begin(); iter != targets->end(); ++iter)
+ {
+ if (GetScript()->IsPlayer((*iter)))
+ {
+ if (me->GetDistance((*iter)->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_WAYPOINT_REACHED, NULL, mCurrentWPID);
+
+ if (mLastWP)
+ {
+ me->SetPosition(mLastWP->x, mLastWP->y, mLastWP->z, me->GetOrientation());
+ me->SetHomePosition(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), me->GetOrientation());
+ }
+
+ 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())
+ {
+ SetRun(mRun);
+ // 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);
+
+ GetScript()->ProcessEventsFor(SMART_EVENT_MOVEMENTINFORM, NULL, 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()
+{
+ // xinef: fixes strange jumps when charming SmartAI npc
+ if (!me->IsAlive() || me->IsInEvadeMode())
+ return;
+
+ if (IS_PLAYER_GUID(me->GetCharmerGUID()) || me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED))
+ {
+ me->AttackStop();
+ return;
+ }
+
+ me->RemoveEvadeAuras();
+
+ me->AddUnitState(UNIT_STATE_EVADE);
+ me->DeleteThreatList();
+ me->CombatStop(true);
+ me->LoadCreaturesAddon(true);
+ me->SetLootRecipient(NULL);
+ me->ResetPlayerDamageReq();
+ me->SetLastDamagedTime(0);
+
+ GetScript()->ProcessEventsFor(SMART_EVENT_EVADE);//must be after aura clear so we can cast spells from db
+
+ SetRun(mRun);
+ 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 (me->GetVictim())
+ return;
+
+ if (me->HasReactState(REACT_PASSIVE) || AssistPlayerInCombat(who))
+ return;
+
+ if (me->CanStartAttack(who))
+ AttackStart(who);
+}
+
+bool SmartAI::CanAIAttack(const Unit* /*who*/) const
+{
+ if (me->GetReactState() == REACT_PASSIVE)
+ return false;
+ return true;
+}
+
+bool SmartAI::AssistPlayerInCombat(Unit* who)
+{
+ // Xinef: if unit has no victim, or victim is player controlled thing
+ if (!who->GetVictim() || IS_PLAYER_GUID(who->GetCharmerOrOwnerOrOwnGUID()))
+ return false;
+
+ //experimental (unknown) flag not present
+ if (!(me->GetCreatureTemplate()->type_flags & CREATURE_TYPEFLAGS_AID_PLAYERS))
+ return false;
+
+ // Xinef: victim of unit has to be a player controlled unit
+ if (!IS_PLAYER_GUID(who->GetVictim()->GetCharmerOrOwnerOrOwnGUID()))
+ 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 = 0;//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;
+}
+
+int SmartAI::Permissible(const Creature* creature)
+{
+ if (creature->GetAIName() == "SmartAI")
+ return PERMIT_BASE_SPECIAL;
+ return PERMIT_BASE_NO;
+}
+
+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()->MovePath(me->GetWaypointPath(), true);
+ }
+
+ mJustReset = false;
+}
+
+void SmartAI::EnterCombat(Unit* enemy)
+{
+ // Xinef: Interrupt channeled spells
+ 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);
+}
+
+void SmartAI::AttackStart(Unit* who)
+{
+ // xinef: dont allow charmed npcs to act on their own
+ if (me->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED))
+ {
+ if (who && mCanAutoAttack)
+ me->Attack(who, true);
+ return;
+ }
+
+ if (who && me->Attack(who, me->IsWithinMeleeRange(who)))
+ {
+ if (mCanCombatMove || GetScript()->GetMaxCombatDist())
+ {
+ SetRun(mRun);
+ MovementGeneratorType type = me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE);
+ if (type == ESCORT_MOTION_TYPE || type == POINT_MOTION_TYPE)
+ {
+ me->GetMotionMaster()->MovementExpired();
+ me->StopMoving();
+ }
+ me->GetMotionMaster()->MoveChase(who, GetScript()->GetCasterActualDist() ? GetScript()->GetCasterActualDist() : GetScript()->GetActualCombatDist());
+ }
+ }
+}
+
+void SmartAI::SpellHit(Unit* unit, const SpellInfo* spellInfo)
+{
+ GetScript()->ProcessEventsFor(SMART_EVENT_SPELLHIT, unit, 0, 0, false, spellInfo);
+}
+
+void SmartAI::SpellHitTarget(Unit* target, const SpellInfo* 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);
+
+ // 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(Unit* summoner)
+{
+ GetScript()->ProcessEventsFor(SMART_EVENT_JUST_SUMMONED, summoner);
+}
+
+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);
+}
+
+void SmartAI::UpdateAIWhileCharmed(const uint32 /*diff*/)
+{
+}
+
+void SmartAI::CorpseRemoved(uint32& respawnDelay)
+{
+ GetScript()->ProcessEventsFor(SMART_EVENT_CORPSE_REMOVED, NULL, 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)
+{
+ GetScript()->ProcessEventsFor(SMART_EVENT_CHARMED, NULL, 0, 0, apply);
+}
+
+void SmartAI::DoAction(int32 param)
+{
+ GetScript()->ProcessEventsFor(SMART_EVENT_ACTION_DONE, NULL, param);
+}
+
+uint32 SmartAI::GetData(uint32 /*id*/) const
+{
+ return 0;
+}
+
+void SmartAI::SetData(uint32 id, uint32 value)
+{
+ GetScript()->ProcessEventsFor(SMART_EVENT_DATA_SET, NULL, id, value);
+}
+
+void SmartAI::SetGUID(uint64 /*guid*/, int32 /*id*/)
+{
+}
+
+uint64 SmartAI::GetGUID(int32 /*id*/) const
+{
+ return 0;
+}
+
+void SmartAI::SetRun(bool run)
+{
+ me->SetWalk(!run);
+ mRun = run;
+}
+
+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::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::SetForcedCombatMove(float dist)
+{
+ if (!me->GetVictim())
+ return;
+
+ SetRun(mRun);
+ me->GetMotionMaster()->MoveChase(me->GetVictim(), dist);
+}
+
+void SmartAI::SetCombatMove(bool on)
+{
+ // Xinef: Fix Combat Movement
+ if (GetScript()->GetMaxCombatDist()/* || GetScript()->GetCasterMaxDist()*/) // Xinef: we only need this hack for old caster movement system
+ return;
+
+ if (mCanCombatMove == on)
+ return;
+
+ mCanCombatMove = on;
+ if (!HasEscortState(SMART_ESCORT_ESCORTING))
+ {
+ if (on && me->GetVictim())
+ {
+ if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == IDLE_MOTION_TYPE)
+ {
+ SetRun(mRun);
+ me->GetMotionMaster()->MoveChase(me->GetVictim());
+ me->CastStop();
+ }
+ }
+ else
+ {
+ me->StopMoving();
+ if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE)
+ me->GetMotionMaster()->Clear(false);
+ me->GetMotionMaster()->MoveIdle();
+ }
+ }
+}
+
+void SmartAI::SetFollow(Unit* target, float dist, float angle, uint32 credit, uint32 end, uint32 creditType, bool aliveState)
+{
+ if (!target)
+ {
+ StopFollow(false);
+ return;
+ }
+
+ SetRun(mRun);
+ mFollowGuid = target->GetGUID();
+ mFollowDist = dist;
+ mFollowAngle = angle;
+ mFollowArrivedTimer = 1000;
+ mFollowCredit = credit;
+ mFollowArrivedEntry = end;
+ mFollowArrivedAlive = !aliveState; // negate - 0 is alive
+
+ me->GetMotionMaster()->MoveFollow(target, mFollowDist, mFollowAngle);
+ mFollowCreditType = creditType;
+}
+
+void SmartAI::StopFollow(bool complete)
+{
+ mFollowGuid = 0;
+ mFollowDist = 0;
+ mFollowAngle = 0;
+ mFollowCredit = 0;
+ mFollowArrivedTimer = 1000;
+ mFollowArrivedEntry = 0;
+ mFollowCreditType = 0;
+
+ me->GetMotionMaster()->Clear(false);
+ 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)
+ 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, NULL, 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);
+}
+
+int SmartGameObjectAI::Permissible(const GameObject* g)
+{
+ if (g->GetAIName() == "SmartGameObjectAI")
+ return PERMIT_BASE_SPECIAL;
+ return PERMIT_BASE_NO;
+}
+
+void SmartGameObjectAI::UpdateAI(uint32 diff)
+{
+ GetScript()->OnUpdate(diff);
+}
+
+void SmartGameObjectAI::InitializeAI()
+{
+ GetScript()->OnInitialize(go);
+
+ // Xinef: do not call respawn event if go is not spawned
+ if (go->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)
+{
+ ;//sLog->outDebug(LOG_FILTER_DATABASE_AI, "SmartGameObjectAI::GossipHello");
+ GetScript()->ProcessEventsFor(SMART_EVENT_GOSSIP_HELLO, player, (uint32)reportUse, 0, false, NULL, go);
+ 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, NULL, go);
+ 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, NULL, go);
+ 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, NULL, go);
+ 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, NULL, go);
+}
+
+void SmartGameObjectAI::SetData(uint32 id, uint32 value)
+{
+ GetScript()->ProcessEventsFor(SMART_EVENT_DATA_SET, NULL, id, value);
+}
+
+void SmartGameObjectAI::SetScript9(SmartScriptHolder& e, uint32 entry, Unit* 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, NULL, 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, NULL, eventId);
+}
+
+void SmartGameObjectAI::SpellHit(Unit* unit, const SpellInfo* spellInfo)
+{
+ GetScript()->ProcessEventsFor(SMART_EVENT_SPELLHIT, unit, 0, 0, false, spellInfo);
+}
+
+class SmartTrigger : public AreaTriggerScript
+{
+ public:
+
+ SmartTrigger() : AreaTriggerScript("SmartTrigger") {}
+
+ bool OnTrigger(Player* player, AreaTriggerEntry const* trigger)
+ {
+ if (!player->IsAlive())
+ return false;
+
+ ;//sLog->outDebug(LOG_FILTER_DATABASE_AI, "AreaTrigger %u is using SmartTrigger script", trigger->id);
+ SmartScript script;
+ script.OnInitialize(NULL, trigger);
+ script.ProcessEventsFor(SMART_EVENT_AREATRIGGER_ONTRIGGER, player, trigger->id);
+ return true;
+ }
+};
+
+void AddSC_SmartScripts()
+{
+ new SmartTrigger();
+}