aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
authorMatan Shukry <matanshukry@gmail.com>2021-12-26 20:14:46 +0200
committerGitHub <noreply@github.com>2021-12-26 19:14:46 +0100
commitb821a729733db0d3742b4aefe05e5a8305724f66 (patch)
tree5950610a9f6e50d906630825457d952c9548f7cd /src/server
parente00b1df36ec021bb80659ab9828e0c1a9b55adcf (diff)
Core/Player: Initial War Mode support (#25926)
* Enable PvP talents * War Mode buff aura * Forced PvP flagging Co-authored-by: Shauren <shauren.trinity@gmail.com>
Diffstat (limited to 'src/server')
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.cpp3
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.h2
-rw-r--r--src/server/game/Entities/Player/Player.cpp109
-rw-r--r--src/server/game/Entities/Player/Player.h7
-rw-r--r--src/server/game/Entities/Unit/Unit.h2
-rw-r--r--src/server/game/Handlers/MiscHandler.cpp33
-rw-r--r--src/server/game/Server/Packets/MiscPackets.cpp5
-rw-r--r--src/server/game/Server/Packets/MiscPackets.h10
-rw-r--r--src/server/game/Server/Packets/SystemPackets.h2
-rw-r--r--src/server/game/Server/Protocol/Opcodes.cpp2
-rw-r--r--src/server/game/Server/WorldSession.h2
-rw-r--r--src/server/game/World/World.cpp80
-rw-r--r--src/server/game/World/World.h16
-rw-r--r--src/server/scripts/Commands/cs_debug.cpp49
-rw-r--r--src/server/scripts/Spells/spell_generic.cpp31
-rw-r--r--src/server/worldserver/worldserver.conf.dist38
16 files changed, 368 insertions, 23 deletions
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp
index 0879d50c074..99c2d8ea45a 100644
--- a/src/server/database/Database/Implementation/CharacterDatabase.cpp
+++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp
@@ -797,6 +797,9 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_DEL_CHARACTER_AURA_STORED_LOCATION, "DELETE FROM character_aura_stored_location WHERE Guid = ? AND Spell = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_INS_CHARACTER_AURA_STORED_LOCATION, "INSERT INTO character_aura_stored_location (Guid, Spell, MapId, PositionX, PositionY, PositionZ, Orientation) "
"VALUES (?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
+
+ // War mode
+ PrepareStatement(CHAR_SEL_WAR_MODE_TUNING, "SELECT race, COUNT(guid) FROM characters WHERE ((playerFlags & 0x00000800) = 0x00000800) AND logout_time >= (UNIX_TIMESTAMP() - 604800) GROUP BY race", CONNECTION_SYNCH);
}
CharacterDatabaseConnection::CharacterDatabaseConnection(MySQLConnectionInfo& connInfo) : MySQLConnection(connInfo)
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h
index e1862ce8eaf..571cc26183c 100644
--- a/src/server/database/Database/Implementation/CharacterDatabase.h
+++ b/src/server/database/Database/Implementation/CharacterDatabase.h
@@ -665,6 +665,8 @@ enum CharacterDatabaseStatements : uint32
CHAR_DEL_CHARACTER_AURA_STORED_LOCATION,
CHAR_INS_CHARACTER_AURA_STORED_LOCATION,
+ CHAR_SEL_WAR_MODE_TUNING,
+
MAX_CHARACTERDATABASE_STATEMENTS
};
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 075fb0fd85d..a77afdfd8af 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -2429,6 +2429,11 @@ void Player::GiveLevel(uint8 level)
sScriptMgr->OnPlayerLevelChanged(this, oldLevel);
}
+bool Player::IsMaxLevel() const
+{
+ return GetLevel() >= m_activePlayerData->MaxLevel;
+}
+
void Player::InitTalentForLevel()
{
uint8 level = GetLevel();
@@ -7279,6 +7284,8 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea)
GetMap()->SendZoneDynamicInfo(newZone, this);
+ UpdateWarModeAuras();
+
UpdateHostileAreaState(zone);
if (zone->Flags[0] & AREA_FLAG_CAPITAL) // Is in a capital city
@@ -7333,15 +7340,15 @@ void Player::UpdateHostileAreaState(AreaTableEntry const* area)
{
if (InBattleground() || area->Flags[0] & AREA_FLAG_COMBAT || (area->PvpCombatWorldStateID != -1 && sWorld->getWorldState(area->PvpCombatWorldStateID) != 0))
pvpInfo.IsInHostileArea = true;
- else if (sWorld->IsPvPRealm() || (area->Flags[0] & AREA_FLAG_UNK3))
+ else if (IsWarModeLocalActive() || (area->Flags[0] & AREA_FLAG_UNK3))
{
if (area->Flags[0] & AREA_FLAG_CONTESTED_AREA)
- pvpInfo.IsInHostileArea = sWorld->IsPvPRealm();
+ pvpInfo.IsInHostileArea = IsWarModeLocalActive();
else
{
FactionTemplateEntry const* factionTemplate = GetFactionTemplateEntry();
if (!factionTemplate || factionTemplate->FriendGroup & area->FactionGroupMask)
- pvpInfo.IsInHostileArea = false;
+ pvpInfo.IsInHostileArea = false; // friend area are considered hostile if war mode is active
else if (factionTemplate->EnemyGroup & area->FactionGroupMask)
pvpInfo.IsInHostileArea = true;
else
@@ -7368,7 +7375,7 @@ void Player::UpdateHostileAreaState(AreaTableEntry const* area)
}
// Treat players having a quest flagging for PvP as always in hostile area
- pvpInfo.IsHostile = pvpInfo.IsInHostileArea || HasPvPForcingQuest();
+ pvpInfo.IsHostile = pvpInfo.IsInHostileArea || HasPvPForcingQuest() || IsWarModeLocalActive();
}
//If players are too far away from the duel flag... they lose the duel
@@ -9214,6 +9221,12 @@ void Player::SendInitWorldStates(uint32 zoneId, uint32 areaId)
packet.Worldstates.emplace_back(2491, 15); // NA_UI_GUARDS_LEFT
}
+ // Horde War Mode bonus
+ packet.Worldstates.emplace_back(17042, 10 + (sWorld->GetWarModeDominantFaction() == TEAM_ALLIANCE ? sWorld->GetWarModeOutnumberedFactionReward() : 0));
+
+ // Alliance War Mode bonus
+ packet.Worldstates.emplace_back(17043, 10 + (sWorld->GetWarModeDominantFaction() == TEAM_HORDE ? sWorld->GetWarModeOutnumberedFactionReward() : 0));
+
switch (zoneId)
{
case 1: // Dun Morogh
@@ -29069,13 +29082,95 @@ void Player::UpdateAverageItemLevelEquipped()
SetAverageItemLevelEquipped(totalItemLevel);
}
+void Player::SetWarModeDesired(bool enabled)
+{
+ // Only allow to toggle on when in stormwind/orgrimmar, and to toggle off in any rested place.
+ // Also disallow when in combat
+ if ((enabled == IsWarModeDesired()) || IsInCombat() || !HasPlayerFlag(PLAYER_FLAGS_RESTING))
+ return;
+
+ if (enabled && !CanEnableWarModeInArea())
+ return;
+
+ // Don't allow to chang when aura SPELL_PVP_RULES_ENABLED is on
+ if (HasAura(SPELL_PVP_RULES_ENABLED))
+ return;
+
+ if (enabled)
+ {
+ AddPlayerFlag(PLAYER_FLAGS_WAR_MODE_DESIRED);
+ TogglePvpTalents(true);
+ SetPvP(true);
+ }
+ else
+ {
+ RemovePlayerFlag(PLAYER_FLAGS_WAR_MODE_DESIRED);
+ TogglePvpTalents(false);
+ SetPvP(false);
+ }
+
+ UpdateWarModeAuras();
+}
+
+void Player::SetWarModeLocal(bool enabled)
+{
+ if (enabled)
+ AddPlayerLocalFlag(PLAYER_LOCAL_FLAG_WAR_MODE);
+ else
+ RemovePlayerLocalFlag(PLAYER_LOCAL_FLAG_WAR_MODE);
+}
+
bool Player::CanEnableWarModeInArea() const
{
- AreaTableEntry const* area = sAreaTableStore.LookupEntry(GetAreaId());
- if (!area || !IsFriendlyArea(area))
+ AreaTableEntry const* zone = sAreaTableStore.LookupEntry(GetZoneId());
+ if (!zone || !IsFriendlyArea(zone))
return false;
- return area->Flags[1] & AREA_FLAG_2_CAN_ENABLE_WAR_MODE;
+ AreaTableEntry const* area = sAreaTableStore.LookupEntry(GetAreaId());
+ if (!area)
+ area = zone;
+
+ do
+ {
+ if (area->Flags[1] & AREA_FLAG_2_CAN_ENABLE_WAR_MODE)
+ return true;
+
+ area = sAreaTableStore.LookupEntry(area->ParentAreaID);
+ } while (area);
+
+ return false;
+}
+
+void Player::UpdateWarModeAuras()
+{
+ uint32 auraInside = 282559;
+ uint32 auraOutside = WARMODE_ENLISTED_SPELL_OUTSIDE;
+
+ if (IsWarModeDesired())
+ {
+ if (CanEnableWarModeInArea())
+ {
+ RemovePlayerFlag(PLAYER_FLAGS_WAR_MODE_ACTIVE);
+ RemoveAurasDueToSpell(auraOutside);
+ CastSpell(this, auraInside, true);
+ }
+ else
+ {
+ AddPlayerFlag(PLAYER_FLAGS_WAR_MODE_ACTIVE);
+ RemoveAurasDueToSpell(auraInside);
+ CastSpell(this, auraOutside, true);
+ }
+ SetWarModeLocal(true);
+ AddPvpFlag(UNIT_BYTE2_FLAG_PVP);
+ }
+ else
+ {
+ SetWarModeLocal(false);
+ RemoveAurasDueToSpell(auraOutside);
+ RemoveAurasDueToSpell(auraInside);
+ RemovePlayerFlag(PLAYER_FLAGS_WAR_MODE_ACTIVE);
+ RemovePvpFlag(UNIT_BYTE2_FLAG_PVP);
+ }
}
std::string Player::GetDebugInfo() const
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index b9c536b04ff..b16d0903e1e 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -1186,6 +1186,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void SetXP(uint32 xp);
void GiveXP(uint32 xp, Unit* victim, float group_rate=1.0f);
void GiveLevel(uint8 level);
+ bool IsMaxLevel() const;
void InitStatsForLevel(bool reapplyMods = false);
@@ -2730,7 +2731,13 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
bool IsInFriendlyArea() const;
bool IsFriendlyArea(AreaTableEntry const* inArea) const;
+ void SetWarModeDesired(bool enabled);
+ bool IsWarModeDesired() const { return HasPlayerFlag(PLAYER_FLAGS_WAR_MODE_DESIRED); }
+ bool IsWarModeActive() const { return HasPlayerFlag(PLAYER_FLAGS_WAR_MODE_ACTIVE); }
+ bool IsWarModeLocalActive() const { return HasPlayerLocalFlag(PLAYER_LOCAL_FLAG_WAR_MODE); }
+ void SetWarModeLocal(bool enabled);
bool CanEnableWarModeInArea() const;
+ void UpdateWarModeAuras();
std::string GetDebugInfo() const override;
diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h
index 668253fe6ca..1de368514a1 100644
--- a/src/server/game/Entities/Unit/Unit.h
+++ b/src/server/game/Entities/Unit/Unit.h
@@ -37,6 +37,8 @@
#define SPELL_DH_DOUBLE_JUMP 196055
#define DISPLAYID_HIDDEN_MOUNT 73200
+#define WARMODE_ENLISTED_SPELL_OUTSIDE 269083
+
#define MAX_SPELL_CHARM 4
#define MAX_SPELL_VEHICLE 6
#define MAX_SPELL_POSSESS 8
diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp
index fe85091db82..9d94b531ac6 100644
--- a/src/server/game/Handlers/MiscHandler.cpp
+++ b/src/server/game/Handlers/MiscHandler.cpp
@@ -313,38 +313,43 @@ void WorldSession::HandleLogoutCancelOpcode(WorldPackets::Character::LogoutCance
void WorldSession::HandleTogglePvP(WorldPackets::Misc::TogglePvP& /*packet*/)
{
- if (GetPlayer()->HasPlayerFlag(PLAYER_FLAGS_IN_PVP))
- {
- GetPlayer()->RemovePlayerFlag(PLAYER_FLAGS_IN_PVP);
- GetPlayer()->AddPlayerFlag(PLAYER_FLAGS_PVP_TIMER);
- if (!GetPlayer()->pvpInfo.IsHostile && GetPlayer()->IsPvP())
- GetPlayer()->pvpInfo.EndTimer = GameTime::GetGameTime() + 300; // start toggle-off
- }
- else
+ if (!GetPlayer()->HasPlayerFlag(PLAYER_FLAGS_IN_PVP))
{
GetPlayer()->AddPlayerFlag(PLAYER_FLAGS_IN_PVP);
GetPlayer()->RemovePlayerFlag(PLAYER_FLAGS_PVP_TIMER);
if (!GetPlayer()->IsPvP() || GetPlayer()->pvpInfo.EndTimer)
GetPlayer()->UpdatePvP(true, true);
}
-}
-
-void WorldSession::HandleSetPvP(WorldPackets::Misc::SetPvP& packet)
-{
- if (!packet.EnablePVP)
+ else if (!GetPlayer()->IsWarModeLocalActive())
{
GetPlayer()->RemovePlayerFlag(PLAYER_FLAGS_IN_PVP);
GetPlayer()->AddPlayerFlag(PLAYER_FLAGS_PVP_TIMER);
if (!GetPlayer()->pvpInfo.IsHostile && GetPlayer()->IsPvP())
GetPlayer()->pvpInfo.EndTimer = GameTime::GetGameTime() + 300; // start toggle-off
}
- else
+}
+
+void WorldSession::HandleSetPvP(WorldPackets::Misc::SetPvP& packet)
+{
+ if (packet.EnablePVP)
{
GetPlayer()->AddPlayerFlag(PLAYER_FLAGS_IN_PVP);
GetPlayer()->RemovePlayerFlag(PLAYER_FLAGS_PVP_TIMER);
if (!GetPlayer()->IsPvP() || GetPlayer()->pvpInfo.EndTimer)
GetPlayer()->UpdatePvP(true, true);
}
+ else if (!GetPlayer()->IsWarModeLocalActive())
+ {
+ GetPlayer()->RemovePlayerFlag(PLAYER_FLAGS_IN_PVP);
+ GetPlayer()->AddPlayerFlag(PLAYER_FLAGS_PVP_TIMER);
+ if (!GetPlayer()->pvpInfo.IsHostile && GetPlayer()->IsPvP())
+ GetPlayer()->pvpInfo.EndTimer = GameTime::GetGameTime() + 300; // start toggle-off
+ }
+}
+
+void WorldSession::HandleSetWarMode(WorldPackets::Misc::SetWarMode& packet)
+{
+ _player->SetWarModeDesired(packet.Enable);
}
void WorldSession::HandlePortGraveyard(WorldPackets::Misc::PortGraveyard& /*packet*/)
diff --git a/src/server/game/Server/Packets/MiscPackets.cpp b/src/server/game/Server/Packets/MiscPackets.cpp
index b568f0108d8..3d31d29c944 100644
--- a/src/server/game/Server/Packets/MiscPackets.cpp
+++ b/src/server/game/Server/Packets/MiscPackets.cpp
@@ -612,6 +612,11 @@ void WorldPackets::Misc::SetPvP::Read()
EnablePVP = _worldPacket.ReadBit();
}
+void WorldPackets::Misc::SetWarMode::Read()
+{
+ Enable = _worldPacket.ReadBit();
+}
+
WorldPacket const* WorldPackets::Misc::AccountHeirloomUpdate::Write()
{
_worldPacket.WriteBit(IsFullUpdate);
diff --git a/src/server/game/Server/Packets/MiscPackets.h b/src/server/game/Server/Packets/MiscPackets.h
index 2e107094c2d..80a38dbbc94 100644
--- a/src/server/game/Server/Packets/MiscPackets.h
+++ b/src/server/game/Server/Packets/MiscPackets.h
@@ -801,6 +801,16 @@ namespace WorldPackets
bool EnablePVP = false;
};
+ class SetWarMode final : public ClientPacket
+ {
+ public:
+ SetWarMode(WorldPacket&& packet) : ClientPacket(CMSG_SET_WAR_MODE, std::move(packet)) { }
+
+ void Read() override;
+
+ bool Enable = false;
+ };
+
class AccountHeirloomUpdate final : public ServerPacket
{
public:
diff --git a/src/server/game/Server/Packets/SystemPackets.h b/src/server/game/Server/Packets/SystemPackets.h
index 65608acea49..6fe675f18eb 100644
--- a/src/server/game/Server/Packets/SystemPackets.h
+++ b/src/server/game/Server/Packets/SystemPackets.h
@@ -135,7 +135,7 @@ namespace WorldPackets
bool KioskModeEnabled = false;
bool CompetitiveModeEnabled = false;
bool TokenBalanceEnabled = false;
- bool WarModeFeatureEnabled = false;
+ bool WarModeFeatureEnabled = true;
bool ClubsEnabled = false;
bool ClubsBattleNetClubTypeAllowed = false;
bool ClubsCharacterClubTypeAllowed = false;
diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp
index ea17f2ffdd7..d9cf29b345c 100644
--- a/src/server/game/Server/Protocol/Opcodes.cpp
+++ b/src/server/game/Server/Protocol/Opcodes.cpp
@@ -824,7 +824,7 @@ void OpcodeTable::Initialize()
DEFINE_HANDLER(CMSG_SET_TRADE_GOLD, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetTradeGoldOpcode);
DEFINE_HANDLER(CMSG_SET_TRADE_ITEM, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetTradeItemOpcode);
DEFINE_HANDLER(CMSG_SET_USING_PARTY_GARRISON, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_SET_WAR_MODE, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
+ DEFINE_HANDLER(CMSG_SET_WAR_MODE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetWarMode);
DEFINE_HANDLER(CMSG_SET_WATCHED_FACTION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSetWatchedFactionOpcode);
DEFINE_HANDLER(CMSG_SHOW_TRADE_SKILL, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_SIGN_PETITION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleSignPetition);
diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h
index 2e34e817284..b80248fcbb8 100644
--- a/src/server/game/Server/WorldSession.h
+++ b/src/server/game/Server/WorldSession.h
@@ -509,6 +509,7 @@ namespace WorldPackets
class OpeningCinematic;
class TogglePvP;
class SetPvP;
+ class SetWarMode;
class MountSpecial;
class SetTaxiBenchmarkMode;
class MountSetFavorite;
@@ -1255,6 +1256,7 @@ class TC_GAME_API WorldSession
void HandleTogglePvP(WorldPackets::Misc::TogglePvP& packet);
void HandleSetPvP(WorldPackets::Misc::SetPvP& packet);
+ void HandleSetWarMode(WorldPackets::Misc::SetWarMode& packet);
void HandleSetSelectionOpcode(WorldPackets::Misc::SetSelection& packet);
void HandleStandStateChangeOpcode(WorldPackets::Misc::StandStateChange& packet);
diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp
index c67ca9b99bd..7d8c91d2398 100644
--- a/src/server/game/World/World.cpp
+++ b/src/server/game/World/World.cpp
@@ -156,6 +156,9 @@ World::World()
_guidAlert = false;
_warnDiff = 0;
_warnShutdownTime = GameTime::GetGameTime();
+
+ _warModeDominantFaction = TEAM_NEUTRAL;
+ _warModeOutnumberedFactionReward = 0;
}
/// World destructor
@@ -1659,6 +1662,12 @@ void World::LoadConfigSettings(bool reload)
// Whether to use LoS from game objects
m_bool_configs[CONFIG_CHECK_GOBJECT_LOS] = sConfigMgr->GetBoolDefault("CheckGameObjectLoS", true);
+ // FactionBalance
+ m_int_configs[CONFIG_FACTION_BALANCE_LEVEL_CHECK_DIFF] = sConfigMgr->GetIntDefault("Pvp.FactionBalance.LevelCheckDiff", 0);
+ m_float_configs[CONFIG_CALL_TO_ARMS_5_PCT] = sConfigMgr->GetFloatDefault("Pvp.FactionBalance.Pct5", 0.6f);
+ m_float_configs[CONFIG_CALL_TO_ARMS_10_PCT] = sConfigMgr->GetFloatDefault("Pvp.FactionBalance.Pct10", 0.7f);
+ m_float_configs[CONFIG_CALL_TO_ARMS_20_PCT] = sConfigMgr->GetFloatDefault("Pvp.FactionBalance.Pct20", 0.8f);
+
// call ScriptMgr if we're reloading the configuration
if (reload)
sScriptMgr->OnConfigLoad(reload);
@@ -2462,6 +2471,17 @@ void World::SetInitialWorldSettings()
TC_METRIC_EVENT("events", "World initialized", "World initialized in " + std::to_string(startupDuration / 60000) + " minutes " + std::to_string((startupDuration % 60000) / 1000) + " seconds");
}
+void World::SetForcedWarModeFactionBalanceState(TeamId team, int32 reward)
+{
+ _warModeDominantFaction = team;
+ _warModeOutnumberedFactionReward = reward;
+}
+
+void World::DisableForcedWarModeFactionBalanceState()
+{
+ UpdateWarModeRewardValues();
+}
+
void World::LoadAutobroadcasts()
{
uint32 oldMSTime = getMSTime();
@@ -3440,6 +3460,9 @@ void World::ResetWeeklyQuests()
// reselect pools
sQuestPoolMgr->ChangeWeeklyQuests();
+ // Update faction balance
+ UpdateWarModeRewardValues();
+
// store next reset time
time_t now = GameTime::GetGameTime();
time_t next = GetNextWeeklyResetTime(now);
@@ -3793,6 +3816,63 @@ void World::RemoveOldCorpses()
m_timers[WUPDATE_CORPSES].SetCurrent(m_timers[WUPDATE_CORPSES].GetInterval());
}
+void World::UpdateWarModeRewardValues()
+{
+ std::array<int64, 2> warModeEnabledFaction = { };
+
+ // Search for characters that have war mode enabled and played during the last week
+ CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_WAR_MODE_TUNING);
+ stmt->setUInt32(0, PLAYER_FLAGS_WAR_MODE_DESIRED);
+ stmt->setUInt32(1, PLAYER_FLAGS_WAR_MODE_DESIRED);
+ if (PreparedQueryResult result = CharacterDatabase.Query(stmt))
+ {
+ do
+ {
+ Field* fields = result->Fetch();
+ uint8 race = fields[0].GetUInt8();
+ if (ChrRacesEntry const* raceEntry = sChrRacesStore.LookupEntry(race))
+ {
+ if (FactionTemplateEntry const* raceFaction = sFactionTemplateStore.AssertEntry(raceEntry->FactionID))
+ {
+ if (raceFaction->FactionGroup & FACTION_MASK_ALLIANCE)
+ warModeEnabledFaction[TEAM_ALLIANCE] += fields[1].GetInt64();
+ else if (raceFaction->FactionGroup & FACTION_MASK_HORDE)
+ warModeEnabledFaction[TEAM_HORDE] += fields[1].GetInt64();
+ }
+ }
+
+ } while (result->NextRow());
+ }
+
+ _warModeDominantFaction = TEAM_NEUTRAL;
+ _warModeOutnumberedFactionReward = 0;
+
+ if (std::all_of(warModeEnabledFaction.begin(), warModeEnabledFaction.end(), [](int64 val) { return val == 0; }))
+ return;
+
+ int64 dominantFactionCount = warModeEnabledFaction[TEAM_ALLIANCE];
+ TeamId dominantFaction = TEAM_ALLIANCE;
+ if (warModeEnabledFaction[TEAM_ALLIANCE] < warModeEnabledFaction[TEAM_HORDE])
+ {
+ dominantFactionCount = warModeEnabledFaction[TEAM_HORDE];
+ dominantFaction = TEAM_HORDE;
+ }
+
+ double total = warModeEnabledFaction[TEAM_ALLIANCE] + warModeEnabledFaction[TEAM_HORDE];
+ double pct = dominantFactionCount / total;
+
+ if (pct >= sWorld->getFloatConfig(CONFIG_CALL_TO_ARMS_20_PCT))
+ _warModeOutnumberedFactionReward = 20;
+ else if (pct >= sWorld->getFloatConfig(CONFIG_CALL_TO_ARMS_10_PCT))
+ _warModeOutnumberedFactionReward = 10;
+ else if (pct >= sWorld->getFloatConfig(CONFIG_CALL_TO_ARMS_5_PCT))
+ _warModeOutnumberedFactionReward = 5;
+ else
+ return;
+
+ _warModeDominantFaction = dominantFaction;
+}
+
Realm realm;
uint32 GetVirtualRealmAddress()
diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h
index 40d6ce3c938..2383b0c6d10 100644
--- a/src/server/game/World/World.h
+++ b/src/server/game/World/World.h
@@ -218,6 +218,9 @@ enum WorldFloatConfigs
CONFIG_ARENA_MATCHMAKER_RATING_MODIFIER,
CONFIG_RESPAWN_DYNAMICRATE_CREATURE,
CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT,
+ CONFIG_CALL_TO_ARMS_5_PCT,
+ CONFIG_CALL_TO_ARMS_10_PCT,
+ CONFIG_CALL_TO_ARMS_20_PCT,
FLOAT_CONFIG_VALUE_COUNT
};
@@ -421,6 +424,7 @@ enum WorldIntConfigs
CONFIG_SOCKET_TIMEOUTTIME_ACTIVE,
CONFIG_BLACKMARKET_MAXAUCTIONS,
CONFIG_BLACKMARKET_UPDATE_PERIOD,
+ CONFIG_FACTION_BALANCE_LEVEL_CHECK_DIFF,
INT_CONFIG_VALUE_COUNT
};
@@ -808,6 +812,12 @@ class TC_GAME_API World
bool IsGuidWarning() { return _guidWarn; }
bool IsGuidAlert() { return _guidAlert; }
+ // War mode balancing
+ TeamId GetWarModeDominantFaction() const { return _warModeDominantFaction; }
+ int32 GetWarModeOutnumberedFactionReward() const { return _warModeOutnumberedFactionReward; }
+ void SetForcedWarModeFactionBalanceState(TeamId team, int32 reward = 0);
+ void DisableForcedWarModeFactionBalanceState();
+
protected:
void _UpdateGameTime();
@@ -931,6 +941,12 @@ class TC_GAME_API World
uint32 _warnDiff;
time_t _warnShutdownTime;
+ // War mode balancing
+ void UpdateWarModeRewardValues();
+
+ TeamId _warModeDominantFaction; // the team that has higher percentage
+ int32 _warModeOutnumberedFactionReward;
+
friend class debug_commandscript;
};
diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp
index 952f5a316c9..6de82fcd0eb 100644
--- a/src/server/scripts/Commands/cs_debug.cpp
+++ b/src/server/scripts/Commands/cs_debug.cpp
@@ -88,6 +88,10 @@ public:
{ "spellfail", rbac::RBAC_PERM_COMMAND_DEBUG_SEND_SPELLFAIL, false, &HandleDebugSendSpellFailCommand, "" },
{ "playerchoice", rbac::RBAC_PERM_COMMAND_DEBUG_SEND_PLAYER_CHOICE, false, &HandleDebugSendPlayerChoiceCommand, "" },
};
+ static std::vector<ChatCommand> debugPvpCommandTable =
+ {
+ { "warmode", rbac::RBAC_PERM_COMMAND_DEBUG, false, &HandleDebugWarModeFactionBalanceCommand, "" },
+ };
static std::vector<ChatCommand> debugAsanCommandTable =
{
{ "memoryleak", rbac::RBAC_PERM_COMMAND_DEBUG_ASAN, true, &HandleDebugMemoryLeak, "" },
@@ -124,6 +128,7 @@ public:
{ "conversation" , rbac::RBAC_PERM_COMMAND_DEBUG_CONVERSATION, false, &HandleDebugConversationCommand, "" },
{ "worldstate" , rbac::RBAC_PERM_COMMAND_DEBUG, false, &HandleDebugWorldStateCommand, "" },
{ "wsexpression" , rbac::RBAC_PERM_COMMAND_DEBUG, false, &HandleDebugWSExpressionCommand, "" },
+ { "pvp", rbac::RBAC_PERM_COMMAND_DEBUG, false, nullptr, "", debugPvpCommandTable },
{ "dummy", rbac::RBAC_PERM_COMMAND_DEBUG_DUMMY, false, &HandleDebugDummyCommand, "" },
{ "asan", rbac::RBAC_PERM_COMMAND_DEBUG_ASAN, true, nullptr, "", debugAsanCommandTable },
{ "guidlimits", rbac::RBAC_PERM_COMMAND_DEBUG, true, &HandleDebugGuidLimitsCommand, "" },
@@ -137,6 +142,50 @@ public:
return commandTable;
}
+ static bool TryExtractTeamId(std::string const &args, TeamId &outFaction)
+ {
+ if ("a" == args || "alliance" == args)
+ outFaction = TEAM_ALLIANCE;
+ else if ("h" == args || "horde" == args)
+ outFaction = TEAM_HORDE;
+ else if ("n" == args || "neutral" == args)
+ outFaction = TEAM_NEUTRAL;
+ else
+ return false;
+
+ return true;
+ }
+
+ static bool HandleDebugWarModeFactionBalanceCommand(ChatHandler* handler, Variant<uint32, ExactSequence<'a','l','l','i','a','n','c','e'>, ExactSequence<'h','o','r','d','e'>, ExactSequence<'n','e','u','t','r','a','l'>, ExactSequence<'o','f','f'>> command, Optional<int32> rewardValue)
+ {
+ // USAGE: .debug pvp fb <alliance|horde|neutral|off> [pct]
+ // neutral Sets faction balance off.
+ // alliance Set faction balance to alliance.
+ // horde Set faction balance to horde.
+ // off Reset the faction balance and use the calculated value of it
+ switch (command.which())
+ {
+ case 0: // workaround for Variant of only ExactSequences not being supported
+ handler->SendSysMessage(LANG_BAD_VALUE);
+ handler->SetSentErrorMessage(true);
+ return false;
+ case 1:
+ sWorld->SetForcedWarModeFactionBalanceState(TEAM_ALLIANCE, rewardValue.get_value_or(0));
+ break;
+ case 2:
+ sWorld->SetForcedWarModeFactionBalanceState(TEAM_HORDE, rewardValue.get_value_or(0));
+ break;
+ case 3:
+ sWorld->SetForcedWarModeFactionBalanceState(TEAM_NEUTRAL);
+ break;
+ case 4:
+ sWorld->DisableForcedWarModeFactionBalanceState();
+ break;
+ }
+
+ return true;
+ }
+
static bool HandleDebugPlayCinematicCommand(ChatHandler* handler, char const* args)
{
// USAGE: .debug play cinematic #cinematicId
diff --git a/src/server/scripts/Spells/spell_generic.cpp b/src/server/scripts/Spells/spell_generic.cpp
index b0d1593e4f3..212717fbd14 100644
--- a/src/server/scripts/Spells/spell_generic.cpp
+++ b/src/server/scripts/Spells/spell_generic.cpp
@@ -44,6 +44,7 @@
#include "SpellPackets.h"
#include "SpellScript.h"
#include "Vehicle.h"
+#include "World.h"
class spell_gen_absorb0_hitlimit1 : public AuraScript
{
@@ -4497,6 +4498,35 @@ class spell_gen_impatient_mind : public AuraScript
}
};
+// 269083 - Enlisted
+// 282559 - Enlisted
+class spell_gen_war_mode_enlisted : public AuraScript
+{
+ PrepareAuraScript(spell_gen_war_mode_enlisted);
+
+ void CalcWarModeBonus(AuraEffect const* /*aurEff*/, int32& amount, bool& /*canBeRecalculated*/)
+ {
+ Player* target = GetUnitOwner()->ToPlayer();
+ if (!target)
+ return;
+
+ if (target->GetTeamId() == sWorld->GetWarModeDominantFaction())
+ return;
+
+ amount += sWorld->GetWarModeOutnumberedFactionReward();
+ }
+
+ void Register() override
+ {
+ DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_gen_war_mode_enlisted::CalcWarModeBonus, EFFECT_ALL, SPELL_AURA_MOD_XP_PCT);
+ DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_gen_war_mode_enlisted::CalcWarModeBonus, EFFECT_ALL, SPELL_AURA_MOD_XP_QUEST_PCT);
+ DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_gen_war_mode_enlisted::CalcWarModeBonus, EFFECT_ALL, SPELL_AURA_MOD_CURRENCY_GAIN_FROM_SOURCE);
+ DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_gen_war_mode_enlisted::CalcWarModeBonus, EFFECT_ALL, SPELL_AURA_MOD_MONEY_GAIN);
+ DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_gen_war_mode_enlisted::CalcWarModeBonus, EFFECT_ALL, SPELL_AURA_MOD_ANIMA_GAIN);
+ DoEffectCalcAmount += AuraEffectCalcAmountFn(spell_gen_war_mode_enlisted::CalcWarModeBonus, EFFECT_ALL, SPELL_AURA_DUMMY);
+ }
+};
+
enum DefenderOfAzerothData
{
SPELL_DEATH_GATE_TELEPORT_STORMWIND = 316999,
@@ -4749,6 +4779,7 @@ void AddSC_generic_spell_scripts()
RegisterSpellScript(spell_gen_azgalor_rain_of_fire_hellfire_citadel);
RegisterAuraScript(spell_gen_face_rage);
RegisterAuraScript(spell_gen_impatient_mind);
+ RegisterAuraScript(spell_gen_war_mode_enlisted);
RegisterSpellScript(spell_defender_of_azeroth_death_gate_selector);
RegisterSpellScript(spell_defender_of_azeroth_speak_with_mograine);
RegisterSpellScript(spell_summon_battle_pet);
diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist
index ed70b69d01d..505c5cc1306 100644
--- a/src/server/worldserver/worldserver.conf.dist
+++ b/src/server/worldserver/worldserver.conf.dist
@@ -40,6 +40,7 @@
# CURRENCIES SETTINGS
# PACKET SPOOF PROTECTION SETTINGS
# METRIC SETTINGS
+# PVP SETTINGS
#
###################################################################################################
@@ -4286,3 +4287,40 @@ Metric.OverallStatusInterval = 1
#
###################################################################################################
+
+###################################################################################################
+# PVP SETTINGS
+#
+#
+# Pvp.FactionBalance.LevelCheckDiff
+# Description: The amount of levels below the maximum level that accounts people for faction balance.
+# That is, if max level is 60 and this has a value of 5, all players that are level 55 and above (including) will be included in the check
+# Default: 0
+
+Pvp.FactionBalance.LevelCheckDiff = 0
+
+# Pvp.FactionBalance.Pct5
+# Description: The percentage at which a faction will receive an extra % percentage on their enlisted buff. Value range: [0...1]
+# That is, if value is 0.6, and alliance outnumber horde by 60%, horde will get 15% bonus (5% + base 10%)
+# Default: 0.6
+
+Pvp.FactionBalance.Pct5 = 0.6
+
+# Pvp.FactionBalance.Pct10
+# Description: The percentage at which a faction will receive an extra % percentage on their enlisted buff. Value range: [0...1]
+# Does not stack with previous pct. Pct5 will not be checked if faction percentage is higher than this value.
+# That is, if value is 0.7, and alliance outnumber horde by 70%, horde will get 20% bonus (10% + base 10%)
+# Default: 0.7
+
+Pvp.FactionBalance.Pct10 = 0.7
+
+# Pvp.FactionBalance.Pct20
+# Description: The percentage at which a faction will receive an extra % percentage on their enlisted buff. Value range: [0...1]
+# Does not stack with previous pct. Pct10 will not be checked if faction percentage is higher than this value.
+# That is, if value is 0.8, and alliance outnumber horde by 80%, horde will get 30% bonus (20% + base 10%)
+# Default: 0.8
+
+Pvp.FactionBalance.Pct20 = 0.8
+
+#
+###################################################################################################