From 572a680c16c75e136fdfc593affa7cc3beaa0e2a Mon Sep 17 00:00:00 2001 From: UltraNix <80540499+UltraNix@users.noreply.github.com> Date: Tue, 2 Aug 2022 04:21:11 +0200 Subject: [PATCH] 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. --- .../rev_1657451078722098300.sql | 2 + src/server/game/DataStores/DBCStores.cpp | 10 +- src/server/game/Entities/Player/Player.cpp | 105 +++++++---- src/server/game/Entities/Player/Player.h | 4 +- .../game/Entities/Player/PlayerStorage.cpp | 6 +- .../game/Entities/Player/PlayerTaxi.cpp | 65 +++++-- src/server/game/Entities/Player/PlayerTaxi.h | 23 ++- src/server/game/Handlers/CharacterHandler.cpp | 2 +- src/server/game/Handlers/MovementHandler.cpp | 16 ++ src/server/game/Handlers/TaxiHandler.cpp | 64 +++++-- .../WaypointMovementGenerator.cpp | 163 ++++++++++-------- .../WaypointMovementGenerator.h | 70 ++++---- src/server/shared/DataStores/DBCStructure.h | 6 +- 13 files changed, 347 insertions(+), 189 deletions(-) create mode 100644 data/sql/updates/pending_db_characters/rev_1657451078722098300.sql diff --git a/data/sql/updates/pending_db_characters/rev_1657451078722098300.sql b/data/sql/updates/pending_db_characters/rev_1657451078722098300.sql new file mode 100644 index 000000000..158dc00aa --- /dev/null +++ b/data/sql/updates/pending_db_characters/rev_1657451078722098300.sql @@ -0,0 +1,2 @@ +-- +UPDATE `characters` SET `taxi_path`=CONCAT('0 ', `taxi_path`) WHERE LENGTH(`taxi_path`) > 0; diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp index 8952741db..1d6192e2b 100644 --- a/src/server/game/DataStores/DBCStores.cpp +++ b/src/server/game/DataStores/DBCStores.cpp @@ -546,11 +546,11 @@ void LoadDBCStores(const std::string& dataPath) if (sInfo->Effect[j] == SPELL_EFFECT_SEND_TAXI) spellPaths.insert(sInfo->EffectMiscValue[j]); - memset(sTaxiNodesMask, 0, sizeof(sTaxiNodesMask)); - memset(sOldContinentsNodesMask, 0, sizeof(sOldContinentsNodesMask)); - memset(sHordeTaxiNodesMask, 0, sizeof(sHordeTaxiNodesMask)); - memset(sAllianceTaxiNodesMask, 0, sizeof(sAllianceTaxiNodesMask)); - memset(sDeathKnightTaxiNodesMask, 0, sizeof(sDeathKnightTaxiNodesMask)); + sTaxiNodesMask.fill(0); + sOldContinentsNodesMask.fill(0); + sHordeTaxiNodesMask.fill(0); + sAllianceTaxiNodesMask.fill(0); + sDeathKnightTaxiNodesMask.fill(0); for (uint32 i = 1; i < sTaxiNodesStore.GetNumRows(); ++i) { diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 2abdf6374..575f410fe 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -1644,10 +1644,8 @@ void Player::ProcessDelayedOperations() { if (m_entryPointData.HasTaxiPath()) { - for (size_t i = 0; i < m_entryPointData.taxiPath.size() - 1; ++i) - m_taxi.AddTaxiDestination(m_entryPointData.taxiPath[i]); - m_taxi.SetTaxiSegment(m_entryPointData.taxiPath[m_entryPointData.taxiPath.size() - 1]); - + m_taxi.AddTaxiDestination(m_entryPointData.taxiPath[0]); + m_taxi.AddTaxiDestination(m_entryPointData.taxiPath[1]); m_entryPointData.ClearTaxiPath(); ContinueTaxiFlight(); } @@ -10002,26 +10000,6 @@ bool Player::ActivateTaxiPathTo(std::vector const& nodes, Creature* npc return false; } - // check node starting pos data set case if provided - if (node->x != 0.0f || node->y != 0.0f || node->z != 0.0f) - { - if (node->map_id != GetMapId() || - (node->x - GetPositionX()) * (node->x - GetPositionX()) + - (node->y - GetPositionY()) * (node->y - GetPositionY()) + - (node->z - GetPositionZ()) * (node->z - GetPositionZ()) > - (2 * INTERACTION_DISTANCE) * (2 * INTERACTION_DISTANCE) * (2 * INTERACTION_DISTANCE)) - { - GetSession()->SendActivateTaxiReply(ERR_TAXITOOFARAWAY); - return false; - } - } - // node must have pos if taxi master case (npc != nullptr) - else if (npc) - { - GetSession()->SendActivateTaxiReply(ERR_TAXIUNSPECIFIEDSERVERERROR); - return false; - } - // Prepare to flight start now // stop combat at start taxi flight if any @@ -10043,6 +10021,7 @@ bool Player::ActivateTaxiPathTo(std::vector const& nodes, Creature* npc // fill destinations path tail uint32 sourcepath = 0; uint32 totalcost = 0; + uint32 firstcost = 0; uint32 prevnode = sourcenode; uint32 lastnode = 0; @@ -10061,6 +10040,8 @@ bool Player::ActivateTaxiPathTo(std::vector const& nodes, Creature* npc } totalcost += cost; + if (i == 1) + firstcost = cost; if (prevnode == sourcenode) sourcepath = path; @@ -10089,7 +10070,16 @@ bool Player::ActivateTaxiPathTo(std::vector const& nodes, Creature* npc uint32 money = GetMoney(); if (npc) - totalcost = (uint32)ceil(totalcost * GetReputationPriceDiscount(npc)); + { + float discount = GetReputationPriceDiscount(npc); + totalcost = uint32(ceil(totalcost * discount)); + firstcost = uint32(ceil(firstcost * discount)); + m_taxi.SetFlightMasterFactionTemplateId(npc->GetFaction()); + } + else + { + m_taxi.SetFlightMasterFactionTemplateId(0); + } if (money < totalcost) { @@ -10100,8 +10090,6 @@ bool Player::ActivateTaxiPathTo(std::vector const& nodes, Creature* npc //Checks and preparations done, DO FLIGHT UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_FLIGHT_PATHS_TAKEN, 1); - ModifyMoney(-(int32)totalcost); - UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_TRAVELLING, totalcost); // prevent stealth flight //RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TALK); @@ -10111,12 +10099,16 @@ bool Player::ActivateTaxiPathTo(std::vector const& nodes, Creature* npc { TaxiNodesEntry const* lastPathNode = sTaxiNodesStore.LookupEntry(nodes[nodes.size() - 1]); m_taxi.ClearTaxiDestinations(); + ModifyMoney(-(int32)totalcost); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_TRAVELLING, totalcost); TeleportTo(lastPathNode->map_id, lastPathNode->x, lastPathNode->y, lastPathNode->z, GetOrientation()); return false; } else { m_flightSpellActivated = spellid; + ModifyMoney(-(int32)firstcost); + UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_GOLD_SPENT_FOR_TRAVELLING, firstcost); GetSession()->SendActivateTaxiReply(ERR_TAXIOK); GetSession()->SendDoFlight(mount_display_id, sourcepath); } @@ -10212,6 +10204,39 @@ void Player::ContinueTaxiFlight() GetSession()->SendDoFlight(mountDisplayId, path, startNode); } +void Player::SendTaxiNodeStatusMultiple() +{ + for (auto itr = m_clientGUIDs.begin(); itr != m_clientGUIDs.end(); ++itr) + { + if (!itr->IsCreature()) + { + continue; + } + + Creature* creature = ObjectAccessor::GetCreature(*this, *itr); + if (!creature || creature->IsHostileTo(this)) + { + continue; + } + + if (!creature->HasNpcFlag(UNIT_NPC_FLAG_FLIGHTMASTER)) + { + continue; + } + + uint32 nearestNode = sObjectMgr->GetNearestTaxiNode(creature->GetPositionX(), creature->GetPositionY(), creature->GetPositionZ(), creature->GetMapId(), GetTeamId()); + if (!nearestNode) + { + continue; + } + + WorldPacket data(SMSG_TAXINODE_STATUS, 9); + data << *itr; + data << uint8(m_taxi.IsTaximaskNodeKnown(nearestNode) ? 1 : 0); + SendDirectMessage(&data); + } +} + void Player::ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs) { PacketCooldowns cooldowns; @@ -10982,11 +11007,8 @@ void Player::SetEntryPoint() m_entryPointData.mountSpell = 0; m_entryPointData.joinPos = WorldLocation(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation()); - std::vector const& taxi = m_taxi.GetPath(); - for (std::vector::const_iterator itr = taxi.begin(); itr != taxi.end(); ++itr) - m_entryPointData.taxiPath.push_back(*itr); - - m_entryPointData.taxiPath.push_back(m_taxi.GetTaxiSegment()); + m_entryPointData.taxiPath[0] = m_taxi.GetTaxiSource(); + m_entryPointData.taxiPath[1] = m_taxi.GetTaxiDestination(); } else { @@ -11351,6 +11373,7 @@ void Player::SendInitialPacketsAfterAddToMap() SendEnchantmentDurations(); // must be after add to map SendItemDurations(); // must be after add to map SendQuestGiverStatusMultiple(); + SendTaxiNodeStatusMultiple(); // raid downscaling - send difficulty to player if (GetMap()->IsRaid()) @@ -12005,13 +12028,21 @@ bool Player::GetBGAccessByLevel(BattlegroundTypeId bgTypeId) const float Player::GetReputationPriceDiscount(Creature const* creature) const { - FactionTemplateEntry const* vendor_faction = creature->GetFactionTemplateEntry(); - if (!vendor_faction || !vendor_faction->faction) - return 1.0f; + return GetReputationPriceDiscount(creature->GetFactionTemplateEntry()); +} - ReputationRank rank = GetReputationRank(vendor_faction->faction); - if (rank <= REP_NEUTRAL) +float Player::GetReputationPriceDiscount(FactionTemplateEntry const* factionTemplate) const +{ + if (!factionTemplate || !factionTemplate->faction) + { return 1.0f; + } + + ReputationRank rank = GetReputationRank(factionTemplate->faction); + if (rank <= REP_NEUTRAL) + { + return 1.0f; + } return 1.0f - 0.05f * (rank - REP_NEUTRAL); } diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 7a5b10271..ddc3c1b68 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1126,6 +1126,7 @@ public: bool ActivateTaxiPathTo(uint32 taxi_path_id, uint32 spellid = 1); void CleanupAfterTaxiFlight(); void ContinueTaxiFlight(); + void SendTaxiNodeStatusMultiple(); // mount_id can be used in scripting calls [[nodiscard]] bool IsDeveloper() const { return HasPlayerFlag(PLAYER_FLAGS_DEVELOPER); } @@ -1336,7 +1337,8 @@ public: bool BuyItemFromVendorSlot(ObjectGuid vendorguid, uint32 vendorslot, uint32 item, uint8 count, uint8 bag, uint8 slot); bool _StoreOrEquipNewItem(uint32 vendorslot, uint32 item, uint8 count, uint8 bag, uint8 slot, int32 price, ItemTemplate const* pProto, Creature* pVendor, VendorItem const* crItem, bool bStore); - float GetReputationPriceDiscount(Creature const* creature) const; + [[nodiscard]] float GetReputationPriceDiscount(Creature const* creature) const; + [[nodiscard]] float GetReputationPriceDiscount(FactionTemplateEntry const* factionTemplate) const; [[nodiscard]] Player* GetTrader() const { return m_trade ? m_trade->GetTrader() : nullptr; } [[nodiscard]] TradeData* GetTradeData() const { return m_trade; } diff --git a/src/server/game/Entities/Player/PlayerStorage.cpp b/src/server/game/Entities/Player/PlayerStorage.cpp index 7e275bd1d..7cf8c69df 100644 --- a/src/server/game/Entities/Player/PlayerStorage.cpp +++ b/src/server/game/Entities/Player/PlayerStorage.cpp @@ -5190,10 +5190,8 @@ bool Player::LoadFromDB(ObjectGuid playerGuid, CharacterDatabaseQueryHolder cons // xinef: restore taxi flight from entry point data if (m_entryPointData.HasTaxiPath()) { - for (size_t i = 0; i < m_entryPointData.taxiPath.size() - 1; ++i) - m_taxi.AddTaxiDestination(m_entryPointData.taxiPath[i]); - m_taxi.SetTaxiSegment(m_entryPointData.taxiPath[m_entryPointData.taxiPath.size() - 1]); - + m_taxi.AddTaxiDestination(m_entryPointData.taxiPath[0]); + m_taxi.AddTaxiDestination(m_entryPointData.taxiPath[1]); m_entryPointData.ClearTaxiPath(); } } diff --git a/src/server/game/Entities/Player/PlayerTaxi.cpp b/src/server/game/Entities/Player/PlayerTaxi.cpp index a4bff7aa6..680af2e28 100644 --- a/src/server/game/Entities/Player/PlayerTaxi.cpp +++ b/src/server/game/Entities/Player/PlayerTaxi.cpp @@ -20,11 +20,6 @@ #include "Tokenize.h" #include "StringConvert.h" -PlayerTaxi::PlayerTaxi() : _taxiSegment(0) -{ - memset(m_taximask, 0, sizeof(m_taximask)); -} - void PlayerTaxi::InitTaxiNodesForLevel(uint32 race, uint32 chrClass, uint8 level) { // class specific initial known nodes @@ -136,9 +131,25 @@ bool PlayerTaxi::LoadTaxiDestinationsFromString(const std::string& values, TeamI { ClearTaxiDestinations(); - for (auto const& itr : Acore::Tokenize(values, ' ', false)) + std::vector tokens = Acore::Tokenize(values, ' ', false); + auto itr = tokens.begin(); + if (itr != tokens.end()) { - if (Optional node = Acore::StringTo(itr)) + if (Optional faction = Acore::StringTo(*itr)) + { + m_flightMasterFactionId = *faction; + } + else + { + return false; + } + } + else + return false; + + while ((++itr) != tokens.end()) + { + if (Optional node = Acore::StringTo(*itr)) { AddTaxiDestination(*node); } @@ -148,26 +159,33 @@ bool PlayerTaxi::LoadTaxiDestinationsFromString(const std::string& values, TeamI } } + if (m_TaxiDestinations.empty()) + { + return true; + } + // Check integrity - if (m_TaxiDestinations.size() < 3) + if (m_TaxiDestinations.size() < 2) + { return false; + } - // xinef: current segment is saved as last destination in db - _taxiSegment = m_TaxiDestinations[m_TaxiDestinations.size() - 1]; - m_TaxiDestinations.pop_back(); - - for (size_t i = 0; i < m_TaxiDestinations.size() - 1; ++i) + for (size_t i = 1; i < m_TaxiDestinations.size(); ++i) { uint32 cost; uint32 path; - sObjectMgr->GetTaxiPath(m_TaxiDestinations[i], m_TaxiDestinations[i + 1], path, cost); + sObjectMgr->GetTaxiPath(m_TaxiDestinations[i - 1], m_TaxiDestinations[i], path, cost); if (!path) + { return false; + } } // can't load taxi path without mount set (quest taxi path?) if (!sObjectMgr->GetTaxiMountDisplayId(GetTaxiSource(), teamId, true)) + { return false; + } return true; } @@ -175,26 +193,34 @@ bool PlayerTaxi::LoadTaxiDestinationsFromString(const std::string& values, TeamI std::string PlayerTaxi::SaveTaxiDestinationsToString() { if (m_TaxiDestinations.empty()) + { return ""; + } + + ASSERT(m_TaxiDestinations.size() >= 2); std::ostringstream ss; + ss << m_flightMasterFactionId << ' '; for (size_t i = 0; i < m_TaxiDestinations.size(); ++i) + { ss << m_TaxiDestinations[i] << ' '; + } - ss << _taxiSegment << ' '; return ss.str(); } uint32 PlayerTaxi::GetCurrentTaxiPath() const { - if (m_TaxiDestinations.size() < 2 || m_TaxiDestinations.size() <= _taxiSegment + 1) + if (m_TaxiDestinations.size() < 2) + { return 0; + } uint32 path; uint32 cost; - sObjectMgr->GetTaxiPath(m_TaxiDestinations[_taxiSegment], m_TaxiDestinations[_taxiSegment + 1], path, cost); + sObjectMgr->GetTaxiPath(m_TaxiDestinations[0], m_TaxiDestinations[1], path, cost); return path; } @@ -205,3 +231,8 @@ std::ostringstream& operator<< (std::ostringstream& ss, PlayerTaxi const& taxi) ss << taxi.m_taximask[i] << ' '; return ss; } + +FactionTemplateEntry const* PlayerTaxi::GetFlightMasterFactionTemplate() const +{ + return sFactionTemplateStore.LookupEntry(m_flightMasterFactionId); +} diff --git a/src/server/game/Entities/Player/PlayerTaxi.h b/src/server/game/Entities/Player/PlayerTaxi.h index 774b8499b..3923a8c54 100644 --- a/src/server/game/Entities/Player/PlayerTaxi.h +++ b/src/server/game/Entities/Player/PlayerTaxi.h @@ -26,7 +26,7 @@ class ByteBuffer; class AC_GAME_API PlayerTaxi { public: - PlayerTaxi(); + PlayerTaxi() : m_flightMasterFactionId(0) { m_taximask.fill(0); } ~PlayerTaxi() = default; // Nodes @@ -59,29 +59,28 @@ public: bool LoadTaxiDestinationsFromString(std::string const& values, TeamId teamId); std::string SaveTaxiDestinationsToString(); - void ClearTaxiDestinations() { m_TaxiDestinations.clear(); _taxiSegment = 0; } + void ClearTaxiDestinations() { m_TaxiDestinations.clear(); } void AddTaxiDestination(uint32 dest) { m_TaxiDestinations.push_back(dest); } - [[nodiscard]] uint32 GetTaxiSource() const { return m_TaxiDestinations.size() <= _taxiSegment + 1 ? 0 : m_TaxiDestinations[_taxiSegment]; } - [[nodiscard]] uint32 GetTaxiDestination() const { return m_TaxiDestinations.size() <= _taxiSegment + 1 ? 0 : m_TaxiDestinations[_taxiSegment + 1]; } + [[nodiscard]] uint32 GetTaxiSource() const { return m_TaxiDestinations.empty() ? 0 : m_TaxiDestinations.front(); } + [[nodiscard]] uint32 GetTaxiDestination() const { return m_TaxiDestinations.size() < 2 ? 0 : m_TaxiDestinations[1]; } [[nodiscard]] uint32 GetCurrentTaxiPath() const; uint32 NextTaxiDestination() { - ++_taxiSegment; + m_TaxiDestinations.pop_front(); return GetTaxiDestination(); } - // xinef: - void SetTaxiSegment(uint32 segment) { _taxiSegment = segment; } - [[nodiscard]] uint32 GetTaxiSegment() const { return _taxiSegment; } - - [[nodiscard]] std::vector const& GetPath() const { return m_TaxiDestinations; } + [[nodiscard]] std::deque const& GetPath() const { return m_TaxiDestinations; } [[nodiscard]] bool empty() const { return m_TaxiDestinations.empty(); } + [[nodiscard]] FactionTemplateEntry const* GetFlightMasterFactionTemplate() const; + void SetFlightMasterFactionTemplateId(uint32 factionTemplateId) { m_flightMasterFactionId = factionTemplateId; } friend std::ostringstream& operator<< (std::ostringstream& ss, PlayerTaxi const& taxi); + private: TaxiMask m_taximask; - std::vector m_TaxiDestinations; - uint32 _taxiSegment; + std::deque m_TaxiDestinations; + uint32 m_flightMasterFactionId; }; #endif diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 949524ce2..e159441c8 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -2195,7 +2195,7 @@ void WorldSession::HandleCharFactionOrRaceChangeCallback(std::shared_ptrSendInitialPacketsAfterAddToMap(); + // flight fast teleport case + if (GetPlayer()->IsInFlight()) + { + if (!GetPlayer()->InBattleground()) + { + // short preparations to continue flight + MovementGenerator* movementGenerator = GetPlayer()->GetMotionMaster()->top(); + movementGenerator->Initialize(GetPlayer()); + return; + } + + // battleground state prepare, stop flight + GetPlayer()->GetMotionMaster()->MovementExpired(); + GetPlayer()->CleanupAfterTaxiFlight(); + } + // resurrect character at enter into instance where his corpse exist after add to map Corpse* corpse = GetPlayer()->GetMap()->GetCorpseByPlayer(GetPlayer()->GetGUID()); if (corpse && corpse->GetType() != CORPSE_BONES) diff --git a/src/server/game/Handlers/TaxiHandler.cpp b/src/server/game/Handlers/TaxiHandler.cpp index 466285a24..e3678168a 100644 --- a/src/server/game/Handlers/TaxiHandler.cpp +++ b/src/server/game/Handlers/TaxiHandler.cpp @@ -15,6 +15,7 @@ * with this program. If not, see . */ +#include "GameTime.h" #include "ObjectMgr.h" #include "Opcodes.h" #include "Player.h" @@ -36,25 +37,24 @@ void WorldSession::HandleTaxiNodeStatusQueryOpcode(WorldPacket& recvData) void WorldSession::SendTaxiStatus(ObjectGuid guid) { - // cheating checks - Creature* unit = GetPlayer()->GetMap()->GetCreature(guid); - if (!unit) + Player* const player = GetPlayer(); + Creature* unit = ObjectAccessor::GetCreature(*player, guid); + if (!unit || unit->IsHostileTo(player) || !unit->HasNpcFlag(UNIT_NPC_FLAG_FLIGHTMASTER)) { LOG_DEBUG("network", "WorldSession::SendTaxiStatus - Unit ({}) not found.", guid.ToString()); return; } - uint32 curloc = sObjectMgr->GetNearestTaxiNode(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ(), unit->GetMapId(), GetPlayer()->GetTeamId()); - - // not found nearest - if (curloc == 0) + // find taxi node + uint32 nearest = sObjectMgr->GetNearestTaxiNode(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ(), unit->GetMapId(), player->GetTeamId()); + if (!nearest) + { return; - - LOG_DEBUG("network", "WORLD: current location {} ", curloc); + } WorldPacket data(SMSG_TAXINODE_STATUS, 9); data << guid; - data << uint8(GetPlayer()->m_taxi.IsTaximaskNodeKnown(curloc) ? 1 : 0); + data << uint8(player->m_taxi.IsTaximaskNodeKnown(nearest) ? 1 : 0); SendPacket(&data); LOG_DEBUG("network", "WORLD: Sent SMSG_TAXINODE_STATUS"); } @@ -166,7 +166,7 @@ void WorldSession::SendDiscoverNewTaxiNode(uint32 nodeid) } } -void WorldSession::HandleActivateTaxiExpressOpcode (WorldPacket& recvData) +void WorldSession::HandleActivateTaxiExpressOpcode(WorldPacket& recvData) { LOG_DEBUG("network", "WORLD: Received CMSG_ACTIVATETAXIEXPRESS"); @@ -179,6 +179,7 @@ void WorldSession::HandleActivateTaxiExpressOpcode (WorldPacket& recvData) if (!npc) { LOG_DEBUG("network", "WORLD: HandleActivateTaxiExpressOpcode - Unit ({}) not found or you can't interact with it.", guid.ToString()); + SendActivateTaxiReply(ERR_TAXITOOFARAWAY); return; } std::vector nodes; @@ -218,6 +219,46 @@ void WorldSession::HandleMoveSplineDoneOpcode(WorldPacket& recvData) ReadMovementInfo(recvData, &movementInfo); recvData.read_skip(); // spline id + + // in taxi flight packet received in 2 case: + // 1) end taxi path in far (multi-node) flight + // 2) switch from one map to other in case multim-map taxi path + // we need process only (1) + + uint32 curDest = GetPlayer()->m_taxi.GetTaxiDestination(); + if (curDest) + { + TaxiNodesEntry const* curDestNode = sTaxiNodesStore.LookupEntry(curDest); + + // far teleport case + if (curDestNode && curDestNode->map_id != GetPlayer()->GetMapId() && GetPlayer()->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE) + { + if (FlightPathMovementGenerator* flight = dynamic_cast(GetPlayer()->GetMotionMaster()->top())) + { + // short preparations to continue flight + flight->SetCurrentNodeAfterTeleport(); + TaxiPathNodeEntry const* node = flight->GetPath()[flight->GetCurrentNode()]; + flight->SkipCurrentNode(); + + GetPlayer()->TeleportTo(curDestNode->map_id, node->x, node->y, node->z, GetPlayer()->GetOrientation(), TELE_TO_NOT_LEAVE_TAXI); + } + } + + return; + } + + // at this point only 1 node is expected (final destination) + if (GetPlayer()->m_taxi.GetPath().size() != 1) + { + return; + } + + GetPlayer()->CleanupAfterTaxiFlight(); + GetPlayer()->SetFallInformation(GameTime::GetGameTime().count(), GetPlayer()->GetPositionZ()); + if (GetPlayer()->pvpInfo.IsHostile) + { + GetPlayer()->CastSpell(GetPlayer(), 2479, true); + } } void WorldSession::HandleActivateTaxiOpcode(WorldPacket& recvData) @@ -234,6 +275,7 @@ void WorldSession::HandleActivateTaxiOpcode(WorldPacket& recvData) if (!npc) { LOG_DEBUG("network", "WORLD: HandleActivateTaxiOpcode - Unit ({}) not found or you can't interact with it.", guid.ToString()); + SendActivateTaxiReply(ERR_TAXITOOFARAWAY); return; } diff --git a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp index 10c9563df..28bb2d426 100644 --- a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp @@ -270,35 +270,71 @@ void WaypointMovementGenerator::MovementInform(Creature* creature) 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::vector const& taxi = player->m_taxi.GetPath(); - for (uint32 src = player->m_taxi.GetTaxiSegment(), dst = player->m_taxi.GetTaxiSegment() + 1; dst < taxi.size(); src = dst++) + std::deque 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) - i_path.push_back(nodes[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)); + _pointsForPathSwitch.push_back({ uint32(i_path.size() - 1), int32(ceil(cost * discount)) }); } } @@ -313,7 +349,8 @@ 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); - // xinef: this should be cleaned by CleanupAfterTaxiFlight(); function! + uint32 taxiNodeId = player->m_taxi.GetTaxiDestination(); + player->m_taxi.ClearTaxiDestinations(); player->Dismount(); player->RemoveUnitFlag(UNIT_FLAG_DISABLE_MOVE | UNIT_FLAG_TAXI_FLIGHT); @@ -324,7 +361,13 @@ void FlightPathMovementGenerator::DoFinalize(Player* player) // this prevent cheating with landing point at lags // when client side flight end early in comparison server side player->StopMoving(); - player->SetFallInformation(GameTime::GetGameTime().count(), player->GetPositionZ()); + + // 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); @@ -334,13 +377,23 @@ void FlightPathMovementGenerator::DoFinalize(Player* player) 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); - uint32 end = GetPathAtMapEnd(); - for (uint32 i = GetCurrentNode(); i < end; ++i) + // 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); @@ -353,77 +406,40 @@ void FlightPathMovementGenerator::DoReset(Player* player) bool FlightPathMovementGenerator::DoUpdate(Player* player, uint32 /*diff*/) { - if (!player) - return false; - - // xinef: map was switched - if (_mapSwitch) - { - DoInitialize(player); - _mapSwitch = false; - return true; - } - - uint32 pointId = (uint32)player->movespline->currentPathIdx(); - if (pointId > i_currentNode) + // 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 { - if (i_currentNode >= i_path.size()) - { - LOG_INFO("misc", "TAXI NODE WAS GREATER THAN PATH SIZE, {}, POINTID: {}, NODESIZE: {}, CURRENT: {}", - player->GetGUID().ToString(), pointId, i_path.size(), i_currentNode); - player->CleanupAfterTaxiFlight(); - return false; - } - - if (i_path[i_currentNode]->mapid != player->GetMapId()) - { - LOG_INFO("misc", "Player on different map, curmap: {}, pointmap: {}, nodesize: {}, currentnode: {}", player->GetMapId(), i_path[i_currentNode]->mapid, i_path.size(), i_currentNode); - player->CleanupAfterTaxiFlight(); - return false; - } + ASSERT(i_currentNode < i_path.size(), "Point Id: {}\n{}", pointId, player->GetGUID().ToString().c_str()); DoEventIfAny(player, i_path[i_currentNode], departureEvent); - - // xinef: erase any previous points - uint32 curSize = _pointsForPathSwitch.size(); - while (!_pointsForPathSwitch.empty() && _pointsForPathSwitch.front() <= i_currentNode) + while (!_pointsForPathSwitch.empty() && _pointsForPathSwitch.front().PathIndex <= i_currentNode) + { _pointsForPathSwitch.pop_front(); - - // xinef: switch destination only once - if (curSize != _pointsForPathSwitch.size()) 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 && player->GetMapId() == _endMapId) + if (i_currentNode == _preloadTargetNode) + { PreloadEndGrid(); - i_currentNode += (uint32)departureEvent; + } + + i_currentNode += departureEvent ? 1 : 0; departureEvent = !departureEvent; - - // xinef: map should be switched, do not rely on client packets QQ - if (i_currentNode + 1 < i_path.size() && i_path[i_currentNode + 1]->mapid != player->GetMapId()) - { - ++i_currentNode; - _mapSwitch = true; - player->TeleportTo(i_path[i_currentNode]->mapid, i_path[i_currentNode]->x, i_path[i_currentNode]->y, i_path[i_currentNode]->z, player->GetOrientation(), TELE_TO_NOT_LEAVE_TAXI); - return true; - } - - // xinef: reached the end - if (i_currentNode >= i_path.size() - 1) - { - player->CleanupAfterTaxiFlight(); - player->SetFallInformation(GameTime::GetGameTime().count(), player->GetPositionZ()); - if (player->pvpInfo.IsHostile) - player->CastSpell(player, 2479, true); - - return false; - } - } while (true); + } while (i_currentNode < i_path.size() - 1); } return i_currentNode < (i_path.size() - 1); @@ -432,7 +448,9 @@ bool FlightPathMovementGenerator::DoUpdate(Player* player, uint32 /*diff*/) 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) @@ -449,11 +467,20 @@ void FlightPathMovementGenerator::DoEventIfAny(Player* player, TaxiPathNodeEntry { 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()); + 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 @@ -485,11 +512,11 @@ void FlightPathMovementGenerator::PreloadEndGrid() // Load the grid if (endMap) { - LOG_DEBUG("movement", "Preloading rid ({}, {}) for map {} at node index {}/{}", _endGridX, _endGridY, _endMapId, _preloadTargetNode, (uint32)(i_path.size() - 1)); + 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("movement", "Unable to determine map to preload flightmaster grid"); + LOG_DEBUG("misc", "Unable to determine map to preload flightmaster grid"); } } diff --git a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.h b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.h index 35eaa932a..1699a71a6 100644 --- a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.h +++ b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.h @@ -101,40 +101,48 @@ private: class FlightPathMovementGenerator : public MovementGeneratorMedium< Player, FlightPathMovementGenerator >, public PathMovementBase { -public: - explicit FlightPathMovementGenerator(uint32 startNode = 0) - { - i_currentNode = startNode; - _endGridX = 0.0f; - _endGridY = 0.0f; - _endMapId = 0; - _preloadTargetNode = 0; - _mapSwitch = false; - } - void LoadPath(Player* player); - void DoInitialize(Player*); - void DoReset(Player*); - void DoFinalize(Player*); - bool DoUpdate(Player*, uint32); - MovementGeneratorType GetMovementGeneratorType() { return FLIGHT_MOTION_TYPE; } + public: + explicit FlightPathMovementGenerator(uint32 startNode = 0) + { + i_currentNode = startNode; + _endGridX = 0.0f; + _endGridY = 0.0f; + _endMapId = 0; + _preloadTargetNode = 0; + } + void LoadPath(Player* player); + void DoInitialize(Player*); + void DoReset(Player*); + void DoFinalize(Player*); + bool DoUpdate(Player*, uint32); + MovementGeneratorType GetMovementGeneratorType() override { return FLIGHT_MOTION_TYPE; } - TaxiPathNodeList const& GetPath() { return i_path; } - uint32 GetPathAtMapEnd() const; - bool HasArrived() const { return (i_currentNode >= i_path.size()); } - void SetCurrentNodeAfterTeleport(); - void SkipCurrentNode() { ++i_currentNode; } - void DoEventIfAny(Player* player, TaxiPathNodeEntry const* node, bool departure); + TaxiPathNodeList const& GetPath() { return i_path; } + uint32 GetPathAtMapEnd() const; + bool HasArrived() const { return (i_currentNode >= i_path.size()); } + void SetCurrentNodeAfterTeleport(); + void SkipCurrentNode() { ++i_currentNode; } + void DoEventIfAny(Player* player, TaxiPathNodeEntry const* node, bool departure); - void InitEndGridInfo(); - void PreloadEndGrid(); + bool GetResetPos(Player*, float& x, float& y, float& z); -private: - float _endGridX; //! X coord of last node location - float _endGridY; //! Y coord of last node location - uint32 _endMapId; //! map Id of last node location - uint32 _preloadTargetNode; //! node index where preloading starts - bool _mapSwitch; + void InitEndGridInfo(); + void PreloadEndGrid(); - std::deque _pointsForPathSwitch; //! node indexes and costs where TaxiPath changes + private: + + float _endGridX; //! X coord of last node location + float _endGridY; //! Y coord of last node location + uint32 _endMapId; //! map Id of last node location + uint32 _preloadTargetNode; //! node index where preloading starts + + struct TaxiNodeChangeInfo + { + uint32 PathIndex; + int32 Cost; + }; + + std::deque _pointsForPathSwitch; //! node indexes and costs where TaxiPath changes }; + #endif diff --git a/src/server/shared/DataStores/DBCStructure.h b/src/server/shared/DataStores/DBCStructure.h index 6b36051f7..38f0d6405 100644 --- a/src/server/shared/DataStores/DBCStructure.h +++ b/src/server/shared/DataStores/DBCStructure.h @@ -22,6 +22,7 @@ #include "Define.h" #include "SharedDefines.h" #include "Util.h" +#include #include #include #include @@ -2203,6 +2204,7 @@ typedef std::map TaxiPathSetBySource; typedef std::vector TaxiPathNodeList; typedef std::vector TaxiPathNodesByPath; -#define TaxiMaskSize 14 -typedef uint32 TaxiMask[TaxiMaskSize]; +static constexpr size_t TaxiMaskSize = 14; +typedef std::array TaxiMask; + #endif