Changed multi-segment taxi paths to fly nearby flight masters along the way, not directly through them. Taxi cost on multi-segment paths is now charged per segment when it is started. Properly send taxi node status on login, as well as if the taxi master is out of range. Apply reputation discount to all points in multi-segment paths. Properly clean up list of taxi destinations upon arrival at final node. Teleport players to the destination taxi node location instead of their current ground position. Don't start a spline with just 1 point in FlightPathMovementGenerator Source: TrinityCore.
522 lines
18 KiB
C++
522 lines
18 KiB
C++
/*
|
|
* 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 Affero General Public License as published by the
|
|
* Free Software Foundation; either version 3 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 Affero 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 "WaypointMovementGenerator.h"
|
|
#include "Creature.h"
|
|
#include "CreatureAI.h"
|
|
#include "CreatureGroups.h"
|
|
#include "GameTime.h"
|
|
#include "MapMgr.h"
|
|
#include "MoveSpline.h"
|
|
#include "MoveSplineInit.h"
|
|
#include "ObjectMgr.h"
|
|
#include "Player.h"
|
|
#include "Spell.h"
|
|
#include "Transport.h"
|
|
#include "World.h"
|
|
|
|
void WaypointMovementGenerator<Creature>::LoadPath(Creature* creature)
|
|
{
|
|
if (!path_id)
|
|
path_id = creature->GetWaypointPath();
|
|
|
|
i_path = sWaypointMgr->GetPath(path_id);
|
|
|
|
if (!i_path)
|
|
{
|
|
// No movement found for entry
|
|
LOG_ERROR("sql.sql", "WaypointMovementGenerator::LoadPath: creature {} ({}) doesn't have waypoint path id: {}",
|
|
creature->GetName(), creature->GetGUID().ToString(), path_id);
|
|
return;
|
|
}
|
|
|
|
StartMoveNow(creature);
|
|
}
|
|
|
|
void WaypointMovementGenerator<Creature>::DoInitialize(Creature* creature)
|
|
{
|
|
LoadPath(creature);
|
|
creature->AddUnitState(UNIT_STATE_ROAMING | UNIT_STATE_ROAMING_MOVE);
|
|
}
|
|
|
|
void WaypointMovementGenerator<Creature>::DoFinalize(Creature* creature)
|
|
{
|
|
creature->ClearUnitState(UNIT_STATE_ROAMING | UNIT_STATE_ROAMING_MOVE);
|
|
creature->SetWalk(false);
|
|
}
|
|
|
|
void WaypointMovementGenerator<Creature>::DoReset(Creature* creature)
|
|
{
|
|
creature->AddUnitState(UNIT_STATE_ROAMING | UNIT_STATE_ROAMING_MOVE);
|
|
StartMoveNow(creature);
|
|
}
|
|
|
|
void WaypointMovementGenerator<Creature>::OnArrived(Creature* creature)
|
|
{
|
|
if (!i_path || i_path->empty())
|
|
return;
|
|
if (m_isArrivalDone)
|
|
return;
|
|
|
|
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
|
|
m_isArrivalDone = true;
|
|
|
|
if (i_path->at(i_currentNode)->event_id && urand(0, 99) < i_path->at(i_currentNode)->event_chance)
|
|
{
|
|
LOG_DEBUG("maps.script", "Creature movement start script {} at point {} for {}.",
|
|
i_path->at(i_currentNode)->event_id, i_currentNode, creature->GetGUID().ToString());
|
|
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
|
|
creature->GetMap()->ScriptsStart(sWaypointScripts, i_path->at(i_currentNode)->event_id, creature, nullptr);
|
|
}
|
|
|
|
// Inform script
|
|
MovementInform(creature);
|
|
creature->UpdateWaypointID(i_currentNode);
|
|
|
|
if (i_path->at(i_currentNode)->delay)
|
|
{
|
|
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
|
|
Stop(i_path->at(i_currentNode)->delay);
|
|
}
|
|
}
|
|
|
|
bool WaypointMovementGenerator<Creature>::StartMove(Creature* creature)
|
|
{
|
|
if (!i_path || i_path->empty())
|
|
return false;
|
|
|
|
// Xinef: Dont allow dead creatures to move
|
|
if (!creature->IsAlive())
|
|
return false;
|
|
|
|
if (Stopped())
|
|
return true;
|
|
|
|
bool transportPath = creature->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && creature->GetTransGUID();
|
|
|
|
if (m_isArrivalDone)
|
|
{
|
|
// Xinef: not true... update this at every waypoint!
|
|
//if ((i_currentNode == i_path->size() - 1) && !repeating) // If that's our last waypoint
|
|
{
|
|
float x = i_path->at(i_currentNode)->x;
|
|
float y = i_path->at(i_currentNode)->y;
|
|
float z = i_path->at(i_currentNode)->z;
|
|
float o = creature->GetOrientation();
|
|
|
|
if (!transportPath)
|
|
creature->SetHomePosition(x, y, z, o);
|
|
else
|
|
{
|
|
if (Transport* trans = (creature->GetTransport() ? creature->GetTransport()->ToMotionTransport() : nullptr))
|
|
{
|
|
o -= trans->GetOrientation();
|
|
creature->SetTransportHomePosition(x, y, z, o);
|
|
trans->CalculatePassengerPosition(x, y, z, &o);
|
|
creature->SetHomePosition(x, y, z, o);
|
|
}
|
|
else
|
|
transportPath = false;
|
|
// else if (vehicle) - this should never happen, vehicle offsets are const
|
|
}
|
|
}
|
|
|
|
// Xinef: moved the upper IF here
|
|
if ((i_currentNode == i_path->size() - 1) && !repeating) // If that's our last waypoint
|
|
{
|
|
creature->AI()->PathEndReached(path_id);
|
|
creature->GetMotionMaster()->Initialize();
|
|
return false;
|
|
}
|
|
|
|
i_currentNode = (i_currentNode + 1) % i_path->size();
|
|
}
|
|
|
|
// xinef: do not initialize motion if we got stunned in movementinform
|
|
if (creature->HasUnitState(UNIT_STATE_NOT_MOVE) || creature->IsMovementPreventedByCasting())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
WaypointData const* node = i_path->at(i_currentNode);
|
|
|
|
m_isArrivalDone = false;
|
|
|
|
creature->AddUnitState(UNIT_STATE_ROAMING_MOVE);
|
|
|
|
Movement::Location formationDest(node->x, node->y, node->z, 0.0f);
|
|
Movement::MoveSplineInit init(creature);
|
|
|
|
//! If creature is on transport, we assume waypoints set in DB are already transport offsets
|
|
if (transportPath)
|
|
{
|
|
init.DisableTransportPathTransformations();
|
|
if (TransportBase* trans = creature->GetDirectTransport())
|
|
trans->CalculatePassengerPosition(formationDest.x, formationDest.y, formationDest.z, &formationDest.orientation);
|
|
}
|
|
|
|
float z = node->z;
|
|
creature->UpdateAllowedPositionZ(node->x, node->y, z);
|
|
//! Do not use formationDest here, MoveTo requires transport offsets due to DisableTransportPathTransformations() call
|
|
//! but formationDest contains global coordinates
|
|
init.MoveTo(node->x, node->y, z, true, true);
|
|
|
|
if (node->orientation.has_value() && node->delay > 0)
|
|
init.SetFacing(*node->orientation);
|
|
|
|
switch (node->move_type)
|
|
{
|
|
case WAYPOINT_MOVE_TYPE_LAND:
|
|
init.SetAnimation(Movement::ToGround);
|
|
break;
|
|
case WAYPOINT_MOVE_TYPE_TAKEOFF:
|
|
init.SetAnimation(Movement::ToFly);
|
|
break;
|
|
case WAYPOINT_MOVE_TYPE_RUN:
|
|
init.SetWalk(false);
|
|
break;
|
|
case WAYPOINT_MOVE_TYPE_WALK:
|
|
init.SetWalk(true);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
init.Launch();
|
|
|
|
//Call for creature group update
|
|
if (creature->GetFormation() && creature->GetFormation()->GetLeader() == creature)
|
|
creature->GetFormation()->LeaderMoveTo(formationDest.x, formationDest.y, formationDest.z, node->move_type == WAYPOINT_MOVE_TYPE_RUN);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WaypointMovementGenerator<Creature>::DoUpdate(Creature* creature, uint32 diff)
|
|
{
|
|
// Waypoint movement can be switched on/off
|
|
// This is quite handy for escort quests and other stuff
|
|
if (creature->HasUnitState(UNIT_STATE_NOT_MOVE) || creature->IsMovementPreventedByCasting())
|
|
{
|
|
creature->StopMoving();
|
|
Stop(1000);
|
|
return true;
|
|
}
|
|
|
|
// prevent a crash at empty waypoint path.
|
|
if (!i_path || i_path->empty())
|
|
return false;
|
|
|
|
// Xinef: Dont allow dead creatures to move
|
|
if (!creature->IsAlive())
|
|
return false;
|
|
|
|
if (Stopped())
|
|
{
|
|
if (CanMove(diff))
|
|
return StartMove(creature);
|
|
}
|
|
else
|
|
{
|
|
if (creature->IsStopped())
|
|
Stop(sWorld->getIntConfig(CONFIG_WAYPOINT_MOVEMENT_STOP_TIME_FOR_PLAYER) * IN_MILLISECONDS);
|
|
else
|
|
{
|
|
bool finished = creature->movespline->Finalized();
|
|
// xinef: code to detect pre-empetively if we should start movement to next waypoint
|
|
// xinef: do not start pre-empetive movement if current node has delay or we are ending waypoint movement
|
|
//if (!finished && !i_path->at(i_currentNode)->delay && ((i_currentNode != i_path->size() - 1) || repeating))
|
|
// finished = (creature->movespline->_Spline().length(creature->movespline->_currentSplineIdx() + 1) - creature->movespline->timePassed()) < 200;
|
|
|
|
if (finished)
|
|
{
|
|
OnArrived(creature);
|
|
return StartMove(creature);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WaypointMovementGenerator<Creature>::MovementInform(Creature* creature)
|
|
{
|
|
if (creature->AI())
|
|
creature->AI()->MovementInform(WAYPOINT_MOTION_TYPE, i_currentNode);
|
|
|
|
if (Unit* owner = creature->GetCharmerOrOwner())
|
|
{
|
|
if (UnitAI* AI = owner->GetAI())
|
|
{
|
|
AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, i_currentNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------//
|
|
|
|
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();
|
|
}
|
|
|
|
#define SKIP_SPLINE_POINT_DISTANCE_SQ (40.0f * 40.0f)
|
|
|
|
bool IsNodeIncludedInShortenedPath(TaxiPathNodeEntry const* p1, TaxiPathNodeEntry const* p2)
|
|
{
|
|
return p1->mapid != p2->mapid || std::pow(p1->x - p2->x, 2) + std::pow(p1->y - p2->y, 2) > SKIP_SPLINE_POINT_DISTANCE_SQ;
|
|
}
|
|
|
|
void FlightPathMovementGenerator::LoadPath(Player* player)
|
|
{
|
|
_pointsForPathSwitch.clear();
|
|
std::deque<uint32> const& taxi = player->m_taxi.GetPath();
|
|
float discount = player->GetReputationPriceDiscount(player->m_taxi.GetFlightMasterFactionTemplate());
|
|
for (uint32 src = 0, dst = 1; dst < taxi.size(); src = dst++)
|
|
{
|
|
uint32 path, cost;
|
|
sObjectMgr->GetTaxiPath(taxi[src], taxi[dst], path, cost);
|
|
if (path > sTaxiPathNodesByPath.size())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TaxiPathNodeList const& nodes = sTaxiPathNodesByPath[path];
|
|
if (!nodes.empty())
|
|
{
|
|
TaxiPathNodeEntry const* start = nodes[0];
|
|
TaxiPathNodeEntry const* end = nodes[nodes.size() - 1];
|
|
bool passedPreviousSegmentProximityCheck = false;
|
|
for (uint32 i = 0; i < nodes.size(); ++i)
|
|
{
|
|
sMapMgr->CreateMap(nodes[i]->mapid, player)->SummonCreature(1, { nodes[i]->x, nodes[i]->y, nodes[i]->z, 0.0f })->SetLevel(i ? i : 1);
|
|
|
|
if (passedPreviousSegmentProximityCheck || !src || i_path.empty() || IsNodeIncludedInShortenedPath(i_path[i_path.size() - 1], nodes[i]))
|
|
{
|
|
if ((!src || (IsNodeIncludedInShortenedPath(start, nodes[i]) && i >= 2)) &&
|
|
(dst == taxi.size() - 1 || (IsNodeIncludedInShortenedPath(end, nodes[i]) && i < nodes.size() - 1)))
|
|
{
|
|
passedPreviousSegmentProximityCheck = true;
|
|
i_path.push_back(nodes[i]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
i_path.pop_back();
|
|
--_pointsForPathSwitch.back().PathIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
_pointsForPathSwitch.push_back({ uint32(i_path.size() - 1), int32(ceil(cost * discount)) });
|
|
}
|
|
}
|
|
|
|
void FlightPathMovementGenerator::DoInitialize(Player* player)
|
|
{
|
|
Reset(player);
|
|
InitEndGridInfo();
|
|
}
|
|
|
|
void FlightPathMovementGenerator::DoFinalize(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_STATE_IN_FLIGHT);
|
|
|
|
uint32 taxiNodeId = player->m_taxi.GetTaxiDestination();
|
|
player->m_taxi.ClearTaxiDestinations();
|
|
player->Dismount();
|
|
player->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE | UNIT_FLAG_TAXI_FLIGHT);
|
|
|
|
if (player->m_taxi.empty())
|
|
{
|
|
player->getHostileRefMgr().setOnlineOfflineState(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();
|
|
|
|
// When the player reaches the last flight point, teleport to destination taxi node location
|
|
if (TaxiNodesEntry const* node = sTaxiNodesStore.LookupEntry(taxiNodeId))
|
|
{
|
|
player->SetFallInformation(GameTime::GetGameTime().count(), player->GetPositionZ());
|
|
player->TeleportTo(node->map_id, node->x, node->y, node->z, player->GetOrientation());
|
|
}
|
|
}
|
|
|
|
player->RemovePlayerFlag(PLAYER_FLAGS_TAXI_BENCHMARK);
|
|
}
|
|
|
|
#define PLAYER_FLIGHT_SPEED 32.0f
|
|
|
|
void FlightPathMovementGenerator::DoReset(Player* player)
|
|
{
|
|
uint32 end = GetPathAtMapEnd();
|
|
uint32 currentNodeId = GetCurrentNode();
|
|
|
|
if (currentNodeId == end)
|
|
{
|
|
LOG_DEBUG("movement.flightpath", "FlightPathMovementGenerator::DoReset: trying to start a flypath from the end point. {}", player->GetGUID().ToString().c_str());
|
|
return;
|
|
}
|
|
|
|
player->getHostileRefMgr().setOnlineOfflineState(false);
|
|
player->AddUnitState(UNIT_STATE_IN_FLIGHT);
|
|
player->SetUnitFlag(UNIT_FLAG_DISABLE_MOVE | UNIT_FLAG_TAXI_FLIGHT);
|
|
|
|
Movement::MoveSplineInit init(player);
|
|
// Providing a starting vertex since the taxi paths do not provide such
|
|
init.Path().push_back(G3D::Vector3(player->GetPositionX(), player->GetPositionY(), player->GetPositionZ()));
|
|
for (uint32 i = currentNodeId; 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::DoUpdate(Player* player, uint32 /*diff*/)
|
|
{
|
|
// skipping the first spline path point because it's our starting point and not a taxi path point
|
|
uint32 pointId = player->movespline->currentPathIdx() <= 0 ? 0 : player->movespline->currentPathIdx() - 1;
|
|
if (pointId > i_currentNode && i_currentNode < i_path.size() - 1)
|
|
{
|
|
bool departureEvent = true;
|
|
do
|
|
{
|
|
ASSERT(i_currentNode < i_path.size(), "Point Id: {}\n{}", pointId, player->GetGUID().ToString().c_str());
|
|
|
|
DoEventIfAny(player, i_path[i_currentNode], departureEvent);
|
|
while (!_pointsForPathSwitch.empty() && _pointsForPathSwitch.front().PathIndex <= i_currentNode)
|
|
{
|
|
_pointsForPathSwitch.pop_front();
|
|
player->m_taxi.NextTaxiDestination();
|
|
if (!_pointsForPathSwitch.empty())
|
|
{
|
|
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_TRAVELLING, _pointsForPathSwitch.front().Cost);
|
|
player->ModifyMoney(-_pointsForPathSwitch.front().Cost);
|
|
}
|
|
}
|
|
|
|
if (pointId == i_currentNode)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (i_currentNode == _preloadTargetNode)
|
|
{
|
|
PreloadEndGrid();
|
|
}
|
|
|
|
i_currentNode += departureEvent ? 1 : 0;
|
|
departureEvent = !departureEvent;
|
|
} while (i_currentNode < i_path.size() - 1);
|
|
}
|
|
|
|
return i_currentNode < (i_path.size() - 1);
|
|
}
|
|
|
|
void FlightPathMovementGenerator::SetCurrentNodeAfterTeleport()
|
|
{
|
|
if (i_path.empty() || i_currentNode >= i_path.size())
|
|
{
|
|
return;
|
|
}
|
|
|
|
uint32 map0 = i_path[i_currentNode]->mapid;
|
|
for (size_t i = i_currentNode + 1; i < i_path.size(); ++i)
|
|
{
|
|
if (i_path[i]->mapid != map0)
|
|
{
|
|
i_currentNode = i;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FlightPathMovementGenerator::DoEventIfAny(Player* player, TaxiPathNodeEntry const* node, bool departure)
|
|
{
|
|
if (uint32 eventid = departure ? node->departureEventID : node->arrivalEventID)
|
|
{
|
|
LOG_DEBUG("maps.script", "Taxi {} event {} of node {} of path {} for player {}", departure ? "departure" : "arrival", eventid, node->index, node->path, player->GetName().c_str());
|
|
player->GetMap()->ScriptsStart(sEventScripts, eventid, player, player);
|
|
}
|
|
}
|
|
|
|
bool FlightPathMovementGenerator::GetResetPos(Player*, float& x, float& y, float& z)
|
|
{
|
|
TaxiPathNodeEntry const* node = i_path[i_currentNode];
|
|
x = node->x;
|
|
y = node->y;
|
|
z = node->z;
|
|
return true;
|
|
}
|
|
|
|
void FlightPathMovementGenerator::InitEndGridInfo()
|
|
{
|
|
/*! Storage to preload flightmaster grid at end of flight. For multi-stop flights, this will
|
|
be reinitialized for each flightmaster at the end of each spline (or stop) in the flight. */
|
|
uint32 nodeCount = i_path.size(); //! Number of nodes in path.
|
|
_endMapId = i_path[nodeCount - 1]->mapid; //! MapId of last node
|
|
|
|
// pussywizard:
|
|
{
|
|
_preloadTargetNode = nodeCount - 1;
|
|
for (uint8 i = 3; i > 0; --i)
|
|
if (nodeCount >= i && _endMapId == i_path[nodeCount - i]->mapid)
|
|
{
|
|
_preloadTargetNode = nodeCount - i;
|
|
break;
|
|
}
|
|
//_preloadTargetNode = nodeCount - 3; // pussywizard: this can be on other map
|
|
}
|
|
|
|
_endGridX = i_path[nodeCount - 1]->x;
|
|
_endGridY = i_path[nodeCount - 1]->y;
|
|
}
|
|
|
|
void FlightPathMovementGenerator::PreloadEndGrid()
|
|
{
|
|
// used to preload the final grid where the flightmaster is
|
|
Map* endMap = sMapMgr->FindBaseNonInstanceMap(_endMapId);
|
|
|
|
// Load the grid
|
|
if (endMap)
|
|
{
|
|
LOG_DEBUG("misc", "Preloading grid ({}, {}) for map %u at node index {}/{}", _endGridX, _endGridY, _endMapId, _preloadTargetNode, (uint32)(i_path.size() - 1));
|
|
endMap->LoadGrid(_endGridX, _endGridY);
|
|
}
|
|
else
|
|
{
|
|
LOG_DEBUG("misc", "Unable to determine map to preload flightmaster grid");
|
|
}
|
|
}
|