This commit is contained in:
luis
2026-05-16 04:37:16 -03:00
parent a4f00f295c
commit cc350894ec
8 changed files with 296 additions and 1 deletions
+58
View File
@@ -56,6 +56,9 @@
#include "World.h"
#include <cstdarg>
#include <zlib.h>
#include "MajorFactionMgr.h"
#include "MajorFactionPackets.h"
#include "ReputationMgr.h"
void WorldSession::HandleRepopRequest(WorldPackets::Misc::RepopRequest& /*packet*/)
{
@@ -1227,3 +1230,58 @@ void WorldSession::HandleSetCurrencyFlags(WorldPackets::Misc::SetCurrencyFlags c
{
_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);
}
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);
bool IsRenownRewardGranted(uint32 renownRewardId, bool accountWide) const;
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
static std::set<int32> const ReputationRankThresholds;
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
+1 -1
View File
@@ -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_RAID, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleConvertRaidOpcode);
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_CLAIM, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_CRAFTING_ORDER_CREATE, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
+13
View File
@@ -748,6 +748,11 @@ namespace WorldPackets
class GetCharacterCurrencyTransferLog;
}
namespace MajorFactions
{
class RequestCatchupState;
}
namespace PerksProgram
{
class RequestStoreFrontInfoUpdate;
@@ -1874,6 +1879,14 @@ class TC_GAME_API WorldSession
void HandleTabardVendorActivateOpcode(WorldPackets::NPC::TabardVendorActivate const& tabardVendorActivate);
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 HandleTrainerBuySpellOpcode(WorldPackets::NPC::TrainerBuySpell& packet);
void HandlePetitionShowList(WorldPackets::Petition::PetitionShowList& packet);
+87
View File
@@ -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);
}
}