aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/Maps
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2022-06-07 16:02:51 +0200
committerShauren <shauren.trinity@gmail.com>2022-06-07 16:02:51 +0200
commita2c1b699e6e4d30c752b65241bc23191920a86fd (patch)
tree7e580185397acd299326a714da71406f15c30ddc /src/server/game/Maps
parentb9353041a6cce58d972b63d5138517274a1bb6d6 (diff)
Core/Transports: Path generation rewrite v2
* No TaxiPathNode row is skipped anymore (events/delays on first node now work) * Fixed transport animation synchronization with client * Fixed stoppable transports randomly resuming their path after relogging Closes #27985
Diffstat (limited to 'src/server/game/Maps')
-rw-r--r--src/server/game/Maps/TransportMgr.cpp533
-rw-r--r--src/server/game/Maps/TransportMgr.h95
2 files changed, 379 insertions, 249 deletions
diff --git a/src/server/game/Maps/TransportMgr.cpp b/src/server/game/Maps/TransportMgr.cpp
index 5bcd0db73b0..3c8fcc3498a 100644
--- a/src/server/game/Maps/TransportMgr.cpp
+++ b/src/server/game/Maps/TransportMgr.cpp
@@ -29,12 +29,146 @@
#include "Spline.h"
#include "Transport.h"
-bool KeyFrame::IsStopFrame() const
+TransportPathLeg::TransportPathLeg() = default;
+TransportPathLeg::~TransportPathLeg() = default;
+TransportPathLeg::TransportPathLeg(TransportPathLeg&&) noexcept = default;
+TransportPathLeg& TransportPathLeg::operator=(TransportPathLeg&&) noexcept = default;
+
+TransportTemplate::TransportTemplate() = default;
+TransportTemplate::~TransportTemplate() = default;
+TransportTemplate::TransportTemplate(TransportTemplate&&) noexcept = default;
+TransportTemplate& TransportTemplate::operator=(TransportTemplate&&) noexcept = default;
+
+Optional<Position> TransportTemplate::ComputePosition(uint32 time, TransportMovementState* moveState, size_t* legIndex) const
{
- return (Node->Flags & TAXI_PATH_NODE_FLAG_STOP) != 0;
+ time %= TotalPathTime;
+
+ // find leg
+ auto legItr = PathLegs.begin();
+ while (legItr->StartTimestamp + legItr->Duration <= time)
+ {
+ ++legItr;
+
+ if (legItr == PathLegs.end())
+ return {};
+ }
+
+ // find segment
+ uint32 prevSegmentTime = legItr->StartTimestamp;
+ auto segmentItr = legItr->Segments.begin();
+ double distanceMoved = 0.0;
+ bool isOnPause = false;
+ for (; segmentItr != std::prev(legItr->Segments.end()); ++segmentItr)
+ {
+ if (time < segmentItr->SegmentEndArrivalTimestamp)
+ break;
+
+ distanceMoved = segmentItr->DistanceFromLegStartAtEnd;
+ if (time < segmentItr->SegmentEndArrivalTimestamp + segmentItr->Delay)
+ {
+ isOnPause = true;
+ break;
+ }
+
+ prevSegmentTime = segmentItr->SegmentEndArrivalTimestamp + segmentItr->Delay;
+ }
+
+ if (!isOnPause)
+ distanceMoved += CalculateDistanceMoved(
+ double(time - prevSegmentTime) * 0.001,
+ double(segmentItr->SegmentEndArrivalTimestamp - prevSegmentTime) * 0.001,
+ segmentItr == legItr->Segments.begin(),
+ segmentItr == std::prev(legItr->Segments.end()));
+
+ Movement::SplineBase::index_type splineIndex;
+ float splinePointProgress;
+ legItr->Spline->computeIndex(std::fmin(distanceMoved / legItr->Spline->length(), 1.0), splineIndex, splinePointProgress);
+
+ G3D::Vector3 pos, dir;
+ legItr->Spline->evaluate_percent(splineIndex, splinePointProgress, pos);
+ legItr->Spline->evaluate_derivative(splineIndex, splinePointProgress, dir);
+
+ if (moveState)
+ *moveState = isOnPause ? TransportMovementState::WaitingOnPauseWaypoint : TransportMovementState::Moving;
+
+ if (legIndex)
+ *legIndex = std::distance(PathLegs.begin(), legItr);
+
+ return Position(pos.x, pos.y, pos.z, std::atan2(dir.y, dir.x) + float(M_PI));
}
-TransportTemplate::~TransportTemplate() = default;
+double TransportTemplate::CalculateDistanceMoved(double timePassedInSegment, double segmentDuration, bool isFirstSegment, bool isLastSegment) const
+{
+ if (isFirstSegment)
+ {
+ if (!isLastSegment)
+ {
+ double accelerationTime = std::fmin(AccelerationTime, segmentDuration);
+ double segmentTimeAtFullSpeed = segmentDuration - accelerationTime;
+ if (timePassedInSegment <= segmentTimeAtFullSpeed)
+ {
+ return timePassedInSegment * Speed;
+ }
+ else
+ {
+ double segmentAccelerationTime = timePassedInSegment - segmentTimeAtFullSpeed;
+ double segmentAccelerationDistance = AccelerationRate * accelerationTime;
+ double segmentDistanceAtFullSpeed = segmentTimeAtFullSpeed * Speed;
+ return (2.0 * segmentAccelerationDistance - segmentAccelerationTime * AccelerationRate) * 0.5 * segmentAccelerationTime + segmentDistanceAtFullSpeed;
+ }
+ }
+
+ return timePassedInSegment * Speed;
+ }
+
+ if (isLastSegment)
+ {
+ if (!isFirstSegment)
+ {
+ if (timePassedInSegment <= std::fmin(AccelerationTime, segmentDuration))
+ return AccelerationRate * timePassedInSegment * 0.5 * timePassedInSegment;
+ else
+ return (timePassedInSegment - AccelerationTime) * Speed + AccelerationDistance;
+ }
+
+ return timePassedInSegment * Speed;
+ }
+
+ double accelerationTime = std::fmin(segmentDuration * 0.5, AccelerationTime);
+ if (timePassedInSegment <= segmentDuration - accelerationTime)
+ {
+ if (timePassedInSegment <= accelerationTime)
+ return AccelerationRate * timePassedInSegment * 0.5 * timePassedInSegment;
+ else
+ return (timePassedInSegment - AccelerationTime) * Speed + AccelerationDistance;
+ }
+ else
+ {
+ double segmentTimeSpentAccelerating = timePassedInSegment - (segmentDuration - accelerationTime);
+ return (segmentDuration - 2 * accelerationTime) * Speed
+ + AccelerationRate * accelerationTime * 0.5 * accelerationTime
+ + (2.0 * AccelerationRate * accelerationTime - segmentTimeSpentAccelerating * AccelerationRate) * 0.5 * segmentTimeSpentAccelerating;
+ }
+}
+
+uint32 TransportTemplate::GetNextPauseWaypointTimestamp(uint32 time) const
+{
+ auto legItr = PathLegs.begin();
+ while (legItr->StartTimestamp + legItr->Duration <= time)
+ {
+ ++legItr;
+
+ if (legItr == PathLegs.end())
+ return time;
+ }
+
+ auto segmentItr = legItr->Segments.begin();
+ for (; segmentItr != std::prev(legItr->Segments.end()); ++segmentItr)
+ if (time < segmentItr->SegmentEndArrivalTimestamp + segmentItr->Delay)
+ break;
+
+ return segmentItr->SegmentEndArrivalTimestamp + segmentItr->Delay;
+}
TransportMgr::TransportMgr() = default;
@@ -87,12 +221,14 @@ void TransportMgr::LoadTransportTemplates()
// paths are generated per template, saves us from generating it again in case of instanced transports
TransportTemplate& transport = _transportTemplates[entry];
- transport.entry = entry;
- GeneratePath(goInfo, &transport);
+
+ std::set<uint32> mapsUsed;
+
+ GeneratePath(goInfo, &transport, &mapsUsed);
// transports in instance are only on one map
- if (transport.inInstance)
- _instanceTransports[*transport.mapsUsed.begin()].insert(entry);
+ if (transport.InInstance)
+ _instanceTransports[*mapsUsed.begin()].insert(entry);
++count;
} while (result->NextRow());
@@ -203,239 +339,206 @@ public:
Movement::PointsArray& _points;
};
-void TransportMgr::GeneratePath(GameObjectTemplate const* goInfo, TransportTemplate* transport)
+static void InitializeLeg(TransportPathLeg* leg, std::vector<TransportPathEvent>* outEvents, std::vector<TaxiPathNodeEntry const*> const& pathPoints, std::vector<TaxiPathNodeEntry const*> const& pauses,
+ std::vector<TaxiPathNodeEntry const*> const& events, GameObjectTemplate const* goInfo, uint32& totalTime)
{
- uint32 pathId = goInfo->moTransport.taxiPathID;
- TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathId];
- std::vector<KeyFrame>& keyFrames = transport->keyFrames;
- Movement::PointsArray splinePath, allPoints;
- bool mapChange = false;
- for (size_t i = 0; i < path.size(); ++i)
- allPoints.push_back(G3D::Vector3(path[i]->Loc.X, path[i]->Loc.Y, path[i]->Loc.Z));
-
- // Add extra points to allow derivative calculations for all path nodes
- allPoints.insert(allPoints.begin(), allPoints.front().lerp(allPoints[1], -0.2f));
- allPoints.push_back(allPoints.back().lerp(allPoints[allPoints.size() - 2], -0.2f));
- allPoints.push_back(allPoints.back().lerp(allPoints[allPoints.size() - 2], -1.0f));
-
- SplineRawInitializer initer(allPoints);
- TransportSpline orientationSpline;
- orientationSpline.init_spline_custom(initer);
- orientationSpline.initLengths();
-
- for (size_t i = 0; i < path.size(); ++i)
+ Movement::PointsArray splinePath;
+ std::transform(pathPoints.begin(), pathPoints.end(), std::back_inserter(splinePath), [](TaxiPathNodeEntry const* node) { return Movement::Vector3(node->Loc.X, node->Loc.Y, node->Loc.Z); });
+ SplineRawInitializer initer(splinePath);
+ leg->Spline = std::make_unique<TransportSpline>();
+ leg->Spline->set_steps_per_segment(20);
+ leg->Spline->init_spline_custom(initer);
+ leg->Spline->initLengths();
+
+ leg->Segments.resize(pauses.size() + 1);
+
+ auto legTimeAccelDecel = [&](double dist)
{
- if (!mapChange)
- {
- TaxiPathNodeEntry const* node_i = path[i];
- if (i != path.size() - 1 && (node_i->Flags & TAXI_PATH_NODE_FLAG_TELEPORT || node_i->ContinentID != path[i + 1]->ContinentID))
- {
- keyFrames.back().Teleport = true;
- mapChange = true;
- }
- else
- {
- KeyFrame k(node_i);
- G3D::Vector3 h;
- orientationSpline.evaluate_derivative(i + 1, 0.0f, h);
- k.InitialOrientation = Position::NormalizeOrientation(std::atan2(h.y, h.x) + float(M_PI));
-
- keyFrames.push_back(k);
- splinePath.push_back(G3D::Vector3(node_i->Loc.X, node_i->Loc.Y, node_i->Loc.Z));
- transport->mapsUsed.insert(k.Node->ContinentID);
- }
- }
+ double speed = double(goInfo->moTransport.moveSpeed);
+ double accel = double(goInfo->moTransport.accelRate);
+ double accelDist = 0.5 * speed * speed / accel;
+ if (accelDist >= dist * 0.5)
+ return uint32(std::sqrt(dist / accel) * 2000.0);
else
- mapChange = false;
- }
+ return uint32((dist - (accelDist + accelDist)) / speed * 1000.0 + speed / accel * 2000.0);
+ };
- if (splinePath.size() >= 2)
+ auto legTimeAccel = [&](double dist)
{
- // Remove special catmull-rom spline points
- if (!keyFrames.front().IsStopFrame() && !keyFrames.front().Node->ArrivalEventID && !keyFrames.front().Node->DepartureEventID)
- {
- splinePath.erase(splinePath.begin());
- keyFrames.erase(keyFrames.begin());
- }
- if (!keyFrames.back().IsStopFrame() && !keyFrames.back().Node->ArrivalEventID && !keyFrames.back().Node->DepartureEventID)
- {
- splinePath.pop_back();
- keyFrames.pop_back();
- }
- }
-
- ASSERT(!keyFrames.empty());
-
- if (transport->mapsUsed.size() > 1)
+ double speed = double(goInfo->moTransport.moveSpeed);
+ double accel = double(goInfo->moTransport.accelRate);
+ double accelDist = 0.5 * speed * speed / accel;
+ if (accelDist >= dist)
+ return uint32(std::sqrt((dist + dist) / accel) * 1000.0);
+ else
+ return uint32(((dist - accelDist) / speed + speed / accel) * 1000.0);
+ };
+
+ // Init segments
+ size_t pauseItr = 0;
+ size_t eventItr = 0;
+ double splineLengthToPreviousNode = 0.0;
+ uint32 delaySum = 0;
+ if (!pauses.empty())
{
- for (std::set<uint32>::const_iterator itr = transport->mapsUsed.begin(); itr != transport->mapsUsed.end(); ++itr)
- ASSERT(!sMapStore.LookupEntry(*itr)->Instanceable());
+ for (; pauseItr < pauses.size(); ++pauseItr)
+ {
+ auto pausePointItr = std::find(pathPoints.begin(), pathPoints.end(), pauses[pauseItr]);
+ if (*pausePointItr == pathPoints.back()) // last point is a "fake" spline point, its position can never be reached so transport cannot stop there
+ break;
- transport->inInstance = false;
- }
- else
- transport->inInstance = sMapStore.LookupEntry(*transport->mapsUsed.begin())->Instanceable();
+ for (; eventItr < events.size(); ++eventItr)
+ {
+ auto eventPointItr = std::find(pathPoints.begin(), pathPoints.end(), events[eventItr]);
+ if (eventPointItr > pausePointItr)
+ break;
- // last to first is always "teleport", even for closed paths
- keyFrames.back().Teleport = true;
+ double length = leg->Spline->length(std::distance(pathPoints.begin(), eventPointItr)) - splineLengthToPreviousNode;
- const float speed = float(goInfo->moTransport.moveSpeed);
- const float accel = float(goInfo->moTransport.accelRate);
- const float accel_dist = 0.5f * speed * speed / accel;
+ uint32 splineTime = 0;
+ if (pauseItr)
+ splineTime = legTimeAccelDecel(length);
+ else
+ splineTime = legTimeAccel(length);
- transport->accelTime = speed / accel;
- transport->accelDist = accel_dist;
+ if ((*eventPointItr)->ArrivalEventID)
+ {
+ TransportPathEvent& event = outEvents->emplace_back();
+ event.Timestamp = totalTime + splineTime + leg->Duration;
+ event.EventId = (*eventPointItr)->ArrivalEventID;
+ }
- int32 firstStop = -1;
- int32 lastStop = -1;
+ if ((*eventPointItr)->DepartureEventID)
+ {
+ TransportPathEvent& event = outEvents->emplace_back();
+ event.Timestamp = totalTime + splineTime + leg->Duration + (pausePointItr == eventPointItr ? (*eventPointItr)->Delay * IN_MILLISECONDS : 0);
+ event.EventId = (*eventPointItr)->DepartureEventID;
+ }
+ }
- // first cell is arrived at by teleportation :S
- keyFrames[0].DistFromPrev = 0;
- keyFrames[0].Index = 1;
- if (keyFrames[0].IsStopFrame())
- {
- firstStop = 0;
- lastStop = 0;
+ double splineLengthToCurrentNode = leg->Spline->length(std::distance(pathPoints.begin(), pausePointItr));
+ double length = splineLengthToCurrentNode - splineLengthToPreviousNode;
+ uint32 movementTime = 0;
+ if (pauseItr)
+ movementTime = legTimeAccelDecel(length);
+ else
+ movementTime = legTimeAccel(length);
+
+ leg->Duration += movementTime;
+ leg->Segments[pauseItr].SegmentEndArrivalTimestamp = leg->Duration + delaySum;
+ leg->Segments[pauseItr].Delay = (*pausePointItr)->Delay * IN_MILLISECONDS;
+ leg->Segments[pauseItr].DistanceFromLegStartAtEnd = splineLengthToCurrentNode;
+ delaySum += (*pausePointItr)->Delay * IN_MILLISECONDS;
+ splineLengthToPreviousNode = splineLengthToCurrentNode;
+ }
}
- // find the rest of the distances between key points
- // Every path segment has its own spline
- size_t start = 0;
- for (size_t i = 1; i < keyFrames.size(); ++i)
+ // Process events happening after last pause
+ for (; eventItr < events.size(); ++eventItr)
{
- if (keyFrames[i - 1].Teleport || i + 1 == keyFrames.size())
- {
- size_t extra = !keyFrames[i - 1].Teleport ? 1 : 0;
- std::shared_ptr<TransportSpline> spline = std::make_shared<TransportSpline>();
- spline->init_spline(&splinePath[start], i - start + extra, Movement::SplineBase::ModeCatmullrom);
- spline->initLengths();
- for (size_t j = start; j < i + extra; ++j)
- {
- keyFrames[j].Index = j - start + 1;
- keyFrames[j].DistFromPrev = float(spline->length(j - start, j + 1 - start));
- if (j > 0)
- keyFrames[j - 1].NextDistFromPrev = keyFrames[j].DistFromPrev;
- keyFrames[j].Spline = spline;
- }
-
- if (keyFrames[i - 1].Teleport)
- {
- keyFrames[i].Index = i - start + 1;
- keyFrames[i].DistFromPrev = 0.0f;
- keyFrames[i - 1].NextDistFromPrev = 0.0f;
- keyFrames[i].Spline = spline;
- }
+ auto eventPointItr = std::find(pathPoints.begin(), pathPoints.end(), events[eventItr]);
+ if (*eventPointItr == pathPoints.back()) // last point is a "fake" spline node, events cannot happen there
+ break;
+
+ double length = leg->Spline->length(std::distance(pathPoints.begin(), eventPointItr)) - splineLengthToPreviousNode;
+ uint32 splineTime = 0;
+ if (pauseItr)
+ splineTime = legTimeAccel(length);
+ else
+ splineTime = uint32(length / double(goInfo->moTransport.moveSpeed) * 1000.0);
- start = i;
+ if ((*eventPointItr)->ArrivalEventID)
+ {
+ TransportPathEvent& event = outEvents->emplace_back();
+ event.Timestamp = totalTime + splineTime + leg->Duration;
+ event.EventId = (*eventPointItr)->ArrivalEventID;
}
- if (keyFrames[i].IsStopFrame())
+ if ((*eventPointItr)->DepartureEventID)
{
- // remember first stop frame
- if (firstStop == -1)
- firstStop = i;
- lastStop = i;
+ TransportPathEvent& event = outEvents->emplace_back();
+ event.Timestamp = totalTime + splineTime + leg->Duration;
+ event.EventId = (*eventPointItr)->DepartureEventID;
}
}
- keyFrames.back().NextDistFromPrev = keyFrames.front().DistFromPrev;
+ // Add segment after last pause
+ double length = leg->Spline->length() - splineLengthToPreviousNode;
+ uint32 splineTime = 0;
+ if (pauseItr)
+ splineTime = legTimeAccel(length);
+ else
+ splineTime = uint32(length / double(goInfo->moTransport.moveSpeed) * 1000.0);
- if (firstStop == -1 || lastStop == -1)
- firstStop = lastStop = 0;
+ leg->StartTimestamp = totalTime;
+ leg->Duration += splineTime + delaySum;
+ leg->Segments[pauseItr].SegmentEndArrivalTimestamp = leg->Duration;
+ leg->Segments[pauseItr].Delay = 0;
+ leg->Segments[pauseItr].DistanceFromLegStartAtEnd = leg->Spline->length();
+ totalTime += leg->Segments[pauseItr].SegmentEndArrivalTimestamp + leg->Segments[pauseItr].Delay;
- // at stopping keyframes, we define distSinceStop == 0,
- // and distUntilStop is to the next stopping keyframe.
- // this is required to properly handle cases of two stopping frames in a row (yes they do exist)
- float tmpDist = 0.0f;
- for (size_t i = 0; i < keyFrames.size(); ++i)
- {
- int32 j = (i + lastStop) % keyFrames.size();
- if (keyFrames[j].IsStopFrame() || j == lastStop)
- tmpDist = 0.0f;
- else
- tmpDist += keyFrames[j].DistFromPrev;
- keyFrames[j].DistSinceStop = tmpDist;
- }
+ for (TransportPathSegment& segment : leg->Segments)
+ segment.SegmentEndArrivalTimestamp += leg->StartTimestamp;
- tmpDist = 0.0f;
- for (int32 i = int32(keyFrames.size()) - 1; i >= 0; i--)
- {
- int32 j = (i + firstStop) % keyFrames.size();
- tmpDist += keyFrames[(j + 1) % keyFrames.size()].DistFromPrev;
- keyFrames[j].DistUntilStop = tmpDist;
- if (keyFrames[j].IsStopFrame() || j == firstStop)
- tmpDist = 0.0f;
- }
+ if (leg->Segments.size() > pauseItr + 1)
+ leg->Segments.resize(pauseItr + 1);
+}
+
+void TransportMgr::GeneratePath(GameObjectTemplate const* goInfo, TransportTemplate* transport, std::set<uint32>* mapsUsed)
+{
+ uint32 pathId = goInfo->moTransport.taxiPathID;
+ TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathId];
- for (size_t i = 0; i < keyFrames.size(); ++i)
+ transport->Speed = double(goInfo->moTransport.moveSpeed);
+ transport->AccelerationRate = double(goInfo->moTransport.accelRate);
+ transport->AccelerationTime = transport->Speed / transport->AccelerationRate;
+ transport->AccelerationDistance = 0.5 * transport->Speed * transport->Speed / transport->AccelerationRate;
+
+ std::vector<TaxiPathNodeEntry const*> pathPoints;
+ std::vector<TaxiPathNodeEntry const*> pauses;
+ std::vector<TaxiPathNodeEntry const*> events;
+ TransportPathLeg* leg = &transport->PathLegs.emplace_back();
+ leg->MapId = path[0]->ContinentID;
+ bool prevNodeWasTeleport = false;
+ uint32 totalTime = 0;
+ for (TaxiPathNodeEntry const* node : path)
{
- float total_dist = keyFrames[i].DistSinceStop + keyFrames[i].DistUntilStop;
- if (total_dist < 2 * accel_dist) // won't reach full speed
- {
- if (keyFrames[i].DistSinceStop < keyFrames[i].DistUntilStop) // is still accelerating
- {
- // calculate accel+brake time for this short segment
- float segment_time = 2.0f * std::sqrt((keyFrames[i].DistUntilStop + keyFrames[i].DistSinceStop) / accel);
- // substract acceleration time
- keyFrames[i].TimeTo = segment_time - std::sqrt(2 * keyFrames[i].DistSinceStop / accel);
- }
- else // slowing down
- keyFrames[i].TimeTo = std::sqrt(2 * keyFrames[i].DistUntilStop / accel);
- }
- else if (keyFrames[i].DistSinceStop < accel_dist) // still accelerating (but will reach full speed)
+ if (node->ContinentID != leg->MapId || prevNodeWasTeleport)
{
- // calculate accel + cruise + brake time for this long segment
- float segment_time = (keyFrames[i].DistUntilStop + keyFrames[i].DistSinceStop) / speed + (speed / accel);
- // substract acceleration time
- keyFrames[i].TimeTo = segment_time - std::sqrt(2 * keyFrames[i].DistSinceStop / accel);
+ InitializeLeg(leg, &transport->Events, pathPoints, pauses, events, goInfo, totalTime);
+
+ leg = &transport->PathLegs.emplace_back();
+ leg->MapId = node->ContinentID;
+ pathPoints.clear();
+ pauses.clear();
+ events.clear();
}
- else if (keyFrames[i].DistUntilStop < accel_dist) // already slowing down (but reached full speed)
- keyFrames[i].TimeTo = std::sqrt(2 * keyFrames[i].DistUntilStop / accel);
- else // at full speed
- keyFrames[i].TimeTo = (keyFrames[i].DistUntilStop / speed) + (0.5f * speed / accel);
- }
- // calculate tFrom times from tTo times
- float segmentTime = 0.0f;
- for (size_t i = 0; i < keyFrames.size(); ++i)
- {
- int32 j = (i + lastStop) % keyFrames.size();
- if (keyFrames[j].IsStopFrame() || j == lastStop)
- segmentTime = keyFrames[j].TimeTo;
- keyFrames[j].TimeFrom = segmentTime - keyFrames[j].TimeTo;
- }
+ prevNodeWasTeleport = (node->Flags & TAXI_PATH_NODE_FLAG_TELEPORT) != 0;
+ pathPoints.push_back(node);
+ if (node->Flags & TAXI_PATH_NODE_FLAG_STOP)
+ pauses.push_back(node);
- // calculate path times
- keyFrames[0].ArriveTime = 0;
- float curPathTime = 0.0f;
- if (keyFrames[0].IsStopFrame())
- {
- curPathTime = float(keyFrames[0].Node->Delay);
- keyFrames[0].DepartureTime = uint32(curPathTime * IN_MILLISECONDS);
+ if (node->ArrivalEventID || node->DepartureEventID)
+ events.push_back(node);
+
+ mapsUsed->insert(node->ContinentID);
}
- for (size_t i = 1; i < keyFrames.size(); ++i)
+ if (!leg->Spline)
+ InitializeLeg(leg, &transport->Events, pathPoints, pauses, events, goInfo, totalTime);
+
+ if (mapsUsed->size() > 1)
{
- curPathTime += keyFrames[i - 1].TimeTo;
- if (keyFrames[i].IsStopFrame())
- {
- keyFrames[i].ArriveTime = uint32(curPathTime * IN_MILLISECONDS);
- keyFrames[i - 1].NextArriveTime = keyFrames[i].ArriveTime;
- curPathTime += float(keyFrames[i].Node->Delay);
- keyFrames[i].DepartureTime = uint32(curPathTime * IN_MILLISECONDS);
- }
- else
- {
- curPathTime -= keyFrames[i].TimeTo;
- keyFrames[i].ArriveTime = uint32(curPathTime * IN_MILLISECONDS);
- keyFrames[i - 1].NextArriveTime = keyFrames[i].ArriveTime;
- keyFrames[i].DepartureTime = keyFrames[i].ArriveTime;
- }
- }
+ for (uint32 mapId : *mapsUsed)
+ ASSERT(!sMapStore.LookupEntry(mapId)->Instanceable());
- keyFrames.back().NextArriveTime = keyFrames.back().DepartureTime;
+ transport->InInstance = false;
+ }
+ else
+ transport->InInstance = sMapStore.LookupEntry(*mapsUsed->begin())->Instanceable();
- transport->pathTime = keyFrames.back().DepartureTime;
+ transport->TotalPathTime = totalTime;
}
void TransportMgr::AddPathNodeToTransport(uint32 transportEntry, uint32 timeSeg, TransportAnimationEntry const* node)
@@ -477,16 +580,22 @@ Transport* TransportMgr::CreateTransport(uint32 entry, ObjectGuid::LowType guid
return nullptr;
}
+ Optional<Position> startingPosition = tInfo->ComputePosition(0, nullptr, nullptr);
+ if (!startingPosition)
+ {
+ TC_LOG_ERROR("sql.sql", "Transport %u will not be loaded, failed to compute starting position", entry);
+ return nullptr;
+ }
+
// create transport...
Transport* trans = new Transport();
// ...at first waypoint
- TaxiPathNodeEntry const* startNode = tInfo->keyFrames.begin()->Node;
- uint32 mapId = startNode->ContinentID;
- float x = startNode->Loc.X;
- float y = startNode->Loc.Y;
- float z = startNode->Loc.Z;
- float o = tInfo->keyFrames.begin()->InitialOrientation;
+ uint32 mapId = tInfo->PathLegs.front().MapId;
+ float x = startingPosition->GetPositionX();
+ float y = startingPosition->GetPositionY();
+ float z = startingPosition->GetPositionZ();
+ float o = startingPosition->GetOrientation();
// initialize the gameobject base
ObjectGuid::LowType guidLow = guid ? guid : ASSERT_NOTNULL(map)->GenerateLowGuid<HighGuid::Transport>();
@@ -500,7 +609,7 @@ Transport* TransportMgr::CreateTransport(uint32 entry, ObjectGuid::LowType guid
if (MapEntry const* mapEntry = sMapStore.LookupEntry(mapId))
{
- if (mapEntry->Instanceable() != tInfo->inInstance)
+ if (mapEntry->Instanceable() != tInfo->InInstance)
{
TC_LOG_ERROR("entities.transport", "Transport %u (name: %s) attempted creation in instance map (id: %u) but it is not an instanced transport!", entry, trans->GetName().c_str(), mapId);
delete trans;
@@ -525,7 +634,7 @@ void TransportMgr::SpawnContinentTransports()
uint32 count = 0;
for (auto itr = _transportSpawns.begin(); itr != _transportSpawns.end(); ++itr)
- if (!ASSERT_NOTNULL(GetTransportTemplate(itr->second.TransportGameObjectId))->inInstance)
+ if (!ASSERT_NOTNULL(GetTransportTemplate(itr->second.TransportGameObjectId))->InInstance)
if (CreateTransport(itr->second.TransportGameObjectId, itr->second.SpawnId, nullptr, itr->second.PhaseUseFlags, itr->second.PhaseId, itr->second.PhaseGroup))
++count;
diff --git a/src/server/game/Maps/TransportMgr.h b/src/server/game/Maps/TransportMgr.h
index 92ce809faee..66f6d59e35c 100644
--- a/src/server/game/Maps/TransportMgr.h
+++ b/src/server/game/Maps/TransportMgr.h
@@ -19,11 +19,12 @@
#define TRANSPORTMGR_H
#include "ObjectGuid.h"
+#include "Optional.h"
+#include "Position.h"
#include <map>
#include <memory>
#include <unordered_map>
-struct KeyFrame;
struct GameObjectTemplate;
struct TaxiPathNodeEntry;
struct TransportAnimationEntry;
@@ -38,49 +39,69 @@ namespace Movement
}
using TransportSpline = Movement::Spline<double>;
-using KeyFrameVec = std::vector<KeyFrame>;
-struct KeyFrame
+enum class TransportMovementState : uint8
{
- explicit KeyFrame(TaxiPathNodeEntry const* node) : Index(0), Node(node), InitialOrientation(0.0f),
- DistSinceStop(-1.0f), DistUntilStop(-1.0f), DistFromPrev(-1.0f), TimeFrom(0.0f), TimeTo(0.0f),
- Teleport(false), ArriveTime(0), DepartureTime(0), Spline(nullptr), NextDistFromPrev(0.0f), NextArriveTime(0)
- {
- }
-
- uint32 Index;
- TaxiPathNodeEntry const* Node;
- float InitialOrientation;
- float DistSinceStop;
- float DistUntilStop;
- float DistFromPrev;
- float TimeFrom;
- float TimeTo;
- bool Teleport;
- uint32 ArriveTime;
- uint32 DepartureTime;
- std::shared_ptr<TransportSpline> Spline;
-
- // Data needed for next frame
- float NextDistFromPrev;
- uint32 NextArriveTime;
-
- bool IsTeleportFrame() const { return Teleport; }
- bool IsStopFrame() const;
+ Moving,
+ WaitingOnPauseWaypoint
+};
+
+// Represents a segment within path leg between stops
+struct TransportPathSegment
+{
+ uint32 SegmentEndArrivalTimestamp = 0;
+ uint32 Delay = 0;
+ double DistanceFromLegStartAtEnd = 0.0;
+};
+
+struct TransportPathEvent
+{
+ uint32 Timestamp = 0;
+ uint32 EventId = 0;
+};
+
+// Represents a contignuous part of transport path (without map changes or teleports)
+struct TransportPathLeg
+{
+ TransportPathLeg();
+ ~TransportPathLeg();
+
+ TransportPathLeg(TransportPathLeg const&) = delete;
+ TransportPathLeg(TransportPathLeg&&) noexcept;
+ TransportPathLeg& operator=(TransportPathLeg const&) = delete;
+ TransportPathLeg& operator=(TransportPathLeg&&) noexcept;
+
+ uint32 MapId = 0;
+ std::unique_ptr<TransportSpline> Spline;
+ uint32 StartTimestamp = 0;
+ uint32 Duration = 0;
+ std::vector<TransportPathSegment> Segments;
};
struct TransportTemplate
{
- TransportTemplate() : inInstance(false), pathTime(0), accelTime(0.0f), accelDist(0.0f), entry(0) { }
+ TransportTemplate();
~TransportTemplate();
- std::set<uint32> mapsUsed;
- bool inInstance;
- uint32 pathTime;
- KeyFrameVec keyFrames;
- float accelTime;
- float accelDist;
- uint32 entry;
+ TransportTemplate(TransportTemplate const&) = delete;
+ TransportTemplate(TransportTemplate&&) noexcept;
+ TransportTemplate& operator=(TransportTemplate const&) = delete;
+ TransportTemplate& operator=(TransportTemplate&&) noexcept;
+
+ uint32 TotalPathTime = 0;
+ double Speed = 0.0;
+ double AccelerationRate = 0.0;
+ double AccelerationTime = 0.0;
+ double AccelerationDistance = 0.0;
+ std::vector<TransportPathLeg> PathLegs;
+ std::vector<TransportPathEvent> Events;
+
+ Optional<Position> ComputePosition(uint32 time, TransportMovementState* moveState, size_t* legIndex) const;
+ uint32 GetNextPauseWaypointTimestamp(uint32 time) const;
+
+ double CalculateDistanceMoved(double timePassedInSegment, double segmentDuration, bool isFirstSegment, bool isLastSegment) const;
+
+ bool InInstance = false;
};
struct TC_GAME_API TransportAnimation
@@ -142,7 +163,7 @@ class TC_GAME_API TransportMgr
TransportMgr& operator=(TransportMgr const&) = delete;
// Generates and precaches a path for transport to avoid generation each time transport instance is created
- void GeneratePath(GameObjectTemplate const* goInfo, TransportTemplate* transport);
+ void GeneratePath(GameObjectTemplate const* goInfo, TransportTemplate* transport, std::set<uint32>* mapsUsed);
void AddPathNodeToTransport(uint32 transportEntry, uint32 timeSeg, TransportAnimationEntry const* node);