Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/CFBG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,60 @@ void CFBG::SetFakeRaceAndMorphForBF(Player* player, TeamId assignedTeam)
_fakePlayerStore.emplace(player, std::move(fakePlayerInfo));
}

void CFBG::PrepareFakeTeamForBF(Player* player, TeamId assignedTeam)
{
if (!player || IsPlayerFake(player))
return;

TeamId realTeam = player->GetTeamId(true);
if (realTeam == assignedTeam)
return;

// Generate race/morph so the full FakePlayer record is ready for when
// ApplyFakeVisualsForBF fires later (on war accept). We do NOT call
// setRace / SetDisplayId / SetNativeDisplayId here — only the faction
// (team) is changed so that every subsequent core bucket write uses the
// assigned team.
RandomSkinInfo skinInfo{ GetRandomRaceMorph(realTeam, player->getClass(), player->getGender()) };

uint8 selectedRace = player->GetPlayerSetting("mod-cfbg", SETTING_CFBG_RACE).value;

if (!RandomizeRaces() && selectedRace && IsRaceValidForFaction(realTeam, selectedRace))
{
skinInfo.first = selectedRace;
skinInfo.second = GetMorphFromRace(skinInfo.first, player->getGender());
}

FakePlayer fakePlayerInfo
{
skinInfo.first,
skinInfo.second,
assignedTeam,
player->getRace(true),
player->GetDisplayId(),
player->GetNativeDisplayId(),
realTeam
};

// Team change only — visuals are deferred to ApplyFakeVisualsForBF.
SetFactionForRace(player, fakePlayerInfo.FakeRace);

_fakePlayerStore.emplace(player, std::move(fakePlayerInfo));
}

void CFBG::ApplyFakeVisualsForBF(Player* player)
{
FakePlayer const* fakeInfo = GetFakePlayer(player);
if (!fakeInfo)
return;

// Visual changes deferred from PrepareFakeTeamForBF: apply them now that
// the player has actually accepted the war invitation.
player->setRace(fakeInfo->FakeRace);
player->SetDisplayId(fakeInfo->FakeMorph);
player->SetNativeDisplayId(fakeInfo->FakeMorph);
}

