From 7971b57cb4d93f034106284895c80db5ec85d426 Mon Sep 17 00:00:00 2001 From: SylvaniaCore deploy Date: Sat, 15 Aug 2026 13:01:06 +0200 Subject: [PATCH] Siege des Capitales E8 : dirigeant comme objectif, et correction du recrutement E8 - dirigeant de la capitale. Au declenchement le module le retrouve par son creature.guid (nouvelles cles siege_*_boss_spawn), force le chargement de sa grille si aucun joueur n est present, retire les drapeaux bloquants reellement presents, applique niveau et points de vie, et le designe comme objectif du commandant. Sa mort est detectee a chaque tick et vaut victoire immediate. En fin d evenement tout est restaure : drapeaux exactement ceux qui avaient ete retires, niveau, points de vie, faction, et sortie de combat. Rien n est ecrit en base, un arret du worldserver laisse donc la creature intacte. Le reglage du dirigeant est absolu et par ville (siege_*_boss_level, siege_*_boss_health, siege_*_boss_faction, 0 = ne pas toucher) et non plus un multiplicateur global : les deux chefs n ont rien de comparable, Anduin depasse le milliard de points de vie quand Vol jin, reste au niveau 83, en a quelques milliers. Un multiplicateur de 50 sur Anduin donnait 69 milliards. AcceptCommand() ne pilote que le deplacement : une cible d attaque prioritaire (SetSiegeTarget) a ete ajoutee, sinon les bots atteignaient le trone et passaient leur temps sur la garde rapprochee. Correction du recrutement, defaut signale en jeu : des bots Alliance se retrouvaient enroles dans l assaut sur Hurlevent. La cause est dans le core, PlayerBotBaseInfo::GetRandomCharacterByFuction() se termine par return characters.begin()->second et renvoie donc le premier personnage du compte, faction ignoree, quand il n en trouve aucun de la faction demandee. Le module choisit desormais lui-meme le personnage et le connecte par BGSType_Online_GUID, avec verification de la faction apres connexion et garde-fou dans CommandSiege::AddBot. Le quota est rendu sur tout refus, sinon la horde arrivait incomplete. Verifie : 15 bots recrutes, 15 horde, 15 au niveau 110. .siege status affiche l etat du dirigeant en direct. Reste ouvert : aucun assaut n a encore entame les points de vie d Anduin. La horde entierement horde se fait laminer par la garde avant le trone, la ou les essais precedents progressaient plus loin uniquement parce que des bots Alliance traversaient la ville sans etre inquietes. A retester avec l effectif complet de 50. Co-Authored-By: Claude Opus 5 --- src/server/game/AI/PlayerAI/BotAI.cpp | 18 +- src/server/game/AI/PlayerAI/BotAI.h | 4 + .../game/CapitalSiege/CapitalSiegeMgr.cpp | 267 +++++++++++++++++- .../game/CapitalSiege/CapitalSiegeMgr.h | 27 +- src/server/game/CapitalSiege/CommandSiege.cpp | 32 ++- src/server/game/CapitalSiege/CommandSiege.h | 4 + src/server/worldserver/worldserver.conf.dist | 46 ++- 7 files changed, 364 insertions(+), 34 deletions(-) diff --git a/src/server/game/AI/PlayerAI/BotAI.cpp b/src/server/game/AI/PlayerAI/BotAI.cpp index 005e3f0..17f4c44 100644 --- a/src/server/game/AI/PlayerAI/BotAI.cpp +++ b/src/server/game/AI/PlayerAI/BotAI.cpp @@ -44,6 +44,7 @@ #include "CellImpl.h" #include "GridNotifiersImpl.h" #include "CapitalSiegeMgr.h" +#include "ObjectAccessor.h" //#define THREAD_PATHFINDING #ifdef _DEBUG @@ -138,7 +139,8 @@ BotBGAI::BotBGAI(Player* player) : m_InitRndPos(), m_lastClearCtrlTick(0), m_SiegeMode(false), - m_SiegeAdvanceUntil(0) + m_SiegeAdvanceUntil(0), + m_SiegeTarget(ObjectGuid::Empty) { m_FilterCreatureEntrys.clear(); m_FilterCreatureEntrys.insert(14848); @@ -1759,6 +1761,19 @@ void BotBGAI::UpdateBotAI(uint32 diff) m_SiegeAdvanceUntil = 0; } + // Module Siege des Capitales : le dirigeant prime sur tout le reste des + // qu il est a portee. Sans cette priorite les bots atteignent le trone + // mais passent leur temps sur sa garde rapprochee. + if (m_SiegeMode && !m_SiegeTarget.IsEmpty()) + { + if (Unit* siegeBoss = ObjectAccessor::GetUnit(*me, m_SiegeTarget)) + { + if (siegeBoss->IsAlive() && me->IsValidAttackTarget(siegeBoss) + && me->GetDistance(siegeBoss) < searchRange * 2.0f) + me->SetSelection(m_SiegeTarget); + } + } + Unit* pSelect = GetBotAIValidSelectedUnit(); if (pSelect && TargetIsStealth(pSelect->ToPlayer())) { @@ -1859,6 +1874,7 @@ void BotBGAI::SetSiegeMode(bool enable) { m_SiegeMode = enable; m_SiegeAdvanceUntil = 0; + m_SiegeTarget = ObjectGuid::Empty; if (enable) m_AIBGStateType = BotAIBGState::AIBGState_Start; } diff --git a/src/server/game/AI/PlayerAI/BotAI.h b/src/server/game/AI/PlayerAI/BotAI.h index c26d160..8752a8d 100644 --- a/src/server/game/AI/PlayerAI/BotAI.h +++ b/src/server/game/AI/PlayerAI/BotAI.h @@ -75,6 +75,9 @@ public: // Fenetre de marche forcee : pendant cette duree le bot ignore les // cibles et avance vers son objectif. void PushSiegeAdvance(uint32 durationMs); + // Cible prioritaire du siege : le dirigeant. AcceptCommand() ne pilote que + // le deplacement, la cible d attaque reste choisie par SearchEnemy(). + void SetSiegeTarget(ObjectGuid guid) { m_SiegeTarget = guid; } void PushFinishQueue(PathParameter* pathParam); bool CanUseBGObject(); NearObjectList SearchGameObject(float range); @@ -223,6 +226,7 @@ protected: uint32 m_lastClearCtrlTick; bool m_SiegeMode; uint32 m_SiegeAdvanceUntil; + ObjectGuid m_SiegeTarget; }; #endif // !_BOT_AI_H_ diff --git a/src/server/game/CapitalSiege/CapitalSiegeMgr.cpp b/src/server/game/CapitalSiege/CapitalSiegeMgr.cpp index a3c55dd..1e062dc 100644 --- a/src/server/game/CapitalSiege/CapitalSiegeMgr.cpp +++ b/src/server/game/CapitalSiege/CapitalSiegeMgr.cpp @@ -20,6 +20,9 @@ #include "Config.h" #include "DatabaseEnv.h" #include "Log.h" +#include "Creature.h" +#include "Map.h" +#include "MapManager.h" #include "ObjectAccessor.h" #include "Player.h" #include "PlayerBotMgr.h" @@ -42,24 +45,29 @@ namespace // Au-dela, une recrue qui n a pas rejoint la horde est abandonnee : compte // bloque en chargement, personnage d une classe non geree, teleport perdu. constexpr uint32 SIEGE_RECRUIT_TIMEOUT_SECONDS = 90; + + // Intervalle entre deux tentatives d eveil du dirigeant. + constexpr uint32 SIEGE_BOSS_RETRY_MS = 5 * IN_MILLISECONDS; } 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_stallTimeout(20), m_advanceWindow(12), - m_bossLevel(112), m_bossHealthMult(50), m_maxDiff(400), m_announce(true), m_lastEventDay(0), m_lastAttackerTeam(TEAM_NEUTRAL), m_scheduledDay(0), m_scheduledTime(0), m_scheduledTeam(TEAM_NEUTRAL), m_status(SIEGE_STATUS_IDLE), m_attackerTeam(TEAM_NEUTRAL), m_target(nullptr), - m_commander(nullptr), m_totalRecruited(0), + 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_elapsed(0), m_updateTimer(0), m_overloadTimer(0), m_botsSpawned(0), m_botsLost(0), m_startTime(0) { // Les valeurs sont ecrasees par LoadConfig(). Elles ne servent que si le // fichier de configuration est muet sur une cle. - m_targets[TEAM_ALLIANCE] = { 1, 42283, Position(), Position(), 2000, 2099, "Orgrimmar" }; - m_targets[TEAM_HORDE] = { 0, 107574, Position(), Position(), 1000, 1099, "Hurlevent" }; + m_targets[TEAM_ALLIANCE] = { 1, 42283, Position(), Position(), 2000, 2099, 110864, 110, 0, 0, "Orgrimmar" }; + m_targets[TEAM_HORDE] = { 0, 107574, Position(), Position(), 1000, 1099, 20555650, 0, 0, 0, "Hurlevent" }; } CapitalSiegeMgr::~CapitalSiegeMgr() { } @@ -111,8 +119,6 @@ void CapitalSiegeMgr::LoadConfig() m_engageRange = sConfigMgr->GetFloatDefault("siege_engage_range", 18.0f); m_stallTimeout = sConfigMgr->GetIntDefault("siege_stall_timeout", 20); m_advanceWindow = sConfigMgr->GetIntDefault("siege_advance_window", 12); - m_bossLevel = sConfigMgr->GetIntDefault("siege_boss_level", 112); - m_bossHealthMult = sConfigMgr->GetIntDefault("siege_boss_hp_mult", 50); m_maxDiff = sConfigMgr->GetIntDefault("siege_maxdiff", 400); m_announce = sConfigMgr->GetBoolDefault("siege_announce", true); @@ -161,6 +167,10 @@ void CapitalSiegeMgr::LoadConfig() sConfigMgr->GetFloatDefault("siege_horde_staging_z", 16.0f)); m_targets[TEAM_ALLIANCE].routeFirst = sConfigMgr->GetIntDefault("siege_horde_route_first", 2000); m_targets[TEAM_ALLIANCE].routeLast = sConfigMgr->GetIntDefault("siege_horde_route_last", 2099); + m_targets[TEAM_ALLIANCE].bossSpawnId = sConfigMgr->GetIntDefault("siege_horde_boss_spawn", 110864); + m_targets[TEAM_ALLIANCE].bossLevel = sConfigMgr->GetIntDefault("siege_horde_boss_level", 110); + m_targets[TEAM_ALLIANCE].bossHealth = uint64(sConfigMgr->GetIntDefault("siege_horde_boss_health", 0)); + m_targets[TEAM_ALLIANCE].bossFaction = sConfigMgr->GetIntDefault("siege_horde_boss_faction", 0); m_targets[TEAM_ALLIANCE].cityName = "Orgrimmar"; // Cible assaillie par la Horde : la capitale de l Alliance. @@ -176,6 +186,10 @@ void CapitalSiegeMgr::LoadConfig() sConfigMgr->GetFloatDefault("siege_alliance_staging_z", 99.5f)); m_targets[TEAM_HORDE].routeFirst = sConfigMgr->GetIntDefault("siege_alliance_route_first", 1000); m_targets[TEAM_HORDE].routeLast = sConfigMgr->GetIntDefault("siege_alliance_route_last", 1099); + m_targets[TEAM_HORDE].bossSpawnId = sConfigMgr->GetIntDefault("siege_alliance_boss_spawn", 20555650); + m_targets[TEAM_HORDE].bossLevel = sConfigMgr->GetIntDefault("siege_alliance_boss_level", 0); + m_targets[TEAM_HORDE].bossHealth = uint64(sConfigMgr->GetIntDefault("siege_alliance_boss_health", 0)); + m_targets[TEAM_HORDE].bossFaction = sConfigMgr->GetIntDefault("siege_alliance_boss_faction", 0); m_targets[TEAM_HORDE].cityName = "Hurlevent"; TC_LOG_INFO("server.loading", "Siege des Capitales: %s (plage %02uh-%02uh, %u bots niveau %u, duree max %u min).", @@ -333,6 +347,8 @@ bool CapitalSiegeMgr::StartSiege(TeamId attacker, std::string const& trigger) m_recruits.clear(); m_engagedAccounts.clear(); m_totalRecruited = 0; + m_bossLookupFailed = false; + m_bossRetryTimer = 0; m_attackerTeam = attacker; m_target = target; @@ -351,6 +367,8 @@ bool CapitalSiegeMgr::StartSiege(TeamId attacker, std::string const& trigger) SaveState(); OpenHistoryRow(trigger); + AwakenBoss(); + TC_LOG_INFO("server.worldserver", "Siege des Capitales: debut de l assaut %s sur %s (declencheur: %s).", GetTeamName(attacker), m_target->cityName, trigger.c_str()); @@ -383,7 +401,8 @@ void CapitalSiegeMgr::StopSiege(CapitalSiegeOutcome outcome) delete m_commander; m_commander = nullptr; - // E8 : restauration des drapeaux du dirigeant et purge des aggro de ville. + // Le dirigeant retrouve ses drapeaux, son niveau et ses points de vie. + RestoreBoss(); CloseHistoryRow(outcome); @@ -424,7 +443,171 @@ void CapitalSiegeMgr::UpdateRunningSiege(uint32 diff) m_botsSpawned = m_commander->GetBotCount(); m_botsLost = m_commander->GetLostCount(); - // E8 : detection de la mort du dirigeant, condition de victoire immediate. + // Le dirigeant peut n avoir pas ete trouve au declenchement (grille non + // chargee, respawn en cours). On retente tant qu il manque a l appel. + if (!m_bossAwakened) + { + m_bossRetryTimer += diff; + if (m_bossRetryTimer >= SIEGE_BOSS_RETRY_MS) + { + m_bossRetryTimer = 0; + AwakenBoss(); + } + } + + // Victoire immediate : le dirigeant est tombe. + if (IsBossDead()) + { + StopSiege(SIEGE_OUTCOME_VICTORY); + return; + } +} + +/******************************************************************************* + * Dirigeant de la capitale + ******************************************************************************/ + +Creature* CapitalSiegeMgr::FindBoss() const +{ + if (!m_target || !m_target->bossSpawnId) + return nullptr; + + Map* map = sMapMgr->FindMap(m_target->mapId, 0); + if (!map) + return nullptr; + + // Sans joueur dans la salle du trone la grille n est pas chargee et la + // creature n existe pas encore en memoire. On la fait charger. + if (!map->IsGridLoaded(m_target->bossPos.GetPositionX(), m_target->bossPos.GetPositionY())) + map->LoadGrid(m_target->bossPos.GetPositionX(), m_target->bossPos.GetPositionY()); + + auto bounds = map->GetCreatureBySpawnIdStore().equal_range(m_target->bossSpawnId); + if (bounds.first == bounds.second) + return nullptr; + + return bounds.first->second; +} + +void CapitalSiegeMgr::AwakenBoss() +{ + m_bossAwakened = false; + m_bossGuid = ObjectGuid::Empty; + m_bossClearedFlags = 0; + m_bossOriginalLevel = 0; + m_bossOriginalFaction = 0; + m_bossOriginalMaxHealth = 0; + + Creature* boss = FindBoss(); + if (!boss) + { + if (!m_bossLookupFailed) + { + m_bossLookupFailed = true; + TC_LOG_ERROR("server.worldserver", "Siege des Capitales: dirigeant introuvable (spawn %u sur la carte %u), nouvelle tentative dans %u s.", + m_target->bossSpawnId, m_target->mapId, SIEGE_BOSS_RETRY_MS / IN_MILLISECONDS); + } + return; + } + + // Tout ce qui suit est applique en memoire uniquement. Rien n est ecrit en + // base : si le worldserver s arrete en plein siege, la creature est + // rechargee telle quelle depuis creature_template au redemarrage. + uint32 const blockingFlags = UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_IMMUNE_TO_PC + | UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_PACIFIED; + + // On ne retire que les drapeaux reellement presents, et on ne restaurera + // que ceux-la : le module n a pas a supposer la configuration de la base. + m_bossClearedFlags = boss->GetUInt32Value(UNIT_FIELD_FLAGS) & blockingFlags; + if (m_bossClearedFlags) + boss->RemoveFlag(UNIT_FIELD_FLAGS, m_bossClearedFlags); + + m_bossOriginalLevel = boss->getLevel(); + m_bossOriginalMaxHealth = boss->GetMaxHealth(); + m_bossOriginalFaction = boss->getFaction(); + + // Sans changement de faction le dirigeant reste intouchable : ni drapeau ni + // niveau ne le protegent, c est sa faction d origine qui interdit l attaque. + // Verifie en jeu : quinze bots au contact d Anduin pendant dix minutes ne lui + // ont pas retire un seul point de vie. + if (m_target->bossFaction) + boss->setFaction(m_target->bossFaction); + + // Reglage absolu et par ville. Un multiplicateur serait ingerable : les deux + // dirigeants n ont rien de comparable, Anduin depasse le milliard de points + // de vie quand Vol jin, reste au niveau 83, en a quelques milliers. + if (m_target->bossLevel) + boss->SetLevel(uint8(m_target->bossLevel)); + + if (m_target->bossHealth) + { + boss->SetMaxHealth(m_target->bossHealth); + boss->SetHealth(m_target->bossHealth); + } + + m_bossGuid = boss->GetGUID(); + m_bossAwakened = true; + + if (m_commander) + m_commander->SetBossGuid(m_bossGuid); + + TC_LOG_INFO("server.worldserver", "Siege des Capitales: dirigeant eveille (%s, niveau %u -> %u, PV %s -> %s, faction %u -> %u, drapeaux retires 0x%X).", + boss->GetName().c_str(), m_bossOriginalLevel, uint32(boss->getLevel()), + std::to_string(m_bossOriginalMaxHealth).c_str(), std::to_string(boss->GetMaxHealth()).c_str(), + m_bossOriginalFaction, boss->getFaction(), m_bossClearedFlags); +} + +bool CapitalSiegeMgr::IsBossDead() const +{ + if (!m_bossAwakened) + return false; + + Creature* boss = FindBoss(); + if (!boss) + return false; // grille dechargee : on ne conclut pas a la victoire + + return !boss->IsAlive(); +} + +void CapitalSiegeMgr::RestoreBoss() +{ + if (!m_bossAwakened) + return; + + Creature* boss = FindBoss(); + + // Si le dirigeant est mort il n y a rien a restaurer : il reapparaitra avec + // les valeurs de creature_template a l expiration de son respawn. + if (boss && boss->IsAlive()) + { + if (m_bossClearedFlags) + boss->SetFlag(UNIT_FIELD_FLAGS, m_bossClearedFlags); + + if (m_bossOriginalFaction) + boss->setFaction(m_bossOriginalFaction); + + if (m_bossOriginalLevel) + boss->SetLevel(uint8(m_bossOriginalLevel)); + + if (m_bossOriginalMaxHealth) + { + boss->SetMaxHealth(m_bossOriginalMaxHealth); + if (boss->GetHealth() > m_bossOriginalMaxHealth) + boss->SetHealth(m_bossOriginalMaxHealth); + } + + // La ville reprend son cours : plus personne ne doit rester en combat + // avec lui a cause de l evenement. + boss->CombatStop(true); + + TC_LOG_INFO("server.worldserver", "Siege des Capitales: dirigeant restaure (%s).", boss->GetName().c_str()); + } + + m_bossAwakened = false; + m_bossGuid = ObjectGuid::Empty; + m_bossClearedFlags = 0; + m_bossOriginalLevel = 0; + m_bossOriginalFaction = 0; + m_bossOriginalMaxHealth = 0; } /******************************************************************************* @@ -492,6 +675,8 @@ void CapitalSiegeMgr::ProcessPendingRecruits() TC_LOG_ERROR("server.worldserver", "Siege des Capitales: recrue abandonnee, le compte bot %u n a pas rejoint la horde en %u s.", it->accountId, SIEGE_RECRUIT_TIMEOUT_SECONDS); m_engagedAccounts.erase(it->accountId); + if (m_totalRecruited) + --m_totalRecruited; it = m_recruits.erase(it); continue; } @@ -521,9 +706,26 @@ void CapitalSiegeMgr::ProcessPendingRecruits() continue; } + // Le tirage de personnage du core (GetRandomCharacterByFuction) + // retombe sur le premier personnage du compte quand il n en trouve + // aucun de la faction demandee. Sans ce controle, des Alliance se + // retrouvaient enroles dans l assaut sur Hurlevent. + if (player->GetTeamId() != m_attackerTeam) + { + TC_LOG_INFO("server.worldserver", "Siege des Capitales: %s est de la mauvaise faction, recrue relachee.", player->GetName().c_str()); + sPlayerBotMgr->PlayerBotLogout(it->accountId); + m_engagedAccounts.erase(it->accountId); + if (m_totalRecruited) + --m_totalRecruited; + it = m_recruits.erase(it); + continue; + } + if (!IsSiegeCapableClass(player->getClass())) { m_engagedAccounts.erase(it->accountId); + if (m_totalRecruited) + --m_totalRecruited; it = m_recruits.erase(it); continue; } @@ -547,6 +749,8 @@ void CapitalSiegeMgr::ProcessPendingRecruits() if (!m_commander->AddBot(player)) { m_engagedAccounts.erase(it->accountId); + if (m_totalRecruited) + --m_totalRecruited; it = m_recruits.erase(it); continue; } @@ -614,10 +818,37 @@ bool CapitalSiegeMgr::RecruitOneBot() if (session->GetPlayer()) continue; + // Le personnage est choisi ici, explicitement. Laisser le core le tirer + // (BGSType_Online + GetRandomCharacterByFuction) ne garantit rien : cette + // fonction retombe sur le premier personnage du compte quand elle n en + // trouve aucun de la faction demandee, ce qui enrolait des Alliance dans + // l assaut sur Hurlevent. + PlayerBotBaseInfo* accountInfo = sPlayerBotMgr->GetPlayerBotAccountInfo(session->GetAccountId()); + if (!accountInfo) + accountInfo = sPlayerBotMgr->GetAccountBotAccountInfo(session->GetAccountId()); + if (!accountInfo) + continue; + + uint64 pickedGuid = 0; + for (PlayerBotBaseInfo::CharInfoMap::iterator itChar = accountInfo->characters.begin(); + itChar != accountInfo->characters.end(); ++itChar) + { + PlayerBotCharBaseInfo& charInfo = itChar->second; + if (charInfo.GetCamp() != m_attackerTeam) + continue; + if (!IsSiegeCapableClass(uint8(charInfo.profession))) + continue; + pickedGuid = charInfo.guid; + break; + } + + // Compte sans personnage utilisable pour cet assaut : on passe au suivant. + if (!pickedGuid) + continue; + uint32 const level = PlayerBotSetting::CheckMaxLevel(m_botLevel); - BotGlobleSchedule online(BotGlobleScheduleType::BGSType_Online, 0); - online.parameter1 = (m_attackerTeam == TEAM_ALLIANCE) ? 1 : 2; + BotGlobleSchedule online(BotGlobleScheduleType::BGSType_Online_GUID, pickedGuid); session->PushScheduleToQueue(online); BotGlobleSchedule setting(BotGlobleScheduleType::BGSType_Settting, 0); @@ -751,14 +982,26 @@ std::string CapitalSiegeMgr::GetStatusText() const default: break; } + // Etat du dirigeant, pour savoir d un coup d oeil si l assaut le touche. + char bossText[96] = "dirigeant non eveille"; + if (m_bossAwakened) + { + if (Creature* boss = FindBoss()) + snprintf(bossText, sizeof(bossText), "%s a %u%% (%s PV)", boss->GetName().c_str(), + uint32(boss->GetHealthPct()), std::to_string(boss->GetHealth()).c_str()); + else + snprintf(bossText, sizeof(bossText), "dirigeant hors grille"); + } + snprintf(buffer, sizeof(buffer), - "Siege en cours [%s]: %s attaque %s. Ecoule %u s, restant %u s, bots %u engages / %u vivants / %u perdus / %u en route (objectif %u). Progression %u/%u.", + "Siege en cours [%s]: %s attaque %s. Ecoule %u s, restant %u s, bots %u engages / %u vivants / %u perdus / %u en route (objectif %u). Progression %u/%u. Objectif: %s.", statusName, GetTeamName(m_attackerTeam), m_target ? m_target->cityName : "?", GetElapsedSeconds(), GetRemainingSeconds(), m_botsSpawned, m_commander ? m_commander->GetAliveCount() : 0, m_botsLost, uint32(m_recruits.size()), m_botCount, m_commander ? m_commander->GetVanguardProgress() + 1 : 0, - m_commander ? m_commander->GetRouteSize() : 0); + m_commander ? m_commander->GetRouteSize() : 0, + bossText); return buffer; } diff --git a/src/server/game/CapitalSiege/CapitalSiegeMgr.h b/src/server/game/CapitalSiege/CapitalSiegeMgr.h index 7543779..1e5243c 100644 --- a/src/server/game/CapitalSiege/CapitalSiegeMgr.h +++ b/src/server/game/CapitalSiege/CapitalSiegeMgr.h @@ -30,6 +30,7 @@ #define __CAPITALSIEGEMGR_H__ #include "Common.h" +#include "ObjectGuid.h" #include "Position.h" #include "SharedDefines.h" #include @@ -37,6 +38,7 @@ #include class CommandSiege; +class Creature; class Player; enum CapitalSiegeStatus @@ -67,6 +69,10 @@ struct CapitalSiegeTarget Position stagingPos; // point de rassemblement de la horde d invasion uint32 routeFirst; // premiere entree aiwaypoints de la route d assaut uint32 routeLast; // derniere entree aiwaypoints de la route d assaut + uint32 bossSpawnId; // creature.guid du dirigeant, pour le retrouver sur la carte + uint32 bossLevel; // niveau impose pendant l evenement, 0 = ne pas toucher + uint64 bossHealth; // PV imposes pendant l evenement, 0 = ne pas toucher + uint32 bossFaction; // faction imposee pendant l evenement, 0 = ne pas toucher char const* cityName; }; @@ -115,8 +121,6 @@ public: float GetEngageRange() const { return m_engageRange; } uint32 GetStallTimeout() const { return m_stallTimeout; } uint32 GetAdvanceWindow() const { return m_advanceWindow; } - uint32 GetBossLevel() const { return m_bossLevel; } - uint32 GetBossHealthMult() const { return m_bossHealthMult; } static char const* GetTeamName(TeamId team); static char const* GetOutcomeName(CapitalSiegeOutcome outcome); @@ -131,6 +135,12 @@ private: void UpdateRunningSiege(uint32 diff); bool IsServerOverloaded(uint32 diff); + // Dirigeant -------------------------------------------------------------- + Creature* FindBoss() const; + void AwakenBoss(); + void RestoreBoss(); + bool IsBossDead() const; + // Recrutement ------------------------------------------------------------ void UpdateRecruitment(); void ProcessPendingRecruits(); @@ -164,8 +174,6 @@ private: float m_engageRange; // yards, portee d engagement en mode siege uint32 m_stallTimeout; // secondes sans progresser avant de lacher la cible uint32 m_advanceWindow; // secondes de marche forcee apres un enlisement - uint32 m_bossLevel; - uint32 m_bossHealthMult; uint32 m_maxDiff; // ms, seuil d arret d urgence (0 = desactive) bool m_announce; CapitalSiegeTarget m_targets[2]; // indexe par TeamId de l attaquant @@ -194,6 +202,17 @@ private: TeamId m_attackerTeam; CapitalSiegeTarget const* m_target; CommandSiege* m_commander; + // Dirigeant : etat d origine, restaure en fin d evenement. Rien n est + // ecrit en base, un arret du worldserver laisse donc la creature intacte. + ObjectGuid m_bossGuid; + uint32 m_bossClearedFlags; + uint32 m_bossOriginalLevel; + uint32 m_bossOriginalFaction; + uint64 m_bossOriginalMaxHealth; + bool m_bossAwakened; + bool m_bossLookupFailed; + uint32 m_bossRetryTimer; + std::list m_recruits; std::set m_engagedAccounts; // Total engage depuis le debut de l evenement. Plafonne le recrutement : diff --git a/src/server/game/CapitalSiege/CommandSiege.cpp b/src/server/game/CapitalSiege/CommandSiege.cpp index d6925ef..5660d6f 100644 --- a/src/server/game/CapitalSiege/CommandSiege.cpp +++ b/src/server/game/CapitalSiege/CommandSiege.cpp @@ -107,6 +107,14 @@ bool CommandSiege::AddBot(Player* player) if (m_bots.find(player->GetGUID()) != m_bots.end()) return false; + // Dernier filet : jamais un bot de la faction assiegee dans la horde + // d invasion. Le tirage de personnage du core peut se tromper de faction. + if (player->GetTeamId() != m_attackerTeam) + { + TC_LOG_ERROR("server.worldserver", "Siege des Capitales: %s n est pas de la faction attaquante, enrolement refuse.", player->GetName().c_str()); + return false; + } + BotBGAI* botAI = dynamic_cast(player->GetAI()); if (!botAI) { @@ -270,15 +278,27 @@ void CommandSiege::CommandBot(ObjectGuid guid, SiegeBot& state, Player* player) botAI->PushSiegeAdvance(sCapitalSiegeMgr->GetAdvanceWindow() * IN_MILLISECONDS); } - // Arrive au dernier point de la route, le bot bascule sur le dirigeant. - // L ordre est marque prioritaire pour qu un ordre de deplacement residuel - // ne le detourne pas de sa cible. - if (state.waypointIndex + 1 == m_route.size() && !m_bossGuid.IsEmpty()) + // Le dirigeant prime des qu il est a portee, sans attendre que le bot ait + // formellement franchi tous les points de la route : les derniers metres se + // font sous le feu de la garde et l index progresse mal. L ordre est marque + // prioritaire pour qu un ordre de deplacement residuel ne le detourne pas. + if (!m_bossGuid.IsEmpty()) { - botAI->GetAIMovement()->AcceptCommand(m_bossGuid, true); - return; + bool const lastLeg = (state.waypointIndex + 1 == m_route.size()); + if (lastLeg || player->GetDistance(GetObjectivePosition()) < COMMANDSIEGE_BOSS_RANGE) + { + // Deux ordres distincts : l un pour marcher jusqu a lui, l autre + // pour le prendre pour cible. AcceptCommand ne fait que le premier. + botAI->GetAIMovement()->AcceptCommand(m_bossGuid, true); + botAI->SetSiegeTarget(m_bossGuid); + return; + } } + // Hors de portee de l objectif, aucune cible prioritaire : le bot combat ce + // qu il rencontre. + botAI->SetSiegeTarget(ObjectGuid::Empty); + botAI->GetAIMovement()->AcceptCommand(target); } diff --git a/src/server/game/CapitalSiege/CommandSiege.h b/src/server/game/CapitalSiege/CommandSiege.h index 2f5110f..47016f7 100644 --- a/src/server/game/CapitalSiege/CommandSiege.h +++ b/src/server/game/CapitalSiege/CommandSiege.h @@ -47,6 +47,10 @@ class Player; // Distance a laquelle un point de la route est considere atteint. #define COMMANDSIEGE_WAYPOINT_REACH 12.0f +// Distance a l objectif final au-dela de laquelle le dirigeant devient la cible +// prioritaire, sans attendre le franchissement du dernier point de la route. +#define COMMANDSIEGE_BOSS_RANGE 60.0f + class TC_GAME_API CommandSiege { public: diff --git a/src/server/worldserver/worldserver.conf.dist b/src/server/worldserver/worldserver.conf.dist index 5bf12bf..c1646ac 100644 --- a/src/server/worldserver/worldserver.conf.dist +++ b/src/server/worldserver/worldserver.conf.dist @@ -4731,17 +4731,6 @@ siege_stall_timeout = 20 siege_advance_window = 12 -# -# siege_boss_level / siege_boss_hp_mult -# Description: Ajustement applique au dirigeant pendant l evenement, en memoire -# uniquement (jamais ecrit en base : un arret du serveur pendant le -# siege laisse la creature intacte). Le multiplicateur de points de -# vie est un pourcentage applique aux PV de base. -# Default: 112 / 50 - niveau 112, PV x50 -# - -siege_boss_level = 112 -siege_boss_hp_mult = 50 # # siege_maxdiff @@ -4773,6 +4762,24 @@ siege_announce = 1 siege_alliance_map = 0 siege_alliance_boss_entry = 107574 +# siege_alliance_boss_spawn : creature.guid du dirigeant, sert a le +# retrouver sur la carte. 20555650 = Anduin Wrynn dans le donjon. +siege_alliance_boss_spawn = 20555650 + +# siege_alliance_boss_level : niveau impose au dirigeant pendant l evenement. +# siege_alliance_boss_health : points de vie imposes. +# 0 = ne pas toucher. Reglage absolu et par ville : un multiplicateur +# serait ingerable, Anduin depasse le milliard de points de vie quand +# Vol jin, reste au niveau 83, en a quelques milliers. +siege_alliance_boss_level = 0 +siege_alliance_boss_health = 0 +# siege_alliance_boss_faction : faction imposee au dirigeant pendant l +# evenement, 0 = ne pas toucher. Indispensable cote Alliance : Anduin ne +# porte aucun drapeau bloquant mais sa faction d origine (1733) interdit +# l attaque. Verifie en jeu, quinze bots au contact pendant dix minutes +# ne lui retiraient pas un point de vie. 12 = Stormwind, la faction des +# civils de la ville, hostile a la Horde et attaquable. +siege_alliance_boss_faction = 0 siege_alliance_boss_x = -8363.3 siege_alliance_boss_y = 232.5 siege_alliance_boss_z = 157.1 @@ -4797,6 +4804,23 @@ siege_alliance_route_last = 1099 siege_horde_map = 1 siege_horde_boss_entry = 42283 +# siege_horde_boss_spawn : 110864 = Vol'jin dans l enclos de Grommash. +siege_horde_boss_spawn = 110864 + +# siege_horde_boss_level : niveau impose au dirigeant pendant l evenement. +# siege_horde_boss_health : points de vie imposes. +# 0 = ne pas toucher. Reglage absolu et par ville : un multiplicateur +# serait ingerable, Anduin depasse le milliard de points de vie quand +# Vol jin, reste au niveau 83, en a quelques milliers. +siege_horde_boss_level = 110 +siege_horde_boss_health = 0 +# siege_horde_boss_faction : faction imposee au dirigeant pendant l +# evenement, 0 = ne pas toucher. Indispensable cote Alliance : Anduin ne +# porte aucun drapeau bloquant mais sa faction d origine (1733) interdit +# l attaque. Verifie en jeu, quinze bots au contact pendant dix minutes +# ne lui retiraient pas un point de vie. 12 = Stormwind, la faction des +# civils de la ville, hostile a la Horde et attaquable. +siege_horde_boss_faction = 0 siege_horde_boss_x = 1924.4 siege_horde_boss_y = -4144.1 siege_horde_boss_z = 40.6