EverWrath/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp
UltraNix 572a680c16
fix(Core/Movement): Improvements to taxi flight movement generator: (#12347)
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.
2022-08-01 23:21:11 -03:00

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");
}
}