diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 226cc0d9c..02c39ee96 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -18280,45 +18280,10 @@ void Player::_LoadDeclinedNames(PreparedQueryResult result) SetUpdateFieldValue(declinedNames.ModifyValue(&UF::DeclinedNames::Name, i), (*result)[i].GetString()); } -void Player::_LoadArenaTeamInfo(PreparedQueryResult result) -{ - // arenateamid, played_week, played_season, personal_rating - uint16 personalRatingCache[] = { 0, 0, 0 }; - - if (result) - { - do - { - Field* fields = result->Fetch(); - - uint32 arenaTeamId = fields[0].GetUInt32(); - - ArenaTeam* arenaTeam = sArenaTeamMgr->GetArenaTeamById(arenaTeamId); - if (!arenaTeam) - { - TC_LOG_ERROR("entities.player.loading", "Player::_LoadArenaTeamInfo: couldn't load arenateam {}", arenaTeamId); - continue; - } - - uint8 arenaSlot = arenaTeam->GetSlot(); - ASSERT(arenaSlot < 3); - - personalRatingCache[arenaSlot] = fields[4].GetUInt16(); - - SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_ID, arenaTeamId); - SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_TYPE, arenaTeam->GetType()); - SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_MEMBER, (arenaTeam->GetCaptain() == GetGUID()) ? 0 : 1); - SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_GAMES_WEEK, uint32(fields[1].GetUInt16())); - SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_GAMES_SEASON, uint32(fields[2].GetUInt16())); - SetArenaTeamInfoField(arenaSlot, ARENA_TEAM_WINS_SEASON, uint32(fields[3].GetUInt16())); - } while (result->NextRow()); - } - - for (uint8 slot = 0; slot <= 2; ++slot) - { - SetArenaTeamInfoField(slot, ARENA_TEAM_PERSONAL_RATING, uint32(personalRatingCache[slot])); - } -} +// Player::_LoadArenaTeamInfo was removed along with its login query (PLAYER_LOGIN_QUERY_LOAD_ARENA_INFO): every +// value it set went through SetArenaTeamInfoField, which is an intentional empty stub in this fork, so the loader +// read the arena rows and wrote them nowhere. Per-player arena team state is derived from ArenaTeam membership at +// runtime instead. void Player::_LoadEquipmentSets(PreparedQueryResult result) { @@ -19283,6 +19248,18 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol if (!m_taxi.LoadTaxiMask(fields.taximask)) // must be before InitTaxiNodesForLevel TC_LOG_WARN("entities.player.loading", "Player::LoadFromDB: Player ({}) has invalid taximask ({}) in DB. Forced partial load.", GetGUID().ToString(), fields.taximask); + //WowCommunity + // Warband (account-wide) taxi: merge the account mask into this character's mask and keep the account copy + // so newly learned nodes can be written back to warband_taxi_mask on save. + if (PreparedQueryResult warbandTaxiResult = holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_WARBAND_TAXI_MASK)) + m_taxi.LoadAccountTaxiMask((*warbandTaxiResult)[0].GetString()); + + // Other max-level characters on this account (excluding self): KillRewarder grants +5% XP per alt, capped at + // 25%. + if (PreparedQueryResult maxLevelCharsResult = holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_WARBAND_MAX_LEVEL_COUNT)) + _warbandMaxLevelCharCount = uint8(std::min((*maxLevelCharsResult)[0].GetUInt64(), 5)); + //WowCommunity + uint32 extraflags = fields.extra_flags; _LoadPetStable(fields.summonedPetNumber, holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_PET_SLOTS)); @@ -21801,6 +21778,16 @@ void Player::SaveToDB(LoginDatabaseTransaction loginTransaction, CharacterDataba _SaveAccountBankTabSettings(trans); _SaveAccountBankItems(trans); _SaveAccountBankCoinage(trans); + // Warband taxi: only rewrite warband_taxi_mask when this character learned a new account-wide node; the mask + // loaded at login is already the account's, so an untouched session has nothing to write. + if (m_taxi.IsAccountTaximaskDirty()) + { + stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_WARBAND_TAXI_MASK); + stmt->setUInt32(0, GetSession()->GetBattlenetAccountId()); + stmt->setString(1, m_taxi.SaveAccountTaxiMaskToString()); + trans->Append(stmt); + m_taxi.ClearAccountTaximaskDirty(); + } for (auto const& [type, garrison] : _garrisons) garrison->SaveToDB(trans); //WowCommunity diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 2c4f98d77..55c9065b6 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -3607,7 +3607,7 @@ class TC_GAME_API Player final : public Unit, public GridObject void _LoadStoredAuraTeleportLocations(PreparedQueryResult result); bool _LoadHomeBind(PreparedQueryResult result); void _LoadDeclinedNames(PreparedQueryResult result); - void _LoadArenaTeamInfo(PreparedQueryResult result); + // _LoadArenaTeamInfo removed: SetArenaTeamInfoField is an empty stub, so it persisted nothing (see Player.cpp). void _LoadEquipmentSets(PreparedQueryResult result); void _LoadTransmogCustomSets(PreparedQueryResult result); void _LoadTransmogOutfits(PreparedQueryResult setsResult, PreparedQueryResult situationsResult, PreparedQueryResult slotsResult, diff --git a/src/server/game/Entities/Player/PlayerTaxi.cpp b/src/server/game/Entities/Player/PlayerTaxi.cpp index d4a1b95e7..80ed0ba29 100644 --- a/src/server/game/Entities/Player/PlayerTaxi.cpp +++ b/src/server/game/Entities/Player/PlayerTaxi.cpp @@ -300,3 +300,51 @@ void PlayerTaxi::MergeAccountTaxiMask(TaxiMask const& accountMask) m_taximask[field] |= submask; } } + +void PlayerTaxi::LoadAccountTaxiMask(std::string const& data) +{ + m_accountTaximask = LoadTaxiMaskFromString(data); + m_accountTaximaskDirty = false; + MergeAccountTaxiMask(m_accountTaximask); + + // Backfill: nodes this character knew before account-wide taxi existed (or before this character ever saved + // with the account mask wired up) become part of the account mask; anything the account already had is a no-op. + for (TaxiNodesEntry const* node : sTaxiNodesStore) + { + if (node->GetFlags().HasFlag(TaxiNodeFlags::NotAccountWide)) + continue; + + uint32 field = uint32((node->ID - 1) / (sizeof(TaxiMask::value_type) * 8)); + TaxiMask::value_type submask = TaxiMask::value_type(1 << ((node->ID - 1) % (sizeof(TaxiMask::value_type) * 8))); + + if ((m_taximask[field] & submask) && !(m_accountTaximask[field] & submask)) + { + m_accountTaximask[field] |= submask; + m_accountTaximaskDirty = true; + } + } +} + +bool PlayerTaxi::SetAccountTaximaskNode(uint32 nodeidx) +{ + TaxiNodesEntry const* node = sTaxiNodesStore.LookupEntry(nodeidx); + if (!node || node->GetFlags().HasFlag(TaxiNodeFlags::NotAccountWide)) + return false; + + uint32 field = uint32((nodeidx - 1) / (sizeof(TaxiMask::value_type) * 8)); + TaxiMask::value_type submask = TaxiMask::value_type(1 << ((nodeidx - 1) % (sizeof(TaxiMask::value_type) * 8))); + if (m_accountTaximask[field] & submask) + return false; + + m_accountTaximask[field] |= submask; + m_accountTaximaskDirty = true; + return true; +} + +std::string PlayerTaxi::SaveAccountTaxiMaskToString() const +{ + std::ostringstream ss; + for (std::size_t i = 0; i < m_accountTaximask.size(); ++i) + ss << uint32(m_accountTaximask[i]) << ' '; + return ss.str(); +} diff --git a/src/server/game/Entities/Player/PlayerTaxi.h b/src/server/game/Entities/Player/PlayerTaxi.h index fcf47095a..62c960435 100644 --- a/src/server/game/Entities/Player/PlayerTaxi.h +++ b/src/server/game/Entities/Player/PlayerTaxi.h @@ -50,6 +50,12 @@ class TC_GAME_API PlayerTaxi //WowCommunity static TaxiMask LoadTaxiMaskFromString(std::string const& data); void MergeAccountTaxiMask(TaxiMask const& accountMask); + void LoadAccountTaxiMask(std::string const& data); + bool SetAccountTaximaskNode(uint32 nodeidx); + TaxiMask const& GetAccountTaxiMask() const { return m_accountTaximask; } + bool IsAccountTaximaskDirty() const { return m_accountTaximaskDirty; } + void ClearAccountTaximaskDirty() { m_accountTaximaskDirty = false; } + std::string SaveAccountTaxiMaskToString() const; //WowCommunity bool IsTaximaskNodeKnown(uint32 nodeidx) const @@ -96,6 +102,10 @@ class TC_GAME_API PlayerTaxi friend std::ostringstream& operator<<(std::ostringstream& ss, PlayerTaxi const& taxi); private: TaxiMask m_taximask; + //WowCommunity + TaxiMask m_accountTaximask; // warband (account-wide) copy, persisted to warband_taxi_mask on save + bool m_accountTaximaskDirty = false; + //WowCommunity std::deque m_TaxiDestinations; uint32 m_flightMasterFactionId = 0; }; diff --git a/src/server/game/Handlers/TaxiHandler.cpp b/src/server/game/Handlers/TaxiHandler.cpp index 5c8082bb9..1bdf0d299 100644 --- a/src/server/game/Handlers/TaxiHandler.cpp +++ b/src/server/game/Handlers/TaxiHandler.cpp @@ -136,6 +136,8 @@ bool WorldSession::SendLearnNewTaxiNode(Creature* unit) if (GetPlayer()->m_taxi.SetTaximaskNode(curloc)) { + GetPlayer()->m_taxi.SetAccountTaximaskNode(curloc); // warband: learned paths are shared account-wide + SendPacket(WorldPackets::Taxi::NewTaxiPath(curloc).Write()); WorldPackets::Taxi::TaxiNodeStatus data; @@ -153,7 +155,10 @@ bool WorldSession::SendLearnNewTaxiNode(Creature* unit) void WorldSession::SendDiscoverNewTaxiNode(uint32 nodeid) { if (GetPlayer()->m_taxi.SetTaximaskNode(nodeid)) + { + GetPlayer()->m_taxi.SetAccountTaximaskNode(nodeid); // warband: discovered paths are shared account-wide SendPacket(WorldPackets::Taxi::NewTaxiPath(nodeid).Write()); + } } void WorldSession::HandleActivateTaxiOpcode(WorldPackets::Taxi::ActivateTaxi& activateTaxi) diff --git a/src/server/game/Handlers/TicketHandler.cpp b/src/server/game/Handlers/TicketHandler.cpp index b5a74671c..2881db678 100644 --- a/src/server/game/Handlers/TicketHandler.cpp +++ b/src/server/game/Handlers/TicketHandler.cpp @@ -41,6 +41,16 @@ void WorldSession::HandleGMTicketSystemStatusOpcode(WorldPackets::Ticket::GMTick SendPacket(response.Write()); } +void WorldSession::HandleGmTicketAcknowledgeSurvey(WorldPackets::Ticket::GMTicketAcknowledgeSurvey& packet) +{ + // The client sends this after the player dismisses the post-resolution survey for a case shown in + // SMSG_GM_TICKET_CASE_STATUS (CaseID = GMTICKET_CASE_ID_* base + ticket id, see SupportMgr.h). Cases are + // derived from open tickets only, so by the time the survey appears the ticket is already closed and drops + // out of the next case list on its own - the acknowledgement is purely a client-side UI dismissal and needs + // no server state or response. Logged so the case id remains traceable if the flow is ever extended. + TC_LOG_DEBUG("network", "WorldSession::HandleGmTicketAcknowledgeSurvey: {} acknowledged survey for case {}.", GetPlayerInfo(), packet.CaseID); +} + void WorldSession::HandleSubmitUserFeedback(WorldPackets::Ticket::SubmitUserFeedback& userFeedback) { if (userFeedback.IsSuggestion) diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index 6251d42b0..0b684c2de 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -151,7 +151,7 @@ void OpcodeTable::InitializeClientOpcodes() DEFINE_HANDLER(CMSG_ACCEPT_WARGAME_INVITE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAcceptWargameInvite); DEFINE_HANDLER(CMSG_ACCOUNT_BANK_DEPOSIT_MONEY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAccountBankDepositMoney); DEFINE_HANDLER(CMSG_ACCOUNT_BANK_WITHDRAW_MONEY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleAccountBankWithdrawMoney); - DEFINE_HANDLER(CMSG_ACCOUNT_NOTIFICATION_ACKNOWLEDGED, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_ACCOUNT_NOTIFICATION_ACKNOWLEDGED, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleAccountNotificationAcknowledged); DEFINE_HANDLER(CMSG_ACCOUNT_STORE_BEGIN_PURCHASE_OR_REFUND, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL); DEFINE_HANDLER(CMSG_ACTIVATE_SOULBIND, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleActivateSoulbind); DEFINE_HANDLER(CMSG_ACTIVATE_TAXI, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleActivateTaxiOpcode); @@ -509,12 +509,12 @@ void OpcodeTable::InitializeClientOpcodes() DEFINE_HANDLER(CMSG_GET_PVP_OPTIONS_ENABLED, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleGetPVPOptionsEnabled); DEFINE_HANDLER(CMSG_GET_RAF_ACCOUNT_INFO, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGetRafAccountInfo); DEFINE_HANDLER(CMSG_GET_REGIONWIDE_CHARACTER_RESTRICTION_AND_MAIL_DATA, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGetRegionwideCharacterRestrictionAndMailData); - DEFINE_HANDLER(CMSG_GET_REMAINING_GAME_TIME, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_GET_REMAINING_GAME_TIME, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleGetRemainingGameTime); DEFINE_HANDLER(CMSG_GET_TROPHY_LIST, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleGetTrophyList); DEFINE_HANDLER(CMSG_GET_UNDELETE_CHARACTER_COOLDOWN_STATUS, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleGetUndeleteCooldownStatus); DEFINE_HANDLER(CMSG_GET_VAS_ACCOUNT_CHARACTER_LIST, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleGetVasAccountCharacterList); DEFINE_HANDLER(CMSG_GET_VAS_TRANSFER_TARGET_REALM_LIST, STATUS_AUTHED, PROCESS_THREADUNSAFE, &WorldSession::HandleGetVasTransferTargetRealmList); - DEFINE_HANDLER(CMSG_GM_TICKET_ACKNOWLEDGE_SURVEY, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL); + DEFINE_HANDLER(CMSG_GM_TICKET_ACKNOWLEDGE_SURVEY, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleGmTicketAcknowledgeSurvey); DEFINE_HANDLER(CMSG_GM_TICKET_GET_CASE_STATUS, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleGMTicketGetCaseStatusOpcode); DEFINE_HANDLER(CMSG_GM_TICKET_GET_SYSTEM_STATUS, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleGMTicketSystemStatusOpcode); DEFINE_HANDLER(CMSG_GOSSIP_REFRESH_OPTIONS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleGossipRefreshOptions); diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 67fd8cc34..f83e1e15b 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -1198,6 +1198,7 @@ namespace WorldPackets class SupportTicketSubmitComplaint; class BugReport; class Complaint; + class GMTicketAcknowledgeSurvey; } namespace Token @@ -1822,6 +1823,7 @@ public: // GM Ticket opcodes void HandleGMTicketGetCaseStatusOpcode(WorldPackets::Ticket::GMTicketGetCaseStatus& packet); void HandleGMTicketSystemStatusOpcode(WorldPackets::Ticket::GMTicketGetSystemStatus& packet); + void HandleGmTicketAcknowledgeSurvey(WorldPackets::Ticket::GMTicketAcknowledgeSurvey& packet); void HandleSubmitUserFeedback(WorldPackets::Ticket::SubmitUserFeedback& userFeedback); void HandleSupportTicketSubmitComplaint(WorldPackets::Ticket::SupportTicketSubmitComplaint& packet); void HandleBugReportOpcode(WorldPackets::Ticket::BugReport& bugReport);