feat(Core/Movement): port smooth waypoint movement from Cataclysm Preservation Project (#25106)

Co-authored-by: blinkysc <blinkysc@users.noreply.github.com>
Co-authored-by: Ovahlord <dreadkiller@gmx.de>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Kitzunu <Kitzunu@users.noreply.github.com>
This commit is contained in:
blinkysc
2026-03-23 08:08:14 -05:00
committed by GitHub
parent 3da6e30196
commit 4201acddd5
69 changed files with 844 additions and 386 deletions
+3 -3
View File
@@ -538,9 +538,9 @@ void MotionMaster::MovePath(uint32 path_id, ForcedMovement forcedMovement, PathS
}
Movement::PointsArray points;
for (auto& point : *path)
for (auto const& node : path->Nodes)
{
points.push_back(G3D::Vector3(point.second.x, point.second.y, point.second.z));
points.push_back(G3D::Vector3(node.X, node.Y, node.Z));
}
// pass the new PointsArray* to the appropriate MoveSplinePath function
@@ -918,7 +918,7 @@ void MotionMaster::MoveWaypoint(uint32 path_id, bool repeatable, PathSource path
if (_owner->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE))
return;
Mutate(new WaypointMovementGenerator<Creature>(path_id, pathSource, repeatable), MOTION_SLOT_IDLE);
Mutate(new WaypointMovementGenerator<Creature>(path_id, repeatable, pathSource), MOTION_SLOT_IDLE);
LOG_DEBUG("movement.motionmaster", "{} ({}) start moving over path(Id:{}, repeatable: {})",
_owner->IsPlayer() ? "Player" : "Creature", _owner->GetGUID().ToString(), path_id, repeatable ? "YES" : "NO");
@@ -27,181 +27,283 @@
#include "Player.h"
#include "Spell.h"
#include "Transport.h"
#include "World.h"
#include "SmartScriptMgr.h"
#include "World.h"
void WaypointMovementGenerator<Creature>::LoadPath(Creature* creature)
inline G3D::Vector3 PositionToVector3(Position const& p) { return { p.GetPositionX(), p.GetPositionY(), p.GetPositionZ() }; }
WaypointMovementGenerator<Creature>::WaypointMovementGenerator(uint32 pathId, bool repeating, PathSource pathSource) : PathMovementBase((WaypointPath const*)nullptr),
_lastSplineId(0), _pathId(pathId), _waypointDelay(0),
_waypointReached(true), _recalculateSpeed(false), _repeating(repeating), _loadedFromDB(true), _stalled(false), _hasBeenStalled(false), _done(false), _pathSource(pathSource),
_smoothSplineLaunched(false), _lastPassedSplineIdx(0)
{
switch (i_pathSource)
{
case PathSource::WAYPOINT_MGR:
{
if (!path_id)
path_id = creature->GetWaypointPath();
}
i_path = sWaypointMgr->GetPath(path_id);
break;
}
case PathSource::SMART_WAYPOINT_MGR:
WaypointMovementGenerator<Creature>::WaypointMovementGenerator(WaypointPath& path, bool repeating) : PathMovementBase((WaypointPath const*)nullptr),
_lastSplineId(0), _pathId(0), _waypointDelay(0),
_waypointReached(true), _recalculateSpeed(false), _repeating(repeating), _loadedFromDB(false), _stalled(false), _hasBeenStalled(false), _done(false), _pathSource(PathSource::WAYPOINT_MGR),
_smoothSplineLaunched(false), _lastPassedSplineIdx(0)
{
i_path = &path;
}
void WaypointMovementGenerator<Creature>::DoInitialize(Creature* creature)
{
_done = false;
if (_loadedFromDB)
{
if (!_pathId)
_pathId = creature->GetWaypointPath();
switch (_pathSource)
{
i_path = sSmartWaypointMgr->GetPath(path_id);
break;
default:
case PathSource::WAYPOINT_MGR:
i_path = sWaypointMgr->GetPath(_pathId);
break;
case PathSource::SMART_WAYPOINT_MGR:
i_path = sSmartWaypointMgr->GetPath(_pathId);
break;
}
}
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);
LOG_ERROR("sql.sql", "WaypointMovementGenerator::DoInitialize: creature {} ({}) doesn't have waypoint path id: {}",
creature->GetName(), creature->GetGUID().ToString(), _pathId);
return;
}
i_currentNode = i_path->begin()->first;
// Determine our first waypoint from the creature's stored waypoint
if (CreatureData const* creatureData = creature->GetCreatureData())
{
if (i_path->Nodes.size() > creatureData->currentwaypoint)
{
creature->UpdateCurrentWaypointInfo(creatureData->currentwaypoint, i_path->Id);
i_currentNode = creatureData->currentwaypoint;
}
}
StartMoveNow(creature);
}
void WaypointMovementGenerator<Creature>::DoInitialize(Creature* creature)
{
LoadPath(creature);
creature->AddUnitState(UNIT_STATE_ROAMING | UNIT_STATE_ROAMING_MOVE);
// Inform AI
if (CreatureAI* AI = creature->AI())
AI->WaypointPathStarted(i_path->Id);
}
void WaypointMovementGenerator<Creature>::DoFinalize(Creature* creature)
{
creature->ClearUnitState(UNIT_STATE_ROAMING | UNIT_STATE_ROAMING_MOVE);
creature->SetWalk(false);
}
void WaypointMovementGenerator<Creature>::DoReset(Creature* creature)
{
if (stalled)
// We did not reach our last waypoint before reset, treat this scenario as resuming movement.
if (!_done && !_waypointReached)
_hasBeenStalled = true;
else if (_done)
{
return;
}
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;
auto currentNodeItr = i_path->find(i_currentNode);
if (currentNodeItr->second.event_id && urand(0, 99) < currentNodeItr->second.event_chance)
{
LOG_DEBUG("maps.script", "Creature movement start script {} at point {} for {}.",
currentNodeItr->second.event_id, i_currentNode, creature->GetGUID().ToString());
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
creature->GetMap()->ScriptsStart(sWaypointScripts, currentNodeItr->second.event_id, creature, nullptr);
}
// Inform script
MovementInform(creature);
creature->UpdateWaypointID(i_currentNode);
if (currentNodeItr->second.delay)
{
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
Stop(currentNodeItr->second.delay);
// mimic IdleMovementGenerator
if (!creature->IsStopped())
creature->StopMoving();
}
}
bool WaypointMovementGenerator<Creature>::StartMove(Creature* creature)
inline void UpdateHomePosition(Creature* creature, WaypointNode const& waypointNode)
{
if (!i_path || i_path->empty())
return false;
// Xinef: Dont allow dead creatures to move
if (!creature->IsAlive())
return false;
if (Stopped())
return true;
float x = waypointNode.X;
float y = waypointNode.Y;
float z = waypointNode.Z;
float o = creature->GetOrientation();
bool transportPath = creature->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && creature->GetTransGUID();
if (m_isArrivalDone)
if (!transportPath)
creature->SetHomePosition(x, y, z, o);
else
{
if (Transport* trans = (creature->GetTransport() ? creature->GetTransport()->ToMotionTransport() : nullptr))
{
auto currentNodeItr = i_path->find(i_currentNode);
float x = currentNodeItr->second.x;
float y = currentNodeItr->second.y;
float z = currentNodeItr->second.z;
float o = creature->GetOrientation();
o -= trans->GetOrientation();
creature->SetTransportHomePosition(x, y, z, o);
trans->CalculatePassengerPosition(x, y, z, &o);
creature->SetHomePosition(x, y, z, o);
}
}
}
if (!transportPath)
creature->SetHomePosition(x, y, z, o);
else
void WaypointMovementGenerator<Creature>::ProcessWaypointArrival(Creature* creature, WaypointNode const& waypoint)
{
if (_waypointReached)
return;
if (waypoint.Delay > 0)
{
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
_waypointDelay = waypoint.Delay;
}
// Check if the waypoint path has reached its end and may not repeat. Inform AI.
if ((i_currentNode == i_path->Nodes.size() - 1) && !_repeating && !_done)
{
_done = true;
creature->UpdateCurrentWaypointInfo(0, 0);
if (CreatureAI* AI = creature->AI())
{
AI->PathEndReached(i_path->Id);
AI->WaypointPathEnded(waypoint.Id, i_path->Id);
}
}
UpdateHomePosition(creature, waypoint);
if (waypoint.EventId && urand(0, 99) < waypoint.EventChance)
{
LOG_DEBUG("maps.script", "Creature movement start script {} at point {} for {}.",
waypoint.EventId, i_currentNode, creature->GetGUID().ToString());
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
creature->GetMap()->ScriptsStart(sWaypointScripts, waypoint.EventId, creature, nullptr);
}
creature->UpdateWaypointID(waypoint.Id);
creature->UpdateCurrentWaypointInfo(waypoint.Id, i_path->Id);
// Inform AI
if (CreatureAI* AI = creature->AI())
{
AI->MovementInform(WAYPOINT_MOTION_TYPE, i_currentNode);
AI->WaypointReached(waypoint.Id, i_path->Id);
}
if (Unit* owner = creature->GetCharmerOrOwner())
{
if (UnitAI* AI = owner->GetAI())
AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, i_currentNode);
}
else
{
if (TempSummon* tempSummon = creature->ToTempSummon())
if (Unit* owner2 = tempSummon->GetSummonerUnit())
if (UnitAI* AI = owner2->GetAI())
AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, i_currentNode);
}
// All hooks called and infos updated. Time to increment the waypoint node id
if (i_path && !i_path->Nodes.empty()) // ensure that the path has not been changed in one of the hooks.
i_currentNode = (i_currentNode + 1) % i_path->Nodes.size();
_waypointReached = true;
}
void WaypointMovementGenerator<Creature>::StartMove(Creature* creature, bool relaunch /*= false*/)
{
// Formation checks. Do not launch a new spline when one of our formation members is currently in combat.
if (!relaunch)
{
if (!IsAllowedToMove(creature) || (creature->IsFormationLeader() && !creature->IsFormationLeaderMoveAllowed()))
{
_waypointDelay = 1000;
return;
}
}
// Dont allow dead creatures to move
if (!creature->IsAlive())
return;
// Step two: node selection is done, build spline data
creature->AddUnitState(UNIT_STATE_ROAMING_MOVE);
WaypointNode const& waypoint = i_path->Nodes.at(i_currentNode);
bool const useTransportPath = creature->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && creature->GetTransGUID();
Movement::MoveSplineInit init(creature);
//! If the creature is on transport, we assume waypoints set in DB are already transport offsets
if (useTransportPath)
init.DisableTransportPathTransformations();
if (waypoint.SmoothTransition && i_path->Nodes.size() > 2)
{
// Build a catmullrom spline segment, stopping at delay waypoints
init.Path().push_back(PositionToVector3(creature->GetPosition()));
bool hasDelayInSegment = false;
uint32 segmentNodes = 0;
for (uint32 i = 0; i < i_path->Nodes.size(); ++i)
{
uint32 idx = (i_currentNode + i) % i_path->Nodes.size();
WaypointNode const& node = i_path->Nodes.at(idx);
init.Path().push_back(G3D::Vector3(node.X, node.Y, node.Z));
segmentNodes++;
// Stop the segment at a waypoint with a delay
if (node.Delay > 0)
{
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
hasDelayInSegment = true;
break;
}
}
// Xinef: moved the upper IF here
uint32 lastPoint = i_path->rbegin()->first;
if ((i_currentNode == lastPoint) && !repeating) // If that's our last waypoint
// If no delays found and repeating, add wrap-around points for seamless loop
if (!hasDelayInSegment && _repeating)
{
creature->AI()->PathEndReached(path_id);
creature->GetMotionMaster()->Initialize();
return false;
for (uint32 i = 0; i < std::min<uint32>(3, i_path->Nodes.size()); ++i)
{
uint32 idx = (i_currentNode + i) % i_path->Nodes.size();
WaypointNode const& node = i_path->Nodes.at(idx);
init.Path().push_back(G3D::Vector3(node.X, node.Y, node.Z));
}
}
++i_currentNode;
if (lastPoint < i_currentNode)
i_currentNode = i_path->begin()->first;
// Need at least 3 waypoints for a meaningful catmullrom spline
if (segmentNodes >= 3)
{
init.SetFirstPointId(i_currentNode);
init.SetSmooth();
_smoothSplineLaunched = true;
_lastPassedSplineIdx = i_currentNode;
}
else
{
// Too few points for catmullrom, fall back to linear point-to-point
init.Path().clear();
init.MoveTo(G3D::Vector3(waypoint.X, waypoint.Y, waypoint.Z));
}
}
// xinef: do not initialize motion if we got stunned in movementinform
if (creature->HasUnitState(UNIT_STATE_NOT_MOVE) || creature->IsMovementPreventedByCasting())
else if (!waypoint.SplinePoints.empty())
{
return true;
// We have spline points in waypoint_data_addon table
int32 splineIndex = 0;
auto itr = waypoint.SplinePoints.begin();
if (splineIndex)
std::advance(itr, splineIndex);
init.Path().reserve(waypoint.SplinePoints.size() - splineIndex);
std::copy(itr, waypoint.SplinePoints.end(), std::back_inserter(init.Path()));
// Add starting vertex and destination
init.Path().insert(init.Path().begin(), PositionToVector3(creature->GetPosition()));
init.Path().insert(init.Path().end(), G3D::Vector3(waypoint.X, waypoint.Y, waypoint.Z));
}
auto currentNodeItr = i_path->find(i_currentNode);
WaypointData const& node = currentNodeItr->second;
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)
else
{
init.DisableTransportPathTransformations();
if (TransportBase* trans = creature->GetDirectTransport())
trans->CalculatePassengerPosition(formationDest.x, formationDest.y, formationDest.z, &formationDest.orientation);
// Smooth transition for short paths (<=2 nodes): use previous spline endpoint as start
if (waypoint.SmoothTransition && !creature->movespline->Finalized() && _lastSplineId == creature->movespline->GetId())
{
init.MoveTo(creature->movespline->FinalDestination(), G3D::Vector3(waypoint.X, waypoint.Y, waypoint.Z));
if (!init.Path().empty())
init.Path().insert(init.Path().begin(), PositionToVector3(creature->GetPosition()));
}
else
init.MoveTo(PositionToVector3(creature->GetPosition()), G3D::Vector3(waypoint.X, waypoint.Y, waypoint.Z));
}
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 (waypoint.Orientation.has_value() && waypoint.Delay > 0)
init.SetFacing(*waypoint.Orientation);
if (node.orientation.has_value() && node.delay > 0)
init.SetFacing(*node.orientation);
switch (node.move_type)
switch (waypoint.MoveType)
{
case WAYPOINT_MOVE_TYPE_LAND:
init.SetAnimation(AnimTier::Ground);
@@ -219,89 +321,203 @@ bool WaypointMovementGenerator<Creature>::StartMove(Creature* creature)
break;
}
if (creature->CanFly())
init.SetFly();
if (waypoint.Velocity > 0.f)
init.SetVelocity(waypoint.Velocity);
init.Launch();
//Call for creature group update
if (creature->GetFormation() && creature->GetFormation()->GetLeader() == creature && creature->GetFormation()->CanLeaderStartMoving())
creature->GetFormation()->LeaderStartedMoving();
if (!creature->movespline->Finalized())
_lastSplineId = creature->movespline->GetId();
return true;
// Inform formation
creature->SignalFormationMovement();
// Inform AI
if (!relaunch)
if (CreatureAI* AI = creature->AI())
AI->WaypointStarted(waypoint.Id, i_path->Id);
_waypointReached = false;
_recalculateSpeed = false;
_hasBeenStalled = false;
}
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 (stalled)
{
Stop(1000);
if (!creature || !creature->IsAlive())
return true;
}
if (creature->HasUnitState(UNIT_STATE_NOT_MOVE) || creature->IsMovementPreventedByCasting())
if (_done || !i_path || i_path->Nodes.empty())
return true;
// Stop movement if paused, rooted, or casting
if (!IsAllowedToMove(creature) && !creature->movespline->Finalized())
{
creature->StopMoving();
Stop(1000);
_lastSplineId = 0;
_smoothSplineLaunched = false;
}
// Set home position to current position.
if (!creature->movespline->Finalized())
{
bool transportPath = creature->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && creature->GetTransGUID();
if (!transportPath)
creature->SetHomePosition(creature->GetPosition());
}
// Smooth spline: track waypoint passages without rebuilding
if (_smoothSplineLaunched && creature->movespline->GetId() == _lastSplineId)
{
int32 currentIdx = creature->movespline->currentPathIdx();
// Process passed waypoints
while (_lastPassedSplineIdx < currentIdx)
{
_lastPassedSplineIdx++;
WaypointNode const& passedWp = i_path->Nodes.at(i_currentNode);
UpdateHomePosition(creature, passedWp);
creature->UpdateWaypointID(passedWp.Id);
creature->UpdateCurrentWaypointInfo(passedWp.Id, i_path->Id);
if (passedWp.EventId && urand(0, 99) < passedWp.EventChance)
{
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
creature->GetMap()->ScriptsStart(sWaypointScripts, passedWp.EventId, creature, nullptr);
}
if (CreatureAI* AI = creature->AI())
{
AI->MovementInform(WAYPOINT_MOTION_TYPE, i_currentNode);
AI->WaypointReached(passedWp.Id, i_path->Id);
}
// Advance node
i_currentNode = (i_currentNode + 1) % i_path->Nodes.size();
// If this waypoint has a delay, stop the spline and pause
if (passedWp.Delay > 0)
{
creature->StopMoving();
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
_waypointDelay = passedWp.Delay;
_waypointReached = true;
_smoothSplineLaunched = false;
if (passedWp.Orientation.has_value())
creature->SetFacingTo(*passedWp.Orientation);
return true;
}
}
if (creature->movespline->Finalized())
{
if (!_repeating)
{
// Path ended
_done = true;
_smoothSplineLaunched = false;
creature->UpdateCurrentWaypointInfo(0, 0);
if (CreatureAI* AI = creature->AI())
{
AI->PathEndReached(i_path->Id);
AI->WaypointPathEnded(i_path->Nodes.at(i_currentNode).Id, i_path->Id);
}
}
else
{
// Repeating: rebuild spline
_smoothSplineLaunched = false;
StartMove(creature);
}
}
return true;
}
// prevent a crash at empty waypoint path.
if (!i_path || i_path->empty())
return false;
// Non-smooth: per-waypoint logic
WaypointNode const& waypoint = i_path->Nodes.at(i_currentNode);
UpdateWaypointState(creature, waypoint);
// Xinef: Dont allow dead creatures to move
if (!creature->IsAlive())
return false;
// Process movement preventing timers
if (_waypointDelay > 0)
{
_waypointDelay -= diff;
if (_waypointDelay > 0)
return true;
}
if (Stopped())
if (_pauseTime.has_value())
{
if (CanMove(diff))
return StartMove(creature);
}
else
{
if (creature->movespline->Finalized())
{
OnArrived(creature);
return StartMove(creature);
}
*_pauseTime -= diff;
if (*_pauseTime > 0)
return true;
else
_pauseTime.reset();
}
// Timers are ready, let's try to move
if (IsAllowedToMove(creature) && (_waypointReached || _recalculateSpeed || _hasBeenStalled))
StartMove(creature, _recalculateSpeed || _hasBeenStalled);
return true;
}
void WaypointMovementGenerator<Creature>::MovementInform(Creature* creature)
void WaypointMovementGenerator<Creature>::Pause(uint32 timer /*= 0*/)
{
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);
}
else
{
if (TempSummon* tempSummon = creature->ToTempSummon())
if (Unit* owner = tempSummon->GetSummonerUnit())
if (UnitAI* AI = owner->GetAI())
AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, i_currentNode);
}
_stalled = timer ? false : true;
_hasBeenStalled = !_waypointReached;
_pauseTime = timer;
}
void WaypointMovementGenerator<Creature>::Pause(uint32 timer)
void WaypointMovementGenerator<Creature>::Resume(uint32 overrideTimer /*= 0*/)
{
if (timer)
i_nextMoveTime.Reset(timer);
else
{
// No timer? Will be paused forever until ::Resume is called
stalled = true;
i_nextMoveTime.Reset(1);
}
_hasBeenStalled = !_waypointReached;
_stalled = false;
if (overrideTimer)
_pauseTime = overrideTimer;
}
void WaypointMovementGenerator<Creature>::Resume(uint32 /*overrideTimer/*/)
bool WaypointMovementGenerator<Creature>::GetResetPosition(float& x, float& y, float& z)
{
stalled = false;
// prevent a crash at empty waypoint path.
if (!i_path || i_path->Nodes.empty())
return false;
ASSERT(i_currentNode < i_path->Nodes.size(), "WaypointMovementGenerator::GetResetPos: tried to reference a node id ({}) which is not included in path ({})", i_currentNode, i_path->Id);
WaypointNode const& waypoint = i_path->Nodes.at(i_currentNode);
x = waypoint.X;
y = waypoint.Y;
z = waypoint.Z;
return true;
}
bool WaypointMovementGenerator<Creature>::IsAllowedToMove(Creature* creature) const
{
if (_stalled || _done)
return false;
if (_pauseTime.has_value())
return false;
if (creature->HasUnitState(UNIT_STATE_NOT_MOVE) || creature->IsMovementPreventedByCasting())
return false;
return true;
}
void WaypointMovementGenerator<Creature>::UpdateWaypointState(Creature* creature, WaypointNode const& waypointNode)
{
if (creature->movespline->GetId() != _lastSplineId)
return;
if (creature->movespline->Finalized())
ProcessWaypointArrival(creature, waypointNode);
}
//----------------------------------------------------//
@@ -54,49 +54,43 @@ class WaypointMovementGenerator<Creature> : public MovementGeneratorMedium< Crea
public PathMovementBase<Creature, WaypointPath const*>
{
public:
WaypointMovementGenerator(uint32 _path_id = 0, PathSource pathSource = PathSource::WAYPOINT_MGR, bool _repeating = true, bool _stalled = false)
: PathMovementBase((WaypointPath const*)nullptr), i_nextMoveTime(0), m_isArrivalDone(false), path_id(_path_id), repeating(_repeating), stalled(_stalled), i_pathSource(pathSource) {}
explicit WaypointMovementGenerator(uint32 pathId = 0, bool repeating = true, PathSource pathSource = PathSource::WAYPOINT_MGR);
explicit WaypointMovementGenerator(WaypointPath& path, bool repeating = true);
~WaypointMovementGenerator() { i_path = nullptr; }
void DoInitialize(Creature*);
void DoFinalize(Creature*);
void DoReset(Creature*);
bool DoUpdate(Creature*, uint32 diff);
void Pause(uint32 timer = 0);
void Resume(uint32 overrideTimer/* = 0*/);
void MovementInform(Creature*);
void unitSpeedChanged() override { _recalculateSpeed = true; }
void Pause(uint32 timer = 0) override;
void Resume(uint32 overrideTimer = 0) override;
bool GetResetPosition(float& x, float& y, float& z) override;
MovementGeneratorType GetMovementGeneratorType() { return WAYPOINT_MOTION_TYPE; }
// now path movement implmementation
void LoadPath(Creature*);
MovementGeneratorType GetMovementGeneratorType() override { return WAYPOINT_MOTION_TYPE; }
private:
void Stop(int32 time) { i_nextMoveTime.Reset(time);}
void ProcessWaypointArrival(Creature*, WaypointNode const&);
void StartMove(Creature*, bool relaunch = false);
bool IsAllowedToMove(Creature*) const;
void UpdateWaypointState(Creature*, WaypointNode const&);
bool Stopped() { return !i_nextMoveTime.Passed();}
uint32 _lastSplineId;
uint32 _pathId;
int32 _waypointDelay;
std::optional<int32> _pauseTime;
bool _waypointReached;
bool CanMove(int32 diff)
{
i_nextMoveTime.Update(diff);
return i_nextMoveTime.Passed();
}
void OnArrived(Creature*);
bool StartMove(Creature*);
void StartMoveNow(Creature* creature)
{
i_nextMoveTime.Reset(0);
StartMove(creature);
}
TimeTrackerSmall i_nextMoveTime;
bool m_isArrivalDone;
uint32 path_id;
bool repeating;
bool stalled;
PathSource i_pathSource;
bool _recalculateSpeed;
bool _repeating;
bool _loadedFromDB;
bool _stalled;
bool _hasBeenStalled;
bool _done;
PathSource _pathSource;
bool _smoothSplineLaunched;
int32 _lastPassedSplineIdx;
};
/** FlightPathMovementGenerator generates movement of the player for the paths
@@ -122,6 +122,7 @@ namespace Movement
[[nodiscard]] Vector3 FinalDestination() const { return Initialized() ? spline.getPoint(spline.last()) : Vector3(); }
[[nodiscard]] Vector3 CurrentDestination() const { return Initialized() ? spline.getPoint(point_Idx + 1) : Vector3(); }
[[nodiscard]] int32 currentPathIdx() const;
[[nodiscard]] int32 MaxPathIdx() const { return spline.last() - 1; }
[[nodiscard]] bool HasAnimation() const { return splineflags.animation; }
[[nodiscard]] uint8 GetAnimationType() const { return splineflags.animId; }
@@ -199,6 +199,25 @@ namespace Movement
args.flags.EnableFacingAngle();
}
void MoveSplineInit::MoveTo(Vector3 const& start, Vector3 const& dest, bool generatePath, bool forceDestination)
{
if (generatePath)
{
PathGenerator path(unit);
bool result = path.CalculatePath(start.x, start.y, start.z, dest.x, dest.y, dest.z, forceDestination);
if (result && !(path.GetPathType() & PATHFIND_NOPATH))
{
MovebyPath(path.GetPath());
return;
}
}
args.path_Idx_offset = 0;
args.path.resize(2);
TransportPathTransform transform(unit, args.TransformForTransport);
args.path[1] = transform(dest);
}
void MoveSplineInit::MoveTo(const Vector3& dest, bool generatePath, bool forceDestination)
{
if (generatePath)
@@ -99,6 +99,7 @@ namespace Movement
/* Initializes simple A to B motion, A is current unit's position, B is destination
*/
void MoveTo(Vector3 const& start, Vector3 const& destination, bool generatePath = true, bool forceDestination = false);
void MoveTo(const Vector3& destination, bool generatePath = false, bool forceDestination = false);
void MoveTo(float x, float y, float z, bool generatePath = false, bool forceDestination = false);
@@ -0,0 +1,72 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ACORE_WAYPOINTDEFINES_H
#define ACORE_WAYPOINTDEFINES_H
#include "Define.h"
#include "G3D/Vector3.h"
#include <optional>
#include <vector>
enum WaypointMoveType
{
WAYPOINT_MOVE_TYPE_WALK,
WAYPOINT_MOVE_TYPE_RUN,
WAYPOINT_MOVE_TYPE_LAND,
WAYPOINT_MOVE_TYPE_TAKEOFF,
WAYPOINT_MOVE_TYPE_MAX
};
struct WaypointNode
{
WaypointNode() : Id(0), X(0.f), Y(0.f), Z(0.f), Velocity(0.f), Delay(0), EventId(0), MoveType(WAYPOINT_MOVE_TYPE_RUN), EventChance(0), SmoothTransition(false) { }
WaypointNode(uint32 id, float x, float y, float z, std::optional<float> orientation = { }, float velocity = 0.f, uint32 delay = 0, bool smoothTransition = false) :
Id(id), X(x), Y(y), Z(z), Orientation(orientation), Velocity(velocity), Delay(delay), SmoothTransition(smoothTransition)
{
EventId = 0;
MoveType = WAYPOINT_MOVE_TYPE_WALK;
EventChance = 100;
}
uint32 Id;
float X, Y, Z;
std::optional<float> Orientation;
float Velocity;
uint32 Delay;
uint32 EventId;
uint32 MoveType;
uint8 EventChance;
bool SmoothTransition;
std::vector<G3D::Vector3> SplinePoints;
};
struct WaypointPath
{
WaypointPath() : Id(0) { }
WaypointPath(uint32 _id, std::vector<WaypointNode>&& _nodes)
{
Id = _id;
Nodes = _nodes;
}
std::vector<WaypointNode> Nodes;
uint32 Id;
};
#endif
@@ -22,20 +22,6 @@
#include "QueryResult.h"
#include "Timer.h"
WaypointMgr::WaypointMgr()
{
}
WaypointMgr::~WaypointMgr()
{
for (WaypointPathContainer::iterator itr = _waypointStore.begin(); itr != _waypointStore.end(); ++itr)
{
itr->second.clear();
}
_waypointStore.clear();
}
WaypointMgr* WaypointMgr::instance()
{
static WaypointMgr instance;
@@ -46,8 +32,8 @@ void WaypointMgr::Load()
{
uint32 oldMSTime = getMSTime();
// 0 1 2 3 4 5 6 7 8 9
QueryResult result = WorldDatabase.Query("SELECT id, point, position_x, position_y, position_z, orientation, move_type, delay, action, action_chance FROM waypoint_data ORDER BY id, point");
// 0 1 2 3 4 5 6 7 8 9 10 11
QueryResult result = WorldDatabase.Query("SELECT id, point, position_x, position_y, position_z, orientation, velocity, delay, smoothTransition, move_type, action, action_chance FROM waypoint_data ORDER BY id, point");
if (!result)
{
@@ -61,66 +47,113 @@ void WaypointMgr::Load()
do
{
Field* fields = result->Fetch();
WaypointData data;
uint32 pathId = fields[0].Get<uint32>();
WaypointPath& path = _waypointStore[pathId];
float x = fields[2].Get<float>();
float y = fields[3].Get<float>();
float z = fields[4].Get<float>();
std::optional<float > o;
std::optional<float> o;
if (!fields[5].IsNull())
o = fields[5].Get<float>();
float velocity = fields[6].Get<float>();
Acore::NormalizeMapCoord(x);
Acore::NormalizeMapCoord(y);
data.id = fields[1].Get<uint32>();
data.x = x;
data.y = y;
data.z = z;
data.orientation = o;
data.move_type = fields[6].Get<uint32>();
WaypointNode waypoint;
waypoint.Id = fields[1].Get<uint32>();
waypoint.X = x;
waypoint.Y = y;
waypoint.Z = z;
if (o.has_value())
waypoint.Orientation = o;
waypoint.Velocity = velocity;
waypoint.Delay = fields[7].Get<uint32>();
waypoint.SmoothTransition = fields[8].Get<bool>();
waypoint.MoveType = fields[9].Get<uint32>();
if (data.move_type >= WAYPOINT_MOVE_TYPE_MAX)
if (waypoint.MoveType >= WAYPOINT_MOVE_TYPE_MAX)
{
//LOG_ERROR("sql.sql", "Waypoint {} in waypoint_data has invalid move_type, ignoring", wp->id);
LOG_ERROR("sql.sql", "Waypoint {} in waypoint_data has invalid move_type, ignoring", waypoint.Id);
continue;
}
data.delay = fields[7].Get<uint32>();
data.event_id = fields[8].Get<uint32>();
data.event_chance = fields[9].Get<int16>();
waypoint.EventId = fields[10].Get<uint32>();
waypoint.EventChance = fields[11].Get<int16>();
path.emplace(data.id, data);
WaypointPath& path = _waypointStore[pathId];
path.Id = pathId;
path.Nodes.push_back(std::move(waypoint));
++count;
} while (result->NextRow());
for (auto itr = _waypointStore.begin(); itr != _waypointStore.end(); )
{
uint32 first = itr->second.begin()->first;
uint32 last = itr->second.rbegin()->first;
if (last - first + 1 != itr->second.size())
{
LOG_ERROR("sql.sql", "Waypoint {} in waypoint_data has non-contiguous pointids, skipping", itr->first);
itr = _waypointStore.erase(itr);
}
else
++itr;
}
LOG_INFO("server.loading", ">> Loaded {} waypoints in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void WaypointMgr::LoadWaypointAddons()
{
uint32 oldMSTime = getMSTime();
// 0 1 2 3 4 5
QueryResult result = WorldDatabase.Query("SELECT PathID, PointID, SplinePointIndex, PositionX, PositionY, PositionZ FROM waypoint_data_addon ORDER BY PathID, PointID, SplinePointIndex");
if (!result)
{
LOG_INFO("server.loading", ">> Loaded 0 waypoint addon data. DB table `waypoint_data_addon` is empty!");
LOG_INFO("server.loading", " ");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint32 pathId = fields[0].Get<uint32>();
auto it = _waypointStore.find(pathId);
if (it == _waypointStore.end())
{
LOG_ERROR("sql.sql", "Tried to load waypoint_data_addon data for PathID {} but there is no such path in waypoint_data. Ignoring.", pathId);
continue;
}
WaypointPath& path = it->second;
uint32 pointId = fields[1].Get<uint32>();
auto itr = std::find_if(path.Nodes.begin(), path.Nodes.end(), [pointId](WaypointNode const& node)
{
return node.Id == pointId;
});
if (itr == path.Nodes.end())
{
LOG_ERROR("sql.sql", "Tried to load waypoint_data_addon data for PointID {} of PathID {} but there is no such point in waypoint_data. Ignoring.", pointId, pathId);
continue;
}
float x = fields[3].Get<float>();
float y = fields[4].Get<float>();
float z = fields[5].Get<float>();
Acore::NormalizeMapCoord(x);
Acore::NormalizeMapCoord(y);
itr->SplinePoints.push_back(G3D::Vector3(x, y, z));
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} waypoint addon data in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void WaypointMgr::ReloadPath(uint32 id)
{
WaypointPathContainer::iterator itr = _waypointStore.find(id);
auto itr = _waypointStore.find(id);
if (itr != _waypointStore.end())
{
_waypointStore.erase(itr);
}
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_WAYPOINT_DATA_BY_ID);
@@ -131,13 +164,10 @@ void WaypointMgr::ReloadPath(uint32 id)
if (!result)
return;
WaypointPath& path = _waypointStore[id];
std::vector<WaypointNode> values;
do
{
Field* fields = result->Fetch();
WaypointData data;
float x = fields[1].Get<float>();
float y = fields[2].Get<float>();
float z = fields[3].Get<float>();
@@ -145,26 +175,34 @@ void WaypointMgr::ReloadPath(uint32 id)
if (!fields[4].IsNull())
o = fields[4].Get<float>();
float velocity = fields[5].Get<float>();
Acore::NormalizeMapCoord(x);
Acore::NormalizeMapCoord(y);
data.id = fields[0].Get<uint32>();
data.x = x;
data.y = y;
data.z = z;
data.orientation = o;
data.move_type = fields[5].Get<uint32>();
WaypointNode waypoint;
waypoint.Id = fields[0].Get<uint32>();
waypoint.X = x;
waypoint.Y = y;
waypoint.Z = z;
if (o.has_value())
waypoint.Orientation = o;
waypoint.Velocity = velocity;
waypoint.Delay = fields[6].Get<uint32>();
waypoint.SmoothTransition = fields[7].Get<bool>();
waypoint.MoveType = fields[8].Get<uint32>();
if (data.move_type >= WAYPOINT_MOVE_TYPE_MAX)
if (waypoint.MoveType >= WAYPOINT_MOVE_TYPE_MAX)
{
//LOG_ERROR("sql.sql", "Waypoint {} in waypoint_data has invalid move_type, ignoring", wp->id);
LOG_ERROR("sql.sql", "Waypoint {} in waypoint_data has invalid move_type, ignoring", waypoint.Id);
continue;
}
data.delay = fields[6].Get<uint32>();
data.event_id = fields[7].Get<uint32>();
data.event_chance = fields[8].Get<uint8>();
waypoint.EventId = fields[9].Get<uint32>();
waypoint.EventChance = fields[10].Get<uint8>();
path.emplace(data.id, data);
values.push_back(std::move(waypoint));
} while (result->NextRow());
_waypointStore[id] = WaypointPath(id, std::move(values));
}
@@ -18,35 +18,8 @@
#ifndef ACORE_WAYPOINTMANAGER_H
#define ACORE_WAYPOINTMANAGER_H
#include "Define.h"
#include <optional>
#include "WaypointDefines.h"
#include <unordered_map>
#include <vector>
#include <map>
enum WaypointMoveType
{
WAYPOINT_MOVE_TYPE_WALK,
WAYPOINT_MOVE_TYPE_RUN,
WAYPOINT_MOVE_TYPE_LAND,
WAYPOINT_MOVE_TYPE_TAKEOFF,
WAYPOINT_MOVE_TYPE_MAX
};
struct WaypointData
{
uint32 id;
float x, y, z;
std::optional<float> orientation;
uint32 delay;
uint32 event_id = 0;
uint32 move_type = 0;
uint8 event_chance = 0;
};
typedef std::map<uint32, WaypointData> WaypointPath;
typedef std::unordered_map<uint32, WaypointPath> WaypointPathContainer;
class WaypointMgr
{
@@ -59,10 +32,13 @@ public:
// Loads all paths from database, should only run on startup
void Load();
// Loads additional path data for waypoints from database. Should only be called on startup.
void LoadWaypointAddons();
// Returns the path from a given id
WaypointPath const* GetPath(uint32 id) const
{
WaypointPathContainer::const_iterator itr = _waypointStore.find(id);
auto itr = _waypointStore.find(id);
if (itr != _waypointStore.end())
return &itr->second;
@@ -70,10 +46,9 @@ public:
}
private:
WaypointMgr();
~WaypointMgr();
WaypointMgr() { }
WaypointPathContainer _waypointStore;
std::unordered_map<uint32, WaypointPath> _waypointStore;
};
#define sWaypointMgr WaypointMgr::instance()