void CFBG::SetFactionForRace(Player* player, uint8 Race)
{
player->setTeamId(player->TeamIdForRace(Race));
Expand Down
2 changes: 2 additions & 0 deletions src/CFBG.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ class CFBG
void ValidatePlayerForBG(Battleground* bg, Player* player);
void SetFakeRaceAndMorph(Player* player);
void SetFakeRaceAndMorphForBF(Player* player, TeamId assignedTeam);
void PrepareFakeTeamForBF(Player* player, TeamId assignedTeam);
void ApplyFakeVisualsForBF(Player* player);
void SetFactionForRace(Player* player, uint8 Race);
void ClearFakePlayer(Player* player);
void DoForgetPlayersInList(Player* player);
Expand Down
74 changes: 49 additions & 25 deletions src/CFBG_SC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ class CFBG_Battlefield : public BattlefieldScript
public:
CFBG_Battlefield() : BattlefieldScript("CFBG_Battlefield", {
BATTLEFIELDHOOK_ON_PLAYER_ENTER_ZONE,
BATTLEFIELDHOOK_BEFORE_INVITE_PLAYER_TO_WAR,
BATTLEFIELDHOOK_ON_PLAYER_JOIN_WAR,
BATTLEFIELDHOOK_ON_PLAYER_LEAVE_WAR,
BATTLEFIELDHOOK_ON_PLAYER_LEAVE_ZONE
Expand Down Expand Up @@ -254,44 +255,67 @@ class CFBG_Battlefield : public BattlefieldScript
assignedTeam = TEAM_ALLIANCE;

if (assignedTeam != realTeam)
sCFBG->SetFakeRaceAndMorphForBF(player, assignedTeam);
sCFBG->PrepareFakeTeamForBF(player, assignedTeam);

// The player is NOT added to wgWarPlayers here; they are only added
// once they actually accept the war invitation (OnBattlefieldPlayerJoinWar).
}

void OnBattlefieldPlayerJoinWar(Battlefield* bf, Player* player) override
void OnBattlefieldBeforeInvitePlayerToWar(Battlefield* bf, Player* player) override
{
if (!sCFBG->IsEnableSystem() || !sCFBG->IsEnableWGSystem())
return;

if (bf->GetTypeId() != BATTLEFIELD_WG)
return;

if (!sCFBG->IsPlayerFake(player))
{
// Fallback path: player was in the zone before war started and still
// has their real team. Balance and assign now using the module's own
// tracking. The core's PlayerAcceptInviteToWar saves the pre-hook
// TeamId and will erase m_InvitedPlayers from the correct (real-team)
// bucket, so changing the team here does not leave a stale invite entry.
uint32 allianceCount = static_cast<uint32>(_wgWarPlayers[TEAM_ALLIANCE].size());
uint32 hordeCount = static_cast<uint32>(_wgWarPlayers[TEAM_HORDE].size());

TeamId realTeam = player->GetTeamId(true);
TeamId assignedTeam = realTeam;

if (realTeam == TEAM_ALLIANCE && allianceCount > hordeCount)
assignedTeam = TEAM_HORDE;
else if (realTeam == TEAM_HORDE && hordeCount > allianceCount)
assignedTeam = TEAM_ALLIANCE;

if (assignedTeam != realTeam)
sCFBG->SetFakeRaceAndMorphForBF(player, assignedTeam);
}
// Players who entered the zone while war was already active are already
// assigned in OnBattlefieldPlayerEnterZone; nothing more to do here.
if (sCFBG->IsPlayerFake(player))
return;

// Fallback for players who were in the zone before war started. This
// hook fires after m_PlayersWillBeKick has been erased using the real
// team (correct), and before m_InvitedPlayers is written. Assigning
// the team here means the invite lands in the right bucket immediately,
// so PlayerAcceptInviteToWar sees a consistent team throughout.
//
// GetPlayersInWarCount is safe to use here: m_PlayersInWar was cleared
// at StartBattle and m_InvitedPlayers is being built up in this same
// loop, so there is no stale state to read.
uint32 allianceCount = bf->GetPlayersInWarCount(TEAM_ALLIANCE);
uint32 hordeCount = bf->GetPlayersInWarCount(TEAM_HORDE);

// Record this player under their (possibly just-changed) assigned team.
TeamId realTeam = player->GetTeamId(true);
TeamId assignedTeam = realTeam;

if (realTeam == TEAM_ALLIANCE && allianceCount > hordeCount)
assignedTeam = TEAM_HORDE;
else if (realTeam == TEAM_HORDE && hordeCount > allianceCount)
assignedTeam = TEAM_ALLIANCE;

if (assignedTeam != realTeam)
sCFBG->PrepareFakeTeamForBF(player, assignedTeam);
}

void OnBattlefieldPlayerJoinWar(Battlefield* bf, Player* player) override
{
if (!sCFBG->IsEnableSystem() || !sCFBG->IsEnableWGSystem())
return;

if (bf->GetTypeId() != BATTLEFIELD_WG)
return;

// By this point the player's team is already final: either assigned in
// OnBattlefieldPlayerEnterZone (zone entry during war) or in
// OnBattlefieldBeforeInvitePlayerToWar (pre-war zone players at invite
// time). Record them in the module's war tracking, then apply the
// visual transformation now that they have actually accepted.
_wgWarPlayers[player->GetTeamId()].insert(player->GetGUID());

// Apply race/morph visuals deferred from the pre-invite phase.
// For players whose team was not reassigned this is a no-op.
sCFBG->ApplyFakeVisualsForBF(player);
}

void OnBattlefieldPlayerLeaveWar(Battlefield* bf, Player* player) override
Expand Down Expand Up @@ -333,7 +357,7 @@ class CFBG_Battlefield : public BattlefieldScript
// Populated when a player accepts a war invitation (JoinWar) and drained
// when they leave the war (LeaveWar) or zone (LeaveZone catch-all).
// Kept separately from the core's m_PlayersInWar / m_InvitedPlayers so that
// balance decisions are never based on stale core state.
// balance decisions during active war are always based on clean state.
GuidUnorderedSet _wgWarPlayers[PVP_TEAMS_COUNT];
};

Expand Down
Loading