Skip to content

Commit eb8d288

Browse files
authored
feat: Implement crossfaction Wintergrasp (#134)
1 parent 345a101 commit eb8d288

6 files changed

Lines changed: 193 additions & 1 deletion

File tree

conf/CFBG.conf.dist

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@
1616
# Description: Enable mixed alliance and horde in one battleground
1717
# Default: 1
1818
#
19+
# CFBG.Battlefield.Enable
20+
# Description: Enable cross-faction queue for Wintergrasp battlefield.
21+
# Requires CFBG.Enable = 1. Players are assigned to the team
22+
# with fewer members, applying a visual race/faction change
23+
# for the duration of their stay in the zone.
24+
# Default: 1
25+
#
1926
# CFBG.Include.Avg.Ilvl.Enable
2027
# Description: Enable check average item level for bg
2128
# Default: 0
@@ -63,6 +70,7 @@
6370
# 0 - Player choice
6471

6572
CFBG.Enable = 1
73+
CFBG.Battlefield.Enable = 1
6674
CFBG.BalancedTeams = 1
6775
CFBG.BalancedTeams.Class.LowLevel = 0
6876
CFBG.BalancedTeams.Class.MinLevel = 10

src/CFBG.cpp

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ void CFBG::LoadConfig()
181181
if (!_IsEnableSystem)
182182
return;
183183

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

471+
void CFBG::SetFakeRaceAndMorphForBF(Player* player, TeamId assignedTeam)
472+
{
473+
if (!player || IsPlayerFake(player))
474+
return;
475+
476+
TeamId realTeam = player->GetTeamId(true);
477+
if (realTeam == assignedTeam)
478+
return;
479+
480+
// Generate a race/morph from the assigned team's faction (opposite of real faction)
481+
RandomSkinInfo skinInfo{ GetRandomRaceMorph(realTeam, player->getClass(), player->getGender()) };
482+
483+
uint8 selectedRace = player->GetPlayerSetting("mod-cfbg", SETTING_CFBG_RACE).value;
484+
485+
if (!RandomizeRaces() && selectedRace && IsRaceValidForFaction(realTeam, selectedRace))
486+
{
487+
skinInfo.first = selectedRace;
488+
skinInfo.second = GetMorphFromRace(skinInfo.first, player->getGender());
489+
}
490+
491+
FakePlayer fakePlayerInfo
492+
{
493+
skinInfo.first,
494+
skinInfo.second,
495+
assignedTeam,
496+
player->getRace(true),
497+
player->GetDisplayId(),
498+
player->GetNativeDisplayId(),
499+
realTeam
500+
};
501+
502+
player->setRace(fakePlayerInfo.FakeRace);
503+
SetFactionForRace(player, fakePlayerInfo.FakeRace);
504+
player->SetDisplayId(fakePlayerInfo.FakeMorph);
505+
player->SetNativeDisplayId(fakePlayerInfo.FakeMorph);
506+
507+
_fakePlayerStore.emplace(player, std::move(fakePlayerInfo));
508+
}
509+
470510
void CFBG::SetFactionForRace(Player* player, uint8 Race)
471511
{
472512
player->setTeamId(player->TeamIdForRace(Race));
473-
474513
ChrRacesEntry const* DBCRace = sChrRacesStore.LookupEntry(Race);
475514
player->SetFaction(DBCRace ? DBCRace->FactionID : 0);
476515
}

src/CFBG.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <vector>
1616

1717
class Player;
18+
class Battlefield;
1819
class Battleground;
1920
class BattlegroundQueue;
2021
class Group;
@@ -119,6 +120,7 @@ class CFBG
119120
void LoadConfig();
120121

121122
inline bool IsEnableSystem() const { return _IsEnableSystem; }
123+
inline bool IsEnableWGSystem() const { return _IsEnableWGSystem; }
122124
inline bool IsEnableAvgIlvl() const { return _IsEnableAvgIlvl; }
123125
inline bool IsEnableBalancedTeams() const { return _IsEnableBalancedTeams; }
124126
inline bool IsEnableBalanceClassLowLevel() const { return _IsEnableBalanceClassLowLevel; }
@@ -147,6 +149,7 @@ class CFBG
147149

148150
void ValidatePlayerForBG(Battleground* bg, Player* player);
149151
void SetFakeRaceAndMorph(Player* player);
152+
void SetFakeRaceAndMorphForBF(Player* player, TeamId assignedTeam);
150153
void SetFactionForRace(Player* player, uint8 Race);
151154
void ClearFakePlayer(Player* player);
152155
void DoForgetPlayersInList(Player* player);
@@ -192,6 +195,7 @@ class CFBG
192195

193196
// For config
194197
bool _IsEnableSystem;
198+
bool _IsEnableWGSystem;
195199
bool _IsEnableAvgIlvl;
196200
bool _IsEnableBalancedTeams;
197201
bool _IsEnableBalanceClassLowLevel;

src/CFBG_SC.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
*/
55

66
#include "CFBG.h"
7+
#include "Battlefield.h"
8+
#include "BattlefieldMgr.h"
79
#include "Group.h"
810
#include "Player.h"
911
#include "ReputationMgr.h"
@@ -118,6 +120,21 @@ class CFBG_Player : public PlayerScript
118120
sCFBG->FitPlayerInTeam(player, player->GetBattleground() && !player->GetBattleground()->isArena(), player->GetBattleground());
119121
}
120122

