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