/* * This file is part of the TrinityCore 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 . */ #include "TransportMgr.h" #include "DB2Stores.h" #include "DatabaseEnv.h" #include "InstanceScript.h" #include "Log.h" #include "Map.h" #include "MapUtils.h" #include "MoveSplineInitArgs.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "PhasingHandler.h" #include "Spline.h" #include "Transport.h" 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 TransportTemplate::ComputePosition(uint32 time, TransportMovementState* moveState, size_t* legIndex) const { time %= TotalPathTime; // find leg TransportPathLeg const* leg = GetLegForTime(time); if (!leg) return {}; // find segment uint32 prevSegmentTime = leg->StartTimestamp; auto segmentItr = leg->Segments.begin(); double distanceMoved = 0.0; bool isOnPause = false; for (; segmentItr != std::prev(leg->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 == leg->Segments.begin(), segmentItr == std::prev(leg->Segments.end())); Movement::SplineBase::index_type splineIndex; float splinePointProgress; leg->Spline->computeIndex(std::fmin(distanceMoved / leg->Spline->length(), 1.0), splineIndex, splinePointProgress); G3D::Vector3 pos, dir; leg->Spline->evaluate_percent(splineIndex, splinePointProgress, pos); leg->Spline->evaluate_derivative(splineIndex, splinePointProgress, dir); if (moveState) *moveState = isOnPause ? TransportMovementState::WaitingOnPauseWaypoint : TransportMovementState::Moving; if (legIndex) *legIndex = std::distance(PathLegs.data(), leg); return Position(pos.x, pos.y, pos.z, std::atan2(dir.y, dir.x) + float(M_PI)); } TransportPathLeg const* TransportTemplate::GetLegForTime(uint32 time) const { auto legItr = PathLegs.begin(); while (legItr->StartTimestamp + legItr->Duration <= time) { ++legItr; if (legItr == PathLegs.end()) return nullptr; } return &*legItr; } uint32 TransportTemplate::GetNextPauseWaypointTimestamp(uint32 time) const { TransportPathLeg const* leg = GetLegForTime(time); if (!leg) return time; auto segmentItr = leg->Segments.begin(); for (; segmentItr != std::prev(leg->Segments.end()); ++segmentItr) if (time < segmentItr->SegmentEndArrivalTimestamp + segmentItr->Delay) break; return segmentItr->SegmentEndArrivalTimestamp + segmentItr->Delay; } 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; } } TransportAnimation::TransportAnimation() = default; TransportAnimation::~TransportAnimation() = default; TransportAnimation::TransportAnimation(TransportAnimation&&) noexcept = default; TransportAnimation& TransportAnimation::operator=(TransportAnimation&&) noexcept = default; TransportMgr::TransportMgr() = default; TransportMgr::~TransportMgr() = default; TransportMgr* TransportMgr::instance() { static TransportMgr instance; return &instance; } void TransportMgr::Unload() { _transportTemplates.clear(); } void TransportMgr::LoadTransportTemplates() { uint32 oldMSTime = getMSTime(); QueryResult result = WorldDatabase.Query("SELECT entry FROM gameobject_template WHERE type = 15 ORDER BY entry ASC"); if (!result) { TC_LOG_INFO("server.loading", ">> Loaded 0 transport templates. DB table `gameobject_template` has no transports!"); return; } uint32 count = 0; do { Field* fields = result->Fetch(); uint32 entry = fields[0].GetUInt32(); GameObjectTemplate const* goInfo = sObjectMgr->GetGameObjectTemplate(entry); if (goInfo == nullptr) { TC_LOG_ERROR("sql.sql", "Transport {} has no associated GameObjectTemplate from `gameobject_template` , skipped.", entry); continue; } if (goInfo->moTransport.taxiPathID >= sTaxiPathNodesByPath.size()) { TC_LOG_ERROR("sql.sql", "Transport {} (name: {}) has an invalid path specified in `gameobject_template`.`Data0` ({}) field, skipped.", entry, goInfo->name, goInfo->moTransport.taxiPathID); continue; } if (!goInfo->moTransport.taxiPathID) continue; // paths are generated per template, saves us from generating it again in case of instanced transports TransportTemplate& transport = _transportTemplates[entry]; GeneratePath(goInfo, &transport); ++count; } while (result->NextRow()); TC_LOG_INFO("server.loading", ">> Loaded {} transport templates in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); } void TransportMgr::LoadTransportAnimationAndRotation() { for (TransportAnimationEntry const* anim : sTransportAnimationStore) AddPathNodeToTransport(anim->TransportID, anim->TimeIndex, anim); for (TransportRotationEntry const* rot : sTransportRotationStore) AddPathRotationToTransport(rot->GameObjectsID, rot->TimeIndex, rot); } void TransportMgr::LoadTransportSpawns() { if (_transportTemplates.empty()) return; uint32 oldMSTime = getMSTime(); QueryResult result = WorldDatabase.Query("SELECT guid, entry, phaseUseFlags, phaseid, phasegroup FROM transports"); uint32 count = 0; if (result) { do { Field* fields = result->Fetch(); ObjectGuid::LowType guid = fields[0].GetUInt64(); uint32 entry = fields[1].GetUInt32(); uint8 phaseUseFlags = fields[2].GetUInt8(); uint32 phaseId = fields[3].GetUInt32(); uint32 phaseGroupId = fields[4].GetUInt32(); TransportTemplate const* transportTemplate = GetTransportTemplate(entry); if (!transportTemplate) { TC_LOG_ERROR("sql.sql", "Table `transports` have transport (GUID: {} Entry: {}) with unknown gameobject `entry` set, skipped.", guid, entry); continue; } if (phaseUseFlags & ~PHASE_USE_FLAGS_ALL) { TC_LOG_ERROR("sql.sql", "Table `transports` have transport (GUID: {} Entry: {}) with unknown `phaseUseFlags` set, removed unknown value.", guid, entry); phaseUseFlags &= PHASE_USE_FLAGS_ALL; } if (phaseUseFlags & PHASE_USE_FLAGS_ALWAYS_VISIBLE && phaseUseFlags & PHASE_USE_FLAGS_INVERSE) { TC_LOG_ERROR("sql.sql", "Table `transports` have transport (GUID: {} Entry: {}) has both `phaseUseFlags` PHASE_USE_FLAGS_ALWAYS_VISIBLE and PHASE_USE_FLAGS_INVERSE," " removing PHASE_USE_FLAGS_INVERSE.", guid, entry); phaseUseFlags &= ~PHASE_USE_FLAGS_INVERSE; } if (phaseGroupId && phaseId) { TC_LOG_ERROR("sql.sql", "Table `transports` have transport (GUID: {} Entry: {}) with both `phaseid` and `phasegroup` set, `phasegroup` set to 0", guid, entry); phaseGroupId = 0; } if (phaseId) { if (!sPhaseStore.LookupEntry(phaseId)) { TC_LOG_ERROR("sql.sql", "Table `transports` have transport (GUID: {} Entry: {}) with `phaseid` {} does not exist, set to 0", guid, entry, phaseId); phaseId = 0; } } if (phaseGroupId) { if (!sDB2Manager.GetPhasesForGroup(phaseGroupId)) { TC_LOG_ERROR("sql.sql", "Table `transports` have transport (GUID: {} Entry: {}) with `phaseGroup` {} does not exist, set to 0", guid, entry, phaseGroupId); phaseGroupId = 0; } } TransportSpawn& spawn = _transportSpawns[guid]; spawn.SpawnId = guid; spawn.TransportGameObjectId = entry; spawn.PhaseUseFlags = phaseUseFlags; spawn.PhaseId = phaseId; spawn.PhaseGroup = phaseGroupId; for (uint32 mapId : transportTemplate->MapIds) _transportsByMap[mapId].insert(&spawn); } while (result->NextRow()); } TC_LOG_INFO("server.loading", ">> Spawned {} continent transports in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); } class SplineRawInitializer { public: SplineRawInitializer(Movement::PointsArray& points) : _points(points) { } void operator()(uint8& mode, bool& cyclic, Movement::PointsArray& points, int& lo, int& hi) const { mode = Movement::SplineBase::ModeCatmullrom; cyclic = false; points.assign(_points.begin(), _points.end()); lo = 1; hi = points.size() - 2; } Movement::PointsArray& _points; }; static void InitializeLeg(TransportPathLeg* leg, std::vector* outEvents, std::vector const& pathPoints, std::vector const& pauses, std::vector const& events, GameObjectTemplate const* goInfo, uint32& totalTime) { 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(); 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) { 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 return uint32((dist - (accelDist + accelDist)) / speed * 1000.0 + speed / accel * 2000.0); }; auto legTimeAccel = [&](double dist) { 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 (; 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; for (; eventItr < events.size(); ++eventItr) { auto eventPointItr = std::find(pathPoints.begin(), pathPoints.end(), events[eventItr]); if (eventPointItr > pausePointItr) break; double length = leg->Spline->length(std::distance(pathPoints.begin(), eventPointItr)) - splineLengthToPreviousNode; uint32 splineTime = 0; if (pauseItr) splineTime = legTimeAccelDecel(length); else splineTime = legTimeAccel(length); if ((*eventPointItr)->ArrivalEventID) { TransportPathEvent& event = outEvents->emplace_back(); event.Timestamp = totalTime + splineTime + leg->Duration + delaySum; event.EventId = (*eventPointItr)->ArrivalEventID; } if ((*eventPointItr)->DepartureEventID) { TransportPathEvent& event = outEvents->emplace_back(); event.Timestamp = totalTime + splineTime + leg->Duration + delaySum + (pausePointItr == eventPointItr ? (*eventPointItr)->Delay * IN_MILLISECONDS : 0); event.EventId = (*eventPointItr)->DepartureEventID; } } 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; } } // Process events happening after last pause for (; eventItr < events.size(); ++eventItr) { 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); if ((*eventPointItr)->ArrivalEventID) { TransportPathEvent& event = outEvents->emplace_back(); event.Timestamp = totalTime + splineTime + leg->Duration + delaySum; event.EventId = (*eventPointItr)->ArrivalEventID; } if ((*eventPointItr)->DepartureEventID) { TransportPathEvent& event = outEvents->emplace_back(); event.Timestamp = totalTime + splineTime + leg->Duration + delaySum; event.EventId = (*eventPointItr)->DepartureEventID; } } // 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); 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; for (TransportPathSegment& segment : leg->Segments) segment.SegmentEndArrivalTimestamp += leg->StartTimestamp; if (leg->Segments.size() > pauseItr + 1) leg->Segments.resize(pauseItr + 1); } void TransportMgr::GeneratePath(GameObjectTemplate const* goInfo, TransportTemplate* transport) { uint32 pathId = goInfo->moTransport.taxiPathID; TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathId]; 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 pathPoints; std::vector pauses; std::vector events; TransportPathLeg* leg = &transport->PathLegs.emplace_back(); leg->MapId = path[0]->ContinentID; bool prevNodeWasTeleport = false; uint32 totalTime = 0; for (TaxiPathNodeEntry const* node : path) { if (node->ContinentID != leg->MapId || prevNodeWasTeleport) { InitializeLeg(leg, &transport->Events, pathPoints, pauses, events, goInfo, totalTime); leg = &transport->PathLegs.emplace_back(); leg->MapId = node->ContinentID; pathPoints.clear(); pauses.clear(); events.clear(); } 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); if (node->ArrivalEventID || node->DepartureEventID) events.push_back(node); transport->MapIds.insert(node->ContinentID); } if (!leg->Spline) InitializeLeg(leg, &transport->Events, pathPoints, pauses, events, goInfo, totalTime); if (transport->MapIds.size() > 1) for (uint32 mapId : transport->MapIds) ASSERT(!sMapStore.LookupEntry(mapId)->Instanceable()); transport->TotalPathTime = totalTime; } void TransportMgr::AddPathNodeToTransport(uint32 transportEntry, uint32 timeSeg, TransportAnimationEntry const* node) { TransportAnimation& animNode = _transportAnimations[transportEntry]; if (animNode.TotalTime < timeSeg) animNode.TotalTime = timeSeg; animNode.Path[timeSeg] = node; } void TransportMgr::AddPathRotationToTransport(uint32 transportEntry, uint32 timeSeg, TransportRotationEntry const* node) { TransportAnimation& animNode = _transportAnimations[transportEntry]; animNode.Rotations[timeSeg] = node; if (animNode.Path.empty() && animNode.TotalTime < timeSeg) animNode.TotalTime = timeSeg; } Transport* TransportMgr::CreateTransport(uint32 entry, Map* map, ObjectGuid::LowType guid /*= 0*/, uint8 phaseUseFlags /*= 0*/, uint32 phaseId /*= 0*/, uint32 phaseGroupId /*= 0*/) { // SetZoneScript() is called after adding to map, so fetch the script using map if (InstanceMap* instanceMap = map->ToInstanceMap()) if (InstanceScript* instance = instanceMap->GetInstanceScript()) entry = instance->GetGameObjectEntry(0, entry); if (!entry) return nullptr; TransportTemplate const* tInfo = GetTransportTemplate(entry); if (!tInfo) { TC_LOG_ERROR("sql.sql", "Transport {} will not be loaded, `transport_template` missing", entry); return nullptr; } if (tInfo->MapIds.find(map->GetId()) == tInfo->MapIds.end()) { TC_LOG_ERROR("entities.transport", "Transport {} attempted creation on map it has no path for {}!", entry, map->GetId()); return nullptr; } Optional startingPosition = tInfo->ComputePosition(0, nullptr, nullptr); if (!startingPosition) { TC_LOG_ERROR("sql.sql", "Transport {} will not be loaded, failed to compute starting position", entry); return nullptr; } // create transport... Transport* trans = new Transport(); // ...at first waypoint 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 : map->GenerateLowGuid(); if (!trans->Create(guidLow, entry, x, y, z, o)) { delete trans; return nullptr; } PhasingHandler::InitDbPhaseShift(trans->GetPhaseShift(), phaseUseFlags, phaseId, phaseGroupId); // use preset map for instances (need to know which instance) trans->SetMap(map); if (InstanceMap* instanceMap = map->ToInstanceMap()) trans->m_zoneScript = instanceMap->GetInstanceScript(); // Passengers will be loaded once a player is near map->AddToMap(trans); return trans; } void TransportMgr::CreateTransportsForMap(Map* map) { auto mapTransports = _transportsByMap.find(map->GetId()); // no transports here if (mapTransports == _transportsByMap.end()) return; // create transports for (TransportSpawn const* transport : mapTransports->second) CreateTransport(transport->TransportGameObjectId, map, transport->SpawnId, transport->PhaseUseFlags, transport->PhaseId, transport->PhaseGroup); } TransportTemplate const* TransportMgr::GetTransportTemplate(uint32 entry) const { return Trinity::Containers::MapGetValuePtr(_transportTemplates, entry); } TransportAnimation const* TransportMgr::GetTransportAnimInfo(uint32 entry) const { return Trinity::Containers::MapGetValuePtr(_transportAnimations, entry); } TransportSpawn const* TransportMgr::GetTransportSpawn(ObjectGuid::LowType spawnId) const { return Trinity::Containers::MapGetValuePtr(_transportSpawns, spawnId); } TransportAnimationEntry const* TransportAnimation::GetPrevAnimNode(uint32 time) const { if (Path.empty()) return nullptr; auto itr = Path.lower_bound(time); if (itr != Path.begin()) return std::prev(itr)->second; return Path.rbegin()->second; } TransportRotationEntry const* TransportAnimation::GetPrevAnimRotation(uint32 time) const { if (Rotations.empty()) return nullptr; auto itr = Rotations.lower_bound(time); if (itr != Rotations.begin()) return std::prev(itr)->second; return Rotations.rbegin()->second; } TransportAnimationEntry const* TransportAnimation::GetNextAnimNode(uint32 time) const { if (Path.empty()) return nullptr; auto itr = Path.lower_bound(time); if (itr != Path.end()) return itr->second; return Path.begin()->second; } TransportRotationEntry const* TransportAnimation::GetNextAnimRotation(uint32 time) const { if (Rotations.empty()) return nullptr; auto itr = Rotations.lower_bound(time); if (itr != Rotations.end()) return itr->second; return Rotations.begin()->second; }