123+
void OnPlayerLogout(Player* player) override
124+
{
125+
if (!sCFBG->IsEnableSystem() || !sCFBG->IsPlayerFake(player))
126+
return;
127+
128+
// Only clear the WG fake state when the battlefield is not actively at
129+
// war. During a running war the player may safely relog and rejoin
130+
// their assigned faction, so we leave the fake state intact for that
131+
// case. BG fakes are always cleaned up by OnBattlegroundRemovePlayerAtLeave
132+
// and do not need to be handled here.
133+
Battlefield* bf = sBattlefieldMgr->GetBattlefieldToZoneId(player->GetZoneId());
134+
if (bf && bf->GetTypeId() == BATTLEFIELD_WG && !bf->IsWarTime())
135+
sCFBG->ClearFakePlayer(player);
136+
}
137+
121138
bool OnPlayerCanJoinInBattlegroundQueue(Player* player, ObjectGuid /*BattlemasterGuid*/ , BattlegroundTypeId /*BGTypeID*/, uint8 joinAsGroup, GroupJoinBattlegroundResult& err) override
122139
{
123140
if (!sCFBG->IsEnableSystem())
@@ -191,6 +208,73 @@ class CFBG_Player : public PlayerScript
191208
uint32 timeCheck = 10000;
192209
};
193210

