Siege des Capitales : composition de la horde par roles
La horde etait recrutee sans regard pour les roles : n importe quelle classe, avec la specialisation que le personnage portait deja. Les IA de classe savent pourtant distinguer les roles - BotPaladinAI soigne en Sacre, BotPriestAI hors Ombre, BotShamanAI en Restauration - mais cette logique ne servait a rien puisque la specialisation n etait jamais imposee. Le recrutement vise desormais des quotas, nouvelles cles siege_tank_pct et siege_healer_pct (10 et 20 par defaut, somme plafonnee a 60). Les soigneurs et les tanks sont engages en premier : sans eux la horde fond avant le trone. FindSpecIndexForRole interroge ChrSpecialization pour trouver la specialisation que la classe doit prendre, et le re-level la lui impose via parameter3. Les specialisations d index superieur a 2 sont ecartees : elles sont hors de portee du re-level, qui refuse talent > 2. Cela concerne la Restauration du druide, dont l IA de classe attend de toute facon un druide a trois specialisations, calibrage d avant Legion. L imposition de la specialisation passe par Player::ActivateTalentGroup, sur son propre tick du re-level. Une premiere tentative ecrivait PLAYER_FIELD_CURRENT_SPEC_ID a la main pendant que stats et auras etaient recalculees, et provoquait un debordement de pile. Verifie en conditions reelles, assaut a 50 bots : apparition de guerriers Protection et de druides Gardien en tank, de chamans Restauration en soigneur, sept talents par bot et par specialisation, aucun plantage, progression 29 a 30 sur 30. Reste a affiner : le quota de soigneurs n est pas toujours atteint faute de comptes disponibles dans les classes soignantes au moment du recrutement. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Log.h"
|
||||
#include "Creature.h"
|
||||
#include "DB2Stores.h"
|
||||
#include "Map.h"
|
||||
#include "MapManager.h"
|
||||
#include "ObjectAccessor.h"
|
||||
@@ -52,7 +53,7 @@ namespace
|
||||
|
||||
CapitalSiegeMgr::CapitalSiegeMgr() :
|
||||
m_enabled(false), m_hourMin(18), m_hourMax(24), m_botCount(50), m_botLevel(110),
|
||||
m_duration(HOUR), m_spawnRate(5), m_pvpMode(1), m_engageRange(18.0f), m_bossEngageRange(40.0f), m_stallTimeout(20), m_advanceWindow(12),
|
||||
m_duration(HOUR), m_spawnRate(5), m_pvpMode(1), m_tankPct(10), m_healerPct(20), m_engageRange(18.0f), m_bossEngageRange(40.0f), m_stallTimeout(20), m_advanceWindow(12),
|
||||
m_maxDiff(400), m_announce(true),
|
||||
m_lastEventDay(0), m_lastAttackerTeam(TEAM_NEUTRAL), m_scheduledDay(0),
|
||||
m_scheduledTime(0), m_scheduledTeam(TEAM_NEUTRAL),
|
||||
@@ -60,7 +61,7 @@ CapitalSiegeMgr::CapitalSiegeMgr() :
|
||||
m_commander(nullptr),
|
||||
m_bossGuid(ObjectGuid::Empty), m_bossClearedFlags(0), m_bossOriginalLevel(0), m_bossOriginalFaction(0),
|
||||
m_bossOriginalMaxHealth(0), m_bossAwakened(false), m_bossLookupFailed(false),
|
||||
m_bossRetryTimer(0), m_totalRecruited(0),
|
||||
m_bossRetryTimer(0), m_totalRecruited(0), m_recruitedByRole{ 0, 0, 0 },
|
||||
m_elapsed(0), m_updateTimer(0), m_overloadTimer(0),
|
||||
m_botsSpawned(0), m_botsLost(0), m_startTime(0)
|
||||
{
|
||||
@@ -116,6 +117,8 @@ void CapitalSiegeMgr::LoadConfig()
|
||||
m_duration = sConfigMgr->GetIntDefault("siege_duration", HOUR);
|
||||
m_spawnRate = sConfigMgr->GetIntDefault("siege_spawn_rate", 5);
|
||||
m_pvpMode = sConfigMgr->GetIntDefault("siege_pvp", 1);
|
||||
m_tankPct = sConfigMgr->GetIntDefault("siege_tank_pct", 10);
|
||||
m_healerPct = sConfigMgr->GetIntDefault("siege_healer_pct", 20);
|
||||
m_engageRange = sConfigMgr->GetFloatDefault("siege_engage_range", 18.0f);
|
||||
m_bossEngageRange = sConfigMgr->GetFloatDefault("siege_boss_engage_range", 40.0f);
|
||||
m_stallTimeout = sConfigMgr->GetIntDefault("siege_stall_timeout", 20);
|
||||
@@ -146,6 +149,14 @@ void CapitalSiegeMgr::LoadConfig()
|
||||
m_duration = MINUTE;
|
||||
if (m_pvpMode > 2)
|
||||
m_pvpMode = 1;
|
||||
// Il faut laisser la majorite de l effectif aux degats, sinon la horde ne
|
||||
// tue plus rien.
|
||||
if (m_tankPct + m_healerPct > 60)
|
||||
{
|
||||
TC_LOG_ERROR("server.worldserver", "Siege des Capitales: tanks + soigneurs = %u%%, ramene a 10/20.", m_tankPct + m_healerPct);
|
||||
m_tankPct = 10;
|
||||
m_healerPct = 20;
|
||||
}
|
||||
// Une portee trop courte rendrait les bots inoffensifs, une portee au-dela
|
||||
// de BOTAI_SEARCH_RANGE (32 yards, BotAITool.h) les ferait courir apres
|
||||
// toute la ville au lieu de suivre leur route.
|
||||
@@ -354,6 +365,7 @@ bool CapitalSiegeMgr::StartSiege(TeamId attacker, std::string const& trigger)
|
||||
m_recruits.clear();
|
||||
m_engagedAccounts.clear();
|
||||
m_totalRecruited = 0;
|
||||
m_recruitedByRole[0] = m_recruitedByRole[1] = m_recruitedByRole[2] = 0;
|
||||
m_bossLookupFailed = false;
|
||||
m_bossRetryTimer = 0;
|
||||
|
||||
@@ -684,6 +696,8 @@ void CapitalSiegeMgr::ProcessPendingRecruits()
|
||||
m_engagedAccounts.erase(it->accountId);
|
||||
if (m_totalRecruited)
|
||||
--m_totalRecruited;
|
||||
if (m_recruitedByRole[it->role])
|
||||
--m_recruitedByRole[it->role];
|
||||
it = m_recruits.erase(it);
|
||||
continue;
|
||||
}
|
||||
@@ -724,6 +738,8 @@ void CapitalSiegeMgr::ProcessPendingRecruits()
|
||||
m_engagedAccounts.erase(it->accountId);
|
||||
if (m_totalRecruited)
|
||||
--m_totalRecruited;
|
||||
if (m_recruitedByRole[it->role])
|
||||
--m_recruitedByRole[it->role];
|
||||
it = m_recruits.erase(it);
|
||||
continue;
|
||||
}
|
||||
@@ -733,6 +749,8 @@ void CapitalSiegeMgr::ProcessPendingRecruits()
|
||||
m_engagedAccounts.erase(it->accountId);
|
||||
if (m_totalRecruited)
|
||||
--m_totalRecruited;
|
||||
if (m_recruitedByRole[it->role])
|
||||
--m_recruitedByRole[it->role];
|
||||
it = m_recruits.erase(it);
|
||||
continue;
|
||||
}
|
||||
@@ -758,6 +776,8 @@ void CapitalSiegeMgr::ProcessPendingRecruits()
|
||||
m_engagedAccounts.erase(it->accountId);
|
||||
if (m_totalRecruited)
|
||||
--m_totalRecruited;
|
||||
if (m_recruitedByRole[it->role])
|
||||
--m_recruitedByRole[it->role];
|
||||
it = m_recruits.erase(it);
|
||||
continue;
|
||||
}
|
||||
@@ -766,9 +786,44 @@ void CapitalSiegeMgr::ProcessPendingRecruits()
|
||||
}
|
||||
}
|
||||
|
||||
int32 CapitalSiegeMgr::FindSpecIndexForRole(uint8 playerClass, uint8 role)
|
||||
{
|
||||
for (uint32 i = 0; i < sChrSpecializationStore.GetNumRows(); ++i)
|
||||
{
|
||||
ChrSpecializationEntry const* spec = sChrSpecializationStore.LookupEntry(i);
|
||||
if (!spec || spec->ClassID != int8(playerClass) || spec->IsPetSpecialization())
|
||||
continue;
|
||||
if (spec->Role != int8(role))
|
||||
continue;
|
||||
// Au-dela de l index 2 la specialisation est hors de portee du re-level,
|
||||
// qui refuse talent > 2. C est le cas de la Restauration du druide, dont
|
||||
// l IA de classe attend de toute facon un druide a trois specialisations.
|
||||
if (spec->OrderIndex > 2)
|
||||
continue;
|
||||
return int32(spec->OrderIndex);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Choisit le role manquant le plus prioritaire au regard des quotas. Les
|
||||
// soigneurs et les tanks passent avant : sans eux la horde fond, et c est
|
||||
// exactement ce qui se produisait quand le recrutement prenait n importe qui.
|
||||
uint8 CapitalSiegeMgr::PickRoleToRecruit() const
|
||||
{
|
||||
uint32 const wantedTanks = std::max<uint32>(1, m_botCount * m_tankPct / 100);
|
||||
uint32 const wantedHealers = std::max<uint32>(1, m_botCount * m_healerPct / 100);
|
||||
|
||||
if (m_recruitedByRole[ROLE_HEALER] < wantedHealers)
|
||||
return ROLE_HEALER;
|
||||
if (m_recruitedByRole[ROLE_TANK] < wantedTanks)
|
||||
return ROLE_TANK;
|
||||
return ROLE_DAMAGE;
|
||||
}
|
||||
|
||||
bool CapitalSiegeMgr::RecruitOneBot()
|
||||
{
|
||||
SessionMap const& sessions = sWorld->GetAllSessions();
|
||||
uint8 const wantedRole = PickRoleToRecruit();
|
||||
|
||||
// Premier choix : un bot deja connecte et inoccupe. Pas de connexion a
|
||||
// payer, et il est deja charge en memoire.
|
||||
@@ -798,16 +853,22 @@ bool CapitalSiegeMgr::RecruitOneBot()
|
||||
if (!IsSiegeCapableClass(player->getClass()))
|
||||
continue;
|
||||
|
||||
int32 const specIndex = FindSpecIndexForRole(player->getClass(), wantedRole);
|
||||
if (specIndex < 0)
|
||||
continue; // cette classe ne peut pas tenir le role demande
|
||||
|
||||
uint32 const level = PlayerBotSetting::CheckMaxLevel(m_botLevel);
|
||||
BotGlobleSchedule setting(BotGlobleScheduleType::BGSType_Settting, 0);
|
||||
setting.parameter1 = level;
|
||||
setting.parameter2 = level;
|
||||
setting.parameter3 = 4;
|
||||
// 1 a 3 impose la specialisation, 4 laisserait celle du personnage.
|
||||
setting.parameter3 = uint32(specIndex) + 1;
|
||||
session->PushScheduleToQueue(setting);
|
||||
|
||||
m_engagedAccounts.insert(session->GetAccountId());
|
||||
m_recruits.push_back(SiegeRecruit(session->GetAccountId()));
|
||||
m_recruits.push_back(SiegeRecruit(session->GetAccountId(), wantedRole));
|
||||
++m_totalRecruited;
|
||||
++m_recruitedByRole[wantedRole];
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -837,6 +898,7 @@ bool CapitalSiegeMgr::RecruitOneBot()
|
||||
continue;
|
||||
|
||||
uint64 pickedGuid = 0;
|
||||
uint8 pickedClass = 0;
|
||||
for (PlayerBotBaseInfo::CharInfoMap::iterator itChar = accountInfo->characters.begin();
|
||||
itChar != accountInfo->characters.end(); ++itChar)
|
||||
{
|
||||
@@ -845,7 +907,10 @@ bool CapitalSiegeMgr::RecruitOneBot()
|
||||
continue;
|
||||
if (!IsSiegeCapableClass(uint8(charInfo.profession)))
|
||||
continue;
|
||||
if (FindSpecIndexForRole(uint8(charInfo.profession), wantedRole) < 0)
|
||||
continue; // cette classe ne peut pas tenir le role demande
|
||||
pickedGuid = charInfo.guid;
|
||||
pickedClass = uint8(charInfo.profession);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -861,12 +926,13 @@ bool CapitalSiegeMgr::RecruitOneBot()
|
||||
BotGlobleSchedule setting(BotGlobleScheduleType::BGSType_Settting, 0);
|
||||
setting.parameter1 = level;
|
||||
setting.parameter2 = level;
|
||||
setting.parameter3 = 4;
|
||||
setting.parameter3 = uint32(FindSpecIndexForRole(pickedClass, wantedRole)) + 1;
|
||||
session->PushScheduleToQueue(setting);
|
||||
|
||||
m_engagedAccounts.insert(session->GetAccountId());
|
||||
m_recruits.push_back(SiegeRecruit(session->GetAccountId()));
|
||||
m_recruits.push_back(SiegeRecruit(session->GetAccountId(), wantedRole));
|
||||
++m_totalRecruited;
|
||||
++m_recruitedByRole[wantedRole];
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -154,6 +154,13 @@ private:
|
||||
// moine ni de chasseur de demons dans la horde d invasion.
|
||||
static bool IsSiegeCapableClass(uint8 playerClass);
|
||||
|
||||
// Composition par roles. Renvoie l index de specialisation (0 a 2) que la
|
||||
// classe doit prendre pour tenir le role, ou -1 si elle ne le peut pas dans
|
||||
// la limite des trois premieres specialisations, seules atteignables par le
|
||||
// re-level (PlayerBotSetting::ResetPlayerToLevel refuse talent > 2).
|
||||
static int32 FindSpecIndexForRole(uint8 playerClass, uint8 role);
|
||||
uint8 PickRoleToRecruit() const;
|
||||
|
||||
// Persistance ------------------------------------------------------------
|
||||
void SaveState();
|
||||
void OpenHistoryRow(std::string const& trigger);
|
||||
@@ -175,6 +182,8 @@ private:
|
||||
uint32 m_duration; // secondes
|
||||
uint32 m_spawnRate; // bots par seconde
|
||||
uint32 m_pvpMode; // 0 = PNJ seuls, 1 = joueurs flagges PvP, 2 = tous
|
||||
uint32 m_tankPct; // part de tanks dans la horde, en pourcent
|
||||
uint32 m_healerPct; // part de soigneurs dans la horde, en pourcent
|
||||
float m_engageRange; // yards, portee d engagement en mode siege
|
||||
float m_bossEngageRange; // yards, rayon de prise a partie du dirigeant
|
||||
uint32 m_stallTimeout; // secondes sans progresser avant de lacher la cible
|
||||
@@ -199,8 +208,10 @@ private:
|
||||
uint32 accountId;
|
||||
uint32 stage; // 0 = connexion et reglage, 1 = trajet
|
||||
uint32 waitSeconds;
|
||||
uint8 role; // ROLE_TANK, ROLE_HEALER ou ROLE_DAMAGE
|
||||
|
||||
explicit SiegeRecruit(uint32 account) : accountId(account), stage(0), waitSeconds(0) { }
|
||||
SiegeRecruit(uint32 account, uint8 wantedRole)
|
||||
: accountId(account), stage(0), waitSeconds(0), role(wantedRole) { }
|
||||
};
|
||||
|
||||
CapitalSiegeStatus m_status;
|
||||
@@ -223,6 +234,8 @@ private:
|
||||
// Total engage depuis le debut de l evenement. Plafonne le recrutement :
|
||||
// un bot tue n est jamais remplace, il n y a pas de vague infinie.
|
||||
uint32 m_totalRecruited;
|
||||
// Effectif engage par role depuis le debut, pour tenir la composition.
|
||||
uint32 m_recruitedByRole[3];
|
||||
uint32 m_elapsed; // ms depuis le debut de l evenement
|
||||
uint32 m_updateTimer; // ms, cadence le tick a la seconde
|
||||
uint32 m_overloadTimer; // ms passees au-dessus du seuil de charge
|
||||
|
||||
Reference in New Issue
Block a user