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
8 changes: 8 additions & 0 deletions conf/CFBG.conf.dist
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
# Description: Enable mixed alliance and horde in one battleground
# Default: 1
#
# CFBG.Battlefield.Enable
# Description: Enable cross-faction queue for Wintergrasp battlefield.
# Requires CFBG.Enable = 1. Players are assigned to the team
# with fewer members, applying a visual race/faction change
# for the duration of their stay in the zone.
# Default: 1
#
# CFBG.Include.Avg.Ilvl.Enable
# Description: Enable check average item level for bg
# Default: 0
Expand Down Expand Up @@ -63,6 +70,7 @@
# 0 - Player choice

CFBG.Enable = 1
CFBG.Battlefield.Enable = 1
CFBG.BalancedTeams = 1
CFBG.BalancedTeams.Class.LowLevel = 0
CFBG.BalancedTeams.Class.MinLevel = 10
Expand Down
41 changes: 40 additions & 1 deletion src/CFBG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ void CFBG::LoadConfig()
if (!_IsEnableSystem)
return;

_IsEnableWGSystem = sConfigMgr->GetOption<bool>("CFBG.Battlefield.Enable", true);
_IsEnableAvgIlvl = sConfigMgr->GetOption<bool>("CFBG.Include.Avg.Ilvl.Enable", false);
_IsEnableBalancedTeams = sConfigMgr->GetOption<bool>("CFBG.BalancedTeams", false);
_IsEnableEvenTeams = sConfigMgr->GetOption<bool>("CFBG.EvenTeams.Enabled", false);
Expand Down Expand Up @@ -467,10 +468,48 @@ void CFBG::SetFakeRaceAndMorph(Player* player)
_fakePlayerStore.emplace(player, std::move(fakePlayerInfo));
}

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

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

// Generate a race/morph from the assigned team's faction (opposite of real faction)
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
};

player->setRace(fakePlayerInfo.FakeRace);
SetFactionForRace(player, fakePlayerInfo.FakeRace);
player->SetDisplayId(fakePlayerInfo.FakeMorph);
player->SetNativeDisplayId(fakePlayerInfo.FakeMorph);

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

void CFBG::SetFactionForRace(Player* player, uint8 Race)
{
player->setTeamId(player->TeamIdForRace(Race));

ChrRacesEntry const* DBCRace = sChrRacesStore.LookupEntry(Race);
player->SetFaction(DBCRace ? DBCRace->FactionID : 0);
}
Expand Down
4 changes: 4 additions & 0 deletions src/CFBG.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <vector>

class Player;
class Battlefield;
class Battleground;
class BattlegroundQueue;
class Group;
Expand Down Expand Up @@ -119,6 +120,7 @@ class CFBG
void LoadConfig();

inline bool IsEnableSystem() const { return _IsEnableSystem; }
inline bool IsEnableWGSystem() const { return _IsEnableWGSystem; }
inline bool IsEnableAvgIlvl() const { return _IsEnableAvgIlvl; }
inline bool IsEnableBalancedTeams() const { return _IsEnableBalancedTeams; }
inline bool IsEnableBalanceClassLowLevel() const { return _IsEnableBalanceClassLowLevel; }
Expand Down Expand Up @@ -147,6 +149,7 @@ class CFBG

void ValidatePlayerForBG(Battleground* bg, Player* player);
void SetFakeRaceAndMorph(Player* player);
void SetFakeRaceAndMorphForBF(Player* player, TeamId assignedTeam);
void SetFactionForRace(Player* player, uint8 Race);
void ClearFakePlayer(Player* player);
void DoForgetPlayersInList(Player* player);
Expand Down Expand Up @@ -192,6 +195,7 @@ class CFBG

// For config
bool _IsEnableSystem;
bool _IsEnableWGSystem;
bool _IsEnableAvgIlvl;
bool _IsEnableBalancedTeams;
bool _IsEnableBalanceClassLowLevel;
Expand Down
85 changes: 85 additions & 0 deletions src/CFBG_SC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*/

#include "CFBG.h"
#include "Battlefield.h"
#include "BattlefieldMgr.h"
#include "Group.h"
#include "Player.h"
#include "ReputationMgr.h"
Expand Down Expand Up @@ -118,6 +120,21 @@ class CFBG_Player : public PlayerScript
sCFBG->FitPlayerInTeam(player, player->GetBattleground() && !player->GetBattleground()->isArena(), player->GetBattleground());
}

void OnPlayerLogout(Player* player) override
{
if (!sCFBG->IsEnableSystem() || !sCFBG->IsPlayerFake(player))
return;

// Only clear the WG fake state when the battlefield is not actively at
// war. During a running war the player may safely relog and rejoin
// their assigned faction, so we leave the fake state intact for that
// case. BG fakes are always cleaned up by OnBattlegroundRemovePlayerAtLeave
// and do not need to be handled here.
Battlefield* bf = sBattlefieldMgr->GetBattlefieldToZoneId(player->GetZoneId());
if (bf && bf->GetTypeId() == BATTLEFIELD_WG && !bf->IsWarTime())
sCFBG->ClearFakePlayer(player);
}

bool OnPlayerCanJoinInBattlegroundQueue(Player* player, ObjectGuid /*BattlemasterGuid*/ , BattlegroundTypeId /*BGTypeID*/, uint8 joinAsGroup, GroupJoinBattlegroundResult& err) override
{
if (!sCFBG->IsEnableSystem())
Expand Down Expand Up @@ -191,6 +208,73 @@ class CFBG_Player : public PlayerScript
uint32 timeCheck = 10000;
};