211+
class CFBG_Battlefield : public BattlefieldScript
212+
{
213+
public:
214+
CFBG_Battlefield() : BattlefieldScript("CFBG_Battlefield", {
215+
BATTLEFIELDHOOK_ON_PLAYER_JOIN_WAR,
216+
BATTLEFIELDHOOK_ON_PLAYER_LEAVE_ZONE
217+
}) {}
218+
219+
void OnBattlefieldPlayerJoinWar(Battlefield* bf, Player* player) override
220+
{
221+
if (!sCFBG->IsEnableSystem() || !sCFBG->IsEnableWGSystem())
222+
return;
223+
224+
if (bf->GetTypeId() != BATTLEFIELD_WG)
225+
return;
226+
227+
if (sCFBG->IsPlayerFake(player))
228+
return;
229+
230+
// This hook fires at the very start of OnBattlefieldPlayerJoinWar, BEFORE the
231+
// player is inserted into any m_players[] bucket. That means:
232+
// 1. GetPlayersInZoneCount reflects the current balanced distribution.
233+
// 2. SetFakeRaceAndMorphForBF changes player->GetTeamId() before the
234+
// bucket insert, so the player lands in the correct (assigned) bucket.
235+
// 3. All subsequent Battlefield operations (queue, war invite, group
236+
// assignment, leave cleanup) see the assigned team via GetTeamId().
237+
uint32 allianceCount = bf->GetPlayersInWarCount(TEAM_ALLIANCE);
238+
uint32 hordeCount = bf->GetPlayersInWarCount(TEAM_HORDE);
239+
240+
TeamId realTeam = player->GetTeamId(true);
241+
TeamId assignedTeam = realTeam;
242+
243+
LOG_ERROR("sql.sql", "Player {} entered WG with real team {}, alliance count {}, horde count {}", player->GetName(), realTeam, allianceCount, hordeCount);
244+
245+
// Assign player to the team with fewer zone members to balance teams.
246+
if (realTeam == TEAM_ALLIANCE && allianceCount > hordeCount)
247+
assignedTeam = TEAM_HORDE;
248+
else if (realTeam == TEAM_HORDE && hordeCount > allianceCount)
249+
assignedTeam = TEAM_ALLIANCE;
250+
251+
if (assignedTeam == realTeam)
252+
return;
253+
254+
// Apply visual + faction transformation so the player looks and acts as
255+
// the assigned faction in-game (PvP targeting, phase shifts, etc.).
256+
// This also calls player->setTeamId(assignedTeam) so all subsequent
257+
// player->GetTeamId() calls return the assigned team.
258+
sCFBG->SetFakeRaceAndMorphForBF(player, assignedTeam);
259+
}
260+
261+
void OnBattlefieldPlayerLeaveZone(Battlefield* bf, Player* player) override
262+
{
263+
if (!sCFBG->IsEnableSystem() || !sCFBG->IsEnableWGSystem())
264+
return;
265+
266+
if (bf->GetTypeId() != BATTLEFIELD_WG)
267+
return;
268+
269+
// HandlePlayerLeaveZone has already cleaned up all Battlefield data
270+
// structures using the assigned team (player->GetTeamId() still returns
271+
// assignedTeam at that point). Now that cleanup is complete it is safe
272+
// to restore the player's real race/faction.
273+
if (sCFBG->IsPlayerFake(player))
274+
sCFBG->ClearFakePlayer(player);
275+
}
276+
};
277+
194278
class CFBG_World : public WorldScript
195279
{
196280
public:
@@ -208,5 +292,6 @@ void AddSC_CFBG()
208292
{
209293
new CFBG_BG();
210294
new CFBG_Player();
295+
new CFBG_Battlefield();
211296
new CFBG_World();
212297
}

src/cfbg_loader.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
// From SC
88
void AddSC_CFBG();
99
void AddSC_cfbg_commandscript();
10+
void AddSC_cfbg_bf_commandscript();
1011

1112
// Add all
1213
void Addmod_cfbgScripts()
1314
{
1415
AddSC_CFBG();
1516
AddSC_cfbg_commandscript();
17+
AddSC_cfbg_bf_commandscript();
1618
}

src/cs_cfbg.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Licence MIT https://opensource.org/MIT
44
*/
55

6+
#include "Battlefield.h"
7+
#include "BattlefieldMgr.h"
68
#include "Chat.h"
79
#include "ObjectMgr.h"
810
#include "Player.h"
@@ -119,3 +121,55 @@ void AddSC_cfbg_commandscript()
119121
{
120122
new cfbg_commandscript();
121123
}
124+
125+
class cfbg_bf_commandscript : public CommandScript
126+
{
127+
public:
128+
cfbg_bf_commandscript() : CommandScript("cfbg_bf_commandscript") { }
129+
130+
ChatCommandTable GetCommands() const override
131+
{
132+
static ChatCommandTable bfSubCommands =
133+
{
134+
{ "list", HandleBFList, SEC_ADMINISTRATOR, Console::No },
135+
};
136+
137+
static ChatCommandTable commandTable =
138+
{
139+
{ "bf", bfSubCommands },
140+
};
141+
142+
return commandTable;
143+
}
144+
145+
static bool HandleBFList(ChatHandler* handler, uint32 battleId)
146+
{
147+
Battlefield* bf = sBattlefieldMgr->GetBattlefieldByBattleId(battleId);
148+
149+
if (!bf)
150+
{
151+
handler->SendErrorMessage("Battlefield {} not found.", battleId);
152+
return false;
153+
}
154+
155+
uint32 const allianceZone = bf->GetPlayersInZoneCount(TEAM_ALLIANCE);
156+
uint32 const hordeZone = bf->GetPlayersInZoneCount(TEAM_HORDE);
157+
uint32 const allianceWar = bf->GetPlayersInWarCount(TEAM_ALLIANCE);
158+
uint32 const hordeWar = bf->GetPlayersInWarCount(TEAM_HORDE);
159+
uint32 const maxPerTeam = bf->GetMaxPlayersPerTeam();
160+
161+
handler->SendSysMessage(Acore::StringFormat("Battlefield {} | {}", battleId,
162+
bf->IsWarTime() ? "WAR" : "PEACE").c_str());
163+
handler->SendSysMessage(Acore::StringFormat(" Alliance: {} in zone, {} in war / {} max",
164+
allianceZone, allianceWar, maxPerTeam).c_str());
165+
handler->SendSysMessage(Acore::StringFormat(" Horde: {} in zone, {} in war / {} max",
166+
hordeZone, hordeWar, maxPerTeam).c_str());
167+
168+
return true;
169+
}
170+
};
171+
172+
void AddSC_cfbg_bf_commandscript()
173+
{
174+
new cfbg_bf_commandscript();
175+
}

0 commit comments

Comments
 (0)