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