/** * MaNGOS is a full featured server for World of Warcraft, supporting * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 * * Copyright (C) 2005-2020 MaNGOS * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * World of Warcraft, and all World of Warcraft or Warcraft art, images, * and lore are copyrighted by Blizzard Entertainment, Inc. */ #include "WaypointMovementGenerator.h" #include "ObjectMgr.h" #include "Player.h" #include "Creature.h" #include "CreatureAI.h" #include "WaypointManager.h" #include "ScriptMgr.h" #include "movement/MoveSplineInit.h" #include "movement/MoveSpline.h" #include //-----------------------------------------------// void WaypointMovementGenerator::LoadPath(Creature& creature, int32 pathId, WaypointPathOrigin wpOrigin, uint32 overwriteEntry) { DETAIL_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "LoadPath: loading waypoint path for %s", creature.GetGuidStr().c_str()); if (!overwriteEntry) { overwriteEntry = creature.GetEntry(); } if (wpOrigin == PATH_NO_PATH && pathId == 0) { i_path = sWaypointMgr.GetDefaultPath(overwriteEntry, creature.GetGUIDLow(), &m_PathOrigin); } else { m_PathOrigin = wpOrigin == PATH_NO_PATH ? PATH_FROM_ENTRY : wpOrigin; i_path = sWaypointMgr.GetPathFromOrigin(overwriteEntry, creature.GetGUIDLow(), pathId, m_PathOrigin); } m_pathId = pathId; // No movement found for entry nor guid if (!i_path) { if (m_PathOrigin == PATH_FROM_EXTERNAL) { sLog.outErrorScriptLib("WaypointMovementGenerator::LoadPath: %s doesn't have waypoint path %i", creature.GetGuidStr().c_str(), pathId); } else { sLog.outErrorDb("WaypointMovementGenerator::LoadPath: %s doesn't have waypoint path %i", creature.GetGuidStr().c_str(), pathId); } return; } if (i_path->empty()) { return; } // Initialize the i_currentNode to point to the first node i_currentNode = i_path->begin()->first; m_lastReachedWaypoint = 0; } void WaypointMovementGenerator::Initialize(Creature& creature) { creature.addUnitState(UNIT_STAT_ROAMING); creature.clearUnitState(UNIT_STAT_WAYPOINT_PAUSED); } void WaypointMovementGenerator::InitializeWaypointPath(Creature& u, int32 id, WaypointPathOrigin wpSource, uint32 initialDelay, uint32 overwriteEntry) { LoadPath(u, id, wpSource, overwriteEntry); i_nextMoveTime.Reset(initialDelay); // Start moving if possible StartMove(u); } void WaypointMovementGenerator::Finalize(Creature& creature) { creature.clearUnitState(UNIT_STAT_ROAMING | UNIT_STAT_ROAMING_MOVE); creature.SetWalk(!creature.hasUnitState(UNIT_STAT_RUNNING_STATE), false); } void WaypointMovementGenerator::Interrupt(Creature& creature) { creature.InterruptMoving(); creature.clearUnitState(UNIT_STAT_ROAMING | UNIT_STAT_ROAMING_MOVE); creature.SetWalk(!creature.hasUnitState(UNIT_STAT_RUNNING_STATE), false); } void WaypointMovementGenerator::Reset(Creature& creature) { creature.addUnitState(UNIT_STAT_ROAMING); StartMove(creature); } void WaypointMovementGenerator::OnArrived(Creature& creature) { if (!i_path || i_path->empty()) { return; } m_lastReachedWaypoint = i_currentNode; if (m_isArrivalDone) { return; } creature.clearUnitState(UNIT_STAT_ROAMING_MOVE); m_isArrivalDone = true; WaypointPath::const_iterator currPoint = i_path->find(i_currentNode); MANGOS_ASSERT(currPoint != i_path->end()); WaypointNode const& node = currPoint->second; if (node.script_id) { DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "Creature movement start script %u at point %u for %s.", node.script_id, i_currentNode, creature.GetGuidStr().c_str()); creature.GetMap()->ScriptsStart(DBS_ON_CREATURE_MOVEMENT, node.script_id, &creature, &creature); } // We have reached the destination and can process behavior if (WaypointBehavior* behavior = node.behavior) { if (behavior->emote != 0) { creature.HandleEmote(behavior->emote); } if (behavior->spell != 0) { creature.CastSpell(&creature, behavior->spell, false); } if (behavior->model1 != 0) { creature.SetDisplayId(behavior->model1); } if (behavior->textid[0]) { int32 textId = behavior->textid[0]; // Not only one text is set if (behavior->textid[1]) { // Select one from max 5 texts (0 and 1 already checked) int i = 2; for (; i < MAX_WAYPOINT_TEXT; ++i) { if (!behavior->textid[i]) { break; } } textId = behavior->textid[urand(0, i - 1)]; } if (MangosStringLocale const* textData = sObjectMgr.GetMangosStringLocale(textId)) { creature.MonsterText(textData, NULL); } else { sLog.outErrorDb("%s reached waypoint %u, attempted to do text %i, but required text-data could not be found", creature.GetGuidStr().c_str(), i_currentNode, textId); } } } // Inform script if (creature.AI()) { uint32 type = WAYPOINT_MOTION_TYPE; if (m_PathOrigin == PATH_FROM_EXTERNAL && m_pathId > 0) { type = EXTERNAL_WAYPOINT_MOVE + m_pathId; } creature.AI()->MovementInform(type, i_currentNode); } // Wait delay ms Stop(node.delay); } void WaypointMovementGenerator::StartMove(Creature& creature) { if (!i_path || i_path->empty()) { return; } if (Stopped(creature)) { return; } if (!creature.IsAlive() || creature.hasUnitState(UNIT_STAT_NOT_MOVE)) { return; } WaypointPath::const_iterator currPoint = i_path->find(i_currentNode); MANGOS_ASSERT(currPoint != i_path->end()); if (WaypointBehavior* behavior = currPoint->second.behavior) { if (behavior->model2 != 0) { creature.SetDisplayId(behavior->model2); } creature.SetUInt32Value(UNIT_NPC_EMOTESTATE, 0); } if (m_isArrivalDone) { bool reachedLast = false; ++currPoint; if (currPoint == i_path->end()) { reachedLast = true; currPoint = i_path->begin(); } // Inform AI if (creature.AI() && m_PathOrigin == PATH_FROM_EXTERNAL && m_pathId > 0) { if (!reachedLast) { creature.AI()->MovementInform(EXTERNAL_WAYPOINT_MOVE_START + m_pathId, currPoint->first); } else { creature.AI()->MovementInform(EXTERNAL_WAYPOINT_FINISHED_LAST + m_pathId, currPoint->first); } if (creature.IsDead() || !creature.IsInWorld()) // Might have happened with above calls { return; } } i_currentNode = currPoint->first; } m_isArrivalDone = false; creature.addUnitState(UNIT_STAT_ROAMING_MOVE); WaypointNode const& nextNode = currPoint->second;; Movement::MoveSplineInit init(creature); init.MoveTo(nextNode.x, nextNode.y, nextNode.z, true); if (nextNode.orientation != 100 && nextNode.delay != 0) { init.SetFacing(nextNode.orientation); } creature.SetWalk(!creature.hasUnitState(UNIT_STAT_RUNNING_STATE) && !creature.IsLevitating(), false); init.Launch(); } bool WaypointMovementGenerator::Update(Creature& creature, const uint32& diff) { // Waypoint movement can be switched on/off // This is quite handy for escort quests and other stuff if (creature.hasUnitState(UNIT_STAT_NOT_MOVE)) { creature.clearUnitState(UNIT_STAT_ROAMING_MOVE); return true; } // prevent a crash at empty waypoint path. if (!i_path || i_path->empty()) { creature.clearUnitState(UNIT_STAT_ROAMING_MOVE); return true; } if (Stopped(creature)) { if (CanMove(diff, creature)) { StartMove(creature); } } else { if (creature.IsStopped()) { Stop(STOP_TIME_FOR_PLAYER); } else if (creature.movespline->Finalized()) { OnArrived(creature); StartMove(creature); } } return true; } bool WaypointMovementGenerator::Stopped(Creature& u) { return !i_nextMoveTime.Passed() || u.hasUnitState(UNIT_STAT_WAYPOINT_PAUSED); } bool WaypointMovementGenerator::CanMove(int32 diff, Creature& u) { i_nextMoveTime.Update(diff); if (i_nextMoveTime.Passed() && u.hasUnitState(UNIT_STAT_WAYPOINT_PAUSED)) { i_nextMoveTime.Reset(1); } return i_nextMoveTime.Passed() && !u.hasUnitState(UNIT_STAT_WAYPOINT_PAUSED); } bool WaypointMovementGenerator::GetResetPosition(Creature&, float& x, float& y, float& z, float& o) const { // prevent a crash at empty waypoint path. if (!i_path || i_path->empty()) { return false; } WaypointPath::const_iterator lastPoint = i_path->find(m_lastReachedWaypoint); // Special case: Before the first waypoint is reached, m_lastReachedWaypoint is set to 0 (which may not be contained in i_path) if (!m_lastReachedWaypoint && lastPoint == i_path->end()) { return false; } MANGOS_ASSERT(lastPoint != i_path->end()); WaypointNode const* curWP = &(lastPoint->second); x = curWP->x; y = curWP->y; z = curWP->z; if (curWP->orientation != 100) { o = curWP->orientation; } else // Calculate the resulting angle based on positions between previous and current waypoint { WaypointNode const* prevWP; if (lastPoint != i_path->begin()) // Not the first waypoint { --lastPoint; prevWP = &(lastPoint->second); } else // Take the last waypoint (crbegin()) as previous { prevWP = &(i_path->rbegin()->second); } float dx = x - prevWP->x; float dy = y - prevWP->y; o = atan2(dy, dx); // returns value between -Pi..Pi o = (o >= 0) ? o : 2 * M_PI_F + o; } return true; } void WaypointMovementGenerator::GetPathInformation(std::ostringstream& oss) const { oss << "WaypointMovement: Last Reached WP: " << m_lastReachedWaypoint << " "; oss << "(Loaded path " << m_pathId << " from " << WaypointManager::GetOriginString(m_PathOrigin) << ")\n"; } void WaypointMovementGenerator::AddToWaypointPauseTime(int32 waitTimeDiff) { if (!i_nextMoveTime.Passed()) { // Prevent <= 0, the code in Update requires to catch the change from moving to not moving int32 newWaitTime = i_nextMoveTime.GetExpiry() + waitTimeDiff; i_nextMoveTime.Reset(newWaitTime > 0 ? newWaitTime : 1); } } bool WaypointMovementGenerator::SetNextWaypoint(uint32 pointId) { if (!i_path || i_path->empty()) { return false; } WaypointPath::const_iterator currPoint = i_path->find(pointId); if (currPoint == i_path->end()) { return false; } // Allow Moving with next tick // Handle allow movement this way to not interact with PAUSED state. // If this function is called while PAUSED, it will move properly when unpaused. i_nextMoveTime.Reset(1); m_isArrivalDone = false; // Set the point i_currentNode = pointId; return true; } //----------------------------------------------------// uint32 FlightPathMovementGenerator::GetPathAtMapEnd() const { if (i_currentNode >= i_path->size()) { return i_path->size(); } uint32 curMapId = (*i_path)[i_currentNode].mapid; for (uint32 i = i_currentNode; i < i_path->size(); ++i) { if ((*i_path)[i].mapid != curMapId) { return i; } } return i_path->size(); } void FlightPathMovementGenerator::Initialize(Player& player) { Reset(player); } void FlightPathMovementGenerator::Finalize(Player& player) { // remove flag to prevent send object build movement packets for flight state and crash (movement generator already not at top of stack) player.clearUnitState(UNIT_STAT_TAXI_FLIGHT); player.Unmount(); player.RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CLIENT_CONTROL_LOST | UNIT_FLAG_TAXI_FLIGHT); if (player.m_taxi.empty()) { player.GetHostileRefManager().setOnlineOfflineState(true); if (player.pvpInfo.inHostileArea) { player.CastSpell(&player, 2479, true); } // update z position to ground and orientation for landing point // this prevent cheating with landing point at lags // when client side flight end early in comparison server side player.StopMoving(true); } } void FlightPathMovementGenerator::Interrupt(Player& player) { player.clearUnitState(UNIT_STAT_TAXI_FLIGHT); } #define PLAYER_FLIGHT_SPEED 32.0f void FlightPathMovementGenerator::Reset(Player& player) { player.GetHostileRefManager().setOnlineOfflineState(false); player.addUnitState(UNIT_STAT_TAXI_FLIGHT); player.SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CLIENT_CONTROL_LOST | UNIT_FLAG_TAXI_FLIGHT); Movement::MoveSplineInit init(player); uint32 end = GetPathAtMapEnd(); for (uint32 i = GetCurrentNode(); i != end; ++i) { G3D::Vector3 vertice((*i_path)[i].x, (*i_path)[i].y, (*i_path)[i].z); init.Path().push_back(vertice); } init.SetFirstPointId(GetCurrentNode()); init.SetFly(); init.SetVelocity(PLAYER_FLIGHT_SPEED); init.Launch(); } bool FlightPathMovementGenerator::Update(Player& player, const uint32& /*diff*/) { uint32 pointId = (uint32)player.movespline->currentPathIdx(); if (pointId > i_currentNode) { bool departureEvent = true; do { if (pointId == i_currentNode) { break; } i_currentNode += (uint32)departureEvent; departureEvent = !departureEvent; } while (true); } return i_currentNode < (i_path->size() - 1); } void FlightPathMovementGenerator::SetCurrentNodeAfterTeleport() { if (i_path->empty()) { return; } uint32 map0 = (*i_path)[0].mapid; for (size_t i = 1; i < i_path->size(); ++i) { if ((*i_path)[i].mapid != map0) { i_currentNode = i; return; } } } bool FlightPathMovementGenerator::GetResetPosition(Player&, float& x, float& y, float& z, float& o) const { const TaxiPathNodeEntry& node = (*i_path)[i_currentNode]; x = node.x; y = node.y; z = node.z; return true; }