Siege des Capitales E1-E3 : ordonnanceur, persistance, commandes GM
Nouveau module d evenement quotidien : une horde de playerbots assaille la capitale adverse et tente d abattre son dirigeant. Cette premiere tranche livre le squelette, la persistance et l ordonnancement ; le spawner, l IA d assaut et l eveil du dirigeant suivent (E4-E8). Le serveur tourne sur DestinyCore, sans systeme de modules : le module prend donc la forme d un script module TrinityCore (decouverte CMake automatique via AddCapitalSiegeScripts()), avec le gestionnaire dans la bibliotheque game comme PlayerBotMgr, puisque l IA d assaut devra y acceder depuis BotBGAI. game/CapitalSiege/CapitalSiegeMgr : tirage quotidien d une heure aleatoire dans une plage configurable, alternance stricte de la faction attaquante, machine a etats et arret d urgence si le diff monde depasse le seuil. Le jour est consomme au declenchement et non a la fin, donc un redemarrage du worldserver en plein evenement ne peut pas le rejouer. scripts/CapitalSiege : branchement par WorldScript (aucune modification de World.cpp), et commandes GM .siege status/start/stop/history sous la permission RBAC_PERM_COMMAND_EVENT existante. Persistance dans dc_characters et non dc_world : il s agit d etat mutable ecrit par le serveur, qu une reimportation de la base de contenu ne doit pas effacer. Six requetes preparees ajoutees a CharacterDatabase. Toute ligne d historique restee ouverte est refermee au demarrage du core. MMaps verifies pour les deux capitales : carte 0 (592 tuiles) et carte 1 (715 tuiles), tuiles de Hurlevent et d Orgrimmar presentes. La navigation des bots passe par PathGenerator/MotionMaster, donc Detour, pas les VMaps. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
--
|
||||
-- SylvaniaCore - Module "Siege des Capitales"
|
||||
-- Base : dc_characters (etat mutable ecrit par le worldserver, donc hors de la
|
||||
-- base de contenu dc_world qui est reimportee a chaque mise a jour).
|
||||
--
|
||||
-- Deux tables :
|
||||
-- capital_siege_state : ligne unique, l ordonnancement du module. Elle porte
|
||||
-- le verrou anti-rejeu quotidien et l alternance de
|
||||
-- faction, qui doivent survivre a un redemarrage.
|
||||
-- capital_siege_history : une ligne par evenement, ouverte au declenchement et
|
||||
-- refermee a la fin (ou au demarrage suivant du core
|
||||
-- si le serveur s est arrete en plein siege).
|
||||
--
|
||||
-- Toutes les ecritures passent par des requetes preparees declarees dans
|
||||
-- src/server/database/Database/Implementation/CharacterDatabase.{h,cpp}.
|
||||
--
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Etat de l ordonnanceur (une seule ligne, id = 1)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `capital_siege_state` (
|
||||
`id` TINYINT(3) UNSIGNED NOT NULL DEFAULT 1
|
||||
COMMENT 'Toujours 1 : la table ne contient qu une ligne',
|
||||
`last_event_day` INT(10) UNSIGNED NOT NULL DEFAULT 0
|
||||
COMMENT 'Jour serveur (epoch local / 86400) du dernier evenement consomme. Verrou anti-rejeu quotidien',
|
||||
`last_attacker_team` TINYINT(4) NOT NULL DEFAULT -1
|
||||
COMMENT 'Derniere faction attaquante : 0 = Alliance, 1 = Horde, -1 = aucune. Sert a l alternance stricte',
|
||||
`scheduled_day` INT(10) UNSIGNED NOT NULL DEFAULT 0
|
||||
COMMENT 'Jour serveur du tirage courant',
|
||||
`scheduled_time` INT(10) UNSIGNED NOT NULL DEFAULT 0
|
||||
COMMENT 'Horodatage unix du declenchement tire pour scheduled_day',
|
||||
`scheduled_team` TINYINT(4) NOT NULL DEFAULT -1
|
||||
COMMENT 'Faction attaquante tiree pour scheduled_day : 0 = Alliance, 1 = Horde, -1 = aucune',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||
COMMENT='Siege des Capitales : ordonnancement persistant';
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Historique des evenements
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `capital_siege_history` (
|
||||
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
`start_time` INT(10) UNSIGNED NOT NULL DEFAULT 0
|
||||
COMMENT 'Horodatage unix du declenchement',
|
||||
`end_time` INT(10) UNSIGNED NOT NULL DEFAULT 0
|
||||
COMMENT 'Horodatage unix de la fin, 0 tant que l evenement est en cours',
|
||||
`attacker_team` TINYINT(4) NOT NULL DEFAULT -1
|
||||
COMMENT '0 = Alliance, 1 = Horde',
|
||||
`target_map` SMALLINT(5) UNSIGNED NOT NULL DEFAULT 0
|
||||
COMMENT 'Carte de la capitale assaillie (0 = Hurlevent, 1 = Orgrimmar)',
|
||||
`boss_entry` INT(10) UNSIGNED NOT NULL DEFAULT 0
|
||||
COMMENT 'creature_template du dirigeant vise',
|
||||
`outcome` TINYINT(4) NOT NULL DEFAULT 0
|
||||
COMMENT '0 = en cours, 1 = victoire, 2 = temps ecoule, 3 = annule GM, 4 = arret d urgence (charge), 5 = interrompu (arret du serveur)',
|
||||
`duration` INT(10) UNSIGNED NOT NULL DEFAULT 0
|
||||
COMMENT 'Duree effective en secondes',
|
||||
`bots_spawned` SMALLINT(5) UNSIGNED NOT NULL DEFAULT 0
|
||||
COMMENT 'Nombre de bots effectivement deployes',
|
||||
`bots_lost` SMALLINT(5) UNSIGNED NOT NULL DEFAULT 0
|
||||
COMMENT 'Nombre de bots tues pendant l assaut',
|
||||
`triggered_by` VARCHAR(32) NOT NULL DEFAULT 'auto'
|
||||
COMMENT 'auto = ordonnanceur, sinon nom du GM declencheur',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_outcome` (`outcome`),
|
||||
KEY `idx_start_time` (`start_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
||||
COMMENT='Siege des Capitales : historique des evenements';
|
||||
|
||||
-- Ligne d etat initiale. Aucune faction n a encore attaque : le premier
|
||||
-- evenement sera donc mene par l Alliance.
|
||||
INSERT IGNORE INTO `capital_siege_state`
|
||||
(`id`, `last_event_day`, `last_attacker_team`, `scheduled_day`, `scheduled_time`, `scheduled_team`)
|
||||
VALUES (1, 0, -1, 0, 0, -1);
|
||||
@@ -806,6 +806,14 @@ void CharacterDatabaseConnection::DoPrepareStatements()
|
||||
PrepareStatement(CHAR_UPD_SPECIALIZATION, "UPDATE characters SET primarySpecialization = ? WHERE guid = ?", CONNECTION_ASYNC);
|
||||
|
||||
PrepareStatement(CHAR_DEL_CHALLENGE_MEMBER, "DELETE FROM challenge_member WHERE member = ?", CONNECTION_ASYNC);
|
||||
|
||||
// Module Siege des Capitales (SylvaniaCore)
|
||||
PrepareStatement(CHAR_SEL_CAPITAL_SIEGE_STATE, "SELECT last_event_day, last_attacker_team, scheduled_day, scheduled_time, scheduled_team FROM capital_siege_state WHERE id = 1", CONNECTION_SYNCH);
|
||||
PrepareStatement(CHAR_REP_CAPITAL_SIEGE_STATE, "REPLACE INTO capital_siege_state (id, last_event_day, last_attacker_team, scheduled_day, scheduled_time, scheduled_team) VALUES (1, ?, ?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_INS_CAPITAL_SIEGE_HISTORY, "INSERT INTO capital_siege_history (start_time, attacker_team, target_map, boss_entry, outcome, triggered_by) VALUES (?, ?, ?, ?, 0, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CAPITAL_SIEGE_HISTORY_CLOSE, "UPDATE capital_siege_history SET end_time = ?, duration = ?, outcome = ?, bots_spawned = ?, bots_lost = ? WHERE outcome = 0", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CAPITAL_SIEGE_HISTORY_ORPHAN, "UPDATE capital_siege_history SET end_time = ?, outcome = ? WHERE outcome = 0", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_CAPITAL_SIEGE_HISTORY, "SELECT start_time, attacker_team, outcome, duration, bots_spawned, bots_lost, triggered_by FROM capital_siege_history ORDER BY id DESC LIMIT 10", CONNECTION_SYNCH);
|
||||
}
|
||||
|
||||
CharacterDatabaseConnection::CharacterDatabaseConnection(MySQLConnectionInfo& connInfo, ConnectionFlags connectionFlags) : MySQLConnection(connInfo, connectionFlags)
|
||||
|
||||
@@ -681,6 +681,14 @@ enum CharacterDatabaseStatements : uint32
|
||||
|
||||
CHAR_UPD_SPECIALIZATION,
|
||||
|
||||
// Module Siege des Capitales (SylvaniaCore)
|
||||
CHAR_SEL_CAPITAL_SIEGE_STATE,
|
||||
CHAR_REP_CAPITAL_SIEGE_STATE,
|
||||
CHAR_INS_CAPITAL_SIEGE_HISTORY,
|
||||
CHAR_UPD_CAPITAL_SIEGE_HISTORY_CLOSE,
|
||||
CHAR_UPD_CAPITAL_SIEGE_HISTORY_ORPHAN,
|
||||
CHAR_SEL_CAPITAL_SIEGE_HISTORY,
|
||||
|
||||
MAX_CHARACTERDATABASE_STATEMENTS
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,513 @@
|
||||
/*
|
||||
* This file is part of the DestinyCore 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 General Public License as published by the
|
||||
* Free Software Foundation; either version 2 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 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 "CapitalSiegeMgr.h"
|
||||
#include "Config.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Log.h"
|
||||
#include "Random.h"
|
||||
#include "World.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
|
||||
namespace
|
||||
{
|
||||
// Duree minimale passee au-dessus du seuil de charge avant l arret d urgence.
|
||||
constexpr uint32 SIEGE_OVERLOAD_GRACE_MS = 10 * 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_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_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(), "Orgrimmar" };
|
||||
m_targets[TEAM_HORDE] = { 0, 107574, Position(), Position(), "Hurlevent" };
|
||||
}
|
||||
|
||||
CapitalSiegeMgr::~CapitalSiegeMgr() { }
|
||||
|
||||
CapitalSiegeMgr* CapitalSiegeMgr::instance()
|
||||
{
|
||||
static CapitalSiegeMgr instance;
|
||||
return &instance;
|
||||
}
|
||||
|
||||
char const* CapitalSiegeMgr::GetTeamName(TeamId team)
|
||||
{
|
||||
switch (team)
|
||||
{
|
||||
case TEAM_ALLIANCE: return "Alliance";
|
||||
case TEAM_HORDE: return "Horde";
|
||||
default: return "aucune";
|
||||
}
|
||||
}
|
||||
|
||||
char const* CapitalSiegeMgr::GetOutcomeName(CapitalSiegeOutcome outcome)
|
||||
{
|
||||
switch (outcome)
|
||||
{
|
||||
case SIEGE_OUTCOME_RUNNING: return "en cours";
|
||||
case SIEGE_OUTCOME_VICTORY: return "victoire";
|
||||
case SIEGE_OUTCOME_TIMEOUT: return "echec (temps ecoule)";
|
||||
case SIEGE_OUTCOME_CANCELLED: return "annule";
|
||||
case SIEGE_OUTCOME_OVERLOAD: return "arret d urgence (charge)";
|
||||
case SIEGE_OUTCOME_INTERRUPTED: return "interrompu (arret du serveur)";
|
||||
default: return "inconnu";
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Configuration
|
||||
******************************************************************************/
|
||||
|
||||
void CapitalSiegeMgr::LoadConfig()
|
||||
{
|
||||
m_enabled = sConfigMgr->GetBoolDefault("siege_enable", false);
|
||||
m_hourMin = sConfigMgr->GetIntDefault("siege_hour_min", 18);
|
||||
m_hourMax = sConfigMgr->GetIntDefault("siege_hour_max", 24);
|
||||
m_botCount = sConfigMgr->GetIntDefault("siege_botcount", 50);
|
||||
m_botLevel = sConfigMgr->GetIntDefault("siege_botlevel", 110);
|
||||
m_duration = sConfigMgr->GetIntDefault("siege_duration", HOUR);
|
||||
m_spawnRate = sConfigMgr->GetIntDefault("siege_spawn_rate", 5);
|
||||
m_pvpMode = sConfigMgr->GetIntDefault("siege_pvp", 1);
|
||||
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);
|
||||
|
||||
// Garde-fous : une plage horaire vide ou inversee rendrait le tirage
|
||||
// impossible, un nombre de bots absurde noierait le serveur.
|
||||
if (m_hourMin > 23)
|
||||
m_hourMin = 23;
|
||||
if (m_hourMax > 24)
|
||||
m_hourMax = 24;
|
||||
if (m_hourMax <= m_hourMin)
|
||||
{
|
||||
TC_LOG_ERROR("server.worldserver", "Siege des Capitales: plage horaire invalide (%u-%u), retour a 18-24.", m_hourMin, m_hourMax);
|
||||
m_hourMin = 18;
|
||||
m_hourMax = 24;
|
||||
}
|
||||
if (m_botCount > 100)
|
||||
{
|
||||
TC_LOG_ERROR("server.worldserver", "Siege des Capitales: siege_botcount=%u plafonne a 100.", m_botCount);
|
||||
m_botCount = 100;
|
||||
}
|
||||
if (m_spawnRate == 0)
|
||||
m_spawnRate = 1;
|
||||
if (m_duration < MINUTE)
|
||||
m_duration = MINUTE;
|
||||
if (m_pvpMode > 2)
|
||||
m_pvpMode = 1;
|
||||
|
||||
// Cible assaillie par l Alliance : la capitale de la Horde.
|
||||
m_targets[TEAM_ALLIANCE].mapId = sConfigMgr->GetIntDefault("siege_horde_map", 1);
|
||||
m_targets[TEAM_ALLIANCE].bossEntry = sConfigMgr->GetIntDefault("siege_horde_boss_entry", 42283);
|
||||
m_targets[TEAM_ALLIANCE].bossPos.Relocate(
|
||||
sConfigMgr->GetFloatDefault("siege_horde_boss_x", 1924.4f),
|
||||
sConfigMgr->GetFloatDefault("siege_horde_boss_y", -4144.1f),
|
||||
sConfigMgr->GetFloatDefault("siege_horde_boss_z", 40.6f));
|
||||
m_targets[TEAM_ALLIANCE].stagingPos.Relocate(
|
||||
sConfigMgr->GetFloatDefault("siege_horde_staging_x", 1570.0f),
|
||||
sConfigMgr->GetFloatDefault("siege_horde_staging_y", -4397.4f),
|
||||
sConfigMgr->GetFloatDefault("siege_horde_staging_z", 16.0f));
|
||||
m_targets[TEAM_ALLIANCE].cityName = "Orgrimmar";
|
||||
|
||||
// Cible assaillie par la Horde : la capitale de l Alliance.
|
||||
m_targets[TEAM_HORDE].mapId = sConfigMgr->GetIntDefault("siege_alliance_map", 0);
|
||||
m_targets[TEAM_HORDE].bossEntry = sConfigMgr->GetIntDefault("siege_alliance_boss_entry", 107574);
|
||||
m_targets[TEAM_HORDE].bossPos.Relocate(
|
||||
sConfigMgr->GetFloatDefault("siege_alliance_boss_x", -8363.3f),
|
||||
sConfigMgr->GetFloatDefault("siege_alliance_boss_y", 232.5f),
|
||||
sConfigMgr->GetFloatDefault("siege_alliance_boss_z", 157.1f));
|
||||
m_targets[TEAM_HORDE].stagingPos.Relocate(
|
||||
sConfigMgr->GetFloatDefault("siege_alliance_staging_x", -8833.1f),
|
||||
sConfigMgr->GetFloatDefault("siege_alliance_staging_y", 622.8f),
|
||||
sConfigMgr->GetFloatDefault("siege_alliance_staging_z", 93.9f));
|
||||
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).",
|
||||
m_enabled ? "actif" : "inactif", m_hourMin, m_hourMax, m_botCount, m_botLevel, m_duration / MINUTE);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Cycle de vie
|
||||
******************************************************************************/
|
||||
|
||||
void CapitalSiegeMgr::LoadState()
|
||||
{
|
||||
// Un evenement laisse ouvert signifie que le worldserver s est arrete en
|
||||
// plein siege : la ligne d historique est refermee et les bots eventuels
|
||||
// sont deja hors ligne (leurs sessions ne survivent pas a l arret).
|
||||
CloseOrphanHistoryRows();
|
||||
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CAPITAL_SIEGE_STATE);
|
||||
PreparedQueryResult result = CharacterDatabase.Query(stmt);
|
||||
if (!result)
|
||||
{
|
||||
TC_LOG_INFO("server.loading", "Siege des Capitales: aucun etat persiste, premier tirage au prochain tick.");
|
||||
return;
|
||||
}
|
||||
|
||||
Field* fields = result->Fetch();
|
||||
m_lastEventDay = fields[0].GetUInt32();
|
||||
m_lastAttackerTeam = TeamId(fields[1].GetInt8() < 0 ? TEAM_NEUTRAL : fields[1].GetInt8());
|
||||
m_scheduledDay = fields[2].GetUInt32();
|
||||
m_scheduledTime = time_t(fields[3].GetUInt32());
|
||||
m_scheduledTeam = TeamId(fields[4].GetInt8() < 0 ? TEAM_NEUTRAL : fields[4].GetInt8());
|
||||
|
||||
TC_LOG_INFO("server.loading", "Siege des Capitales: etat recharge (derniere faction attaquante: %s, jour du dernier evenement: %u).",
|
||||
GetTeamName(m_lastAttackerTeam), m_lastEventDay);
|
||||
}
|
||||
|
||||
void CapitalSiegeMgr::Update(uint32 diff)
|
||||
{
|
||||
if (!m_enabled)
|
||||
{
|
||||
if (IsRunning())
|
||||
{
|
||||
TC_LOG_INFO("server.worldserver", "Siege des Capitales: module desactive en cours d evenement, arret.");
|
||||
StopSiege(SIEGE_OUTCOME_CANCELLED);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// La charge est surveillee a chaque tick monde, pas seulement a la seconde.
|
||||
bool const overloaded = IsServerOverloaded(diff);
|
||||
|
||||
m_updateTimer += diff;
|
||||
if (m_updateTimer < IN_MILLISECONDS)
|
||||
return;
|
||||
|
||||
uint32 const elapsedMs = m_updateTimer;
|
||||
m_updateTimer = 0;
|
||||
|
||||
if (IsRunning())
|
||||
{
|
||||
if (overloaded)
|
||||
{
|
||||
TC_LOG_ERROR("server.worldserver", "Siege des Capitales: arret d urgence, le monde tourne au-dessus de %u ms depuis %u s.",
|
||||
m_maxDiff, SIEGE_OVERLOAD_GRACE_MS / IN_MILLISECONDS);
|
||||
StopSiege(SIEGE_OUTCOME_OVERLOAD);
|
||||
return;
|
||||
}
|
||||
UpdateRunningSiege(elapsedMs);
|
||||
}
|
||||
else
|
||||
UpdateSchedule(sWorld->GetGameTime());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Ordonnancement
|
||||
******************************************************************************/
|
||||
|
||||
void CapitalSiegeMgr::UpdateSchedule(time_t now)
|
||||
{
|
||||
uint32 const today = GetServerDay(now);
|
||||
|
||||
if (m_scheduledDay != today)
|
||||
DrawScheduleForDay(now);
|
||||
|
||||
if (m_lastEventDay == today) // deja joue aujourd hui
|
||||
return;
|
||||
if (now < m_scheduledTime)
|
||||
return;
|
||||
|
||||
// Rattrapage. Si le worldserver etait arrete a l heure tiree, on ne
|
||||
// declenche que tant qu on est encore dans la plage horaire configuree :
|
||||
// sinon le siege se lancerait a 7h du matin apres une nuit d arret.
|
||||
if (!IsInsideWindow(now))
|
||||
{
|
||||
TC_LOG_INFO("server.worldserver", "Siege des Capitales: creneau du jour manque (serveur arrete), report a demain.");
|
||||
m_lastEventDay = today;
|
||||
SaveState();
|
||||
return;
|
||||
}
|
||||
|
||||
StartSiege(m_scheduledTeam, "auto");
|
||||
}
|
||||
|
||||
void CapitalSiegeMgr::DrawScheduleForDay(time_t now)
|
||||
{
|
||||
m_scheduledDay = GetServerDay(now);
|
||||
|
||||
// Alternance stricte : jamais deux fois la meme faction attaquante.
|
||||
m_scheduledTeam = (m_lastAttackerTeam == TEAM_ALLIANCE) ? TEAM_HORDE : TEAM_ALLIANCE;
|
||||
|
||||
uint32 const windowLength = (m_hourMax - m_hourMin) * HOUR;
|
||||
m_scheduledTime = GetDayStart(now) + time_t(m_hourMin * HOUR) + time_t(urand(0, windowLength - 1));
|
||||
|
||||
SaveState();
|
||||
|
||||
tm scheduledTm;
|
||||
time_t const scheduled = m_scheduledTime;
|
||||
localtime_r(&scheduled, &scheduledTm);
|
||||
TC_LOG_INFO("server.worldserver", "Siege des Capitales: tirage du jour -> %s a %02u:%02u:%02u.",
|
||||
GetTeamName(m_scheduledTeam), uint32(scheduledTm.tm_hour), uint32(scheduledTm.tm_min), uint32(scheduledTm.tm_sec));
|
||||
}
|
||||
|
||||
bool CapitalSiegeMgr::IsInsideWindow(time_t now) const
|
||||
{
|
||||
tm localTm;
|
||||
localtime_r(&now, &localTm);
|
||||
uint32 const hour = uint32(localTm.tm_hour);
|
||||
return hour >= m_hourMin && hour < m_hourMax;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Pilotage
|
||||
******************************************************************************/
|
||||
|
||||
bool CapitalSiegeMgr::StartSiege(TeamId attacker, std::string const& trigger)
|
||||
{
|
||||
if (IsRunning())
|
||||
return false;
|
||||
if (attacker != TEAM_ALLIANCE && attacker != TEAM_HORDE)
|
||||
return false;
|
||||
|
||||
m_attackerTeam = attacker;
|
||||
m_target = &m_targets[attacker];
|
||||
m_status = SIEGE_STATUS_SPAWNING;
|
||||
m_elapsed = 0;
|
||||
m_overloadTimer = 0;
|
||||
m_botsSpawned = 0;
|
||||
m_botsLost = 0;
|
||||
m_startTime = sWorld->GetGameTime();
|
||||
|
||||
// Le jour est consomme des le declenchement, avant toute autre operation :
|
||||
// un redemarrage du worldserver en plein evenement ne peut pas le rejouer.
|
||||
m_lastEventDay = GetServerDay(m_startTime);
|
||||
m_lastAttackerTeam = attacker;
|
||||
SaveState();
|
||||
OpenHistoryRow(trigger);
|
||||
|
||||
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());
|
||||
|
||||
if (m_announce)
|
||||
Announce("|cffff2020[Siege des Capitales]|r Une armee de la %s marche sur %s !", GetTeamName(attacker), m_target->cityName);
|
||||
|
||||
// E7 : demarrage du spawn etale de la horde d invasion.
|
||||
// E8 : eveil du dirigeant de la capitale.
|
||||
return true;
|
||||
}
|
||||
|
||||
void CapitalSiegeMgr::StopSiege(CapitalSiegeOutcome outcome)
|
||||
{
|
||||
if (!IsRunning())
|
||||
return;
|
||||
|
||||
m_status = SIEGE_STATUS_ENDING;
|
||||
|
||||
// E7 : despawn des bots restants.
|
||||
// E8 : restauration des drapeaux du dirigeant et purge des aggro de ville.
|
||||
|
||||
CloseHistoryRow(outcome);
|
||||
|
||||
TC_LOG_INFO("server.worldserver", "Siege des Capitales: fin de l assaut %s sur %s apres %u s -> %s.",
|
||||
GetTeamName(m_attackerTeam), m_target ? m_target->cityName : "?", GetElapsedSeconds(), GetOutcomeName(outcome));
|
||||
|
||||
if (m_announce)
|
||||
{
|
||||
if (outcome == SIEGE_OUTCOME_VICTORY)
|
||||
Announce("|cffff2020[Siege des Capitales]|r %s est tombee : le dirigeant a ete abattu par la %s !",
|
||||
m_target ? m_target->cityName : "La capitale", GetTeamName(m_attackerTeam));
|
||||
else if (outcome == SIEGE_OUTCOME_TIMEOUT)
|
||||
Announce("|cff20ff20[Siege des Capitales]|r L assaut sur %s a ete repousse.", m_target ? m_target->cityName : "la capitale");
|
||||
}
|
||||
|
||||
m_status = SIEGE_STATUS_IDLE;
|
||||
m_attackerTeam = TEAM_NEUTRAL;
|
||||
m_target = nullptr;
|
||||
m_elapsed = 0;
|
||||
}
|
||||
|
||||
void CapitalSiegeMgr::UpdateRunningSiege(uint32 diff)
|
||||
{
|
||||
m_elapsed += diff;
|
||||
|
||||
if (m_elapsed >= m_duration * IN_MILLISECONDS)
|
||||
{
|
||||
StopSiege(SIEGE_OUTCOME_TIMEOUT);
|
||||
return;
|
||||
}
|
||||
|
||||
// E5 : tick du commandant d assaut (progression, ciblage, morts de bots).
|
||||
// E7 : suite du spawn etale tant que le quota n est pas atteint.
|
||||
}
|
||||
|
||||
bool CapitalSiegeMgr::IsServerOverloaded(uint32 diff)
|
||||
{
|
||||
if (!m_maxDiff)
|
||||
return false;
|
||||
|
||||
if (diff > m_maxDiff)
|
||||
m_overloadTimer += diff;
|
||||
else
|
||||
m_overloadTimer = 0;
|
||||
|
||||
return m_overloadTimer >= SIEGE_OVERLOAD_GRACE_MS;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Persistance
|
||||
******************************************************************************/
|
||||
|
||||
void CapitalSiegeMgr::SaveState()
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CAPITAL_SIEGE_STATE);
|
||||
stmt->setUInt32(0, m_lastEventDay);
|
||||
stmt->setInt8(1, int8(m_lastAttackerTeam == TEAM_NEUTRAL ? -1 : int8(m_lastAttackerTeam)));
|
||||
stmt->setUInt32(2, m_scheduledDay);
|
||||
stmt->setUInt32(3, uint32(m_scheduledTime));
|
||||
stmt->setInt8(4, int8(m_scheduledTeam == TEAM_NEUTRAL ? -1 : int8(m_scheduledTeam)));
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
void CapitalSiegeMgr::OpenHistoryRow(std::string const& trigger)
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CAPITAL_SIEGE_HISTORY);
|
||||
stmt->setUInt32(0, uint32(m_startTime));
|
||||
stmt->setInt8(1, int8(m_attackerTeam));
|
||||
stmt->setUInt16(2, uint16(m_target ? m_target->mapId : 0));
|
||||
stmt->setUInt32(3, m_target ? m_target->bossEntry : 0);
|
||||
stmt->setString(4, trigger);
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
void CapitalSiegeMgr::CloseHistoryRow(CapitalSiegeOutcome outcome)
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CAPITAL_SIEGE_HISTORY_CLOSE);
|
||||
stmt->setUInt32(0, uint32(sWorld->GetGameTime()));
|
||||
stmt->setUInt32(1, GetElapsedSeconds());
|
||||
stmt->setInt8(2, int8(outcome));
|
||||
stmt->setUInt16(3, uint16(m_botsSpawned));
|
||||
stmt->setUInt16(4, uint16(m_botsLost));
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
void CapitalSiegeMgr::CloseOrphanHistoryRows()
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CAPITAL_SIEGE_HISTORY_ORPHAN);
|
||||
stmt->setUInt32(0, uint32(sWorld->GetGameTime()));
|
||||
stmt->setInt8(1, int8(SIEGE_OUTCOME_INTERRUPTED));
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Consultation
|
||||
******************************************************************************/
|
||||
|
||||
uint32 CapitalSiegeMgr::GetRemainingSeconds() const
|
||||
{
|
||||
if (!IsRunning())
|
||||
return 0;
|
||||
uint32 const elapsed = GetElapsedSeconds();
|
||||
return elapsed >= m_duration ? 0 : m_duration - elapsed;
|
||||
}
|
||||
|
||||
std::string CapitalSiegeMgr::GetStatusText() const
|
||||
{
|
||||
char buffer[256];
|
||||
|
||||
if (!m_enabled)
|
||||
return "Siege des Capitales: module desactive (siege_enable = 0).";
|
||||
|
||||
if (!IsRunning())
|
||||
return GetScheduleText();
|
||||
|
||||
char const* statusName = "?";
|
||||
switch (m_status)
|
||||
{
|
||||
case SIEGE_STATUS_SPAWNING: statusName = "deploiement"; break;
|
||||
case SIEGE_STATUS_ASSAULT: statusName = "assaut"; break;
|
||||
case SIEGE_STATUS_ENDING: statusName = "nettoyage"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
snprintf(buffer, sizeof(buffer),
|
||||
"Siege en cours [%s]: %s attaque %s. Ecoule %u s, restant %u s, bots %u deployes / %u perdus.",
|
||||
statusName, GetTeamName(m_attackerTeam), m_target ? m_target->cityName : "?",
|
||||
GetElapsedSeconds(), GetRemainingSeconds(), m_botsSpawned, m_botsLost);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string CapitalSiegeMgr::GetScheduleText() const
|
||||
{
|
||||
char buffer[256];
|
||||
time_t const now = sWorld->GetGameTime();
|
||||
|
||||
if (m_lastEventDay == GetServerDay(now))
|
||||
{
|
||||
snprintf(buffer, sizeof(buffer),
|
||||
"Aucun siege en cours. Le creneau du jour est deja consomme (derniere faction attaquante: %s). Prochain tirage demain.",
|
||||
GetTeamName(m_lastAttackerTeam));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
if (!m_scheduledTime)
|
||||
return "Aucun siege en cours. Tirage du jour pas encore effectue.";
|
||||
|
||||
tm scheduledTm;
|
||||
time_t const scheduled = m_scheduledTime;
|
||||
localtime_r(&scheduled, &scheduledTm);
|
||||
snprintf(buffer, sizeof(buffer),
|
||||
"Aucun siege en cours. Prochain assaut: %s a %02u:%02u:%02u (dans %d s).",
|
||||
GetTeamName(m_scheduledTeam), uint32(scheduledTm.tm_hour), uint32(scheduledTm.tm_min), uint32(scheduledTm.tm_sec),
|
||||
int32(m_scheduledTime - now));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Utilitaires
|
||||
******************************************************************************/
|
||||
|
||||
time_t CapitalSiegeMgr::GetDayStart(time_t t)
|
||||
{
|
||||
tm localTm;
|
||||
localtime_r(&t, &localTm);
|
||||
localTm.tm_hour = 0;
|
||||
localTm.tm_min = 0;
|
||||
localTm.tm_sec = 0;
|
||||
localTm.tm_isdst = -1;
|
||||
return mktime(&localTm);
|
||||
}
|
||||
|
||||
uint32 CapitalSiegeMgr::GetServerDay(time_t t)
|
||||
{
|
||||
return uint32(GetDayStart(t) / DAY);
|
||||
}
|
||||
|
||||
void CapitalSiegeMgr::Announce(char const* format, ...) const
|
||||
{
|
||||
char buffer[512];
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsnprintf(buffer, sizeof(buffer), format, args);
|
||||
va_end(args);
|
||||
|
||||
sWorld->SendGlobalText(buffer, nullptr);
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* This file is part of the DestinyCore 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 General Public License as published by the
|
||||
* Free Software Foundation; either version 2 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 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/>.
|
||||
*/
|
||||
|
||||
// SylvaniaCore - Module "Siege des Capitales"
|
||||
//
|
||||
// Une fois par jour serveur, a une heure tiree au hasard dans une plage
|
||||
// configurable, une horde de playerbots de haut niveau assaille la capitale de
|
||||
// la faction adverse et tente d abattre son dirigeant. La faction attaquante
|
||||
// alterne strictement d un jour a l autre.
|
||||
//
|
||||
// Ce gestionnaire porte l ordonnancement, la persistance et la machine a etats.
|
||||
// L IA d assaut vit dans CommandSiege, le pilotage GM dans
|
||||
// scripts/CapitalSiege/cs_capital_siege.cpp.
|
||||
|
||||
#ifndef __CAPITALSIEGEMGR_H__
|
||||
#define __CAPITALSIEGEMGR_H__
|
||||
|
||||
#include "Common.h"
|
||||
#include "Position.h"
|
||||
#include "SharedDefines.h"
|
||||
#include <string>
|
||||
|
||||
class Player;
|
||||
|
||||
enum CapitalSiegeStatus
|
||||
{
|
||||
SIEGE_STATUS_IDLE = 0, // aucun evenement en cours
|
||||
SIEGE_STATUS_SPAWNING = 1, // vague en cours de connexion / equipement
|
||||
SIEGE_STATUS_ASSAULT = 2, // progression vers le trone
|
||||
SIEGE_STATUS_ENDING = 3 // nettoyage en cours
|
||||
};
|
||||
|
||||
// Valeurs ecrites telles quelles dans capital_siege_history.outcome
|
||||
enum CapitalSiegeOutcome
|
||||
{
|
||||
SIEGE_OUTCOME_RUNNING = 0, // ligne ouverte, evenement en cours
|
||||
SIEGE_OUTCOME_VICTORY = 1, // dirigeant abattu
|
||||
SIEGE_OUTCOME_TIMEOUT = 2, // duree maximale atteinte
|
||||
SIEGE_OUTCOME_CANCELLED = 3, // annulation GM
|
||||
SIEGE_OUTCOME_OVERLOAD = 4, // arret d urgence, serveur sous charge
|
||||
SIEGE_OUTCOME_INTERRUPTED = 5 // arret/crash du worldserver pendant l evenement
|
||||
};
|
||||
|
||||
// Cible d une invasion : la capitale de la faction qui subit l assaut.
|
||||
struct CapitalSiegeTarget
|
||||
{
|
||||
uint32 mapId; // carte de la capitale
|
||||
uint32 bossEntry; // creature_template du dirigeant
|
||||
Position bossPos; // position du trone
|
||||
Position stagingPos; // point de rassemblement de la horde d invasion
|
||||
char const* cityName;
|
||||
};
|
||||
|
||||
class TC_GAME_API CapitalSiegeMgr
|
||||
{
|
||||
private:
|
||||
CapitalSiegeMgr();
|
||||
~CapitalSiegeMgr();
|
||||
|
||||
public:
|
||||
CapitalSiegeMgr(CapitalSiegeMgr const&) = delete;
|
||||
CapitalSiegeMgr(CapitalSiegeMgr&&) = delete;
|
||||
CapitalSiegeMgr& operator=(CapitalSiegeMgr const&) = delete;
|
||||
CapitalSiegeMgr& operator=(CapitalSiegeMgr&&) = delete;
|
||||
|
||||
static CapitalSiegeMgr* instance();
|
||||
|
||||
// Cycle de vie -----------------------------------------------------------
|
||||
void LoadConfig(); // (re)lecture de worldserver.conf
|
||||
void LoadState(); // au demarrage du monde
|
||||
void Update(uint32 diff); // tick monde
|
||||
|
||||
// Pilotage ---------------------------------------------------------------
|
||||
bool StartSiege(TeamId attacker, std::string const& trigger);
|
||||
void StopSiege(CapitalSiegeOutcome outcome);
|
||||
|
||||
// Consultation -----------------------------------------------------------
|
||||
bool IsEnabled() const { return m_enabled; }
|
||||
CapitalSiegeStatus GetStatus() const { return m_status; }
|
||||
bool IsRunning() const { return m_status != SIEGE_STATUS_IDLE; }
|
||||
TeamId GetAttackerTeam() const { return m_attackerTeam; }
|
||||
TeamId GetScheduledTeam() const { return m_scheduledTeam; }
|
||||
CapitalSiegeTarget const* GetTarget() const { return m_target; }
|
||||
uint32 GetElapsedSeconds() const { return m_elapsed / IN_MILLISECONDS; }
|
||||
uint32 GetRemainingSeconds() const;
|
||||
std::string GetStatusText() const;
|
||||
std::string GetScheduleText() const;
|
||||
|
||||
// Parametres exposes aux etapes suivantes (spawner, IA d assaut, boss)
|
||||
uint32 GetBotCount() const { return m_botCount; }
|
||||
uint32 GetBotLevel() const { return m_botLevel; }
|
||||
uint32 GetSpawnRate() const { return m_spawnRate; }
|
||||
uint32 GetPvpMode() const { return m_pvpMode; }
|
||||
uint32 GetBossLevel() const { return m_bossLevel; }
|
||||
uint32 GetBossHealthMult() const { return m_bossHealthMult; }
|
||||
|
||||
static char const* GetTeamName(TeamId team);
|
||||
static char const* GetOutcomeName(CapitalSiegeOutcome outcome);
|
||||
|
||||
private:
|
||||
// Ordonnancement ---------------------------------------------------------
|
||||
void UpdateSchedule(time_t now);
|
||||
void DrawScheduleForDay(time_t now);
|
||||
bool IsInsideWindow(time_t now) const;
|
||||
|
||||
// Deroulement ------------------------------------------------------------
|
||||
void UpdateRunningSiege(uint32 diff);
|
||||
bool IsServerOverloaded(uint32 diff);
|
||||
|
||||
// Persistance ------------------------------------------------------------
|
||||
void SaveState();
|
||||
void OpenHistoryRow(std::string const& trigger);
|
||||
void CloseHistoryRow(CapitalSiegeOutcome outcome);
|
||||
void CloseOrphanHistoryRows();
|
||||
|
||||
// Utilitaires ------------------------------------------------------------
|
||||
static time_t GetDayStart(time_t t);
|
||||
static uint32 GetServerDay(time_t t);
|
||||
void Announce(char const* format, ...) const ATTR_PRINTF(2, 3);
|
||||
|
||||
private:
|
||||
// Configuration
|
||||
bool m_enabled;
|
||||
uint32 m_hourMin;
|
||||
uint32 m_hourMax;
|
||||
uint32 m_botCount;
|
||||
uint32 m_botLevel;
|
||||
uint32 m_duration; // secondes
|
||||
uint32 m_spawnRate; // bots par seconde
|
||||
uint32 m_pvpMode; // 0 = PNJ seuls, 1 = joueurs flagges PvP, 2 = tous
|
||||
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
|
||||
|
||||
// Etat persiste
|
||||
uint32 m_lastEventDay; // dernier jour ou un evenement a ete consomme
|
||||
TeamId m_lastAttackerTeam; // derniere faction attaquante (alternance)
|
||||
uint32 m_scheduledDay; // jour du tirage courant
|
||||
time_t m_scheduledTime; // horodatage du declenchement tire
|
||||
TeamId m_scheduledTeam;
|
||||
|
||||
// Etat volatil
|
||||
CapitalSiegeStatus m_status;
|
||||
TeamId m_attackerTeam;
|
||||
CapitalSiegeTarget const* m_target;
|
||||
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
|
||||
uint32 m_botsSpawned;
|
||||
uint32 m_botsLost;
|
||||
time_t m_startTime;
|
||||
};
|
||||
|
||||
#define sCapitalSiegeMgr CapitalSiegeMgr::instance()
|
||||
|
||||
#endif // __CAPITALSIEGEMGR_H__
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* This file is part of the DestinyCore 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 General Public License as published by the
|
||||
* Free Software Foundation; either version 2 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 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/>.
|
||||
*/
|
||||
|
||||
// SylvaniaCore - Module "Siege des Capitales"
|
||||
//
|
||||
// Branchement du gestionnaire sur la boucle du monde. Le module ne modifie pas
|
||||
// World.cpp : tout passe par les hooks WorldScript, dans le sens autorise
|
||||
// scripts -> game.
|
||||
|
||||
#include "CapitalSiegeMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
|
||||
class capital_siege_world : public WorldScript
|
||||
{
|
||||
public:
|
||||
capital_siege_world() : WorldScript("capital_siege_world") { }
|
||||
|
||||
// Lecture des cles de configuration, au demarrage comme sur .reload config.
|
||||
void OnConfigLoad(bool /*reload*/) override
|
||||
{
|
||||
sCapitalSiegeMgr->LoadConfig();
|
||||
}
|
||||
|
||||
// Rechargement de l ordonnancement persiste et fermeture d un evenement
|
||||
// reste ouvert si le worldserver s est arrete en plein siege.
|
||||
void OnStartup() override
|
||||
{
|
||||
sCapitalSiegeMgr->LoadState();
|
||||
}
|
||||
|
||||
void OnUpdate(uint32 diff) override
|
||||
{
|
||||
sCapitalSiegeMgr->Update(diff);
|
||||
}
|
||||
|
||||
// Arret propre : la ligne d historique est refermee tout de suite plutot
|
||||
// que d attendre le prochain demarrage du core.
|
||||
void OnShutdown() override
|
||||
{
|
||||
if (sCapitalSiegeMgr->IsRunning())
|
||||
sCapitalSiegeMgr->StopSiege(SIEGE_OUTCOME_INTERRUPTED);
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_capital_siege_world()
|
||||
{
|
||||
new capital_siege_world();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* This file is part of the DestinyCore 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 General Public License as published by the
|
||||
* Free Software Foundation; either version 2 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 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/>.
|
||||
*/
|
||||
|
||||
// This is where scripts' loading functions should be declared:
|
||||
|
||||
void AddSC_capital_siege_world();
|
||||
void AddSC_capital_siege_commandscript();
|
||||
|
||||
void AddCapitalSiegeScripts()
|
||||
{
|
||||
AddSC_capital_siege_world();
|
||||
AddSC_capital_siege_commandscript();
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* This file is part of the DestinyCore 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 General Public License as published by the
|
||||
* Free Software Foundation; either version 2 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 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/>.
|
||||
*/
|
||||
|
||||
// SylvaniaCore - Module "Siege des Capitales"
|
||||
//
|
||||
// Commandes GM :
|
||||
// .siege status etat courant ou prochain declenchement
|
||||
// .siege start [alliance|horde] declenchement manuel (faction par defaut :
|
||||
// celle que l alternance a tiree pour aujourd hui)
|
||||
// .siege stop annulation de l evenement en cours
|
||||
// .siege history les dix derniers evenements
|
||||
//
|
||||
// Le declenchement manuel consomme le creneau du jour, exactement comme un
|
||||
// declenchement automatique : pas de double siege dans la meme journee.
|
||||
|
||||
#include "CapitalSiegeMgr.h"
|
||||
#include "Chat.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "Player.h"
|
||||
#include "RBAC.h"
|
||||
#include "ScriptMgr.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string FormatTimestamp(uint32 unixTime)
|
||||
{
|
||||
if (!unixTime)
|
||||
return "-";
|
||||
|
||||
time_t const t = time_t(unixTime);
|
||||
tm localTm;
|
||||
localtime_r(&t, &localTm);
|
||||
|
||||
char buffer[32];
|
||||
strftime(buffer, sizeof(buffer), "%d/%m %H:%M", &localTm);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
static bool HandleSiegeStatus(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
handler->SendSysMessage(sCapitalSiegeMgr->GetStatusText().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleSiegeStart(ChatHandler* handler, char const* args)
|
||||
{
|
||||
if (!sCapitalSiegeMgr->IsEnabled())
|
||||
{
|
||||
handler->SendSysMessage("Le module Siege des Capitales est desactive (siege_enable = 0).");
|
||||
handler->SetSentErrorMessage(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sCapitalSiegeMgr->IsRunning())
|
||||
{
|
||||
handler->SendSysMessage("Un siege est deja en cours. Utilisez .siege stop pour l interrompre.");
|
||||
handler->SetSentErrorMessage(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
TeamId attacker = TEAM_NEUTRAL;
|
||||
if (args && *args)
|
||||
{
|
||||
if (!strncmp(args, "alliance", 8))
|
||||
attacker = TEAM_ALLIANCE;
|
||||
else if (!strncmp(args, "horde", 5))
|
||||
attacker = TEAM_HORDE;
|
||||
else
|
||||
{
|
||||
handler->SendSysMessage("Syntaxe : .siege start [alliance|horde]");
|
||||
handler->SetSentErrorMessage(true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sans argument, on respecte l alternance : la faction qui devait
|
||||
// attaquer aujourd hui.
|
||||
attacker = sCapitalSiegeMgr->GetScheduledTeam();
|
||||
if (attacker == TEAM_NEUTRAL)
|
||||
attacker = TEAM_ALLIANCE;
|
||||
}
|
||||
|
||||
std::string trigger = "GM";
|
||||
if (Player* gm = handler->GetSession() ? handler->GetSession()->GetPlayer() : nullptr)
|
||||
trigger = gm->GetName();
|
||||
|
||||
if (!sCapitalSiegeMgr->StartSiege(attacker, trigger))
|
||||
{
|
||||
handler->SendSysMessage("Impossible de declencher le siege.");
|
||||
handler->SetSentErrorMessage(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
handler->PSendSysMessage("Siege declenche : assaut %s.", CapitalSiegeMgr::GetTeamName(attacker));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleSiegeStop(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
if (!sCapitalSiegeMgr->IsRunning())
|
||||
{
|
||||
handler->SendSysMessage("Aucun siege en cours.");
|
||||
handler->SetSentErrorMessage(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
sCapitalSiegeMgr->StopSiege(SIEGE_OUTCOME_CANCELLED);
|
||||
handler->SendSysMessage("Siege annule et nettoye.");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool HandleSiegeHistory(ChatHandler* handler, char const* /*args*/)
|
||||
{
|
||||
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_SEL_CAPITAL_SIEGE_HISTORY);
|
||||
PreparedQueryResult result = CharacterDatabase.Query(stmt);
|
||||
if (!result)
|
||||
{
|
||||
handler->SendSysMessage("Aucun siege dans l historique.");
|
||||
return true;
|
||||
}
|
||||
|
||||
handler->SendSysMessage("Derniers sieges (date, attaquant, issue, duree, bots) :");
|
||||
do
|
||||
{
|
||||
Field* fields = result->Fetch();
|
||||
uint32 const startTime = fields[0].GetUInt32();
|
||||
int8 const attackerTeam = fields[1].GetInt8();
|
||||
int8 const outcome = fields[2].GetInt8();
|
||||
uint32 const duration = fields[3].GetUInt32();
|
||||
uint16 const botsSpawned = fields[4].GetUInt16();
|
||||
uint16 const botsLost = fields[5].GetUInt16();
|
||||
std::string const trigger = fields[6].GetString();
|
||||
|
||||
handler->PSendSysMessage(" %s | %s | %s | %u s | %u deployes, %u perdus | %s",
|
||||
FormatTimestamp(startTime).c_str(),
|
||||
CapitalSiegeMgr::GetTeamName(attackerTeam < 0 ? TEAM_NEUTRAL : TeamId(attackerTeam)),
|
||||
CapitalSiegeMgr::GetOutcomeName(CapitalSiegeOutcome(outcome)),
|
||||
duration, uint32(botsSpawned), uint32(botsLost), trigger.c_str());
|
||||
}
|
||||
while (result->NextRow());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class capital_siege_commandscript : public CommandScript
|
||||
{
|
||||
public:
|
||||
capital_siege_commandscript() : CommandScript("capital_siege_commandscript") { }
|
||||
|
||||
std::vector<ChatCommand> GetCommands() const override
|
||||
{
|
||||
static std::vector<ChatCommand> siegeCommandTable =
|
||||
{
|
||||
{ "status", rbac::RBAC_PERM_COMMAND_EVENT, true, &HandleSiegeStatus, "" },
|
||||
{ "start", rbac::RBAC_PERM_COMMAND_EVENT, true, &HandleSiegeStart, "" },
|
||||
{ "stop", rbac::RBAC_PERM_COMMAND_EVENT, true, &HandleSiegeStop, "" },
|
||||
{ "history", rbac::RBAC_PERM_COMMAND_EVENT, true, &HandleSiegeHistory, "" },
|
||||
};
|
||||
|
||||
static std::vector<ChatCommand> commandTable =
|
||||
{
|
||||
{ "siege", rbac::RBAC_PERM_COMMAND_EVENT, true, nullptr, "", siegeCommandTable },
|
||||
};
|
||||
|
||||
return commandTable;
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_capital_siege_commandscript()
|
||||
{
|
||||
new capital_siege_commandscript();
|
||||
}
|
||||
@@ -4621,4 +4621,141 @@ pbotbg_idlelogout = 300
|
||||
pbotasl = 88
|
||||
|
||||
#
|
||||
####################################################################################################
|
||||
####################################################################################################
|
||||
###################################################################################################
|
||||
# MODULE SIEGE DES CAPITALES (SylvaniaCore)
|
||||
#
|
||||
# siege_enable
|
||||
# Description: Evenement quotidien d invasion. Une fois par jour serveur, a une
|
||||
# heure tiree au hasard dans la plage horaire ci-dessous, une horde
|
||||
# de playerbots assaille la capitale de la faction adverse et tente
|
||||
# d abattre son dirigeant. La faction attaquante alterne strictement
|
||||
# d un jour a l autre.
|
||||
# Default: 0 - Desactive
|
||||
# 1 - Active
|
||||
#
|
||||
|
||||
siege_enable = 0
|
||||
|
||||
#
|
||||
# siege_hour_min / siege_hour_max
|
||||
# Description: Plage horaire (heure locale du serveur) dans laquelle l heure de
|
||||
# declenchement est tiree. Bornes en heures pleines, min inclus,
|
||||
# max exclu. 18 / 24 = tirage entre 18h00m00s et 23h59m59s.
|
||||
# Si le worldserver etait arrete a l heure tiree, l evenement n est
|
||||
# rattrape que tant qu on est encore dans cette plage.
|
||||
# Default: 18 / 24
|
||||
#
|
||||
|
||||
siege_hour_min = 18
|
||||
siege_hour_max = 24
|
||||
|
||||
#
|
||||
# siege_botcount
|
||||
# Description: Nombre de playerbots composant la horde d invasion.
|
||||
# Plafonne a 100 par securite.
|
||||
# Default: 50
|
||||
#
|
||||
|
||||
siege_botcount = 50
|
||||
|
||||
#
|
||||
# siege_botlevel
|
||||
# Description: Niveau auquel les bots de l invasion sont ajustes.
|
||||
# Default: 110
|
||||
#
|
||||
|
||||
siege_botlevel = 110
|
||||
|
||||
#
|
||||
# siege_duration
|
||||
# Description: Duree maximale d un evenement, en secondes. Passe ce delai
|
||||
# l assaut est declare repousse et les bots restants sont retires.
|
||||
# Default: 3600 - une heure
|
||||
#
|
||||
|
||||
siege_duration = 3600
|
||||
|
||||
#
|
||||
# siege_spawn_rate
|
||||
# Description: Nombre de bots connectes et deployes par seconde. Etale le cout
|
||||
# du spawn pour eviter un pic de charge au declenchement.
|
||||
# Default: 5
|
||||
#
|
||||
|
||||
siege_spawn_rate = 5
|
||||
|
||||
#
|
||||
# siege_pvp
|
||||
# Description: Comportement des bots vis-a-vis des joueurs reels rencontres.
|
||||
# Default: 1 - N engagent que les joueurs deja flagges PvP
|
||||
# 0 - Ignorent les joueurs, n attaquent que gardes et PNJ
|
||||
# 2 - Engagent tout joueur de la faction adverse
|
||||
#
|
||||
|
||||
siege_pvp = 1
|
||||
|
||||
#
|
||||
# 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
|
||||
# Description: Arret d urgence. Si le temps de mise a jour du monde depasse cette
|
||||
# valeur (en ms) pendant plus de dix secondes consecutives, l
|
||||
# evenement en cours est interrompu et nettoye.
|
||||
# 0 = surveillance desactivee.
|
||||
# Default: 400
|
||||
#
|
||||
|
||||
siege_maxdiff = 400
|
||||
|
||||
#
|
||||
# siege_announce
|
||||
# Description: Annonce serveur au declenchement et a la fin de l evenement.
|
||||
# Default: 1 - Active
|
||||
# 0 - Desactive
|
||||
#
|
||||
|
||||
siege_announce = 1
|
||||
|
||||
#
|
||||
# Cible assaillie par la Horde : la capitale de l Alliance (Hurlevent).
|
||||
# siege_alliance_map carte de la capitale
|
||||
# siege_alliance_boss_entry creature_template du dirigeant
|
||||
# siege_alliance_boss_* position du trone
|
||||
# siege_alliance_staging_* point de rassemblement de la horde d invasion
|
||||
#
|
||||
|
||||
siege_alliance_map = 0
|
||||
siege_alliance_boss_entry = 107574
|
||||
siege_alliance_boss_x = -8363.3
|
||||
siege_alliance_boss_y = 232.5
|
||||
siege_alliance_boss_z = 157.1
|
||||
siege_alliance_staging_x = -8833.1
|
||||
siege_alliance_staging_y = 622.8
|
||||
siege_alliance_staging_z = 93.9
|
||||
|
||||
#
|
||||
# Cible assaillie par l Alliance : la capitale de la Horde (Orgrimmar).
|
||||
#
|
||||
|
||||
siege_horde_map = 1
|
||||
siege_horde_boss_entry = 42283
|
||||
siege_horde_boss_x = 1924.4
|
||||
siege_horde_boss_y = -4144.1
|
||||
siege_horde_boss_z = 40.6
|
||||
siege_horde_staging_x = 1570.0
|
||||
siege_horde_staging_y = -4397.4
|
||||
siege_horde_staging_z = 16.0
|
||||
|
||||
#
|
||||
###################################################################################################
|
||||
|
||||
Reference in New Issue
Block a user