feat(Core/Arena): Add support for arena seasons completion with progression in runtime. (#19858)

Co-authored-by: Winfidonarleyan <dowlandtop@yandex.com>
This commit is contained in:
Anton Popovichenko
2025-02-12 11:09:31 +01:00
committed by GitHub
parent 24dd7dfc21
commit f6a0433297
22 changed files with 1250 additions and 59 deletions

View File

@@ -3789,21 +3789,6 @@ Arena.QueueAnnouncer.PlayerOnly = 0
Arena.QueueAnnouncer.Detail = 3
#
# Arena.ArenaSeason.ID
# Description: Current arena season id shown in clients.
# Default: 8
Arena.ArenaSeason.ID = 8
#
# Arena.ArenaSeason.InProgress
# Description: State of current arena season.
# Default: 1 - (Active)
# 0 - (Finished)
Arena.ArenaSeason.InProgress = 1
#
# Arena.ArenaStartRating
# Description: Start rating for new arena teams.

View File

@@ -0,0 +1,224 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ArenaSeasonMgr.h"
#include "ArenaTeamMgr.h"
#include "ArenaSeasonRewardsDistributor.h"
#include "BattlegroundMgr.h"
#include "GameEventMgr.h"
#include "MapMgr.h"
#include "Player.h"
ArenaSeasonMgr* ArenaSeasonMgr::instance()
{
static ArenaSeasonMgr instance;
return &instance;
}
void ArenaSeasonMgr::LoadRewards()
{
uint32 oldMSTime = getMSTime();
std::unordered_map<std::string, ArenaSeasonRewardGroupCriteriaType> stringToArenaSeasonRewardGroupCriteriaType = {
{"pct", ArenaSeasonRewardGroupCriteriaType::ARENA_SEASON_REWARD_CRITERIA_TYPE_PERCENT_VALUE},
{"abs", ArenaSeasonRewardGroupCriteriaType::ARENA_SEASON_REWARD_CRITERIA_TYPE_ABSOLUTE_VALUE}
};
QueryResult result = WorldDatabase.Query("SELECT id, arena_season, criteria_type, min_criteria, max_criteria, reward_mail_template_id, reward_mail_subject, reward_mail_body, gold_reward FROM arena_season_reward_group");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 arena season rewards. DB table `arena_season_reward_group` is empty.");
LOG_INFO("server.loading", " ");
return;
}
std::unordered_map<uint32, ArenaSeasonRewardGroup> groupsMap;
do
{
Field* fields = result->Fetch();
uint32 id = fields[0].Get<uint32>();
ArenaSeasonRewardGroup group;
group.season = fields[1].Get<uint8>();
group.criteriaType = stringToArenaSeasonRewardGroupCriteriaType[fields[2].Get<std::string>()];
group.minCriteria = fields[3].Get<float>();
group.maxCriteria = fields[4].Get<float>();
group.rewardMailTemplateID = fields[5].Get<uint32>();
group.rewardMailSubject = fields[6].Get<std::string>();
group.rewardMailBody = fields[7].Get<std::string>();
group.goldReward = fields[8].Get<uint32>();
groupsMap[id] = group;
} while (result->NextRow());
std::unordered_map<std::string, ArenaSeasonRewardType> stringToArenaSeasonRewardType = {
{"achievement", ArenaSeasonRewardType::ARENA_SEASON_REWARD_TYPE_ACHIEVEMENT},
{"item", ArenaSeasonRewardType::ARENA_SEASON_REWARD_TYPE_ITEM}
};
result = WorldDatabase.Query("SELECT group_id, type, entry FROM arena_season_reward");
if (!result)
{
LOG_WARN("server.loading", ">> Loaded 0 arena season rewards. DB table `arena_season_reward` is empty.");
LOG_INFO("server.loading", " ");
return;
}
do
{
Field* fields = result->Fetch();
uint32 groupId = fields[0].Get<uint32>();
ArenaSeasonReward reward;
reward.type = stringToArenaSeasonRewardType[fields[1].Get<std::string>()];
reward.entry = fields[2].Get<uint32>();
auto itr = groupsMap.find(groupId);
ASSERT(itr != groupsMap.end(), "Unknown arena_season_reward_group ({}) in arena_season_reward", groupId);
(reward.type == ARENA_SEASON_REWARD_TYPE_ITEM) ?
groupsMap[groupId].itemRewards.push_back(reward) :
groupsMap[groupId].achievementRewards.push_back(reward);
} while (result->NextRow());
for (auto const& itr : groupsMap)
_arenaSeasonRewardGroupsStore[itr.second.season].push_back(itr.second);
LOG_INFO("server.loading", ">> Loaded {} arena season rewards in {} ms", (uint32)groupsMap.size(), GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void ArenaSeasonMgr::LoadActiveSeason()
{
QueryResult result = CharacterDatabase.Query("SELECT season_id, season_state FROM active_arena_season");
ASSERT(result, "active_arena_season can't be empty");
Field* fields = result->Fetch();
_currentSeason = fields[0].Get<uint8>();
_currentSeasonState = static_cast<ArenaSeasonState>(fields[1].Get<uint8>());
uint16 eventID = GameEventForArenaSeason(_currentSeason);
sGameEventMgr->StartEvent(eventID, true);
LOG_INFO("server.loading", "Arena Season {} loaded...", _currentSeason);
LOG_INFO("server.loading", " ");
}
void ArenaSeasonMgr::RewardTeamsForTheSeason(std::shared_ptr<ArenaTeamFilter> teamsFilter)
{
ArenaSeasonTeamRewarderImpl rewarder = ArenaSeasonTeamRewarderImpl();
ArenaSeasonRewardDistributor distributor = ArenaSeasonRewardDistributor(&rewarder);
std::vector<ArenaSeasonRewardGroup> rewards = _arenaSeasonRewardGroupsStore[GetCurrentSeason()];
ArenaTeamMgr::ArenaTeamContainer filteredTeams = teamsFilter->Filter(sArenaTeamMgr->GetArenaTeams());
distributor.DistributeRewards(filteredTeams, rewards);
}
bool ArenaSeasonMgr::CanDeleteArenaTeams()
{
std::vector<ArenaSeasonRewardGroup> rewards = _arenaSeasonRewardGroupsStore[GetCurrentSeason()];
if (rewards.empty())
return false;
for (auto const& bg : sBattlegroundMgr->GetActiveBattlegrounds())
if (bg->isRated())
return false;
return true;
}
void ArenaSeasonMgr::DeleteArenaTeams()
{
if (!CanDeleteArenaTeams())
return;
// Cleanup queue first.
std::vector<BattlegroundQueueTypeId> arenasQueueTypes = {BATTLEGROUND_QUEUE_2v2, BATTLEGROUND_QUEUE_3v3, BATTLEGROUND_QUEUE_5v5};
for (BattlegroundQueueTypeId queueType : arenasQueueTypes)
{
auto queue = sBattlegroundMgr->GetBattlegroundQueue(queueType);
for (auto const& [playerGUID, other] : queue.m_QueuedPlayers)
queue.RemovePlayer(playerGUID, true);
}
sArenaTeamMgr->DeleteAllArenaTeams();
}
void ArenaSeasonMgr::ChangeCurrentSeason(uint8 season)
{
if (_currentSeason == season)
return;
uint16 currentEventID = GameEventForArenaSeason(_currentSeason);
sGameEventMgr->StopEvent(currentEventID, true);
uint16 newEventID = GameEventForArenaSeason(season);
sGameEventMgr->StartEvent(newEventID, true);
_currentSeason = season;
_currentSeasonState = ARENA_SEASON_STATE_IN_PROGRESS;
CharacterDatabase.Execute("UPDATE active_arena_season SET season_id = {}, season_state = {}", _currentSeason, _currentSeasonState);
BroadcastUpdatedWorldState();
}
void ArenaSeasonMgr::SetSeasonState(ArenaSeasonState state)
{
if (_currentSeasonState == state)
return;
_currentSeasonState = state;
CharacterDatabase.Execute("UPDATE active_arena_season SET season_state = {}", _currentSeasonState);
BroadcastUpdatedWorldState();
}
uint16 ArenaSeasonMgr::GameEventForArenaSeason(uint8 season)
{
QueryResult result = WorldDatabase.Query("SELECT eventEntry FROM game_event_arena_seasons WHERE season = '{}'", season);
if (!result)
{
LOG_ERROR("arenaseasonmgr", "ArenaSeason ({}) must be an existant Arena Season", season);
return 0;
}
Field* fields = result->Fetch();
return fields[0].Get<uint16>();
}
void ArenaSeasonMgr::BroadcastUpdatedWorldState()
{
sMapMgr->DoForAllMaps([](Map* map)
{
// Ignore instanceable maps, players will get a fresh state once they change the map.
if (map->Instanceable())
return;
map->DoForAllPlayers([&](Player* player)
{
uint32 currZone, currArea;
player->GetZoneAndAreaId(currZone, currArea);
player->SendInitWorldStates(currZone, currArea);
});
});
}

View File

@@ -0,0 +1,126 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _ARENASEASONMGR_H
#define _ARENASEASONMGR_H
#include "Common.h"
#include "ArenaTeamFilter.h"
#include <vector>
#include <unordered_map>
enum ArenaSeasonState
{
ARENA_SEASON_STATE_DISABLED = 0,
ARENA_SEASON_STATE_IN_PROGRESS = 1
};
enum ArenaSeasonRewardType
{
ARENA_SEASON_REWARD_TYPE_ITEM,
ARENA_SEASON_REWARD_TYPE_ACHIEVEMENT
};
enum ArenaSeasonRewardGroupCriteriaType
{
ARENA_SEASON_REWARD_CRITERIA_TYPE_PERCENT_VALUE,
ARENA_SEASON_REWARD_CRITERIA_TYPE_ABSOLUTE_VALUE
};
// ArenaSeasonReward represents one reward, it can be an item or achievement.
struct ArenaSeasonReward
{
ArenaSeasonReward() = default;
// Item or acheivement entry.
uint32 entry{};
ArenaSeasonRewardType type{ARENA_SEASON_REWARD_TYPE_ITEM};
// Used in unit tests.
bool operator==(const ArenaSeasonReward& other) const
{
return entry == other.entry && type == other.type;
}
};
struct ArenaSeasonRewardGroup
{
ArenaSeasonRewardGroup() = default;
uint8 season{};
ArenaSeasonRewardGroupCriteriaType criteriaType;
float minCriteria{};
float maxCriteria{};
uint32 rewardMailTemplateID{};
std::string rewardMailSubject{};
std::string rewardMailBody{};
uint32 goldReward{};
std::vector<ArenaSeasonReward> itemRewards;
std::vector<ArenaSeasonReward> achievementRewards;
// Used in unit tests.
bool operator==(const ArenaSeasonRewardGroup& other) const
{
return minCriteria == other.minCriteria &&
maxCriteria == other.maxCriteria &&
criteriaType == other.criteriaType &&
itemRewards == other.itemRewards &&
achievementRewards == other.achievementRewards;
}
};
class ArenaSeasonMgr
{
public:
static ArenaSeasonMgr* instance();
using ArenaSeasonRewardGroupsBySeasonContainer = std::unordered_map<uint8, std::vector<ArenaSeasonRewardGroup>>;
// Loading functions
void LoadRewards();
void LoadActiveSeason();
// Season management functions
void ChangeCurrentSeason(uint8 season);
uint8 GetCurrentSeason() { return _currentSeason; }
void SetSeasonState(ArenaSeasonState state);
ArenaSeasonState GetSeasonState() { return _currentSeasonState; }
// Season completion functions
void RewardTeamsForTheSeason(std::shared_ptr<ArenaTeamFilter> teamsFilter);
bool CanDeleteArenaTeams();
void DeleteArenaTeams();
private:
uint16 GameEventForArenaSeason(uint8 season);
void BroadcastUpdatedWorldState();
ArenaSeasonRewardGroupsBySeasonContainer _arenaSeasonRewardGroupsStore;
uint8 _currentSeason{};
ArenaSeasonState _currentSeasonState{};
};
#define sArenaSeasonMgr ArenaSeasonMgr::instance()
#endif // _ARENASEASONMGR_H

View File

@@ -0,0 +1,166 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ArenaSeasonRewardsDistributor.h"
#include "AchievementMgr.h"
#include "CharacterDatabase.h"
#include "Mail.h"
#include "Player.h"
#include <algorithm>
constexpr float minPctTeamGamesForMemberToGetReward = 30;
void ArenaSeasonTeamRewarderImpl::RewardTeamWithRewardGroup(ArenaTeam *arenaTeam, const ArenaSeasonRewardGroup &rewardGroup)
{
RewardWithMail(arenaTeam, rewardGroup);
RewardWithAchievements(arenaTeam, rewardGroup);
}
void ArenaSeasonTeamRewarderImpl::RewardWithMail(ArenaTeam* arenaTeam, ArenaSeasonRewardGroup const & rewardGroup)
{
if (rewardGroup.itemRewards.empty() && rewardGroup.goldReward == 0)
return;
const uint32 npcKingDondSender = 18897;
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
for (auto const& member : arenaTeam->GetMembers())
{
uint32 teamSeasonGames = arenaTeam->GetStats().SeasonGames;
// Avoid division by zero.
if (teamSeasonGames == 0)
continue;
float memberParticipationPercentage = (static_cast<float>(member.SeasonGames) / teamSeasonGames) * 100;
if (memberParticipationPercentage < minPctTeamGamesForMemberToGetReward)
continue;
Player* player = ObjectAccessor::FindPlayer(member.Guid);
auto draft = rewardGroup.rewardMailTemplateID > 0 ?
MailDraft(rewardGroup.rewardMailTemplateID, false) :
MailDraft(rewardGroup.rewardMailSubject, rewardGroup.rewardMailBody);
if (rewardGroup.goldReward > 0)
draft.AddMoney(rewardGroup.goldReward);
for (auto const& reward : rewardGroup.itemRewards)
if (Item* item = Item::CreateItem(reward.entry, 1))
{
item->SaveToDB(trans);
draft.AddItem(item);
}
draft.SendMailTo(trans, MailReceiver(player, member.Guid.GetCounter()), MailSender(npcKingDondSender));
}
CharacterDatabase.CommitTransaction(trans);
}
void ArenaSeasonTeamRewarderImpl::RewardWithAchievements(ArenaTeam* arenaTeam, ArenaSeasonRewardGroup const & rewardGroup)
{
if (rewardGroup.achievementRewards.empty())
return;
for (auto const& member : arenaTeam->GetMembers())
{
uint32 teamSeasonGames = arenaTeam->GetStats().SeasonGames;
// Avoid division by zero.
if (teamSeasonGames == 0)
continue;
float memberParticipationPercentage = (static_cast<float>(member.SeasonGames) / teamSeasonGames) * 100;
if (memberParticipationPercentage < minPctTeamGamesForMemberToGetReward)
continue;
Player* player = ObjectAccessor::FindPlayer(member.Guid);
for (auto const& reward : rewardGroup.achievementRewards)
{
AchievementEntry const* achievement = sAchievementStore.LookupEntry(reward.entry);
if (!achievement)
continue;
if (player)
player->CompletedAchievement(achievement);
else
sAchievementMgr->CompletedAchievementForOfflinePlayer(member.Guid.GetCounter(), achievement);
}
}
}
ArenaSeasonRewardDistributor::ArenaSeasonRewardDistributor(ArenaSeasonTeamRewarder* rewarder)
: _rewarder(rewarder)
{
}
void ArenaSeasonRewardDistributor::DistributeRewards(ArenaTeamMgr::ArenaTeamContainer &arenaTeams, std::vector<ArenaSeasonRewardGroup> &rewardGroups)
{
std::vector<ArenaTeam*> sortedTeams;
sortedTeams.reserve(arenaTeams.size());
static constexpr uint16 minRequiredGames = 30;
for (auto const& [id, team] : arenaTeams)
if (team->GetStats().SeasonGames >= minRequiredGames)
sortedTeams.push_back(team);
std::sort(sortedTeams.begin(), sortedTeams.end(), [](ArenaTeam* a, ArenaTeam* b) {
return a->GetRating() > b->GetRating();
});
std::vector<ArenaSeasonRewardGroup> pctRewardGroup;
std::vector<ArenaSeasonRewardGroup> absRewardGroup;
for (auto const& reward : rewardGroups)
{
if (reward.criteriaType == ARENA_SEASON_REWARD_CRITERIA_TYPE_PERCENT_VALUE)
pctRewardGroup.push_back(reward);
else
absRewardGroup.push_back(reward);
}
size_t totalTeams = sortedTeams.size();
for (auto const& rewardGroup : pctRewardGroup)
{
size_t minIndex = static_cast<size_t>(rewardGroup.minCriteria * totalTeams / 100);
size_t maxIndex = static_cast<size_t>(rewardGroup.maxCriteria * totalTeams / 100);
minIndex = std::min(minIndex, totalTeams);
maxIndex = std::min(maxIndex, totalTeams);
for (size_t i = minIndex; i < maxIndex; ++i)
{
ArenaTeam* team = sortedTeams[i];
_rewarder->RewardTeamWithRewardGroup(team, rewardGroup);
}
}
for (auto const& rewardGroup : absRewardGroup)
{
size_t minIndex = rewardGroup.minCriteria-1; // Top 1 team is the team with index 0, so we need make -1.
size_t maxIndex = rewardGroup.maxCriteria;
minIndex = std::max(minIndex, size_t(0));
minIndex = std::min(minIndex, totalTeams);
maxIndex = std::min(maxIndex, totalTeams);
for (size_t i = minIndex; i < maxIndex; ++i)
{
ArenaTeam* team = sortedTeams[i];
_rewarder->RewardTeamWithRewardGroup(team, rewardGroup);
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _ARENASEASONREWARDDISTRIBUTOR_H
#define _ARENASEASONREWARDDISTRIBUTOR_H
#include "ArenaSeasonMgr.h"
#include "ArenaTeam.h"
#include "ArenaTeamMgr.h"
class ArenaSeasonTeamRewarder
{
public:
virtual ~ArenaSeasonTeamRewarder() = default;
virtual void RewardTeamWithRewardGroup(ArenaTeam* arenaTeam, ArenaSeasonRewardGroup const & rewardGroup) = 0;
};
class ArenaSeasonTeamRewarderImpl: public ArenaSeasonTeamRewarder
{
public:
void RewardTeamWithRewardGroup(ArenaTeam* arenaTeam, ArenaSeasonRewardGroup const & rewardGroup) override;
private:
void RewardWithMail(ArenaTeam* arenaTeam, ArenaSeasonRewardGroup const & rewardGroup);
void RewardWithAchievements(ArenaTeam* arenaTeam, ArenaSeasonRewardGroup const & rewardGroup);
};
class ArenaSeasonRewardDistributor
{
public:
ArenaSeasonRewardDistributor(ArenaSeasonTeamRewarder* rewarder);
void DistributeRewards(ArenaTeamMgr::ArenaTeamContainer& arenaTeams, std::vector<ArenaSeasonRewardGroup>& rewardGroups);
private:
ArenaSeasonTeamRewarder* _rewarder;
};
#endif // _ARENASEASONREWARDDISTRIBUTOR_H

View File

@@ -0,0 +1,113 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _ARENATEAMFILTER_H
#define _ARENATEAMFILTER_H
#include "Common.h"
#include "ArenaTeamMgr.h"
#include "ArenaTeam.h"
#include "Tokenize.h"
#include "StringConvert.h"
#include <sstream>
#include <string>
#include <memory>
#include <unordered_map>
#include <algorithm>
#include <cctype>
class ArenaTeamFilter
{
public:
virtual ~ArenaTeamFilter() = default;
virtual ArenaTeamMgr::ArenaTeamContainer Filter(ArenaTeamMgr::ArenaTeamContainer teams) = 0;
};
class ArenaTeamFilterByTypes : public ArenaTeamFilter
{
public:
ArenaTeamFilterByTypes(std::vector<uint8> validTypes) : _validTypes(validTypes) {}
ArenaTeamMgr::ArenaTeamContainer Filter(ArenaTeamMgr::ArenaTeamContainer teams) override
{
ArenaTeamMgr::ArenaTeamContainer result;
for (auto const& pair : teams)
{
ArenaTeam* team = pair.second;
for (uint8 arenaType : _validTypes)
{
if (team->GetType() == arenaType)
{
result[pair.first] = team;
break;
}
}
}
return result;
}
private:
std::vector<uint8> _validTypes;
};
class ArenaTeamFilterAllTeams : public ArenaTeamFilter
{
public:
ArenaTeamMgr::ArenaTeamContainer Filter(ArenaTeamMgr::ArenaTeamContainer teams) override
{
return teams;
}
};
class ArenaTeamFilterFactoryByUserInput
{
public:
std::unique_ptr<ArenaTeamFilter> CreateFilterByUserInput(std::string userInput)
{
std::transform(userInput.begin(), userInput.end(), userInput.begin(),
[](unsigned char c) { return std::tolower(c); });
if (userInput == "all")
return std::make_unique<ArenaTeamFilterAllTeams>();
// Parse the input string (e.g., "2,3") into valid types
std::vector<uint8> validTypes = ParseTypes(userInput);
if (!validTypes.empty())
return std::make_unique<ArenaTeamFilterByTypes>(validTypes);
return nullptr;
}
private:
std::vector<uint8> ParseTypes(std::string_view userInput)
{
std::vector<uint8> validTypes;
auto tokens = Acore::Tokenize(userInput, ',', false);
for (auto const& token : tokens)
if (auto typeOpt = Acore::StringTo<uint8>(token))
validTypes.push_back(*typeOpt);
return validTypes;
}
};
#endif // _ARENATEAMFILTER_H

View File

@@ -17,6 +17,7 @@
#include "ArenaTeam.h"
#include "ArenaTeamMgr.h"
#include "ArenaSeasonMgr.h"
#include "BattlegroundMgr.h"
#include "CharacterCache.h"
#include "Group.h"
@@ -658,7 +659,7 @@ uint32 ArenaTeam::GetPoints(uint32 memberRating)
if (rating <= 1500)
{
if (sWorld->getIntConfig(CONFIG_ARENA_SEASON_ID) < 6 && !sWorld->getIntConfig(CONFIG_LEGACY_ARENA_POINTS_CALC))
if (sArenaSeasonMgr->GetCurrentSeason() < 6 && !sWorld->getIntConfig(CONFIG_LEGACY_ARENA_POINTS_CALC))
points = (float)rating * 0.22f + 14.0f;
else
points = 344;

View File

@@ -125,6 +125,27 @@ void ArenaTeamMgr::RemoveArenaTeam(uint32 arenaTeamId)
ArenaTeamStore.erase(arenaTeamId);
}
void ArenaTeamMgr::DeleteAllArenaTeams()
{
for (auto const& [id, team] : ArenaTeamStore)
{
while (team->GetMembersSize() > 0)
team->DelMember(team->GetMembers().front().Guid, false);
delete team;
}
ArenaTeamStore.clear();
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
trans->Append("DELETE FROM arena_team_member");
trans->Append("DELETE FROM arena_team");
trans->Append("DELETE FROM character_arena_stats");
CharacterDatabase.CommitTransaction(trans);
NextArenaTeamId = 1;
}
uint32 ArenaTeamMgr::GenerateArenaTeamId()
{
if (NextArenaTeamId >= MAX_ARENA_TEAM_ID)

View File

@@ -43,6 +43,8 @@ public:
void AddArenaTeam(ArenaTeam* arenaTeam);
void RemoveArenaTeam(uint32 Id);
void DeleteAllArenaTeams();
ArenaTeamContainer::iterator GetArenaTeamMapBegin() { return ArenaTeamStore.begin(); }
ArenaTeamContainer::iterator GetArenaTeamMapEnd() { return ArenaTeamStore.end(); }
ArenaTeamContainer& GetArenaTeams() { return ArenaTeamStore; }

View File

@@ -334,6 +334,18 @@ Battleground* BattlegroundMgr::GetBattlegroundTemplate(BattlegroundTypeId bgType
return bgs.empty() ? nullptr : bgs.begin()->second;
}
std::vector<Battleground const*> BattlegroundMgr::GetActiveBattlegrounds()
{
std::vector<Battleground const*> result;
for (auto const& [bgType, bgData] : bgDataStore)
for (auto const& [id, bg] : bgData._Battlegrounds)
if (bg->GetStatus() == STATUS_WAIT_JOIN || bg->GetStatus() == STATUS_IN_PROGRESS)
result.push_back(static_cast<const Battleground*>(bg));
return result;
}
uint32 BattlegroundMgr::CreateClientVisibleInstanceId(BattlegroundTypeId bgTypeId, BattlegroundBracketId bracket_id)
{
if (IsArenaType(bgTypeId))

View File

@@ -82,6 +82,7 @@ public:
Battleground* GetBattleground(uint32 instanceID, BattlegroundTypeId bgTypeId);
Battleground* GetBattlegroundTemplate(BattlegroundTypeId bgTypeId);
Battleground* CreateNewBattleground(BattlegroundTypeId bgTypeId, PvPDifficultyEntry const* bracketEntry, uint8 arenaType, bool isRated);
std::vector<Battleground const*> GetActiveBattlegrounds();
void AddBattleground(Battleground* bg);
void RemoveBattleground(BattlegroundTypeId bgTypeId, uint32 instanceId);

View File

@@ -21,6 +21,7 @@
#include "ArenaSpectator.h"
#include "ArenaTeam.h"
#include "ArenaTeamMgr.h"
#include "ArenaSeasonMgr.h"
#include "Battlefield.h"
#include "BattlefieldMgr.h"
#include "BattlefieldWG.h"
@@ -8243,9 +8244,9 @@ void Player::SendInitWorldStates(uint32 zoneid, uint32 areaid)
data << uint32(0x8d4) << uint32(0x0); // 5
data << uint32(0x8d3) << uint32(0x0); // 6
// 7 1 - Arena season in progress, 0 - end of season
data << uint32(0xC77) << uint32(sWorld->getBoolConfig(CONFIG_ARENA_SEASON_IN_PROGRESS));
data << uint32(0xC77) << uint32(sArenaSeasonMgr->GetSeasonState() == ARENA_SEASON_STATE_IN_PROGRESS);
// 8 Arena season id
data << uint32(0xF3D) << uint32(sWorld->getIntConfig(CONFIG_ARENA_SEASON_ID));
data << uint32(0xF3D) << uint32(sArenaSeasonMgr->GetCurrentSeason());
if (mapid == 530) // Outland
{

View File

@@ -1163,34 +1163,6 @@ uint32 GameEventMgr::StartSystem() // return the next
return delay;
}
void GameEventMgr::StartArenaSeason()
{
uint8 season = sWorld->getIntConfig(CONFIG_ARENA_SEASON_ID);
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_GAME_EVENT_ARENA_SEASON);
stmt->SetData(0, season);
PreparedQueryResult result = WorldDatabase.Query(stmt);
if (!result)
{
LOG_ERROR("gameevent", "ArenaSeason ({}) must be an existant Arena Season", season);
return;
}
Field* fields = result->Fetch();
uint16 eventId = fields[0].Get<uint8>();
if (eventId >= _gameEvent.size())
{
LOG_ERROR("gameevent", "EventEntry {} for ArenaSeason ({}) does not exists", eventId, season);
return;
}
StartEvent(eventId, true);
LOG_INFO("server.loading", "Arena Season {} started...", season);
LOG_INFO("server.loading", " ");
}
uint32 GameEventMgr::Update() // return the next event delay in ms
{
time_t currenttime = GameTime::GetGameTime().count();

View File

@@ -115,11 +115,10 @@ public:
bool IsActiveEvent(uint16 eventId) { return (_activeEvents.find(eventId) != _activeEvents.end()); }
uint32 StartSystem();
void Initialize();
void StartArenaSeason();
void StartInternalEvent(uint16 eventId);
bool StartEvent(uint16 eventId, bool overwrite = false);
void StopEvent(uint16 eventId, bool overwrite = false);
void HandleQuestComplete(uint32 questId); // called on world event type quest completions
void StartInternalEvent(uint16 event_id);
bool StartEvent(uint16 event_id, bool overwrite = false);
void StopEvent(uint16 event_id, bool overwrite = false);
void HandleQuestComplete(uint32 quest_id); // called on world event type quest completions
uint32 GetNPCFlag(Creature* cr);
// Load the game event npc vendor table from the DB
void LoadEventVendors();

View File

@@ -17,6 +17,7 @@
#include "ArenaTeam.h"
#include "ArenaTeamMgr.h"
#include "ArenaSeasonMgr.h"
#include "Battleground.h"
#include "BattlegroundMgr.h"
#include "Chat.h"
@@ -812,7 +813,7 @@ void WorldSession::HandleBattlemasterJoinArena(WorldPacket& recvData)
if (isRated)
{
// pussywizard: for rated matches check if season is in progress!
if (!sWorld->getBoolConfig(CONFIG_ARENA_SEASON_IN_PROGRESS))
if (sArenaSeasonMgr->GetSeasonState() == ARENA_SEASON_STATE_DISABLED)
return;
ateamId = _player->GetArenaTeamId(arenaslot);

View File

@@ -116,7 +116,6 @@ enum WorldBoolConfigs
CONFIG_BATTLEGROUND_TRACK_DESERTERS,
CONFIG_BG_XP_FOR_KILL,
CONFIG_ARENA_AUTO_DISTRIBUTE_POINTS,
CONFIG_ARENA_SEASON_IN_PROGRESS,
CONFIG_ARENA_QUEUE_ANNOUNCER_ENABLE,
CONFIG_ARENA_QUEUE_ANNOUNCER_PLAYERONLY,
CONFIG_OFFHAND_CHECK_AT_SPELL_UNLEARN,
@@ -325,7 +324,6 @@ enum WorldIntConfigs
CONFIG_ARENA_PREV_OPPONENTS_DISCARD_TIMER,
CONFIG_ARENA_AUTO_DISTRIBUTE_INTERVAL_DAYS,
CONFIG_ARENA_GAMES_REQUIRED,
CONFIG_ARENA_SEASON_ID,
CONFIG_ARENA_START_RATING,
CONFIG_LEGACY_ARENA_POINTS_CALC,
CONFIG_ARENA_START_PERSONAL_RATING,

View File

@@ -24,6 +24,7 @@
#include "AchievementMgr.h"
#include "AddonMgr.h"
#include "ArenaTeamMgr.h"
#include "ArenaSeasonMgr.h"
#include "AuctionHouseMgr.h"
#include "AutobroadcastMgr.h"
#include "BattlefieldMgr.h"
@@ -1178,12 +1179,10 @@ void World::LoadConfigSettings(bool reload)
_bool_configs[CONFIG_ARENA_AUTO_DISTRIBUTE_POINTS] = sConfigMgr->GetOption<bool>("Arena.AutoDistributePoints", false);
_int_configs[CONFIG_ARENA_AUTO_DISTRIBUTE_INTERVAL_DAYS] = sConfigMgr->GetOption<uint32>("Arena.AutoDistributeInterval", 7); // pussywizard: spoiled by implementing constant day and hour, always 7 now
_int_configs[CONFIG_ARENA_GAMES_REQUIRED] = sConfigMgr->GetOption<uint32>("Arena.GamesRequired", 10);
_int_configs[CONFIG_ARENA_SEASON_ID] = sConfigMgr->GetOption<uint32>("Arena.ArenaSeason.ID", 8);
_int_configs[CONFIG_ARENA_START_RATING] = sConfigMgr->GetOption<uint32>("Arena.ArenaStartRating", 0);
_int_configs[CONFIG_LEGACY_ARENA_POINTS_CALC] = sConfigMgr->GetOption<uint32>("Arena.LegacyArenaPoints", 0);
_int_configs[CONFIG_ARENA_START_PERSONAL_RATING] = sConfigMgr->GetOption<uint32>("Arena.ArenaStartPersonalRating", 0);
_int_configs[CONFIG_ARENA_START_MATCHMAKER_RATING] = sConfigMgr->GetOption<uint32>("Arena.ArenaStartMatchmakerRating", 1500);
_bool_configs[CONFIG_ARENA_SEASON_IN_PROGRESS] = sConfigMgr->GetOption<bool>("Arena.ArenaSeason.InProgress", true);
_float_configs[CONFIG_ARENA_WIN_RATING_MODIFIER_1] = sConfigMgr->GetOption<float>("Arena.ArenaWinRatingModifier1", 48.0f);
_float_configs[CONFIG_ARENA_WIN_RATING_MODIFIER_2] = sConfigMgr->GetOption<float>("Arena.ArenaWinRatingModifier2", 24.0f);
_float_configs[CONFIG_ARENA_LOSE_RATING_MODIFIER] = sConfigMgr->GetOption<float>("Arena.ArenaLoseRatingModifier", 24.0f);
@@ -2123,9 +2122,10 @@ void World::SetInitialWorldSettings()
LOG_INFO("server.loading", "Initializing Opcodes...");
opcodeTable.Initialize();
LOG_INFO("server.loading", "Starting Arena Season...");
LOG_INFO("server.loading", " ");
sGameEventMgr->StartArenaSeason();
LOG_INFO("server.loading", "Loading Arena Season Rewards...");
sArenaSeasonMgr->LoadRewards();
LOG_INFO("server.loading", "Loading Active Arena Season...");
sArenaSeasonMgr->LoadActiveSeason();
LOG_INFO("server.loading", "Loading WorldState...");
sWorldState->Load();

View File

@@ -23,6 +23,8 @@ Category: commandscripts
EndScriptData */
#include "ArenaTeamMgr.h"
#include "ArenaSeasonMgr.h"
#include "ArenaTeamFilter.h"
#include "Chat.h"
#include "CommandScript.h"
#include "Player.h"
@@ -36,6 +38,19 @@ public:
ChatCommandTable GetCommands() const override
{
static ChatCommandTable arenaSeasonSetCommandTable =
{
{ "state", HandleArenaSeasonSetStateCommand, SEC_ADMINISTRATOR, Console::Yes }
};
static ChatCommandTable arenaSeasonCommandTable =
{
{ "reward", HandleArenaSeasonRewardCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "deleteteams", HandleArenaSeasonDeleteTeamsCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "start", HandleArenaSeasonStartCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "set", arenaSeasonSetCommandTable }
};
static ChatCommandTable arenaCommandTable =
{
{ "create", HandleArenaCreateCommand, SEC_ADMINISTRATOR, Console::Yes },
@@ -44,6 +59,7 @@ public:
{ "captain", HandleArenaCaptainCommand, SEC_ADMINISTRATOR, Console::No },
{ "info", HandleArenaInfoCommand, SEC_GAMEMASTER, Console::Yes },
{ "lookup", HandleArenaLookupCommand, SEC_GAMEMASTER, Console::No },
{ "season", arenaSeasonCommandTable }
};
static ChatCommandTable commandTable =
@@ -229,6 +245,80 @@ public:
return true;
}
static bool HandleArenaSeasonRewardCommand(ChatHandler* handler, std::string teamsFilterStr)
{
std::unique_ptr<ArenaTeamFilter> uniqueFilter = ArenaTeamFilterFactoryByUserInput().CreateFilterByUserInput(teamsFilterStr);
if (!uniqueFilter)
{
handler->PSendSysMessage("Invalid filter. Please check your input.");
return false;
}
std::shared_ptr<ArenaTeamFilter> sharedFilter = std::move(uniqueFilter);
if (!sArenaSeasonMgr->CanDeleteArenaTeams())
{
handler->PSendSysMessage("Cannot proceed. Make sure there are no active arenas and that rewards exist for the current season.");
handler->PSendSysMessage("Hint: You can disable the arena queue using the following command: .arena season set state 0");
return false;
}
handler->PSendSysMessage("Distributing rewards for arena teams (types: "+teamsFilterStr+")...");
sArenaSeasonMgr->RewardTeamsForTheSeason(sharedFilter);
handler->PSendSysMessage("Rewards distributed.");
return true;
}
static bool HandleArenaSeasonDeleteTeamsCommand(ChatHandler* handler)
{
handler->PSendSysMessage("Deleting arena teams...");
sArenaSeasonMgr->DeleteArenaTeams();
handler->PSendSysMessage("Arena teams deleted.");
return true;
}
static bool HandleArenaSeasonStartCommand(ChatHandler* handler, uint8 seasonId)
{
if (seasonId == sArenaSeasonMgr->GetCurrentSeason())
{
sArenaSeasonMgr->SetSeasonState(ARENA_SEASON_STATE_IN_PROGRESS);
handler->PSendSysMessage("Arena season updated.");
return true;
}
const uint8 maxSeasonId = 8;
if (seasonId > maxSeasonId)
{
handler->PSendSysMessage("Invalid season id.");
return false;
}
sArenaSeasonMgr->ChangeCurrentSeason(seasonId);
handler->PSendSysMessage("Arena season changed to season {}.", seasonId);
return true;
}
static bool HandleArenaSeasonSetStateCommand(ChatHandler* handler, uint8 state)
{
ArenaSeasonState seasonState;
switch (state) {
case ARENA_SEASON_STATE_DISABLED:
seasonState = ARENA_SEASON_STATE_DISABLED;
break;
case ARENA_SEASON_STATE_IN_PROGRESS:
seasonState = ARENA_SEASON_STATE_IN_PROGRESS;
break;
default:
handler->PSendSysMessage("Invalid state.");
return false;
}
sArenaSeasonMgr->SetSeasonState(seasonState);
handler->PSendSysMessage("Arena season updated.");
return true;
}
};
void AddSC_arena_commandscript()