├── .editorconfig ├── .git_commit_template.txt ├── .gitattributes ├── .github └── workflows │ └── core-build.yml ├── .gitignore ├── LICENSE ├── README.md ├── conf └── CFBG.conf.dist ├── data └── sql │ └── db-world │ ├── avcreaturesfaction.sql │ └── cfbg_commands.sql ├── include.sh ├── setup_git_commit_template.sh └── src ├── CFBG.cpp ├── CFBG.h ├── CFBG_SC.cpp ├── cfbg_loader.cpp └── cs_cfbg.cpp /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | indent_style = space 4 | indent_size = 4 5 | tab_width = 4 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | max_line_length = 80 9 | -------------------------------------------------------------------------------- /.git_commit_template.txt: -------------------------------------------------------------------------------- 1 | ### TITLE 2 | ## Type(Scope/Subscope): Commit ultra short explanation 3 | ## |---- Write below the examples with a maximum of 50 characters ----| 4 | ## Example 1: fix(DB/SAI): Missing spell to NPC Hogger 5 | ## Example 2: fix(CORE/Raid): Phase 2 of Ragnaros 6 | ## Example 3: feat(CORE/Commands): New GM command to do something 7 | 8 | ### DESCRIPTION 9 | ## Explain why this change is being made, what does it fix etc... 10 | ## |---- Write below the examples with a maximum of 72 characters per lines ----| 11 | ## Example: Hogger (id: 492) was not charging player when being engaged. 12 | 13 | ## Provide links to any issue, commit, pull request or other resource 14 | ## Example 1: Closes issue #23 15 | ## Example 2: Ported from other project's commit (link) 16 | ## Example 3: References taken from wowpedia / wowhead / wowwiki / https://wowgaming.altervista.org/aowow/ 17 | 18 | ## ======================================================= 19 | ## EXTRA INFOS 20 | ## ======================================================= 21 | ## "Type" can be: 22 | ## feat (new feature) 23 | ## fix (bug fix) 24 | ## refactor (refactoring production code) 25 | ## style (formatting, missing semi colons, etc; no code change) 26 | ## docs (changes to documentation) 27 | ## test (adding or refactoring tests; no production code change) 28 | ## chore (updating bash scripts, git files etc; no production code change) 29 | ## -------------------- 30 | ## Remember to 31 | ## Capitalize the subject line 32 | ## Use the imperative mood in the subject line 33 | ## Do not end the subject line with a period 34 | ## Separate subject from body with a blank line 35 | ## Use the body to explain what and why rather than how 36 | ## Can use multiple lines with "-" for bullet points in body 37 | ## -------------------- 38 | ## More info here https://www.conventionalcommits.org/en/v1.0.0-beta.2/ 39 | ## ======================================================= 40 | ## "Scope" can be: 41 | ## CORE (core related, c++) 42 | ## DB (database related, sql) 43 | ## ======================================================= 44 | ## "Subscope" is optional and depends on the nature of the commit. 45 | ## ======================================================= 46 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ## AUTO-DETECT 2 | ## Handle line endings automatically for files detected as 3 | ## text and leave all files detected as binary untouched. 4 | ## This will handle all files NOT defined below. 5 | * text=auto eol=lf 6 | 7 | # Text 8 | *.conf text 9 | *.conf.dist text 10 | *.cmake text 11 | 12 | ## Scripts 13 | *.sh text 14 | *.fish text 15 | *.lua text 16 | 17 | ## SQL 18 | *.sql text 19 | 20 | ## C++ 21 | *.c text 22 | *.cc text 23 | *.cxx text 24 | *.cpp text 25 | *.c++ text 26 | *.hpp text 27 | *.h text 28 | *.h++ text 29 | *.hh text 30 | 31 | 32 | ## For documentation 33 | 34 | # Documents 35 | *.doc diff=astextplain 36 | *.DOC diff=astextplain 37 | *.docx diff=astextplain 38 | *.DOCX diff=astextplain 39 | *.dot diff=astextplain 40 | *.DOT diff=astextplain 41 | *.pdf diff=astextplain 42 | *.PDF diff=astextplain 43 | *.rtf diff=astextplain 44 | *.RTF diff=astextplain 45 | 46 | ## DOCUMENTATION 47 | *.markdown text 48 | *.md text 49 | *.mdwn text 50 | *.mdown text 51 | *.mkd text 52 | *.mkdn text 53 | *.mdtxt text 54 | *.mdtext text 55 | *.txt text 56 | AUTHORS text 57 | CHANGELOG text 58 | CHANGES text 59 | CONTRIBUTING text 60 | COPYING text 61 | copyright text 62 | *COPYRIGHT* text 63 | INSTALL text 64 | license text 65 | LICENSE text 66 | NEWS text 67 | readme text 68 | *README* text 69 | TODO text 70 | 71 | ## GRAPHICS 72 | *.ai binary 73 | *.bmp binary 74 | *.eps binary 75 | *.gif binary 76 | *.ico binary 77 | *.jng binary 78 | *.jp2 binary 79 | *.jpg binary 80 | *.jpeg binary 81 | *.jpx binary 82 | *.jxr binary 83 | *.pdf binary 84 | *.png binary 85 | *.psb binary 86 | *.psd binary 87 | *.svg text 88 | *.svgz binary 89 | *.tif binary 90 | *.tiff binary 91 | *.wbmp binary 92 | *.webp binary 93 | 94 | 95 | ## ARCHIVES 96 | *.7z binary 97 | *.gz binary 98 | *.jar binary 99 | *.rar binary 100 | *.tar binary 101 | *.zip binary 102 | 103 | ## EXECUTABLES 104 | *.exe binary 105 | *.pyc binary 106 | -------------------------------------------------------------------------------- /.github/workflows/core-build.yml: -------------------------------------------------------------------------------- 1 | name: core-build 2 | on: 3 | push: 4 | branches: 5 | - 'master' 6 | pull_request: 7 | 8 | jobs: 9 | build: 10 | uses: azerothcore/reusable-workflows/.github/workflows/core_build_modules.yml@main 11 | with: 12 | module_repo: ${{ github.event.repository.name }} 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | 3 | # 4 | # Generic 5 | # 6 | 7 | .directory 8 | .mailmap 9 | *.orig 10 | *.rej 11 | *.*~ 12 | .hg/ 13 | *.kdev* 14 | .DS_Store 15 | CMakeLists.txt.user 16 | *.bak 17 | *.patch 18 | *.diff 19 | *.REMOTE.* 20 | *.BACKUP.* 21 | *.BASE.* 22 | *.LOCAL.* 23 | 24 | # 25 | # IDE & other softwares 26 | # 27 | 28 | /.settings/ 29 | /.externalToolBuilders/* 30 | 31 | # 32 | # Exclude in all levels 33 | # 34 | 35 | nbproject/ 36 | .sync.ffs_db 37 | *.kate-swp 38 | 39 | # 40 | # Eclipse 41 | # 42 | 43 | *.pydevproject 44 | .metadata 45 | .gradle 46 | tmp/ 47 | *.tmp 48 | *.swp 49 | *~.nib 50 | local.properties 51 | .settings/ 52 | .loadpath 53 | .project 54 | .cproject 55 | 56 | # Private modules 57 | /src/mod-cfbg/* 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Andrei Guluaev (Winfidonarleyan/Kargatum) 4 | Copyright (С) since 2019+ AzerothCore 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![logo](https://raw.githubusercontent.com/azerothcore/azerothcore.github.io/master/images/logo-github.png) AzerothCore module 2 | ## CrossFaction Battleground 3 | - Latest build status with azerothcore: [![Build Status](https://github.com/azerothcore/mod-cfbg/workflows/core-build/badge.svg?branch=master&event=push)](https://github.com/azerothcore/mod-cfbg) 4 | 5 | ### Module currently requires: 6 | * Need AC commit [`d40e8946`](https://github.com/azerothcore/azerothcore-wotlk/commit/d40e8946180129b39172c2a1b4d690aa71723917) or newer. 7 | 8 | ## About module 9 | This module based patch https://gist.github.com/irancore/10913800. 10 | But, all mechanics of change of fraction and so on is remade. Faction change occurs only for BG and nowhere else. 11 | 12 | #### Features: 13 | - Change you faction in bg for balance faction. 14 | 15 | #### Config option (CFBG.conf.dist) 16 | ```ini 17 | ################################################################################################### 18 | # CrossFaction BattleGround 19 | # 20 | # CFBG.Enable 21 | # Description: Enable mixed alliance and horde in one battleground 22 | # Default: 1 23 | # 24 | # CFBG.Include.Avg.Ilvl.Enable 25 | # Description: Enable check average item level for bg 26 | # Default: 1 27 | # 28 | 29 | CFBG.Enable = 1 30 | CFBG.Include.Avg.Ilvl.Enable = 1 31 | CFBG.Players.Count.In.Group = 3 32 | ``` 33 | 34 | ### How to install 35 | 1. Simply place the module under the `modules` folder of your AzerothCore source folder. 36 | 2. Re-run cmake and launch a clean build of AzerothCore 37 | 3. Make sure your `Battleground.InvitationType` is set to `0` in `worldserver.conf` 38 | 4. Done :) 39 | 40 | ### Edit module configuration (optional) 41 | If you need to change the module configuration, go to your server configuration folder (where your worldserver or `worldserver.exe` is), copy `CFBG.conf.dist` to `CFBG.conf` and edit that new file. 42 | 43 | ### Usage 44 | - Enable system `CFBG.Enable = 1` 45 | - Enter BG 46 | 47 | ## Credits 48 | - [Winfidonarleyan](https://github.com/Winfidonarleyan) (Author of the module) 49 | - [Viste](https://github.com/Viste) (Port Irancore's code to AC) 50 | - [Irancore](https://github.com/Irancore) (Author original code for TrinityCore) 51 | - [AzerothCore repository](https://github.com/azerothcore/azerothcore-wotlk) 52 | - [Discord AzerothCore](https://discord.gg/PaqQRkd) 53 | -------------------------------------------------------------------------------- /conf/CFBG.conf.dist: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (С) since 2019 Andrei Guluaev (Winfidonarleyan/Kargatum) https://github.com/Winfidonarleyan 3 | # Copyright (С) since 2019+ AzerothCore 4 | # Licence MIT https://opensource.org/MIT 5 | # 6 | 7 | ############################# 8 | # CFBG configuration system # 9 | ############################# 10 | [worldserver] 11 | 12 | ################################################################################################### 13 | # CFBG 14 | # 15 | # CFBG.Enable 16 | # Description: Enable mixed alliance and horde in one battleground 17 | # Default: 1 18 | # 19 | # CFBG.Include.Avg.Ilvl.Enable 20 | # Description: Enable check average item level for bg 21 | # Default: 0 22 | # 23 | # CFBG.BalancedTeams 24 | # Description: Enable check average player level for bg 25 | # Default: 1 26 | # 27 | # CFBG.BalancedTeams.Class.LowLevel 28 | # Description: Enable class balance for BGs LowLevel 29 | # Default: 0 30 | # 1 - (Enabled. Balance the number of Hunters per team) 31 | # 32 | # CFBG.BalancedTeams.Class.MinLevel 33 | # Description: Define min level to use the LowLevelClass balance 34 | # Default: 10 35 | # 36 | # CFBG.BalancedTeams.Class.MaxLevel 37 | # Description: Define max level to use the LowLevelClass balance 38 | # Default: 19 39 | # 40 | # CFBG.BalancedTeams.Class.LevelDiff 41 | # Description: Only taking into account characters having level >= BgMaxLevel - LevelDiff 42 | # for example in a 10-19 BG, if this value is set to 2, then it will consider only 43 | # players of level 17, 18, 19 (>= 19-2) 44 | # Default: 2 45 | # 46 | # CFBG.Players.Count.In.Group 47 | # Description: Maximum players in party for enter queue 48 | # Default: 3 49 | # 50 | # CFBG.EvenTeams.Enabled 51 | # Description: Set Battleground invitation type. 52 | # Default: 0 - (Disabled, one team can have one extra player if an odd number of players queues) 53 | # 1 - (Enabled. Get even teams.) 54 | # 55 | # CFBG.EvenTeams.MaxPlayersThreshold 56 | # Description: team member quantity until the EvenTeams rule is valid 57 | # Default: 5 - (Quantity of players per team after the EvenTeams rule will be ignored) 58 | # 0 - (No treshold) 59 | # 60 | # CFBG.RandomRaceSelection 61 | # Description: allows players to choose the race they will be morphed into when joining battlegrounds as the opposite team. 62 | # Default: 1 - Random 63 | # 0 - Player choice 64 | 65 | CFBG.Enable = 1 66 | CFBG.BalancedTeams = 1 67 | CFBG.BalancedTeams.Class.LowLevel = 0 68 | CFBG.BalancedTeams.Class.MinLevel = 10 69 | CFBG.BalancedTeams.Class.MaxLevel = 19 70 | CFBG.BalancedTeams.Class.LevelDiff = 2 71 | CFBG.Include.Avg.Ilvl.Enable = 0 72 | CFBG.Players.Count.In.Group = 3 73 | CFBG.EvenTeams.Enabled = 0 74 | CFBG.EvenTeams.MaxPlayersThreshold = 5 75 | CFBG.ResetCooldowns = 0 76 | CFBG.Show.PlayerName = 0 77 | CFBG.RandomRaceSelection = 1 78 | -------------------------------------------------------------------------------- /data/sql/db-world/avcreaturesfaction.sql: -------------------------------------------------------------------------------- 1 | UPDATE `creature_template` SET `faction` = 1216 WHERE `entry` IN (14187, 22528, 31924, 37240, 14188, 22699,31963, 37281); 2 | UPDATE `creature_template` SET `faction` = 1214 WHERE `entry` IN (14185, 22712, 32049, 37370, 14186, 22569, 32053, 37374); 3 | -------------------------------------------------------------------------------- /data/sql/db-world/cfbg_commands.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM `command` WHERE `name` IN ('cfbg', 'cfbg race'); 2 | INSERT INTO `command` (`name`, `security`, `help`) VALUES 3 | ('cfbg', 0, 'Crossfaction battleground module commands.'), 4 | ('cfbg race', 0, 'Morphs your character to the selected race once you join a battleground.\nBy default, the following races are available: human, dwarf, gnome, draenei ("broken ones"), orc, bloodelf, troll, tauren.'); 5 | -------------------------------------------------------------------------------- /include.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azerothcore/mod-cfbg/345a101f9a02360e6fcb656d0ab240a39015b3af/include.sh -------------------------------------------------------------------------------- /setup_git_commit_template.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ## Set a local git commit template 4 | git config --local commit.template ".git_commit_template.txt" ; 5 | -------------------------------------------------------------------------------- /src/CFBG.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (С) since 2019 Andrei Guluaev (Winfidonarleyan/Kargatum) https://github.com/Winfidonarleyan 3 | * Copyright (С) since 2019+ AzerothCore 4 | * Licence MIT https://opensource.org/MIT 5 | */ 6 | 7 | #include "CFBG.h" 8 | #include "BattlegroundMgr.h" 9 | #include "BattlegroundUtils.h" 10 | #include "Chat.h" 11 | #include "Config.h" 12 | #include "Containers.h" 13 | #include "GroupMgr.h" 14 | #include "Language.h" 15 | #include "Log.h" 16 | #include "Opcodes.h" 17 | #include "ReputationMgr.h" 18 | #include "ScriptMgr.h" 19 | #include "GameTime.h" 20 | #include "Player.h" 21 | #include "WorldSessionMgr.h" 22 | 23 | constexpr uint32 MapAlteracValley = 30; 24 | 25 | CrossFactionGroupInfo::CrossFactionGroupInfo(GroupQueueInfo* groupInfo) 26 | { 27 | uint32 sumLevels = 0; 28 | uint32 sumAverageItemLevels = 0; 29 | uint32 playersCount = 0; 30 | 31 | for (auto const& playerGuid : groupInfo->Players) 32 | { 33 | auto player = ObjectAccessor::FindConnectedPlayer(playerGuid); 34 | if (!player) 35 | continue; 36 | 37 | if (player->getClass() == CLASS_HUNTER && !IsHunterJoining) 38 | IsHunterJoining = true; 39 | 40 | sumLevels += player->GetLevel(); 41 | sumAverageItemLevels += player->GetAverageItemLevel(); 42 | playersCount++; 43 | 44 | SumAverageItemLevel += player->GetAverageItemLevel(); 45 | SumPlayerLevel += player->GetLevel(); 46 | } 47 | 48 | if (!playersCount) 49 | return; 50 | 51 | AveragePlayersLevel = sumLevels / playersCount; 52 | AveragePlayersItemLevel = sumAverageItemLevels / playersCount; 53 | } 54 | 55 | CrossFactionQueueInfo::CrossFactionQueueInfo(BattlegroundQueue* bgQueue) 56 | { 57 | auto FillStats = [this, bgQueue](TeamId team) 58 | { 59 | for (auto const& groupInfo : bgQueue->m_SelectionPools[team].SelectedGroups) 60 | { 61 | for (auto const& playerGuid : groupInfo->Players) 62 | { 63 | auto player = ObjectAccessor::FindConnectedPlayer(playerGuid); 64 | if (!player) 65 | continue; 66 | 67 | SumAverageItemLevel[team] += player->GetAverageItemLevel(); 68 | SumPlayerLevel[team] += player->GetLevel(); 69 | PlayersCount[team]++; 70 | } 71 | } 72 | }; 73 | 74 | FillStats(TEAM_ALLIANCE); 75 | FillStats(TEAM_HORDE); 76 | } 77 | 78 | TeamId CrossFactionQueueInfo::GetLowerTeamIdInBG(GroupQueueInfo* groupInfo) 79 | { 80 | int32 plCountA = PlayersCount.at(TEAM_ALLIANCE); 81 | int32 plCountH = PlayersCount.at(TEAM_HORDE); 82 | uint32 diff = std::abs(plCountA - plCountH); 83 | 84 | if (diff) 85 | return plCountA < plCountH ? TEAM_ALLIANCE : TEAM_HORDE; 86 | 87 | if (sCFBG->IsEnableBalancedTeams()) 88 | return SelectBgTeam(groupInfo); 89 | 90 | if (sCFBG->IsEnableAvgIlvl() && SumAverageItemLevel.at(TEAM_ALLIANCE) != SumAverageItemLevel.at(TEAM_HORDE)) 91 | return GetLowerAverageItemLevelTeam(); 92 | 93 | return groupInfo->teamId; 94 | } 95 | 96 | TeamId CrossFactionQueueInfo::SelectBgTeam(GroupQueueInfo* groupInfo) 97 | { 98 | uint32 allianceLevels = SumPlayerLevel.at(TeamId::TEAM_ALLIANCE); 99 | uint32 hordeLevels = SumPlayerLevel.at(TeamId::TEAM_HORDE); 100 | TeamId team = groupInfo->teamId; 101 | 102 | // First select team - where the sum of the levels is less 103 | if (allianceLevels != hordeLevels) 104 | team = allianceLevels < hordeLevels ? TEAM_ALLIANCE : TEAM_HORDE; 105 | 106 | // Config option `CFBG.EvenTeams.Enabled = 1` 107 | // if players in queue is equal to an even number 108 | //if (sCFBG->IsEnableEvenTeams() && groupInfo->Players.size() % 2 == 0) 109 | //{ 110 | // auto cfGroupInfo = CrossFactionGroupInfo(groupInfo); 111 | // auto playerLevel = cfGroupInfo.AveragePlayersLevel; 112 | 113 | // auto playerCountH = PlayersCount.at(TEAM_HORDE); 114 | // auto playerCountA = PlayersCount.at(TEAM_ALLIANCE); 115 | 116 | // // We need to have a diff of 0.5f 117 | // // Range of calculation: [minBgLevel, maxBgLevel], i.e: [10,20) 118 | // float avgLvlAlliance = SumPlayerLevel.at(TEAM_ALLIANCE) / (float)playerCountA; 119 | // float avgLvlHorde = SumPlayerLevel.at(TEAM_HORDE) / (float)playerCountH; 120 | 121 | // if (std::abs(avgLvlAlliance - avgLvlHorde) >= 0.5f) 122 | // { 123 | // team = avgLvlAlliance < avgLvlHorde ? TEAM_ALLIANCE : TEAM_HORDE; 124 | // } 125 | // else // it's balanced, so we should only check the ilvl 126 | // team = GetLowerAverageItemLevelTeam(); 127 | //} 128 | //else if (allianceLevels == hordeLevels) 129 | if (allianceLevels == hordeLevels && SumAverageItemLevel.at(TEAM_ALLIANCE) != SumAverageItemLevel.at(TEAM_HORDE)) 130 | team = GetLowerAverageItemLevelTeam(); 131 | 132 | return team; 133 | } 134 | 135 | TeamId CrossFactionQueueInfo::GetLowerAverageItemLevelTeam() 136 | { 137 | return SumAverageItemLevel.at(TEAM_ALLIANCE) < SumAverageItemLevel.at(TEAM_HORDE) ? TEAM_ALLIANCE : TEAM_HORDE; 138 | } 139 | 140 | CFBG::CFBG() 141 | { 142 | _raceData = 143 | { 144 | RaceData{ CLASS_NONE, { 0 }, { 0 } }, 145 | RaceData{ CLASS_WARRIOR, { RACE_HUMAN, RACE_DWARF, RACE_GNOME, RACE_DRAENEI }, { RACE_ORC, RACE_TAUREN, RACE_TROLL } }, 146 | RaceData{ CLASS_PALADIN, { RACE_HUMAN, RACE_DWARF, RACE_DRAENEI }, { RACE_BLOODELF } }, 147 | RaceData{ CLASS_HUNTER, { RACE_DWARF, RACE_DRAENEI }, { RACE_ORC, RACE_TAUREN, RACE_TROLL, RACE_BLOODELF } }, 148 | RaceData{ CLASS_ROGUE, { RACE_HUMAN, RACE_DWARF, RACE_GNOME }, { RACE_ORC, RACE_TROLL, RACE_BLOODELF } }, 149 | RaceData{ CLASS_PRIEST, { RACE_HUMAN, RACE_DWARF, RACE_DRAENEI }, { RACE_TROLL, RACE_BLOODELF } }, 150 | RaceData{ CLASS_DEATH_KNIGHT, { RACE_HUMAN, RACE_DWARF, RACE_GNOME, RACE_DRAENEI }, { RACE_ORC, RACE_TAUREN, RACE_TROLL, RACE_BLOODELF } }, 151 | RaceData{ CLASS_SHAMAN, { RACE_DRAENEI }, { RACE_ORC, RACE_TAUREN, RACE_TROLL } }, 152 | RaceData{ CLASS_MAGE, { RACE_HUMAN, RACE_GNOME }, { RACE_BLOODELF, RACE_TROLL } }, 153 | RaceData{ CLASS_WARLOCK, { RACE_HUMAN, RACE_GNOME }, { RACE_ORC, RACE_BLOODELF } }, 154 | RaceData{ CLASS_NONE, { 0 }, { 0 } }, 155 | RaceData{ CLASS_DRUID, { RACE_HUMAN }, { RACE_TAUREN } } 156 | }; 157 | 158 | _raceInfo = 159 | { 160 | CFBGRaceInfo{ RACE_HUMAN, "human", TEAM_HORDE }, 161 | CFBGRaceInfo{ RACE_NIGHTELF, "nightelf", TEAM_HORDE }, 162 | CFBGRaceInfo{ RACE_DWARF, "dwarf", TEAM_HORDE }, 163 | CFBGRaceInfo{ RACE_GNOME, "gnome", TEAM_HORDE }, 164 | CFBGRaceInfo{ RACE_DRAENEI, "draenei", TEAM_HORDE }, 165 | CFBGRaceInfo{ RACE_ORC, "orc", TEAM_ALLIANCE }, 166 | CFBGRaceInfo{ RACE_BLOODELF, "bloodelf", TEAM_ALLIANCE }, 167 | CFBGRaceInfo{ RACE_TROLL, "troll", TEAM_ALLIANCE }, 168 | CFBGRaceInfo{ RACE_TAUREN, "tauren", TEAM_ALLIANCE } 169 | }; 170 | } 171 | 172 | CFBG* CFBG::instance() 173 | { 174 | static CFBG instance; 175 | return &instance; 176 | } 177 | 178 | void CFBG::LoadConfig() 179 | { 180 | _IsEnableSystem = sConfigMgr->GetOption("CFBG.Enable", false); 181 | if (!_IsEnableSystem) 182 | return; 183 | 184 | _IsEnableAvgIlvl = sConfigMgr->GetOption("CFBG.Include.Avg.Ilvl.Enable", false); 185 | _IsEnableBalancedTeams = sConfigMgr->GetOption("CFBG.BalancedTeams", false); 186 | _IsEnableEvenTeams = sConfigMgr->GetOption("CFBG.EvenTeams.Enabled", false); 187 | _IsEnableBalanceClassLowLevel = sConfigMgr->GetOption("CFBG.BalancedTeams.Class.LowLevel", true); 188 | _IsEnableResetCooldowns = sConfigMgr->GetOption("CFBG.ResetCooldowns", false); 189 | _showPlayerName = sConfigMgr->GetOption("CFBG.Show.PlayerName", false); 190 | _EvenTeamsMaxPlayersThreshold = sConfigMgr->GetOption("CFBG.EvenTeams.MaxPlayersThreshold", 5); 191 | _MaxPlayersCountInGroup = sConfigMgr->GetOption("CFBG.Players.Count.In.Group", 3); 192 | _balanceClassMinLevel = sConfigMgr->GetOption("CFBG.BalancedTeams.Class.MinLevel", 10); 193 | _balanceClassMaxLevel = sConfigMgr->GetOption("CFBG.BalancedTeams.Class.MaxLevel", 19); 194 | _balanceClassLevelDiff = sConfigMgr->GetOption("CFBG.BalancedTeams.Class.LevelDiff", 2); 195 | _randomizeRaces = sConfigMgr->GetOption("CFBG.RandomRaceSelection", true); 196 | } 197 | 198 | uint32 CFBG::GetBGTeamAverageItemLevel(Battleground* bg, TeamId team) 199 | { 200 | if (!bg) 201 | { 202 | return 0; 203 | } 204 | 205 | uint32 sum = 0; 206 | uint32 count = 0; 207 | 208 | for (auto const& [playerGuid, player] : bg->GetPlayers()) 209 | { 210 | if (player && player->GetTeamId() == team) 211 | { 212 | sum += player->GetAverageItemLevel(); 213 | count++; 214 | } 215 | } 216 | 217 | if (!count || !sum) 218 | { 219 | return 0; 220 | } 221 | 222 | return sum / count; 223 | } 224 | 225 | uint32 CFBG::GetBGTeamSumPlayerLevel(Battleground* bg, TeamId team) 226 | { 227 | if (!bg) 228 | { 229 | return 0; 230 | } 231 | 232 | uint32 sum = 0; 233 | 234 | for (auto const& [playerGuid, player] : bg->GetPlayers()) 235 | { 236 | if (player && player->GetTeamId() == team) 237 | { 238 | sum += player->GetLevel(); 239 | } 240 | } 241 | 242 | return sum; 243 | } 244 | 245 | TeamId CFBG::GetLowerTeamIdInBG(Battleground* bg, BattlegroundQueue* bgQueue, GroupQueueInfo* groupInfo) 246 | { 247 | auto queueInfo = CrossFactionQueueInfo{ bgQueue }; 248 | 249 | int32 plCountA = bg->GetPlayersCountByTeam(TEAM_ALLIANCE) + queueInfo.PlayersCount.at(TEAM_ALLIANCE); 250 | int32 plCountH = bg->GetPlayersCountByTeam(TEAM_HORDE) + queueInfo.PlayersCount.at(TEAM_HORDE); 251 | 252 | if (uint32 diff = std::abs(plCountA - plCountH)) 253 | return plCountA < plCountH ? TEAM_ALLIANCE : TEAM_HORDE; 254 | 255 | if (IsEnableBalancedTeams()) 256 | return SelectBgTeam(bg, groupInfo, &queueInfo); 257 | 258 | if (IsEnableAvgIlvl() && !IsAvgIlvlTeamsInBgEqual(bg)) 259 | return GetLowerAvgIlvlTeamInBg(bg); 260 | 261 | return groupInfo->teamId; 262 | } 263 | 264 | TeamId CFBG::SelectBgTeam(Battleground* bg, GroupQueueInfo* groupInfo, CrossFactionQueueInfo* cfQueueInfo) 265 | { 266 | auto cfGroupInfo = CrossFactionGroupInfo(groupInfo); 267 | 268 | uint32 allianceLevels = GetBGTeamSumPlayerLevel(bg, TEAM_ALLIANCE) + cfQueueInfo->SumPlayerLevel.at(TEAM_ALLIANCE); 269 | uint32 hordeLevels = GetBGTeamSumPlayerLevel(bg, TEAM_HORDE) + cfQueueInfo->SumPlayerLevel.at(TEAM_HORDE); 270 | 271 | if (groupInfo->teamId == TeamId::TEAM_ALLIANCE) 272 | allianceLevels += cfGroupInfo.SumPlayerLevel; 273 | else 274 | hordeLevels += cfGroupInfo.SumPlayerLevel; 275 | 276 | TeamId team = groupInfo->teamId; 277 | 278 | // First select team - where the sum of the levels is less 279 | if (allianceLevels != hordeLevels) 280 | team = (allianceLevels < hordeLevels) ? TEAM_ALLIANCE : TEAM_HORDE; 281 | 282 | // Config option `CFBG.EvenTeams.Enabled = 1` 283 | // if players in queue is equal to an even number 284 | if (IsEnableEvenTeams() /*&& groupInfo->Players.size() % 2 == 0*/) 285 | { 286 | auto cfGroupInfo = CrossFactionGroupInfo(groupInfo); 287 | auto playerLevel = cfGroupInfo.AveragePlayersLevel; 288 | 289 | // if CFBG.BalancedTeams.LowLevelClass is enabled, check the quantity of hunter per team if the player is an hunter 290 | if (IsEnableBalanceClassLowLevel() && 291 | (playerLevel >= _balanceClassMinLevel && playerLevel <= _balanceClassMaxLevel) && 292 | (playerLevel >= getBalanceClassMinLevel(bg)) && 293 | (cfGroupInfo.IsHunterJoining)) // if the current player is hunter OR there is a hunter in the joining queue while a non-hunter player is joining 294 | { 295 | team = getTeamWithLowerClass(bg, CLASS_HUNTER); 296 | } 297 | else 298 | { 299 | auto playerCountH = bg->GetPlayersCountByTeam(TEAM_HORDE) + cfQueueInfo->PlayersCount.at(TEAM_HORDE); 300 | auto playerCountA = bg->GetPlayersCountByTeam(TEAM_ALLIANCE) + cfQueueInfo->PlayersCount.at(TEAM_ALLIANCE); 301 | 302 | // We need to have a diff of 0.5f 303 | // Range of calculation: [minBgLevel, maxBgLevel], i.e: [10,20) 304 | float avgLvlAlliance = allianceLevels / (float)playerCountA; 305 | float avgLvlHorde = hordeLevels / (float)playerCountH; 306 | 307 | if (std::abs(avgLvlAlliance - avgLvlHorde) >= 0.5f) 308 | { 309 | team = avgLvlAlliance < avgLvlHorde ? TEAM_ALLIANCE : TEAM_HORDE; 310 | } 311 | else // it's balanced, so we should only check the ilvl 312 | team = GetLowerAvgIlvlTeamInBg(bg); 313 | } 314 | } 315 | else if (allianceLevels == hordeLevels) 316 | { 317 | team = GetLowerAvgIlvlTeamInBg(bg); 318 | } 319 | 320 | return team; 321 | } 322 | 323 | uint8 CFBG::getBalanceClassMinLevel(const Battleground* bg) const 324 | { 325 | return static_cast(bg->GetMaxLevel()) - _balanceClassLevelDiff; 326 | } 327 | 328 | TeamId CFBG::getTeamWithLowerClass(Battleground *bg, Classes c) 329 | { 330 | uint16 hordeClassQty = 0; 331 | uint16 allianceClassQty = 0; 332 | 333 | for (auto const& [playerGuid, player] : bg->GetPlayers()) 334 | { 335 | if (player && player->getClass() == c) 336 | { 337 | if (player->GetTeamId() == TEAM_ALLIANCE) 338 | { 339 | allianceClassQty++; 340 | } 341 | else 342 | { 343 | hordeClassQty++; 344 | } 345 | } 346 | } 347 | 348 | return hordeClassQty > allianceClassQty ? TEAM_ALLIANCE : TEAM_HORDE; 349 | } 350 | 351 | TeamId CFBG::GetLowerAvgIlvlTeamInBg(Battleground* bg) 352 | { 353 | return (GetBGTeamAverageItemLevel(bg, TeamId::TEAM_ALLIANCE) < GetBGTeamAverageItemLevel(bg, TeamId::TEAM_HORDE)) ? TEAM_ALLIANCE : TEAM_HORDE; 354 | } 355 | 356 | bool CFBG::IsAvgIlvlTeamsInBgEqual(Battleground* bg) 357 | { 358 | return GetBGTeamAverageItemLevel(bg, TeamId::TEAM_ALLIANCE) == GetBGTeamAverageItemLevel(bg, TeamId::TEAM_HORDE); 359 | } 360 | 361 | void CFBG::ValidatePlayerForBG(Battleground* bg, Player* player) 362 | { 363 | if (!_IsEnableSystem || !bg || bg->isArena() || !player) 364 | return; 365 | 366 | TeamId teamId{ player->GetBgTeamId() }; 367 | 368 | if (player->GetTeamId(true) == teamId) 369 | return; 370 | 371 | BGData& bgdata = player->GetBGData(); 372 | 373 | if (bgdata.bgTeamId != teamId) 374 | bgdata.bgTeamId = teamId; 375 | 376 | SetFakeRaceAndMorph(player); 377 | 378 | if (bg->GetMapId() == MapAlteracValley) 379 | { 380 | if (teamId == TEAM_HORDE) 381 | { 382 | player->GetReputationMgr().ApplyForceReaction(FACTION_FROSTWOLF_CLAN, REP_FRIENDLY, true); 383 | player->GetReputationMgr().ApplyForceReaction(FACTION_STORMPIKE_GUARD, REP_HOSTILE, true); 384 | } 385 | else 386 | { 387 | player->GetReputationMgr().ApplyForceReaction(FACTION_FROSTWOLF_CLAN, REP_HOSTILE, true); 388 | player->GetReputationMgr().ApplyForceReaction(FACTION_STORMPIKE_GUARD, REP_FRIENDLY, true); 389 | } 390 | 391 | player->GetReputationMgr().SendForceReactions(); 392 | } 393 | } 394 | 395 | uint32 CFBG::GetAllPlayersCountInBG(Battleground* bg) 396 | { 397 | return bg->GetPlayersSize(); 398 | } 399 | 400 | uint32 CFBG::GetMorphFromRace(uint8 race, uint8 gender) 401 | { 402 | switch (race) 403 | { 404 | case RACE_BLOODELF: 405 | return gender == GENDER_MALE ? FAKE_M_BLOOD_ELF : FAKE_F_BLOOD_ELF; 406 | case RACE_ORC: 407 | return gender == GENDER_MALE ? FAKE_M_FEL_ORC : FAKE_F_ORC; 408 | case RACE_TROLL: 409 | return gender == GENDER_MALE ? FAKE_M_TROLL : FAKE_F_BLOOD_ELF; 410 | case RACE_TAUREN: 411 | return gender == GENDER_MALE ? FAKE_M_TAUREN : FAKE_F_TAUREN; 412 | case RACE_DRAENEI: 413 | return gender == GENDER_MALE ? FAKE_M_BROKEN_DRAENEI : FAKE_F_DRAENEI; 414 | case RACE_DWARF: 415 | return gender == GENDER_MALE ? FAKE_M_DWARF : FAKE_F_HUMAN; 416 | case RACE_GNOME: 417 | return gender == GENDER_MALE ? FAKE_M_GNOME : FAKE_F_GNOME; 418 | case RACE_NIGHTELF: // female is missing and male causes client crashes... 419 | case RACE_HUMAN: 420 | return gender == GENDER_MALE ? FAKE_M_HUMAN : FAKE_F_HUMAN; 421 | default: 422 | // Default: Blood elf. 423 | return gender == GENDER_MALE ? FAKE_M_BLOOD_ELF : FAKE_F_BLOOD_ELF; 424 | } 425 | } 426 | 427 | CFBG::RandomSkinInfo CFBG::GetRandomRaceMorph(TeamId team, uint8 playerClass, uint8 gender) 428 | { 429 | uint8 playerRace = Acore::Containers::SelectRandomContainerElement(team == TEAM_ALLIANCE ? _raceData[playerClass].availableRacesH : _raceData[playerClass].availableRacesA); 430 | uint32 playerMorph = GetMorphFromRace(playerRace, gender); 431 | 432 | return { playerRace, playerMorph }; 433 | } 434 | 435 | void CFBG::SetFakeRaceAndMorph(Player* player) 436 | { 437 | if (!player->InBattleground() || player->GetTeamId(true) == player->GetBgTeamId() || IsPlayerFake(player)) 438 | return; 439 | 440 | // generate random race and morph 441 | RandomSkinInfo skinInfo{ GetRandomRaceMorph(player->GetTeamId(true), player->getClass(), player->getGender()) }; 442 | 443 | uint8 selectedRace = player->GetPlayerSetting("mod-cfbg", SETTING_CFBG_RACE).value; 444 | 445 | if (!RandomizeRaces() && selectedRace && IsRaceValidForFaction(player->GetTeamId(true), selectedRace)) 446 | { 447 | skinInfo.first = selectedRace; 448 | skinInfo.second = GetMorphFromRace(skinInfo.first, player->getGender()); 449 | } 450 | 451 | FakePlayer fakePlayerInfo 452 | { 453 | skinInfo.first, 454 | skinInfo.second, 455 | player->TeamIdForRace(skinInfo.first), 456 | player->getRace(true), 457 | player->GetDisplayId(), 458 | player->GetNativeDisplayId(), 459 | player->GetTeamId(true) 460 | }; 461 | 462 | player->setRace(fakePlayerInfo.FakeRace); 463 | SetFactionForRace(player, fakePlayerInfo.FakeRace); 464 | player->SetDisplayId(fakePlayerInfo.FakeMorph); 465 | player->SetNativeDisplayId(fakePlayerInfo.FakeMorph); 466 | 467 | _fakePlayerStore.emplace(player, std::move(fakePlayerInfo)); 468 | } 469 | 470 | void CFBG::SetFactionForRace(Player* player, uint8 Race) 471 | { 472 | player->setTeamId(player->TeamIdForRace(Race)); 473 | 474 | ChrRacesEntry const* DBCRace = sChrRacesStore.LookupEntry(Race); 475 | player->SetFaction(DBCRace ? DBCRace->FactionID : 0); 476 | } 477 | 478 | void CFBG::ClearFakePlayer(Player* player) 479 | { 480 | if (!IsPlayerFake(player)) 481 | return; 482 | 483 | player->setRace(_fakePlayerStore[player].RealRace); 484 | player->SetDisplayId(_fakePlayerStore[player].RealMorph); 485 | player->SetNativeDisplayId(_fakePlayerStore[player].RealNativeMorph); 486 | SetFactionForRace(player, _fakePlayerStore[player].RealRace); 487 | 488 | // Clear forced faction reactions. Rank doesn't matter here, not used when they are removed. 489 | player->GetReputationMgr().ApplyForceReaction(FACTION_FROSTWOLF_CLAN, REP_FRIENDLY, false); 490 | player->GetReputationMgr().ApplyForceReaction(FACTION_STORMPIKE_GUARD, REP_FRIENDLY, false); 491 | 492 | _fakePlayerStore.erase(player); 493 | } 494 | 495 | bool CFBG::IsPlayerFake(Player* player) 496 | { 497 | return _fakePlayerStore.contains(player); 498 | } 499 | 500 | FakePlayer const* CFBG::GetFakePlayer(Player* player) const 501 | { 502 | return Acore::Containers::MapGetValuePtr(_fakePlayerStore, player); 503 | } 504 | 505 | void CFBG::DoForgetPlayersInList(Player* player) 506 | { 507 | // m_FakePlayers is filled from a vector within the battleground 508 | // they were in previously so all players that have been in that BG will be invalidated. 509 | for (auto const& itr : _fakeNamePlayersStore) 510 | { 511 | WorldPacket data(SMSG_INVALIDATE_PLAYER, 8); 512 | data << itr.second; 513 | player->GetSession()->SendPacket(&data); 514 | 515 | if (Player* _player = ObjectAccessor::FindPlayer(itr.second)) 516 | player->GetSession()->SendNameQueryOpcode(_player->GetGUID()); 517 | } 518 | 519 | _fakeNamePlayersStore.erase(player); 520 | } 521 | 522 | void CFBG::FitPlayerInTeam(Player* player, bool action, Battleground* bg) 523 | { 524 | if (!_IsEnableSystem) 525 | return; 526 | 527 | if (!bg) 528 | bg = player->GetBattleground(); 529 | 530 | if ((!bg || bg->isArena()) && action) 531 | return; 532 | 533 | if (action) 534 | SetForgetBGPlayers(player, true); 535 | else 536 | SetForgetInListPlayers(player, true); 537 | } 538 | 539 | void CFBG::SetForgetBGPlayers(Player* player, bool value) 540 | { 541 | _forgetBGPlayersStore[player] = value; 542 | } 543 | 544 | bool CFBG::ShouldForgetBGPlayers(Player* player) 545 | { 546 | return _forgetBGPlayersStore[player]; 547 | } 548 | 549 | void CFBG::SetForgetInListPlayers(Player* player, bool value) 550 | { 551 | _forgetInListPlayersStore[player] = value; 552 | } 553 | 554 | bool CFBG::ShouldForgetInListPlayers(Player* player) 555 | { 556 | return _forgetInListPlayersStore.find(player) != _forgetInListPlayersStore.end() && _forgetInListPlayersStore[player]; 557 | } 558 | 559 | void CFBG::DoForgetPlayersInBG(Player* player, Battleground* bg) 560 | { 561 | for (auto const& itr : bg->GetPlayers()) 562 | { 563 | // Here we invalidate players in the bg to the added player 564 | WorldPacket data1(SMSG_INVALIDATE_PLAYER, 8); 565 | data1 << itr.first; 566 | player->GetSession()->SendPacket(&data1); 567 | 568 | if (Player* _player = ObjectAccessor::FindPlayer(itr.first)) 569 | { 570 | player->GetSession()->SendNameQueryOpcode(_player->GetGUID()); // Send namequery answer instantly if player is available 571 | 572 | // Here we invalidate the player added to players in the bg 573 | WorldPacket data2(SMSG_INVALIDATE_PLAYER, 8); 574 | data2 << player->GetGUID(); 575 | _player->GetSession()->SendPacket(&data2); 576 | _player->GetSession()->SendNameQueryOpcode(player->GetGUID()); 577 | } 578 | } 579 | } 580 | 581 | bool CFBG::SendRealNameQuery(Player* player) 582 | { 583 | if (IsPlayingNative(player)) 584 | return false; 585 | 586 | WorldPacket data(SMSG_NAME_QUERY_RESPONSE, (8 + 1 + 1 + 1 + 1 + 1 + 10)); 587 | data << player->GetGUID().WriteAsPacked(); // player guid 588 | data << uint8(0); // added in 3.1; if > 1, then end of packet 589 | data << player->GetName(); // played name 590 | data << uint8(0); // realm name for cross realm BG usage 591 | data << uint8(player->getRace(true)); 592 | data << uint8(player->getGender()); 593 | data << uint8(player->getClass()); 594 | data << uint8(0); // is not declined 595 | player->GetSession()->SendPacket(&data); 596 | 597 | return true; 598 | } 599 | 600 | bool CFBG::IsPlayingNative(Player* player) 601 | { 602 | return player->GetTeamId(true) == player->GetBGData().bgTeamId; 603 | } 604 | 605 | bool CFBG::CheckCrossFactionMatch(BattlegroundQueue* queue, BattlegroundBracketId bracket_id, uint32 minPlayers, uint32 maxPlayers) 606 | { 607 | if (!IsEnableSystem()) 608 | return false; 609 | 610 | queue->m_SelectionPools[TEAM_ALLIANCE].Init(); 611 | queue->m_SelectionPools[TEAM_HORDE].Init(); 612 | 613 | GroupsList groups{ queue->m_QueuedGroups[bracket_id][BG_QUEUE_CFBG].begin(), queue->m_QueuedGroups[bracket_id][BG_QUEUE_CFBG].end() }; 614 | 615 | if (IsEnableEvenTeams()) 616 | { 617 | // Sort for check same count groups 618 | std::sort(groups.begin(), groups.end(), [](GroupQueueInfo const* a, GroupQueueInfo const* b) { return a->Players.size() > b->Players.size(); }); 619 | 620 | InviteSameCountGroups(groups, queue, maxPlayers, maxPlayers); 621 | } 622 | else 623 | { 624 | // Default sort 625 | std::sort(groups.begin(), groups.end(), [](GroupQueueInfo const* a, GroupQueueInfo const* b) { return a->JoinTime > b->JoinTime; }); 626 | 627 | for (auto const& gInfo : groups) 628 | { 629 | if (gInfo->IsInvitedToBGInstanceGUID) 630 | continue; 631 | 632 | auto queueInfo = CrossFactionQueueInfo{ queue }; 633 | auto targetTeam = queueInfo.GetLowerTeamIdInBG(gInfo); 634 | gInfo->teamId = targetTeam; 635 | 636 | if (!queue->m_SelectionPools[targetTeam].AddGroup(gInfo, maxPlayers)) 637 | break; 638 | } 639 | } 640 | 641 | // Return when we're ready to start a BG, if we're in startup process 642 | if (queue->m_SelectionPools[TEAM_ALLIANCE].GetPlayerCount() >= minPlayers && 643 | queue->m_SelectionPools[TEAM_HORDE].GetPlayerCount() >= minPlayers) 644 | return true; 645 | 646 | // Return false when we didn't manage to fill the BattleGround in Filling "mode". 647 | // reset selectionpool for further attempts 648 | queue->m_SelectionPools[TEAM_ALLIANCE].Init(); 649 | queue->m_SelectionPools[TEAM_HORDE].Init(); 650 | return true; 651 | } 652 | 653 | bool CFBG::FillPlayersToCFBG(BattlegroundQueue* bgqueue, Battleground* bg, BattlegroundBracketId bracket_id) 654 | { 655 | if (!IsEnableSystem() || bg->isArena() || bg->isRated()) 656 | return false; 657 | 658 | uint32 maxAli{ bg->GetFreeSlotsForTeam(TEAM_ALLIANCE) }; 659 | uint32 maxHorde{ bg->GetFreeSlotsForTeam(TEAM_HORDE) }; 660 | 661 | GroupsList groups{ bgqueue->m_QueuedGroups[bracket_id][BG_QUEUE_CFBG].begin(), bgqueue->m_QueuedGroups[bracket_id][BG_QUEUE_CFBG].end() }; 662 | 663 | // Sort for check same groups 664 | std::sort(groups.begin(), groups.end(), [](GroupQueueInfo const* a, GroupQueueInfo const* b) { return a->Players.size() < b->Players.size(); }); 665 | 666 | std::array playersInvitedToBGCount{}; 667 | 668 | if (IsEnableEvenTeams()) 669 | { 670 | std::vector> sameGroups; 671 | 672 | // Check groups with equal players 673 | for (auto itr = groups.begin(); itr != groups.end();) 674 | { 675 | if ((*itr)->IsInvitedToBGInstanceGUID) 676 | { 677 | itr++; 678 | continue; 679 | } 680 | 681 | auto nextItr{ itr + 1 }; 682 | if (nextItr != groups.end()) 683 | { 684 | if ((*nextItr)->IsInvitedToBGInstanceGUID || (*itr)->Players.size() != (*nextItr)->Players.size()) 685 | { 686 | itr++; 687 | continue; 688 | } 689 | 690 | sameGroups.emplace_back(*itr, *nextItr); 691 | itr = itr + 2; 692 | 693 | if (itr == groups.end()) 694 | break; 695 | else 696 | continue; 697 | } 698 | 699 | itr++; 700 | } 701 | 702 | if (!sameGroups.empty()) 703 | { 704 | auto InviteGroupToBG = [this, bg, bgqueue, maxAli, maxHorde](GroupQueueInfo* gInfo) 705 | { 706 | TeamId targetTeam = GetLowerTeamIdInBG(bg, bgqueue, gInfo); 707 | gInfo->teamId = targetTeam; 708 | 709 | if (bgqueue->m_SelectionPools[targetTeam].AddGroup(gInfo, targetTeam == TEAM_ALLIANCE ? maxAli : maxHorde)) 710 | return targetTeam; 711 | 712 | return TEAM_NEUTRAL; 713 | }; 714 | 715 | for (auto& [group1, group2] : sameGroups) 716 | { 717 | auto team1{ InviteGroupToBG(group1) }; 718 | auto team2{ InviteGroupToBG(group2) }; 719 | 720 | if (team1 != TEAM_NEUTRAL && team2 != TEAM_NEUTRAL) 721 | { 722 | std::erase(groups, group1); 723 | std::erase(groups, group2); 724 | playersInvitedToBGCount.at(team1) += group1->Players.size(); 725 | playersInvitedToBGCount.at(team2) += group2->Players.size(); 726 | } 727 | } 728 | } 729 | 730 | if (groups.empty()) 731 | return true; // we invited all players, done 732 | } 733 | 734 | // Sort with join time (default) 735 | std::sort(groups.begin(), groups.end(), [](GroupQueueInfo const* a, GroupQueueInfo const* b) { return a->JoinTime < b->JoinTime; }); 736 | 737 | if (IsEnableEvenTeams()) 738 | { 739 | InviteSameCountGroups(groups, bgqueue, maxAli, maxHorde, bg); 740 | 741 | if (groups.empty()) 742 | return true; // we invited all players, done 743 | } 744 | 745 | // Check invited players to bg 746 | for (auto const& gInfo : bgqueue->m_QueuedGroups[bracket_id][BG_QUEUE_CFBG]) 747 | { 748 | if (gInfo->IsInvitedToBGInstanceGUID) 749 | playersInvitedToBGCount.at(gInfo->teamId) += gInfo->Players.size(); 750 | } 751 | 752 | auto DefaultInvitePlayersToBG = [this, bg, bgqueue, &groups, maxAli, maxHorde]() 753 | { 754 | GroupsList toDeleteGroups; 755 | 756 | for (auto const& gInfo : groups) 757 | { 758 | if (gInfo->IsInvitedToBGInstanceGUID) 759 | continue; 760 | 761 | TeamId targetTeam = GetLowerTeamIdInBG(bg, bgqueue, gInfo); 762 | gInfo->teamId = targetTeam; 763 | 764 | if (bgqueue->m_SelectionPools[targetTeam].AddGroup(gInfo, targetTeam == TEAM_ALLIANCE ? maxAli : maxHorde)) 765 | toDeleteGroups.emplace_back(gInfo); 766 | } 767 | 768 | for (auto const& itr : toDeleteGroups) 769 | std::erase(groups, itr); 770 | }; 771 | 772 | auto playersInBGAli{ bg->GetPlayersCountByTeam(TEAM_ALLIANCE) + playersInvitedToBGCount.at(TEAM_ALLIANCE) }; 773 | auto playersInBGHorde{ bg->GetPlayersCountByTeam(TEAM_HORDE) + playersInvitedToBGCount.at(TEAM_HORDE) }; 774 | auto playersInBG{ static_cast(playersInBGAli + playersInBGHorde) }; 775 | auto evenTeamsCount{ EvenTeamsMaxPlayersThreshold() }; 776 | 777 | if (IsEnableEvenTeams() && evenTeamsCount && playersInBG < evenTeamsCount * 2) 778 | { 779 | int32 aliNeed = evenTeamsCount - playersInBGAli; 780 | int32 hordeNeed = evenTeamsCount - playersInBGHorde; 781 | 782 | if (aliNeed < 0) 783 | aliNeed = 0; 784 | 785 | if (hordeNeed < 0) 786 | hordeNeed = 0; 787 | 788 | if ((aliNeed || hordeNeed) && (aliNeed != hordeNeed)) 789 | { 790 | uint32 playersNeed{ 0 }; 791 | TeamId targetTeam = TEAM_NEUTRAL; 792 | 793 | if (aliNeed && aliNeed > hordeNeed) 794 | { 795 | playersNeed = aliNeed - hordeNeed; 796 | targetTeam = TEAM_ALLIANCE; 797 | } 798 | else if (hordeNeed && hordeNeed > aliNeed) 799 | { 800 | playersNeed = hordeNeed - aliNeed; 801 | targetTeam = TEAM_HORDE; 802 | } 803 | 804 | if (playersNeed > 0 && targetTeam != TEAM_NEUTRAL) 805 | { 806 | GroupsList toDeleteGroups; 807 | 808 | // #1. Try fill players to even team 809 | for (auto const& gInfo : groups) 810 | { 811 | // We can add only single players 812 | if (gInfo->IsInvitedToBGInstanceGUID) 813 | continue; 814 | 815 | gInfo->teamId = targetTeam; 816 | 817 | if (bgqueue->m_SelectionPools[targetTeam].AddGroup(gInfo, playersNeed)) 818 | { 819 | auto groupPlayerSize{ gInfo->Players.size() }; 820 | playersNeed -= groupPlayerSize; 821 | toDeleteGroups.emplace_back(gInfo); 822 | targetTeam == TEAM_ALLIANCE ? aliNeed -= groupPlayerSize : hordeNeed -= groupPlayerSize; 823 | } 824 | 825 | // Stop invited if found players for even teams 826 | if (!playersNeed) 827 | break; 828 | } 829 | 830 | // Delete invited groups 831 | for (auto const& gInfo : toDeleteGroups) 832 | std::erase(groups, gInfo); 833 | } 834 | else 835 | LOG_FATAL("module", "> CFBG: Incorrect conditions for check even teams. Players need: {}. Target team: {}", playersNeed, targetTeam); 836 | } 837 | 838 | // #2 if all teams even and `MaxPlayersThreshold` complete 839 | if (!aliNeed && !hordeNeed) 840 | DefaultInvitePlayersToBG(); 841 | } 842 | else 843 | DefaultInvitePlayersToBG(); 844 | 845 | return true; 846 | } 847 | 848 | bool CFBG::isClassJoining(uint8 _class, Player* player, uint32 minLevel) 849 | { 850 | if (!player) 851 | { 852 | return false; 853 | } 854 | 855 | return player->getClass() == _class && (player->GetLevel() >= minLevel); 856 | } 857 | 858 | void CFBG::UpdateForget(Player* player) 859 | { 860 | Battleground* bg = player->GetBattleground(); 861 | if (bg) 862 | { 863 | if (ShouldForgetBGPlayers(player) && bg) 864 | { 865 | DoForgetPlayersInBG(player, bg); 866 | SetForgetBGPlayers(player, false); 867 | } 868 | } 869 | else if (ShouldForgetInListPlayers(player)) 870 | { 871 | DoForgetPlayersInList(player); 872 | SetForgetInListPlayers(player, false); 873 | } 874 | } 875 | 876 | std::unordered_map BGSpamProtectionCFBG; 877 | void CFBG::SendMessageQueue(BattlegroundQueue* bgQueue, Battleground* bg, PvPDifficultyEntry const* bracketEntry, Player* leader) 878 | { 879 | BattlegroundBracketId bracketId = bracketEntry->GetBracketId(); 880 | 881 | auto bgName = bg->GetName(); 882 | uint32 q_min_level = std::min(bracketEntry->minLevel, (uint32)80); 883 | uint32 q_max_level = std::min(bracketEntry->maxLevel, (uint32)80); 884 | uint32 MinPlayers = GetMinPlayersPerTeam(bg, bracketEntry) * 2; 885 | uint32 qTotal = bgQueue->GetPlayersCountInGroupsQueue(bracketId, (BattlegroundQueueGroupTypes)BG_QUEUE_CFBG); 886 | 887 | if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_PLAYERONLY)) 888 | { 889 | ChatHandler(leader->GetSession()).PSendSysMessage("CFBG {} (Levels: {} - {}). Registered: {}/{}", bgName.c_str(), q_min_level, q_max_level, qTotal, MinPlayers); 890 | } 891 | else 892 | { 893 | if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_TIMED)) 894 | { 895 | if (bgQueue->GetQueueAnnouncementTimer(bracketEntry->bracketId) < 0) 896 | { 897 | bgQueue->SetQueueAnnouncementTimer(bracketEntry->bracketId, sWorld->getIntConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_TIMER)); 898 | } 899 | } 900 | else 901 | { 902 | auto searchGUID = BGSpamProtectionCFBG.find(leader->GetGUID()); 903 | 904 | if (searchGUID == BGSpamProtectionCFBG.end()) 905 | BGSpamProtectionCFBG[leader->GetGUID()] = 0s; 906 | 907 | // Skip if spam time < 30 secs (default) 908 | if (GameTime::GetGameTime() - BGSpamProtectionCFBG[leader->GetGUID()] < Seconds(sWorld->getIntConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_SPAM_DELAY))) 909 | { 910 | return; 911 | } 912 | 913 | // When limited, it announces only if there are at least CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_LIMIT_MIN_PLAYERS in queue 914 | auto limitQueueMinLevel = sWorld->getIntConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_LIMIT_MIN_LEVEL); 915 | if (limitQueueMinLevel != 0 && q_min_level >= limitQueueMinLevel) 916 | { 917 | // limit only RBG for 80, WSG for lower levels 918 | auto bgTypeToLimit = q_min_level == 80 ? BATTLEGROUND_RB : BATTLEGROUND_WS; 919 | 920 | if (bg->GetBgTypeID() == bgTypeToLimit && qTotal < sWorld->getIntConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_LIMIT_MIN_PLAYERS)) 921 | { 922 | return; 923 | } 924 | } 925 | 926 | BGSpamProtectionCFBG[leader->GetGUID()] = GameTime::GetGameTime(); 927 | 928 | if (_showPlayerName) 929 | { 930 | std::string msg = Acore::StringFormat("{} |cffffffffHas Joined|r |cffff0000{}|r|cffffffff(|r|cff00ffff{}|r|cffffffff/|r|cff00ffff{}|r|cffffffff)|r", 931 | leader->GetPlayerName(), bg->GetName(), qTotal, MinPlayers); 932 | 933 | auto const& sessions = sWorldSessionMgr->GetAllSessions(); 934 | for (auto const& session : sessions) 935 | { 936 | if (Player* player = session.second->GetPlayer()) 937 | { 938 | if (player->GetPlayerSetting(AzerothcorePSSource, SETTING_ANNOUNCER_FLAGS).HasFlag(ANNOUNCER_FLAG_DISABLE_BG_QUEUE)) 939 | { 940 | continue; 941 | } 942 | 943 | WorldPacket data(SMSG_CHAT_SERVER_MESSAGE, (msg.size() + 1)); 944 | data << uint32(3); 945 | data << msg; 946 | player->GetSession()->SendPacket(&data); 947 | } 948 | } 949 | } 950 | else 951 | { 952 | ChatHandler(nullptr).SendWorldTextOptional(LANG_BG_QUEUE_ANNOUNCE_WORLD, ANNOUNCER_FLAG_DISABLE_BG_QUEUE, bgName, q_min_level, q_max_level, qTotal, MinPlayers); 953 | } 954 | } 955 | } 956 | } 957 | 958 | bool CFBG::IsRaceValidForFaction(uint8 teamId, uint8 race) 959 | { 960 | for (auto const& raceVariable : _raceInfo) 961 | { 962 | if (race == raceVariable.RaceId && teamId == raceVariable.TeamId) 963 | { 964 | return true; 965 | } 966 | } 967 | 968 | return false; 969 | } 970 | 971 | void CFBG::InviteSameCountGroups(GroupsList& groups, BattlegroundQueue* bgQueue, uint32 maxAli, uint32 maxHorde, Battleground* bg /*= nullptr*/) 972 | { 973 | if (groups.size() < 2 || !bgQueue) 974 | return; 975 | 976 | GroupsList groupList; 977 | GroupsList addedGroups; 978 | SameCountGroupsList container; 979 | 980 | for (auto const& targetGroup : groups) 981 | { 982 | if (targetGroup->IsInvitedToBGInstanceGUID) 983 | continue; 984 | 985 | if (std::find(addedGroups.begin(), addedGroups.end(), targetGroup) != addedGroups.end()) 986 | continue; 987 | 988 | groupList.clear(); 989 | auto groupSizeNeed{ targetGroup->Players.size() }; 990 | 991 | for (auto const& itrGroup : groups) 992 | { 993 | if (itrGroup == targetGroup) 994 | continue; 995 | 996 | if (itrGroup->IsInvitedToBGInstanceGUID) 997 | continue; 998 | 999 | if (std::find(addedGroups.begin(), addedGroups.end(), itrGroup) != addedGroups.end()) 1000 | continue; 1001 | 1002 | auto groupSizeItr{ itrGroup->Players.size() }; 1003 | if (groupSizeItr <= groupSizeNeed) 1004 | { 1005 | groupList.emplace_back(itrGroup); 1006 | groupSizeNeed -= groupSizeItr; 1007 | } 1008 | 1009 | if (!groupSizeNeed) 1010 | { 1011 | container.emplace_back(targetGroup, groupList); 1012 | addedGroups.emplace_back(targetGroup); 1013 | 1014 | for (auto const& itr : groupList) 1015 | addedGroups.emplace_back(itr); 1016 | 1017 | groupList.clear(); 1018 | break; 1019 | } 1020 | } 1021 | } 1022 | 1023 | if (container.empty()) 1024 | return; 1025 | 1026 | auto DeleteGroup = [bgQueue](GroupQueueInfo* gInfo) 1027 | { 1028 | std::erase(bgQueue->m_SelectionPools[TEAM_ALLIANCE].SelectedGroups, gInfo); 1029 | std::erase(bgQueue->m_SelectionPools[TEAM_HORDE].SelectedGroups, gInfo); 1030 | }; 1031 | 1032 | for (auto& [groupTarget, groupListForTarger] : container) 1033 | { 1034 | auto teamTarget{ InviteGroupToBG(groupTarget, bgQueue, maxAli, maxHorde, bg) }; 1035 | if (teamTarget == TEAM_NEUTRAL) 1036 | continue; 1037 | 1038 | bool IsAllInvited{ true }; 1039 | 1040 | for (auto const& groupItr : groupListForTarger) 1041 | { 1042 | auto teamItr{ InviteGroupToBG(groupItr, bgQueue, maxAli, maxHorde, bg) }; 1043 | if (teamItr == TEAM_NEUTRAL) 1044 | { 1045 | IsAllInvited = false; 1046 | break; 1047 | } 1048 | } 1049 | 1050 | if (!IsAllInvited) 1051 | { 1052 | for (auto const& groupItr : groupListForTarger) 1053 | DeleteGroup(groupItr); 1054 | 1055 | DeleteGroup(groupTarget); 1056 | continue; 1057 | } 1058 | 1059 | for (auto const& groupItr : groupListForTarger) 1060 | std::erase(groups, groupItr); 1061 | 1062 | std::erase(groups, groupTarget); 1063 | } 1064 | } 1065 | 1066 | TeamId CFBG::InviteGroupToBG(GroupQueueInfo* gInfo, BattlegroundQueue* bgQueue, uint32 maxAli, uint32 maxHorde, Battleground* bg /*= nullptr*/) 1067 | { 1068 | if (bg) 1069 | { 1070 | auto targetTeam = GetLowerTeamIdInBG(bg, bgQueue, gInfo); 1071 | gInfo->teamId = targetTeam; 1072 | } 1073 | else 1074 | { 1075 | auto queueInfo = CrossFactionQueueInfo{ bgQueue }; 1076 | auto targetTeam = queueInfo.GetLowerTeamIdInBG(gInfo); 1077 | gInfo->teamId = targetTeam; 1078 | } 1079 | 1080 | if (bgQueue->m_SelectionPools[gInfo->teamId].AddGroup(gInfo, gInfo->teamId == TEAM_ALLIANCE ? maxAli : maxHorde)) 1081 | return gInfo->teamId; 1082 | 1083 | return TEAM_NEUTRAL; 1084 | } 1085 | -------------------------------------------------------------------------------- /src/CFBG.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (С) since 2019 Andrei Guluaev (Winfidonarleyan/Kargatum) https://github.com/Winfidonarleyan 3 | * Copyright (С) since 2019+ AzerothCore 4 | * Licence MIT https://opensource.org/MIT 5 | */ 6 | 7 | #ifndef _CFBG_H_ 8 | #define _CFBG_H_ 9 | 10 | #include "SharedDefines.h" 11 | #include "DBCEnums.h" 12 | #include "ObjectGuid.h" 13 | #include 14 | #include 15 | #include 16 | 17 | class Player; 18 | class Battleground; 19 | class BattlegroundQueue; 20 | class Group; 21 | 22 | struct GroupQueueInfo; 23 | struct PvPDifficultyEntry; 24 | 25 | enum FakeMorphs 26 | { 27 | FAKE_M_FEL_ORC = 21267, 28 | FAKE_F_ORC = 20316, 29 | FAKE_M_DWARF = 20317, 30 | FAKE_M_NIGHT_ELF = 20318, 31 | FAKE_F_DRAENEI = 20323, 32 | FAKE_M_BROKEN_DRAENEI = 21105, 33 | FAKE_M_TROLL = 20321, 34 | FAKE_M_HUMAN = 19723, 35 | FAKE_F_HUMAN = 19724, 36 | FAKE_M_BLOOD_ELF = 20578, 37 | FAKE_F_BLOOD_ELF = 20579, 38 | FAKE_F_GNOME = 20320, 39 | FAKE_M_GNOME = 20580, 40 | FAKE_F_TAUREN = 20584, 41 | FAKE_M_TAUREN = 20585 42 | }; 43 | 44 | constexpr auto FACTION_FROSTWOLF_CLAN = 729; 45 | constexpr auto FACTION_STORMPIKE_GUARD = 730; 46 | 47 | // Cfbg settings 48 | constexpr auto SETTING_CFBG_RACE = 0; 49 | 50 | struct FakePlayer 51 | { 52 | // Fake 53 | uint8 FakeRace; 54 | uint32 FakeMorph; 55 | TeamId FakeTeamID; 56 | 57 | // Real 58 | uint8 RealRace; 59 | uint32 RealMorph; 60 | uint32 RealNativeMorph; 61 | TeamId RealTeamID; 62 | }; 63 | 64 | struct RaceData 65 | { 66 | uint8 charClass; 67 | std::vector availableRacesA; 68 | std::vector availableRacesH; 69 | }; 70 | 71 | struct CFBGRaceInfo 72 | { 73 | uint8 RaceId; 74 | std::string RaceName; 75 | uint8 TeamId; 76 | }; 77 | 78 | struct CrossFactionGroupInfo 79 | { 80 | explicit CrossFactionGroupInfo(GroupQueueInfo* groupInfo); 81 | 82 | uint32 AveragePlayersLevel{ 0 }; 83 | uint32 AveragePlayersItemLevel{ 0 }; 84 | bool IsHunterJoining{ false }; 85 | uint32 SumAverageItemLevel{ 0 }; 86 | uint32 SumPlayerLevel{ 0 }; 87 | 88 | CrossFactionGroupInfo() = delete; 89 | CrossFactionGroupInfo(CrossFactionGroupInfo&&) = delete; 90 | }; 91 | 92 | struct CrossFactionQueueInfo 93 | { 94 | explicit CrossFactionQueueInfo(BattlegroundQueue* bgQueue); 95 | 96 | TeamId GetLowerTeamIdInBG(GroupQueueInfo* groupInfo); 97 | 98 | std::array PlayersCount{}; 99 | std::array SumAverageItemLevel{}; 100 | std::array SumPlayerLevel{}; 101 | 102 | private: 103 | TeamId SelectBgTeam(GroupQueueInfo* groupInfo); 104 | TeamId GetLowerAverageItemLevelTeam(); 105 | 106 | CrossFactionQueueInfo() = delete; 107 | CrossFactionQueueInfo(CrossFactionQueueInfo&&) = delete; 108 | }; 109 | 110 | class CFBG 111 | { 112 | public: 113 | using RandomSkinInfo = std::pair; 114 | using GroupsList = std::vector; 115 | using SameCountGroupsList = std::vector>; 116 | 117 | static CFBG* instance(); 118 | 119 | void LoadConfig(); 120 | 121 | inline bool IsEnableSystem() const { return _IsEnableSystem; } 122 | inline bool IsEnableAvgIlvl() const { return _IsEnableAvgIlvl; } 123 | inline bool IsEnableBalancedTeams() const { return _IsEnableBalancedTeams; } 124 | inline bool IsEnableBalanceClassLowLevel() const { return _IsEnableBalanceClassLowLevel; } 125 | inline bool IsEnableEvenTeams() const { return _IsEnableEvenTeams; } 126 | inline bool IsEnableResetCooldowns() const { return _IsEnableResetCooldowns; } 127 | inline uint32 EvenTeamsMaxPlayersThreshold() const { return _EvenTeamsMaxPlayersThreshold; } 128 | inline uint32 GetMaxPlayersCountInGroup() const { return _MaxPlayersCountInGroup; } 129 | inline uint8 GetBalanceClassMinLevel() const { return _balanceClassMinLevel; } 130 | inline uint8 GetBalanceClassMaxLevel() const { return _balanceClassMaxLevel; } 131 | inline uint8 GetBalanceClassLevelDiff() const { return _balanceClassLevelDiff; } 132 | inline bool RandomizeRaces() const { return _randomizeRaces; } 133 | 134 | uint32 GetBGTeamAverageItemLevel(Battleground* bg, TeamId team); 135 | uint32 GetBGTeamSumPlayerLevel(Battleground* bg, TeamId team); 136 | uint32 GetAllPlayersCountInBG(Battleground* bg); 137 | 138 | TeamId GetLowerTeamIdInBG(Battleground* bg, BattlegroundQueue* bgQueue, GroupQueueInfo* groupInfo); 139 | TeamId GetLowerAvgIlvlTeamInBg(Battleground* bg); 140 | TeamId SelectBgTeam(Battleground* bg, GroupQueueInfo* groupInfo, CrossFactionQueueInfo* cfQueueInfo); 141 | 142 | bool IsAvgIlvlTeamsInBgEqual(Battleground* bg); 143 | bool SendRealNameQuery(Player* player); 144 | bool IsPlayerFake(Player* player); 145 | bool ShouldForgetInListPlayers(Player* player); 146 | bool IsPlayingNative(Player* player); 147 | 148 | void ValidatePlayerForBG(Battleground* bg, Player* player); 149 | void SetFakeRaceAndMorph(Player* player); 150 | void SetFactionForRace(Player* player, uint8 Race); 151 | void ClearFakePlayer(Player* player); 152 | void DoForgetPlayersInList(Player* player); 153 | void FitPlayerInTeam(Player* player, bool action, Battleground* bg); 154 | void DoForgetPlayersInBG(Player* player, Battleground* bg); 155 | void SetForgetBGPlayers(Player* player, bool value); 156 | bool ShouldForgetBGPlayers(Player* player); 157 | void SetForgetInListPlayers(Player* player, bool value); 158 | void UpdateForget(Player* player); 159 | void SendMessageQueue(BattlegroundQueue* bgQueue, Battleground* bg, PvPDifficultyEntry const* bracketEntry, Player* leader); 160 | 161 | bool FillPlayersToCFBG(BattlegroundQueue* bgqueue, Battleground* bg, BattlegroundBracketId bracket_id); 162 | bool CheckCrossFactionMatch(BattlegroundQueue* bgqueue, BattlegroundBracketId bracket_id, uint32 minPlayers, uint32 maxPlayers); 163 | 164 | bool IsRaceValidForFaction(uint8 teamId, uint8 race); 165 | TeamId getTeamWithLowerClass(Battleground* bg, Classes c); 166 | uint8 getBalanceClassMinLevel(const Battleground* bg) const; 167 | 168 | inline auto GetRaceData() { return &_raceData; } 169 | inline auto GetRaceInfo() { return &_raceInfo; } 170 | 171 | void OnAddGroupToBGQueue(GroupQueueInfo* ginfo, Group* group); 172 | 173 | private: 174 | bool isClassJoining(uint8 _class, Player* player, uint32 minLevel); 175 | 176 | RandomSkinInfo GetRandomRaceMorph(TeamId team, uint8 playerClass, uint8 gender); 177 | 178 | uint32 GetMorphFromRace(uint8 race, uint8 gender); 179 | FakePlayer const* GetFakePlayer(Player* player) const; 180 | 181 | void InviteSameCountGroups(GroupsList& groups, BattlegroundQueue* bgQueue, uint32 maxAli, uint32 maxHorde, Battleground* bg = nullptr); 182 | TeamId InviteGroupToBG(GroupQueueInfo* gInfo, BattlegroundQueue* bgQueue, uint32 maxAli, uint32 maxHorde, Battleground* bg = nullptr); 183 | 184 | std::unordered_map _fakePlayerStore; 185 | std::unordered_map _fakeNamePlayersStore; 186 | std::unordered_map _forgetBGPlayersStore; 187 | std::unordered_map _forgetInListPlayersStore; 188 | std::unordered_map _groupsInfo; 189 | 190 | std::array _raceData{}; 191 | std::array _raceInfo{}; 192 | 193 | // For config 194 | bool _IsEnableSystem; 195 | bool _IsEnableAvgIlvl; 196 | bool _IsEnableBalancedTeams; 197 | bool _IsEnableBalanceClassLowLevel; 198 | bool _IsEnableEvenTeams; 199 | bool _IsEnableResetCooldowns; 200 | bool _showPlayerName; 201 | bool _randomizeRaces; 202 | uint32 _EvenTeamsMaxPlayersThreshold; 203 | uint32 _MaxPlayersCountInGroup; 204 | uint8 _balanceClassMinLevel; 205 | uint8 _balanceClassMaxLevel; 206 | uint8 _balanceClassLevelDiff; 207 | 208 | CFBG(); 209 | ~CFBG() = default; 210 | 211 | CFBG(CFBG const&) = delete; 212 | CFBG(CFBG&&) = delete; 213 | CFBG& operator=(CFBG const&) = delete; 214 | CFBG& operator=(CFBG&&) = delete; 215 | }; 216 | 217 | #define sCFBG CFBG::instance() 218 | 219 | #endif // _KARGATUM_CFBG_H_ 220 | -------------------------------------------------------------------------------- /src/CFBG_SC.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (С) since 2019 Andrei Guluaev (Winfidonarleyan/Kargatum) https://github.com/Winfidonarleyan 3 | * Copyright (С) since 2019+ AzerothCore 4 | */ 5 | 6 | #include "CFBG.h" 7 | #include "Group.h" 8 | #include "Player.h" 9 | #include "ReputationMgr.h" 10 | #include "ScriptMgr.h" 11 | #include "BattlegroundQueue.h" 12 | 13 | // CFBG custom script 14 | class CFBG_BG : public BGScript 15 | { 16 | public: 17 | CFBG_BG() : BGScript("CFBG_BG", { 18 | ALLBATTLEGROUNDHOOK_ON_BATTLEGROUND_BEFORE_ADD_PLAYER, 19 | ALLBATTLEGROUNDHOOK_ON_BATTLEGROUND_ADD_PLAYER, 20 | ALLBATTLEGROUNDHOOK_ON_BATTLEGROUND_END_REWARD, 21 | ALLBATTLEGROUNDHOOK_ON_BATTLEGROUND_REMOVE_PLAYER_AT_LEAVE, 22 | ALLBATTLEGROUNDHOOK_ON_ADD_GROUP, 23 | ALLBATTLEGROUNDHOOK_CAN_FILL_PLAYERS_TO_BG, 24 | ALLBATTLEGROUNDHOOK_IS_CHECK_NORMAL_MATCH, 25 | ALLBATTLEGROUNDHOOK_CAN_SEND_MESSAGE_BG_QUEUE 26 | }) {} 27 | 28 | void OnBattlegroundBeforeAddPlayer(Battleground* bg, Player* player) override 29 | { 30 | sCFBG->ValidatePlayerForBG(bg, player); 31 | } 32 | 33 | void OnBattlegroundAddPlayer(Battleground* bg, Player* player) override 34 | { 35 | sCFBG->FitPlayerInTeam(player, true, bg); 36 | 37 | if (sCFBG->IsEnableResetCooldowns()) 38 | player->RemoveArenaSpellCooldowns(true); 39 | } 40 | 41 | void OnBattlegroundEndReward(Battleground* bg, Player* player, TeamId /*winnerTeamId*/) override 42 | { 43 | if (!sCFBG->IsEnableSystem() || !bg || !player || bg->isArena()) 44 | return; 45 | 46 | if (sCFBG->IsPlayerFake(player)) 47 | sCFBG->ClearFakePlayer(player); 48 | } 49 | 50 | void OnBattlegroundRemovePlayerAtLeave(Battleground* bg, Player* player) override 51 | { 52 | if (!sCFBG->IsEnableSystem() || bg->isArena()) 53 | return; 54 | 55 | sCFBG->FitPlayerInTeam(player, false, bg); 56 | 57 | if (sCFBG->IsPlayerFake(player)) 58 | sCFBG->ClearFakePlayer(player); 59 | } 60 | 61 | void OnAddGroup(BattlegroundQueue* queue, GroupQueueInfo* ginfo, uint32& index, Player* /*leader*/, Group* /*group*/, BattlegroundTypeId /* bgTypeId */, PvPDifficultyEntry const* /* bracketEntry */, 62 | uint8 /* arenaType */, bool /* isRated */, bool /* isPremade */, uint32 /* arenaRating */, uint32 /* matchmakerRating */, uint32 /* arenaTeamId */, uint32 /* opponentsArenaTeamId */) override 63 | { 64 | if (!queue) 65 | return; 66 | 67 | if (sCFBG->IsEnableSystem() && !ginfo->ArenaType && !ginfo->IsRated) 68 | index = BG_QUEUE_CFBG; 69 | 70 | // After rework hook 71 | // sCFBG->OnAddGroupToBGQueue(ginfo, group); 72 | } 73 | 74 | bool CanFillPlayersToBG(BattlegroundQueue* queue, Battleground* bg, BattlegroundBracketId bracket_id) override 75 | { 76 | return !sCFBG->FillPlayersToCFBG(queue, bg, bracket_id); 77 | } 78 | 79 | bool IsCheckNormalMatch(BattlegroundQueue* queue, Battleground* bg, BattlegroundBracketId bracket_id, uint32 minPlayers, uint32 maxPlayers) override 80 | { 81 | if (!sCFBG->IsEnableSystem() || bg->isArena()) 82 | return false; 83 | 84 | return sCFBG->CheckCrossFactionMatch(queue, bracket_id, minPlayers, maxPlayers); 85 | } 86 | 87 | bool CanSendMessageBGQueue(BattlegroundQueue* queue, Player* leader, Battleground* bg, PvPDifficultyEntry const* bracketEntry) override 88 | { 89 | if (bg->isArena() || !sCFBG->IsEnableSystem()) 90 | { 91 | // if it's arena OR the CFBG is disabled, let the core handle the announcement 92 | return true; 93 | } 94 | 95 | // otherwise, let the CFBG module handle the announcement 96 | sCFBG->SendMessageQueue(queue, bg, bracketEntry, leader); 97 | return false; 98 | } 99 | }; 100 | 101 | class CFBG_Player : public PlayerScript 102 | { 103 | public: 104 | CFBG_Player() : PlayerScript("CFBG_Player", { 105 | PLAYERHOOK_ON_LOGIN, 106 | PLAYERHOOK_CAN_JOIN_IN_BATTLEGROUND_QUEUE, 107 | PLAYERHOOK_ON_BEFORE_UPDATE, 108 | PLAYERHOOK_ON_BEFORE_SEND_CHAT_MESSAGE, 109 | PLAYERHOOK_ON_REPUTATION_CHANGE 110 | }) { } 111 | 112 | void OnPlayerLogin(Player* player) override 113 | { 114 | if (!sCFBG->IsEnableSystem()) 115 | return; 116 | 117 | if (player->GetTeamId(true) != player->GetBgTeamId()) 118 | sCFBG->FitPlayerInTeam(player, player->GetBattleground() && !player->GetBattleground()->isArena(), player->GetBattleground()); 119 | } 120 | 121 | bool OnPlayerCanJoinInBattlegroundQueue(Player* player, ObjectGuid /*BattlemasterGuid*/ , BattlegroundTypeId /*BGTypeID*/, uint8 joinAsGroup, GroupJoinBattlegroundResult& err) override 122 | { 123 | if (!sCFBG->IsEnableSystem()) 124 | return true; 125 | 126 | if (joinAsGroup) 127 | { 128 | Group* group = player->GetGroup(); 129 | if (!group) 130 | return true; 131 | 132 | if (group->isRaidGroup() || group->GetMembersCount() > sCFBG->GetMaxPlayersCountInGroup()) 133 | err = ERR_BATTLEGROUND_JOIN_FAILED; 134 | 135 | return false; 136 | } 137 | 138 | return true; 139 | } 140 | 141 | void OnPlayerBeforeUpdate(Player* player, uint32 diff) override 142 | { 143 | if (timeCheck <= diff) 144 | { 145 | sCFBG->UpdateForget(player); 146 | timeCheck = 10000; 147 | } 148 | else 149 | timeCheck -= diff; 150 | } 151 | 152 | void OnPlayerBeforeSendChatMessage(Player* player, uint32& type, uint32& lang, std::string& /*msg*/) override 153 | { 154 | if (!player || !sCFBG->IsEnableSystem()) 155 | return; 156 | 157 | Battleground* bg = player->GetBattleground(); 158 | 159 | if (!bg || bg->isArena()) 160 | return; 161 | 162 | // skip addon lang and universal 163 | if (lang == LANG_UNIVERSAL || lang == LANG_ADDON) 164 | return; 165 | 166 | // skip addon and system message 167 | if (type == CHAT_MSG_ADDON || type == CHAT_MSG_SYSTEM) 168 | return; 169 | 170 | // to gm lang 171 | lang = LANG_UNIVERSAL; 172 | } 173 | 174 | bool OnPlayerReputationChange(Player* player, uint32 factionID, int32& standing, bool /*incremental*/) override 175 | { 176 | uint32 repGain = player->GetReputation(factionID); 177 | TeamId teamId = player->GetTeamId(true); 178 | 179 | if ((factionID == FACTION_FROSTWOLF_CLAN && teamId == TEAM_ALLIANCE) || 180 | (factionID == FACTION_STORMPIKE_GUARD && teamId == TEAM_HORDE)) 181 | { 182 | uint32 diff = standing - repGain; 183 | player->GetReputationMgr().ModifyReputation(sFactionStore.LookupEntry(teamId == TEAM_ALLIANCE ? FACTION_STORMPIKE_GUARD : FACTION_FROSTWOLF_CLAN), diff); 184 | return false; 185 | } 186 | 187 | return true; 188 | } 189 | 190 | private: 191 | uint32 timeCheck = 10000; 192 | }; 193 | 194 | class CFBG_World : public WorldScript 195 | { 196 | public: 197 | CFBG_World() : WorldScript("CFBG_World", { 198 | WORLDHOOK_ON_AFTER_CONFIG_LOAD 199 | }) { } 200 | 201 | void OnAfterConfigLoad(bool /*Reload*/) override 202 | { 203 | sCFBG->LoadConfig(); 204 | } 205 | }; 206 | 207 | void AddSC_CFBG() 208 | { 209 | new CFBG_BG(); 210 | new CFBG_Player(); 211 | new CFBG_World(); 212 | } 213 | -------------------------------------------------------------------------------- /src/cfbg_loader.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (С) since 2019 Andrei Guluaev (Winfidonarleyan/Kargatum) https://github.com/Winfidonarleyan 3 | * Copyright (С) since 2019+ AzerothCore 4 | * Licence MIT https://opensource.org/MIT 5 | */ 6 | 7 | // From SC 8 | void AddSC_CFBG(); 9 | void AddSC_cfbg_commandscript(); 10 | 11 | // Add all 12 | void Addmod_cfbgScripts() 13 | { 14 | AddSC_CFBG(); 15 | AddSC_cfbg_commandscript(); 16 | } 17 | -------------------------------------------------------------------------------- /src/cs_cfbg.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (С) since 2019+ AzerothCore 3 | * Licence MIT https://opensource.org/MIT 4 | */ 5 | 6 | #include "Chat.h" 7 | #include "ObjectMgr.h" 8 | #include "Player.h" 9 | #include "ScriptMgr.h" 10 | #include "CFBG.h" 11 | 12 | using namespace Acore::ChatCommands; 13 | 14 | class cfbg_commandscript : public CommandScript 15 | { 16 | public: 17 | cfbg_commandscript() : CommandScript("cfbg_commandscript") { } 18 | 19 | ChatCommandTable GetCommands() const override 20 | { 21 | static ChatCommandTable cfbgCommands = 22 | { 23 | { "race", HandleCFBGChooseRace, SEC_PLAYER, Console::No }, 24 | }; 25 | 26 | static ChatCommandTable commandTable = 27 | { 28 | { "cfbg", cfbgCommands }, 29 | }; 30 | 31 | return commandTable; 32 | } 33 | 34 | static bool HandleCFBGChooseRace(ChatHandler* handler, std::string raceInput) 35 | { 36 | Player* player = handler->GetPlayer(); 37 | 38 | uint8 raceId = 0; 39 | 40 | if (sCFBG->RandomizeRaces()) 41 | { 42 | handler->SendSysMessage("Race selection is currently disabled."); 43 | handler->SetSentErrorMessage(true); 44 | return true; 45 | } 46 | 47 | for (auto const& raceVariable : *sCFBG->GetRaceInfo()) 48 | { 49 | if (raceInput == raceVariable.RaceName) 50 | { 51 | if (player->GetTeamId(true) == raceVariable.TeamId) 52 | { 53 | raceId = raceVariable.RaceId; 54 | } 55 | else 56 | { 57 | handler->SendSysMessage("Race not available to your faction."); 58 | handler->SetSentErrorMessage(true); 59 | return true; 60 | } 61 | 62 | if (!IsRaceValidForClass(player, raceId)) 63 | { 64 | handler->SendSysMessage("Race not available to your class."); 65 | handler->SetSentErrorMessage(true); 66 | return true; 67 | } 68 | 69 | if (raceId == RACE_NIGHTELF) 70 | { 71 | handler->SendSysMessage("Night elf models are not available as the female model is missing and the male one causes client crashes."); 72 | handler->SetSentErrorMessage(true); 73 | return true; 74 | } 75 | 76 | if (player->getGender() == GENDER_FEMALE && (raceId == RACE_TROLL || raceId == RACE_DWARF)) 77 | { 78 | handler->SendSysMessage("Female models are not available for the following races: troll, dwarf."); 79 | handler->SetSentErrorMessage(true); 80 | return true; 81 | } 82 | } 83 | } 84 | 85 | player->UpdatePlayerSetting("mod-cfbg", SETTING_CFBG_RACE, raceId); 86 | 87 | if (!raceId) 88 | { 89 | handler->SendSysMessage("Race unavailable. CFBG selected race set to random. You will be morphed into a random race when you enter a battleground on the opposite team."); 90 | } 91 | else 92 | { 93 | handler->PSendSysMessage("CFBG selected race set to %s", raceInput); 94 | } 95 | 96 | return true; 97 | } 98 | 99 | static bool IsRaceValidForClass(Player* player, uint8 fakeRace) 100 | { 101 | auto raceData{ *sCFBG->GetRaceData() }; 102 | 103 | std::vector availableRacesForClass = player->GetTeamId(true) == TEAM_HORDE ? 104 | raceData[player->getClass()].availableRacesA : raceData[player->getClass()].availableRacesH; 105 | 106 | for (auto const& races : availableRacesForClass) 107 | { 108 | if (races == fakeRace) 109 | { 110 | return true; 111 | } 112 | } 113 | 114 | return false; 115 | } 116 | }; 117 | 118 | void AddSC_cfbg_commandscript() 119 | { 120 | new cfbg_commandscript(); 121 | } 122 | --------------------------------------------------------------------------------