From 622b24367e93d29c9f937c1b4a9e4d0a42ed3b4e Mon Sep 17 00:00:00 2001 From: FrancescoBorzi Date: Thu, 2 Jul 2026 17:33:32 +0200 Subject: [PATCH 1/6] fix: signed delta and enable gate in AV reputation redirect --- src/CFBG_SC.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/CFBG_SC.cpp b/src/CFBG_SC.cpp index 3447810..b5447a0 100644 --- a/src/CFBG_SC.cpp +++ b/src/CFBG_SC.cpp @@ -246,13 +246,19 @@ class CFBG_Player : public PlayerScript bool OnPlayerReputationChange(Player* player, uint32 factionID, int32& standing, bool /*incremental*/) override { - uint32 repGain = player->GetReputation(factionID); + if (!sCFBG->IsEnableSystem()) + return true; + TeamId teamId = player->GetTeamId(true); if ((factionID == FACTION_FROSTWOLF_CLAN && teamId == TEAM_ALLIANCE) || (factionID == FACTION_STORMPIKE_GUARD && teamId == TEAM_HORDE)) { - uint32 diff = standing - repGain; + // Signed arithmetic: a reputation LOSS must arrive as a negative + // delta; an unsigned difference would wrap and slam the mirror + // faction to the reputation floor. + int32 current = player->GetReputationMgr().GetReputation(sFactionStore.LookupEntry(factionID)); + int32 diff = standing - current; player->GetReputationMgr().ModifyReputation(sFactionStore.LookupEntry(teamId == TEAM_ALLIANCE ? FACTION_STORMPIKE_GUARD : FACTION_FROSTWOLF_CLAN), diff); return false; } From 17e3337c80395c1ecd3429b16543e1395265d707 Mon Sep 17 00:00:00 2001 From: FrancescoBorzi Date: Thu, 2 Jul 2026 17:34:46 +0200 Subject: [PATCH 2/6] fix: restore display through RestoreDisplayId when clearing a fake player --- src/CFBG.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/CFBG.cpp b/src/CFBG.cpp index b2d9b97..56ef9f5 100644 --- a/src/CFBG.cpp +++ b/src/CFBG.cpp @@ -537,8 +537,12 @@ void CFBG::ClearFakePlayer(Player* player) return; player->setRace(_fakePlayerStore[player].RealRace); - player->SetDisplayId(_fakePlayerStore[player].RealMorph); + // Restore via the aura-resolution path, not the entry-time snapshot: a + // player who dropped a shapeshift/transform mid-BG must get the real model + // back, one still in the form keeps the form model. RestoreDisplayId's + // no-aura fallback is the native display, so set that first. player->SetNativeDisplayId(_fakePlayerStore[player].RealNativeMorph); + player->RestoreDisplayId(); SetFactionForRace(player, _fakePlayerStore[player].RealRace, _fakePlayerStore[player].RealTeamID); // Clear forced faction reactions. Rank doesn't matter here, not used when they are removed. From 8e6d8fb2071d805f3cc2ce3b0062d921967bc5f2 Mon Sep 17 00:00:00 2001 From: FrancescoBorzi Date: Thu, 2 Jul 2026 17:35:10 +0200 Subject: [PATCH 3/6] fix: explain the group size cap when rejecting a group queue join --- src/CFBG_SC.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/CFBG_SC.cpp b/src/CFBG_SC.cpp index b5447a0..06bd57e 100644 --- a/src/CFBG_SC.cpp +++ b/src/CFBG_SC.cpp @@ -6,6 +6,7 @@ #include "CFBG.h" #include "Battlefield.h" #include "BattlefieldMgr.h" +#include "Chat.h" #include "Group.h" #include "ObjectAccessor.h" #include "Player.h" @@ -175,7 +176,12 @@ class CFBG_Player : public PlayerScript return true; if (group->isRaidGroup() || group->GetMembersCount() > sCFBG->GetMaxPlayersCountInGroup()) + { + // The client shows only a generic "Join as a group failed."; + // name the actual limit so the leader knows what to change. + ChatHandler(player->GetSession()).PSendSysMessage("Battleground groups are limited to {} players.", sCFBG->GetMaxPlayersCountInGroup()); err = ERR_BATTLEGROUND_JOIN_FAILED; + } return false; } From 46b62cf71a6aa9d87fa2dadae99e9e98bf1c8696 Mon Sep 17 00:00:00 2001 From: FrancescoBorzi Date: Thu, 2 Jul 2026 17:35:37 +0200 Subject: [PATCH 4/6] fix: keep say and yell in native language inside battlegrounds --- src/CFBG_SC.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/CFBG_SC.cpp b/src/CFBG_SC.cpp index 06bd57e..a716795 100644 --- a/src/CFBG_SC.cpp +++ b/src/CFBG_SC.cpp @@ -218,6 +218,11 @@ class CFBG_Player : public PlayerScript if (type == CHAT_MSG_ADDON || type == CHAT_MSG_SYSTEM) return; + // keep proximity chat in the native language so enemies get + // the normal cross-faction scramble instead of readable text + if (type == CHAT_MSG_SAY || type == CHAT_MSG_YELL) + return; + // to gm lang lang = LANG_UNIVERSAL; } From 74ff6be70e6f45a853ec88d92c1d2fa46539edba Mon Sep 17 00:00:00 2001 From: FrancescoBorzi Date: Thu, 2 Jul 2026 17:36:57 +0200 Subject: [PATCH 5/6] fix: remove dead leave-side name invalidation code _fakeNamePlayersStore has had no writer since the 2019 repo import, so the leave-side invalidation path (DoForgetPlayersInList and its store, flags and callers) has never sent a packet. Remove it whole; the live BG-entry invalidation (DoForgetPlayersInBG) is untouched. With the false leg gone every FitPlayerInTeam caller passed action=true, so the parameter goes too. --- src/CFBG.cpp | 69 +++++-------------------------------------------- src/CFBG.h | 8 +----- src/CFBG_SC.cpp | 6 ++--- src/cs_cfbg.cpp | 2 -- 4 files changed, 9 insertions(+), 76 deletions(-) diff --git a/src/CFBG.cpp b/src/CFBG.cpp index 56ef9f5..cf375e3 100644 --- a/src/CFBG.cpp +++ b/src/CFBG.cpp @@ -626,24 +626,7 @@ TeamId CFBG::ResolveWGWarTeam(Player* player, uint32 nativeAllianceInvited, uint return (_wgMajorityTeam == TEAM_ALLIANCE) ? TEAM_HORDE : TEAM_ALLIANCE; } -void CFBG::DoForgetPlayersInList(Player* player) -{ - // m_FakePlayers is filled from a vector within the battleground - // they were in previously so all players that have been in that BG will be invalidated. - for (auto const& itr : _fakeNamePlayersStore) - { - WorldPacket data(SMSG_INVALIDATE_PLAYER, 8); - data << itr.second; - player->GetSession()->SendPacket(&data); - - if (Player* _player = ObjectAccessor::FindPlayer(itr.second)) - player->GetSession()->SendNameQueryOpcode(_player->GetGUID()); - } - - _fakeNamePlayersStore.erase(player); -} - -void CFBG::FitPlayerInTeam(Player* player, bool action, Battleground* bg) +void CFBG::FitPlayerInTeam(Player* player, Battleground* bg) { if (!_IsEnableSystem) return; @@ -651,13 +634,10 @@ void CFBG::FitPlayerInTeam(Player* player, bool action, Battleground* bg) if (!bg) bg = player->GetBattleground(); - if ((!bg || bg->isArena()) && action) + if (!bg || bg->isArena()) return; - if (action) - SetForgetBGPlayers(player, true); - else - SetForgetInListPlayers(player, true); + SetForgetBGPlayers(player, true); } void CFBG::SetForgetBGPlayers(Player* player, bool value) @@ -670,16 +650,6 @@ bool CFBG::ShouldForgetBGPlayers(Player* player) return _forgetBGPlayersStore[player]; } -void CFBG::SetForgetInListPlayers(Player* player, bool value) -{ - _forgetInListPlayersStore[player] = value; -} - -bool CFBG::ShouldForgetInListPlayers(Player* player) -{ - return _forgetInListPlayersStore.find(player) != _forgetInListPlayersStore.end() && _forgetInListPlayersStore[player]; -} - void CFBG::DoForgetPlayersInBG(Player* player, Battleground* bg) { for (auto const& itr : bg->GetPlayers()) @@ -702,25 +672,6 @@ void CFBG::DoForgetPlayersInBG(Player* player, Battleground* bg) } } -bool CFBG::SendRealNameQuery(Player* player) -{ - if (IsPlayingNative(player)) - return false; - - WorldPacket data(SMSG_NAME_QUERY_RESPONSE, (8 + 1 + 1 + 1 + 1 + 1 + 10)); - data << player->GetGUID().WriteAsPacked(); // player guid - data << uint8(0); // added in 3.1; if > 1, then end of packet - data << player->GetName(); // played name - data << uint8(0); // realm name for cross realm BG usage - data << uint8(player->getRace(true)); - data << uint8(player->getGender()); - data << uint8(player->getClass()); - data << uint8(0); // is not declined - player->GetSession()->SendPacket(&data); - - return true; -} - bool CFBG::IsPlayingNative(Player* player) { return player->GetTeamId(true) == player->GetBGData().bgTeamId; @@ -951,18 +902,10 @@ bool CFBG::isClassJoining(uint8 _class, Player* player, uint32 minLevel) void CFBG::UpdateForget(Player* player) { Battleground* bg = player->GetBattleground(); - if (bg) - { - if (ShouldForgetBGPlayers(player) && bg) - { - DoForgetPlayersInBG(player, bg); - SetForgetBGPlayers(player, false); - } - } - else if (ShouldForgetInListPlayers(player)) + if (bg && ShouldForgetBGPlayers(player)) { - DoForgetPlayersInList(player); - SetForgetInListPlayers(player, false); + DoForgetPlayersInBG(player, bg); + SetForgetBGPlayers(player, false); } } diff --git a/src/CFBG.h b/src/CFBG.h index 0cfb317..fbd1be1 100644 --- a/src/CFBG.h +++ b/src/CFBG.h @@ -143,7 +143,6 @@ class CFBG TeamId ResolveBalancedTeam(TeamBalanceContext const& ctx); - bool SendRealNameQuery(Player* player); bool IsPlayerFake(Player* player); FakePlayer const* GetFakePlayer(Player* player) const; @@ -158,7 +157,6 @@ class CFBG // of a war, reset in ClearWGWarAssignments. TeamId ResolveWGWarTeam(Player* player, uint32 nativeAllianceInvited, uint32 nativeHordeInvited); - bool ShouldForgetInListPlayers(Player* player); bool IsPlayingNative(Player* player); void ValidatePlayerForBG(Battleground* bg, Player* player); @@ -170,12 +168,10 @@ class CFBG void SetFactionForRace(Player* player, uint8 Race, TeamId teamId); void ClearFakePlayer(Player* player); void ReapplyFakePlayer(Player* player); - void DoForgetPlayersInList(Player* player); - void FitPlayerInTeam(Player* player, bool action, Battleground* bg); + void FitPlayerInTeam(Player* player, Battleground* bg); void DoForgetPlayersInBG(Player* player, Battleground* bg); void SetForgetBGPlayers(Player* player, bool value); bool ShouldForgetBGPlayers(Player* player); - void SetForgetInListPlayers(Player* player, bool value); void UpdateForget(Player* player); void SendMessageQueue(BattlegroundQueue* bgQueue, Battleground* bg, PvPDifficultyEntry const* bracketEntry, Player* leader); @@ -219,9 +215,7 @@ class CFBG TeamId _wgMajorityTeam = TEAM_ALLIANCE; uint32 _wgMajorityFairShare = 0; uint32 _wgMajorityNativeKept = 0; - std::unordered_map _fakeNamePlayersStore; std::unordered_map _forgetBGPlayersStore; - std::unordered_map _forgetInListPlayersStore; std::array _raceData{}; std::array _raceInfo{}; diff --git a/src/CFBG_SC.cpp b/src/CFBG_SC.cpp index a716795..794da2f 100644 --- a/src/CFBG_SC.cpp +++ b/src/CFBG_SC.cpp @@ -36,7 +36,7 @@ class CFBG_BG : public BGScript void OnBattlegroundAddPlayer(Battleground* bg, Player* player) override { - sCFBG->FitPlayerInTeam(player, true, bg); + sCFBG->FitPlayerInTeam(player, bg); if (sCFBG->IsEnableResetCooldowns()) player->RemoveArenaSpellCooldowns(true); @@ -56,8 +56,6 @@ class CFBG_BG : public BGScript if (!sCFBG->IsEnableSystem() || bg->isArena()) return; - sCFBG->FitPlayerInTeam(player, false, bg); - if (sCFBG->IsPlayerFake(player)) sCFBG->ClearFakePlayer(player); } @@ -119,7 +117,7 @@ class CFBG_Player : public PlayerScript return; if (player->GetTeamId(true) != player->GetBgTeamId()) - sCFBG->FitPlayerInTeam(player, player->GetBattleground() && !player->GetBattleground()->isArena(), player->GetBattleground()); + sCFBG->FitPlayerInTeam(player, player->GetBattleground()); } void OnPlayerLogout(Player* player) override diff --git a/src/cs_cfbg.cpp b/src/cs_cfbg.cpp index 83c1e0d..20e12fb 100644 --- a/src/cs_cfbg.cpp +++ b/src/cs_cfbg.cpp @@ -129,7 +129,6 @@ class cfbg_commandscript : public CommandScript bool const isFake = sCFBG->IsPlayerFake(target); bool const native = sCFBG->IsPlayingNative(target); bool const forgetBG = sCFBG->ShouldForgetBGPlayers(target); - bool const forgetList = sCFBG->ShouldForgetInListPlayers(target); bool const inBG = target->InBattleground(); uint8 const preferredRace = target->GetPlayerSetting("mod-cfbg", SETTING_CFBG_RACE).value; FakePlayer const* fake = sCFBG->GetFakePlayer(target); @@ -161,7 +160,6 @@ class cfbg_commandscript : public CommandScript uint32(target->getClass()), uint32(target->getGender())); handler->PSendSysMessage(" Preferred race {}", uint32(preferredRace)); handler->PSendSysMessage(" Forget BG players {}", YesNo(forgetBG)); - handler->PSendSysMessage(" Forget in list {}", YesNo(forgetList)); // === Fake record === if (fake) From 65868e4fc5740fe754995d8d90926aa572fd1d32 Mon Sep 17 00:00:00 2001 From: FrancescoBorzi Date: Thu, 2 Jul 2026 17:37:39 +0200 Subject: [PATCH 6/6] fix: replace shared forget throttle with per player pending flag check OnPlayerBeforeUpdate decremented one 10s counter shared by every online player, so whoever ticked when it expired got served and everyone else waited for a later window: fresh BG entrants could keep stale cached names/races for many seconds. A const, non-inserting per-player flag probe now serves a flagged player on their first update tick; the flag set/clear cycle self-rate-limits, so no timer is needed. --- src/CFBG.cpp | 6 ++++++ src/CFBG.h | 4 ++++ src/CFBG_SC.cpp | 14 ++++---------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/CFBG.cpp b/src/CFBG.cpp index cf375e3..c1a9383 100644 --- a/src/CFBG.cpp +++ b/src/CFBG.cpp @@ -650,6 +650,12 @@ bool CFBG::ShouldForgetBGPlayers(Player* player) return _forgetBGPlayersStore[player]; } +bool CFBG::HasPendingForget(Player* player) const +{ + auto const itr = _forgetBGPlayersStore.find(player); + return itr != _forgetBGPlayersStore.end() && itr->second; +} + void CFBG::DoForgetPlayersInBG(Player* player, Battleground* bg) { for (auto const& itr : bg->GetPlayers()) diff --git a/src/CFBG.h b/src/CFBG.h index fbd1be1..2b7e6e3 100644 --- a/src/CFBG.h +++ b/src/CFBG.h @@ -172,6 +172,10 @@ class CFBG void DoForgetPlayersInBG(Player* player, Battleground* bg); void SetForgetBGPlayers(Player* player, bool value); bool ShouldForgetBGPlayers(Player* player); + // Const, non-inserting flag probe for the per-tick update hook + // (ShouldForgetBGPlayers' operator[] would default-insert an entry for + // every online player every tick). + bool HasPendingForget(Player* player) const; void UpdateForget(Player* player); void SendMessageQueue(BattlegroundQueue* bgQueue, Battleground* bg, PvPDifficultyEntry const* bracketEntry, Player* leader); diff --git a/src/CFBG_SC.cpp b/src/CFBG_SC.cpp index 794da2f..1fb0ff5 100644 --- a/src/CFBG_SC.cpp +++ b/src/CFBG_SC.cpp @@ -187,15 +187,12 @@ class CFBG_Player : public PlayerScript return true; } - void OnPlayerBeforeUpdate(Player* player, uint32 diff) override + void OnPlayerBeforeUpdate(Player* player, uint32 /*diff*/) override { - if (timeCheck <= diff) - { + // The flag is set once per BG add (or login into a BG) and cleared + // after service, so serving it on the next tick self-rate-limits. + if (sCFBG->HasPendingForget(player)) sCFBG->UpdateForget(player); - timeCheck = 10000; - } - else - timeCheck -= diff; } void OnPlayerBeforeSendChatMessage(Player* player, uint32& type, uint32& lang, std::string& /*msg*/) override @@ -274,9 +271,6 @@ class CFBG_Player : public PlayerScript return true; } - -private: - uint32 timeCheck = 10000; }; // WG constants duplicated here to avoid pulling in BattlefieldWG.h