class CFBG_Battlefield : public BattlefieldScript
{
public:
CFBG_Battlefield() : BattlefieldScript("CFBG_Battlefield", {
BATTLEFIELDHOOK_ON_PLAYER_JOIN_WAR,
BATTLEFIELDHOOK_ON_PLAYER_LEAVE_ZONE
}) {}

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

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

if (sCFBG->IsPlayerFake(player))
return;

// This hook fires at the very start of OnBattlefieldPlayerJoinWar, BEFORE the
// player is inserted into any m_players[] bucket. That means:
// 1. GetPlayersInZoneCount reflects the current balanced distribution.
// 2. SetFakeRaceAndMorphForBF changes player->GetTeamId() before the
// bucket insert, so the player lands in the correct (assigned) bucket.
// 3. All subsequent Battlefield operations (queue, war invite, group
// assignment, leave cleanup) see the assigned team via GetTeamId().
uint32 allianceCount = bf->GetPlayersInWarCount(TEAM_ALLIANCE);
uint32 hordeCount = bf->GetPlayersInWarCount(TEAM_HORDE);

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

LOG_ERROR("sql.sql", "Player {} entered WG with real team {}, alliance count {}, horde count {}", player->GetName(), realTeam, allianceCount, hordeCount);

// Assign player to the team with fewer zone members to balance teams.
if (realTeam == TEAM_ALLIANCE && allianceCount > hordeCount)
assignedTeam = TEAM_HORDE;
else if (realTeam == TEAM_HORDE && hordeCount > allianceCount)
assignedTeam = TEAM_ALLIANCE;

if (assignedTeam == realTeam)
return;

// Apply visual + faction transformation so the player looks and acts as
// the assigned faction in-game (PvP targeting, phase shifts, etc.).
// This also calls player->setTeamId(assignedTeam) so all subsequent
// player->GetTeamId() calls return the assigned team.
sCFBG->SetFakeRaceAndMorphForBF(player, assignedTeam);
}

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

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

// HandlePlayerLeaveZone has already cleaned up all Battlefield data
// structures using the assigned team (player->GetTeamId() still returns
// assignedTeam at that point). Now that cleanup is complete it is safe
// to restore the player's real race/faction.
if (sCFBG->IsPlayerFake(player))
sCFBG->ClearFakePlayer(player);
}
};

class CFBG_World : public WorldScript
{
public:
Expand All @@ -208,5 +292,6 @@ void AddSC_CFBG()
{
new CFBG_BG();
new CFBG_Player();
new CFBG_Battlefield();
new CFBG_World();
}
2 changes: 2 additions & 0 deletions src/cfbg_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
// From SC
void AddSC_CFBG();
void AddSC_cfbg_commandscript();
void AddSC_cfbg_bf_commandscript();

// Add all
void Addmod_cfbgScripts()
{
AddSC_CFBG();
AddSC_cfbg_commandscript();
AddSC_cfbg_bf_commandscript();
}
54 changes: 54 additions & 0 deletions src/cs_cfbg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* Licence MIT https://opensource.org/MIT
*/

#include "Battlefield.h"
#include "BattlefieldMgr.h"
#include "Chat.h"
#include "ObjectMgr.h"
#include "Player.h"
Expand Down Expand Up @@ -119,3 +121,55 @@ void AddSC_cfbg_commandscript()
{
new cfbg_commandscript();
}

class cfbg_bf_commandscript : public CommandScript
{
public:
cfbg_bf_commandscript() : CommandScript("cfbg_bf_commandscript") { }

ChatCommandTable GetCommands() const override
{
static ChatCommandTable bfSubCommands =
{
{ "list", HandleBFList, SEC_ADMINISTRATOR, Console::No },
};

static ChatCommandTable commandTable =
{
{ "bf", bfSubCommands },
};

return commandTable;
}

static bool HandleBFList(ChatHandler* handler, uint32 battleId)
{
Battlefield* bf = sBattlefieldMgr->GetBattlefieldByBattleId(battleId);

if (!bf)
{
handler->SendErrorMessage("Battlefield {} not found.", battleId);
return false;
}

uint32 const allianceZone = bf->GetPlayersInZoneCount(TEAM_ALLIANCE);
uint32 const hordeZone = bf->GetPlayersInZoneCount(TEAM_HORDE);
uint32 const allianceWar = bf->GetPlayersInWarCount(TEAM_ALLIANCE);
uint32 const hordeWar = bf->GetPlayersInWarCount(TEAM_HORDE);
uint32 const maxPerTeam = bf->GetMaxPlayersPerTeam();

handler->SendSysMessage(Acore::StringFormat("Battlefield {} | {}", battleId,
bf->IsWarTime() ? "WAR" : "PEACE").c_str());
handler->SendSysMessage(Acore::StringFormat(" Alliance: {} in zone, {} in war / {} max",
allianceZone, allianceWar, maxPerTeam).c_str());
handler->SendSysMessage(Acore::StringFormat(" Horde: {} in zone, {} in war / {} max",
hordeZone, hordeWar, maxPerTeam).c_str());

return true;
}
};

void AddSC_cfbg_bf_commandscript()
{
new cfbg_bf_commandscript();
}