++
This commit is contained in:
@@ -56,6 +56,9 @@
|
|||||||
#include "World.h"
|
#include "World.h"
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
|
#include "MajorFactionMgr.h"
|
||||||
|
#include "MajorFactionPackets.h"
|
||||||
|
#include "ReputationMgr.h"
|
||||||
|
|
||||||
void WorldSession::HandleRepopRequest(WorldPackets::Misc::RepopRequest& /*packet*/)
|
void WorldSession::HandleRepopRequest(WorldPackets::Misc::RepopRequest& /*packet*/)
|
||||||
{
|
{
|
||||||
@@ -1227,3 +1230,58 @@ void WorldSession::HandleSetCurrencyFlags(WorldPackets::Misc::SetCurrencyFlags c
|
|||||||
{
|
{
|
||||||
_player->SetCurrencyFlagsFromClient(setCurrenctFlags.CurrencyID, setCurrenctFlags.Flags);
|
_player->SetCurrencyFlagsFromClient(setCurrenctFlags.CurrencyID, setCurrenctFlags.Flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WorldSession::HandleCovenantRenownRequestCatchupState(WorldPackets::MajorFactions::RequestCatchupState const& /*packet*/)
|
||||||
|
{
|
||||||
|
// Phase 10E: respond with the per-faction catchup state. For every Major
|
||||||
|
// Faction where this character's renown is below the account-max, emit
|
||||||
|
// a (FactionID, CatchupPercent) record. CatchupPercent is computed as a
|
||||||
|
// 0..100 ratio of how far the character has to catch up; the client uses
|
||||||
|
// this to drive the renown-catchup-bonus reputation multiplier.
|
||||||
|
//
|
||||||
|
// Wire format header byte is (payloadLen << 1) & 0xFE; max payload 127B
|
||||||
|
// = 15 records. With 20 majors today this is sufficient in practice
|
||||||
|
// because typically only a few factions need catchup at any time.
|
||||||
|
|
||||||
|
if (!_player)
|
||||||
|
return;
|
||||||
|
|
||||||
|
WorldPackets::MajorFactions::CovenantRenownSendCatchupState response;
|
||||||
|
|
||||||
|
constexpr size_t MaxEntries = 127 / 8; // 15 - matches the 127B header cap
|
||||||
|
|
||||||
|
for (uint32 factionId : sMajorFactionMgr->GetMajorFactionIDs())
|
||||||
|
{
|
||||||
|
if (response.Entries.size() >= MaxEntries)
|
||||||
|
{
|
||||||
|
TC_LOG_WARN("network", "HandleCovenantRenownRequestCatchupState: more than {} catchup-eligible majors for player {} - truncating",
|
||||||
|
MaxEntries, _player->GetName());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
FactionEntry const* faction = sFactionStore.LookupEntry(factionId);
|
||||||
|
if (!faction || !faction->CanHaveReputation())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Read the per-character renown level via existing ReputationMgr path.
|
||||||
|
int32 charRenown = _player->GetReputationMgr().GetRenownLevel(faction);
|
||||||
|
uint32 maxRenown = sMajorFactionMgr->GetMaxRenownLevel(factionId);
|
||||||
|
if (maxRenown == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Look up the account-max renown level for this faction from the
|
||||||
|
// already-loaded ReputationMgr cache (populated by warband Phase 4
|
||||||
|
// LoadAccountWideFromDB at login). Returns -1 when the faction is
|
||||||
|
// not warband-shared or no row exists yet -> no catchup applies.
|
||||||
|
int32 accountRenown = _player->GetReputationMgr().GetAccountRenownLevel(factionId);
|
||||||
|
if (accountRenown < 0 || accountRenown <= charRenown)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int32 delta = accountRenown - charRenown;
|
||||||
|
int32 catchupPercent = std::min<int32>(100, (delta * 100) / int32(maxRenown));
|
||||||
|
|
||||||
|
response.Entries.push_back({ .FactionID = int32(factionId), .CatchupPercent = catchupPercent });
|
||||||
|
}
|
||||||
|
|
||||||
|
SendPacket(response.Write());
|
||||||
|
}
|
||||||
|
|||||||
@@ -1062,3 +1062,14 @@ void ReputationMgr::MarkRenownRewardGranted(uint32 renownRewardId, bool accountW
|
|||||||
|
|
||||||
CharacterDatabase.CommitTransaction(trans);
|
CharacterDatabase.CommitTransaction(trans);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int32 ReputationMgr::GetAccountRenownLevel(uint32 factionId) const
|
||||||
|
{
|
||||||
|
auto itr = _accountReputation.find(factionId);
|
||||||
|
if (itr != _accountReputation.end())
|
||||||
|
return itr->second.renownLevel;
|
||||||
|
|
||||||
|
// Fallback: not in account-wide cache (faction is not warband-shared,
|
||||||
|
// or no row exists yet). Caller should treat this as "no catchup data".
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -89,6 +89,14 @@ class TC_GAME_API ReputationMgr
|
|||||||
void LoadRenownRewardsGrantedFromDB(PreparedQueryResult charResult, PreparedQueryResult accountResult);
|
void LoadRenownRewardsGrantedFromDB(PreparedQueryResult charResult, PreparedQueryResult accountResult);
|
||||||
bool IsRenownRewardGranted(uint32 renownRewardId, bool accountWide) const;
|
bool IsRenownRewardGranted(uint32 renownRewardId, bool accountWide) const;
|
||||||
void MarkRenownRewardGranted(uint32 renownRewardId, bool accountWide);
|
void MarkRenownRewardGranted(uint32 renownRewardId, bool accountWide);
|
||||||
|
|
||||||
|
// -- Account-wide reputation read accessor (Phase 10E) ---------------
|
||||||
|
// Looks up the cached account-max renown level for a given faction,
|
||||||
|
// populated by LoadAccountWideFromDB at login. Returns the per-char
|
||||||
|
// renown level if the faction is not warband-shared or no account row
|
||||||
|
// exists.
|
||||||
|
int32 GetAccountRenownLevel(uint32 factionId) const;
|
||||||
|
|
||||||
public: // statics
|
public: // statics
|
||||||
static std::set<int32> const ReputationRankThresholds;
|
static std::set<int32> const ReputationRankThresholds;
|
||||||
static const int32 Reputation_Cap;
|
static const int32 Reputation_Cap;
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "MajorFactionPackets.h"
|
||||||
|
|
||||||
|
namespace WorldPackets::MajorFactions
|
||||||
|
{
|
||||||
|
WorldPacket const* CovenantRenownSendCatchupState::Write()
|
||||||
|
{
|
||||||
|
// Build the payload first so we can compute the length-prefix byte.
|
||||||
|
ByteBuffer payload;
|
||||||
|
for (Entry const& entry : Entries)
|
||||||
|
{
|
||||||
|
payload << int32(entry.FactionID);
|
||||||
|
payload << int32(entry.CatchupPercent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header byte: (payloadLen << 1) & 0xFE. The client masks bit 0
|
||||||
|
// off and right-shifts by one to recover the length. The 7-bit
|
||||||
|
// length field caps total payload at 127 bytes (15 entries).
|
||||||
|
ASSERT(payload.size() <= 127, "Catchup state payload exceeds 127 bytes (%zu bytes for %zu entries)",
|
||||||
|
payload.size(), Entries.size());
|
||||||
|
|
||||||
|
uint8 header = uint8((payload.size() << 1) & 0xFE);
|
||||||
|
_worldPacket << header;
|
||||||
|
//_worldPacket.append(payload.contents(), payload.size()); todo
|
||||||
|
return &_worldPacket;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TRINITYCORE_MAJOR_FACTION_PACKETS_H
|
||||||
|
#define TRINITYCORE_MAJOR_FACTION_PACKETS_H
|
||||||
|
|
||||||
|
#include "Packet.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace WorldPackets
|
||||||
|
{
|
||||||
|
namespace MajorFactions
|
||||||
|
{
|
||||||
|
// CMSG_COVENANT_RENOWN_REQUEST_CATCHUP_STATE (0x3B0111).
|
||||||
|
// The client sends this when opening the Renown UI (covenant or major
|
||||||
|
// faction). Empty payload - the server replies with the current
|
||||||
|
// catchup state for every applicable faction.
|
||||||
|
class RequestCatchupState final : public ClientPacket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit RequestCatchupState(WorldPacket&& packet) : ClientPacket(CMSG_COVENANT_RENOWN_REQUEST_CATCHUP_STATE, std::move(packet)) { }
|
||||||
|
|
||||||
|
void Read() override { }
|
||||||
|
};
|
||||||
|
|
||||||
|
// SMSG_COVENANT_RENOWN_SEND_CATCHUP_STATE (0x42030D).
|
||||||
|
//
|
||||||
|
// Wire format derived from IDA decomp of ParseCovenantRenownCatchupState
|
||||||
|
// (sub_7FF75C0EB140) at build 12.0.5.67186:
|
||||||
|
// uint8 Header - (PayloadLen << 1) & 0xFE; top 7 bits = byte length,
|
||||||
|
// bit 0 always 0 (reserved).
|
||||||
|
// uint8 Payload[PayloadLen] - array of inner records, each:
|
||||||
|
// int32 FactionID
|
||||||
|
// int32 CatchupPercent (0..100; client divides by 100 if
|
||||||
|
// Faction.dynamicFlags & 8, else uses raw)
|
||||||
|
//
|
||||||
|
// The header's 7-bit length limits the total payload to 127 bytes,
|
||||||
|
// i.e. up to 15 (factionID, percent) records per packet. With 20
|
||||||
|
// major factions today this is sufficient for any realistic catchup
|
||||||
|
// state because only factions where char renown < account renown
|
||||||
|
// are emitted.
|
||||||
|
class CovenantRenownSendCatchupState final : public ServerPacket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct Entry
|
||||||
|
{
|
||||||
|
int32 FactionID;
|
||||||
|
int32 CatchupPercent;
|
||||||
|
};
|
||||||
|
|
||||||
|
CovenantRenownSendCatchupState()
|
||||||
|
: ServerPacket(SMSG_COVENANT_RENOWN_SEND_CATCHUP_STATE, 1 + 15 * 8) { }
|
||||||
|
|
||||||
|
WorldPacket const* Write() override;
|
||||||
|
|
||||||
|
std::vector<Entry> Entries;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // TRINITYCORE_MAJOR_FACTION_PACKETS_H
|
||||||
@@ -405,7 +405,7 @@ void OpcodeTable::InitializeClientOpcodes()
|
|||||||
DEFINE_HANDLER(CMSG_CONVERT_ITEM_TO_BIND_TO_ACCOUNT, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
DEFINE_HANDLER(CMSG_CONVERT_ITEM_TO_BIND_TO_ACCOUNT, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
||||||
DEFINE_HANDLER(CMSG_CONVERT_RAID, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleConvertRaidOpcode);
|
DEFINE_HANDLER(CMSG_CONVERT_RAID, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleConvertRaidOpcode);
|
||||||
DEFINE_HANDLER(CMSG_CONVERT_TIMERUNNING_CHARACTER, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
DEFINE_HANDLER(CMSG_CONVERT_TIMERUNNING_CHARACTER, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
||||||
DEFINE_HANDLER(CMSG_COVENANT_RENOWN_REQUEST_CATCHUP_STATE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
|
DEFINE_HANDLER(CMSG_COVENANT_RENOWN_REQUEST_CATCHUP_STATE, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleCovenantRenownRequestCatchupState);
|
||||||
DEFINE_HANDLER(CMSG_CRAFTING_ORDER_CANCEL, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
DEFINE_HANDLER(CMSG_CRAFTING_ORDER_CANCEL, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
||||||
DEFINE_HANDLER(CMSG_CRAFTING_ORDER_CLAIM, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
DEFINE_HANDLER(CMSG_CRAFTING_ORDER_CLAIM, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
||||||
DEFINE_HANDLER(CMSG_CRAFTING_ORDER_CREATE, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
DEFINE_HANDLER(CMSG_CRAFTING_ORDER_CREATE, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
||||||
|
|||||||
@@ -748,6 +748,11 @@ namespace WorldPackets
|
|||||||
class GetCharacterCurrencyTransferLog;
|
class GetCharacterCurrencyTransferLog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace MajorFactions
|
||||||
|
{
|
||||||
|
class RequestCatchupState;
|
||||||
|
}
|
||||||
|
|
||||||
namespace PerksProgram
|
namespace PerksProgram
|
||||||
{
|
{
|
||||||
class RequestStoreFrontInfoUpdate;
|
class RequestStoreFrontInfoUpdate;
|
||||||
@@ -1874,6 +1879,14 @@ class TC_GAME_API WorldSession
|
|||||||
|
|
||||||
void HandleTabardVendorActivateOpcode(WorldPackets::NPC::TabardVendorActivate const& tabardVendorActivate);
|
void HandleTabardVendorActivateOpcode(WorldPackets::NPC::TabardVendorActivate const& tabardVendorActivate);
|
||||||
void HandleBankerActivateOpcode(WorldPackets::Bank::BankerActivate const& bankerActivate);
|
void HandleBankerActivateOpcode(WorldPackets::Bank::BankerActivate const& bankerActivate);
|
||||||
|
|
||||||
|
// Phase 10E - covenant/major-faction renown catchup request handler.
|
||||||
|
// The client sends CMSG_COVENANT_RENOWN_REQUEST_CATCHUP_STATE when
|
||||||
|
// opening the Renown UI; the server replies with the current catchup
|
||||||
|
// state for every major faction where the character is behind the
|
||||||
|
// account-max renown.
|
||||||
|
void HandleCovenantRenownRequestCatchupState(WorldPackets::MajorFactions::RequestCatchupState const& packet);
|
||||||
|
|
||||||
void HandleTrainerListOpcode(WorldPackets::NPC::Hello& packet);
|
void HandleTrainerListOpcode(WorldPackets::NPC::Hello& packet);
|
||||||
void HandleTrainerBuySpellOpcode(WorldPackets::NPC::TrainerBuySpell& packet);
|
void HandleTrainerBuySpellOpcode(WorldPackets::NPC::TrainerBuySpell& packet);
|
||||||
void HandlePetitionShowList(WorldPackets::Petition::PetitionShowList& packet);
|
void HandlePetitionShowList(WorldPackets::Petition::PetitionShowList& packet);
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "tc_catch2.h"
|
||||||
|
|
||||||
|
#include "MajorFactionPackets.h"
|
||||||
|
#include "WorldPacket.h"
|
||||||
|
|
||||||
|
// Phase 10K - Wire-format tests for the SMSG_COVENANT_RENOWN_SEND_CATCHUP_STATE
|
||||||
|
// builder. The packet's byte layout was reconstructed from IDA decompilation
|
||||||
|
// of the 12.0.5.67186 client (sub_7FF75C0EB140); these tests assert the
|
||||||
|
// server side encodes exactly that format.
|
||||||
|
|
||||||
|
TEST_CASE("[MajorFactions] CovenantRenownSendCatchupState: empty payload writes single header byte 0", "[MajorFactions][packets]")
|
||||||
|
{
|
||||||
|
WorldPackets::MajorFactions::CovenantRenownSendCatchupState pkt;
|
||||||
|
WorldPacket const* serialized = pkt.Write();
|
||||||
|
|
||||||
|
REQUIRE(serialized->size() == 1);
|
||||||
|
REQUIRE(serialized->contents()[0] == 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[MajorFactions] CovenantRenownSendCatchupState: one entry writes header (8<<1)=0x10 + 8B payload", "[MajorFactions][packets]")
|
||||||
|
{
|
||||||
|
WorldPackets::MajorFactions::CovenantRenownSendCatchupState pkt;
|
||||||
|
pkt.Entries.push_back({ .FactionID = 2507 /* Dragonscale */, .CatchupPercent = 42 });
|
||||||
|
|
||||||
|
WorldPacket const* serialized = pkt.Write();
|
||||||
|
uint8 const* data = serialized->contents();
|
||||||
|
|
||||||
|
REQUIRE(serialized->size() == 1 + 8);
|
||||||
|
|
||||||
|
// Header: (payloadLen << 1) & 0xFE -> payloadLen=8 -> header = 0x10.
|
||||||
|
REQUIRE(data[0] == 0x10);
|
||||||
|
|
||||||
|
// Payload: int32 FactionID little-endian, int32 CatchupPercent little-endian.
|
||||||
|
REQUIRE(data[1] == uint8(2507 & 0xFF));
|
||||||
|
REQUIRE(data[2] == uint8((2507 >> 8) & 0xFF));
|
||||||
|
REQUIRE(data[3] == 0);
|
||||||
|
REQUIRE(data[4] == 0);
|
||||||
|
REQUIRE(data[5] == 42);
|
||||||
|
REQUIRE(data[6] == 0);
|
||||||
|
REQUIRE(data[7] == 0);
|
||||||
|
REQUIRE(data[8] == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[MajorFactions] CovenantRenownSendCatchupState: 15-entry packet fits exactly in the 127-byte header cap", "[MajorFactions][packets]")
|
||||||
|
{
|
||||||
|
WorldPackets::MajorFactions::CovenantRenownSendCatchupState pkt;
|
||||||
|
for (int32 i = 0; i < 15; ++i)
|
||||||
|
pkt.Entries.push_back({ .FactionID = 2500 + i, .CatchupPercent = i * 6 });
|
||||||
|
|
||||||
|
WorldPacket const* serialized = pkt.Write();
|
||||||
|
REQUIRE(serialized->size() == 1 + 15 * 8);
|
||||||
|
|
||||||
|
// Header byte = (120 << 1) & 0xFE = 240 = 0xF0.
|
||||||
|
REQUIRE(serialized->contents()[0] == 0xF0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[MajorFactions] CovenantRenownSendCatchupState: header bit 0 is always clear", "[MajorFactions][packets]")
|
||||||
|
{
|
||||||
|
// Per IDA decomp, the client masks bit 0 off before right-shifting by 1
|
||||||
|
// to recover the length. Verify it is never set.
|
||||||
|
for (int32 n = 0; n <= 15; ++n)
|
||||||
|
{
|
||||||
|
WorldPackets::MajorFactions::CovenantRenownSendCatchupState pkt;
|
||||||
|
for (int32 i = 0; i < n; ++i)
|
||||||
|
pkt.Entries.push_back({ .FactionID = 1 + i, .CatchupPercent = 50 });
|
||||||
|
|
||||||
|
WorldPacket const* serialized = pkt.Write();
|
||||||
|
REQUIRE((serialized->contents()[0] & 0x01) == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user