diff --git a/chantiers/rivage_brise/outils/extraire_chemins_scriptes.py b/chantiers/rivage_brise/outils/extraire_chemins_scriptes.py new file mode 100644 index 0000000..3443126 --- /dev/null +++ b/chantiers/rivage_brise/outils/extraire_chemins_scriptes.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +"""Extrait les 5 chemins scriptes du Rivage brise (carte 1666). + +Ils vivent dans `waypoint_data_script`, une table que notre coeur n'a +pas : notre sWaypointMgr ne lit que `waypoint_data`. C'est pourquoi +l'extraction precedente, qui ne regardait que cette derniere, les avait +manques -- et pourquoi les alliés comme le corbeau restaient immobiles. + +Correspondance des colonnes, pilotee par les NOMS : + reference nous + id, point, position_x/y/z, identiques + orientation, delay, + action, action_chance + move_flag -> move_type (voir ci-dessous) + speed, entry -> ABANDONNEES, aucun equivalent + wpguid -> 0 + +move_flag vaut 1 sur la totalite des 87 points des cinq chemins. Notre +enumeration donne 0=marche, 1=course, 2=atterrissage, 3=decollage : la +valeur 1 designe la course dans les deux. Le vol du corbeau ne vient pas +du chemin mais de SetFlyMode et du vehicule. + +CONTROLE : les effectifs attendus sont ecrits en dur et verifies. Un +extracteur qui renvoie zero sans broncher est le piege deja rencontre +sur ce chantier. +""" +import io, re, sys + +DUMP = '/home/ubuntu/tmp/abs/LegionCore_world_2020_04_25.sql' +ATTENDU = {11322705: 24, 11322706: 18, 11322707: 23, 11322708: 21, 11322709: 1} + +# colonnes de waypoint_data_script, relevees dans l'entete de l'INSERT +COLS = ['id','point','position_x','position_y','position_z','orientation', + 'delay','move_flag','speed','action','action_chance','entry','wpguid'] + +motif = re.compile(r'^\((\d+),(.*)\)[,;]\s*$') +trouves = {k: [] for k in ATTENDU} + +with io.open(DUMP, encoding='utf-8', errors='surrogateescape') as f: + for ligne in f: + m = motif.match(ligne) + if not m: + continue + ident = int(m.group(1)) + if ident not in ATTENDU: + continue + champs = [ident] + [c.strip() for c in m.group(2).split(',')] + if len(champs) != len(COLS): + sys.exit('ERREUR : %d champs pour le chemin %d, %d attendus' + % (len(champs), ident, len(COLS))) + trouves[ident].append(dict(zip(COLS, champs))) + +for ident, n in sorted(ATTENDU.items()): + reel = len(trouves[ident]) + if reel != n: + sys.exit('ERREUR : chemin %d -> %d points extraits, %d attendus.' + % (ident, reel, n)) + +total = sum(len(v) for v in trouves.values()) + +L = [] +L.append('-- ' + '=' * 69) +L.append("-- Assaut du Rivage brisé (carte 1666) — les 5 chemins scriptés") +L.append('--') +L.append("-- Ces chemins vivent dans `waypoint_data_script` chez la référence,") +L.append("-- une table que notre cœur n'a pas : sWaypointMgr ne lit que") +L.append("-- `waypoint_data`. L'extraction du 27/08 ne regardait que cette") +L.append("-- dernière et les avait donc manqués — d'où des alliés immobiles et,") +L.append("-- plus grave, un corbeau arcanique qui ne décollait jamais.") +L.append('--') +L.append("-- Le 11322708 est le vol d'arrivée : c'est l'atteinte de son point 20") +L.append("-- qui déclenche l'étape 0 du scénario. Sans lui, rien ne démarre.") +L.append('--') +L.append("-- move_flag → move_type : la valeur est 1 sur les %d points." % total) +L.append("-- Notre énumération donne 0=marche, 1=course, 2=atterrissage,") +L.append("-- 3=décollage ; la course correspond. `speed` et `entry` n'ont") +L.append("-- aucun équivalent chez nous et sont abandonnées.") +L.append('--') +for ident, n in sorted(ATTENDU.items()): + L.append('-- %d : %2d points' % (ident, n)) +L.append('-- ' + '=' * 69) +L.append('') +L.append('DELETE FROM `waypoint_data` WHERE `id` BETWEEN 11322705 AND 11322709;') +L.append('') +L.append('INSERT INTO `waypoint_data` (`id`,`point`,`position_x`,`position_y`,' + '`position_z`,`orientation`,`delay`,`move_type`,`action`,`action_chance`,`wpguid`) VALUES') + +lignes = [] +for ident in sorted(ATTENDU): + for r in sorted(trouves[ident], key=lambda x: int(x['point'])): + lignes.append('(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,0)' % ( + r['id'], r['point'], r['position_x'], r['position_y'], r['position_z'], + r['orientation'], r['delay'], r['move_flag'], r['action'], r['action_chance'])) + +L.append(',\n'.join(lignes) + ';') + +io.open('/tmp/rivage_1666_chemins.sql', 'w', encoding='utf-8').write('\n'.join(L) + '\n') +print('%d points extraits sur %d chemins, controles conformes' % (total, len(ATTENDU))) diff --git a/sql/sylvania/rivage_1666_chemins.sql b/sql/sylvania/rivage_1666_chemins.sql new file mode 100644 index 0000000..d9bf7eb --- /dev/null +++ b/sql/sylvania/rivage_1666_chemins.sql @@ -0,0 +1,114 @@ +-- ===================================================================== +-- Assaut du Rivage brisé (carte 1666) — les 5 chemins scriptés +-- +-- Ces chemins vivent dans `waypoint_data_script` chez la référence, +-- une table que notre cœur n'a pas : sWaypointMgr ne lit que +-- `waypoint_data`. L'extraction du 27/08 ne regardait que cette +-- dernière et les avait donc manqués — d'où des alliés immobiles et, +-- plus grave, un corbeau arcanique qui ne décollait jamais. +-- +-- Le 11322708 est le vol d'arrivée : c'est l'atteinte de son point 20 +-- qui déclenche l'étape 0 du scénario. Sans lui, rien ne démarre. +-- +-- move_flag → move_type : la valeur est 1 sur les 87 points. +-- Notre énumération donne 0=marche, 1=course, 2=atterrissage, +-- 3=décollage ; la course correspond. `speed` et `entry` n'ont +-- aucun équivalent chez nous et sont abandonnées. +-- +-- 11322705 : 24 points +-- 11322706 : 18 points +-- 11322707 : 23 points +-- 11322708 : 21 points +-- 11322709 : 1 points +-- ===================================================================== + +DELETE FROM `waypoint_data` WHERE `id` BETWEEN 11322705 AND 11322709; + +INSERT INTO `waypoint_data` (`id`,`point`,`position_x`,`position_y`,`position_z`,`orientation`,`delay`,`move_type`,`action`,`action_chance`,`wpguid`) VALUES +(11322705,1,-1895.48,3181.44,3.94356,0,0,1,0,100,0), +(11322705,2,-1895.48,3181.44,3.94356,0,0,1,0,100,0), +(11322705,3,-1896.09,3194.66,3.0589,0,0,1,0,100,0), +(11322705,4,-1879.45,3226.71,5.81127,0,0,1,0,100,0), +(11322705,5,-1855.82,3233.47,14.6567,0,0,1,0,100,0), +(11322705,6,-1857.58,3265.25,25.8483,0,0,1,0,100,0), +(11322705,7,-1867.03,3288.59,32.05,0,0,1,0,100,0), +(11322705,8,-1852.08,3304.66,37.4348,0,0,1,0,100,0), +(11322705,9,-1839.8,3322.75,40.8837,0,0,1,0,100,0), +(11322705,10,-1839.04,3343.29,42.466,0,0,1,0,100,0), +(11322705,11,-1836.7,3363.89,45.3895,0,0,1,0,100,0), +(11322705,12,-1850.58,3384.72,46.7613,0,0,1,0,100,0), +(11322705,13,-1825.16,3371.11,48.1092,0,0,1,0,100,0), +(11322705,14,-1800.48,3368.29,52.986,0,0,1,0,100,0), +(11322705,15,-1781.35,3380.74,58.1253,0,0,1,0,100,0), +(11322705,16,-1767.17,3386.07,61.2137,0,0,1,0,100,0), +(11322705,17,-1749.35,3385.12,65.4982,0,0,1,0,100,0), +(11322705,18,-1743.22,3376.4,66.7185,0,0,1,0,100,0), +(11322705,19,-1733.85,3361.1,69.025,0,0,1,0,100,0), +(11322705,20,-1727.28,3348.62,73.3198,0,0,1,0,100,0), +(11322705,21,-1719.09,3337.02,76.6384,0,0,1,0,100,0), +(11322705,22,-1710.71,3331.34,79.6968,0,0,1,0,100,0), +(11322705,23,-1693.39,3326.75,83.6209,0,0,1,0,100,0), +(11322705,24,-1665.47,3322.98,84.213,0,0,1,0,100,0), +(11322706,1,-1661.46,3342.18,84.7206,0,0,1,0,100,0), +(11322706,2,-1661.46,3342.18,84.7206,0,0,1,0,100,0), +(11322706,3,-1643.34,3358.99,91.1839,0,0,1,0,100,0), +(11322706,4,-1614.3,3365.34,99.9272,0,0,1,0,100,0), +(11322706,5,-1590.46,3353.51,107.609,0,0,1,0,100,0), +(11322706,6,-1568.88,3331.64,108.463,0,0,1,0,100,0), +(11322706,7,-1544.19,3323.67,108.452,0,0,1,0,100,0), +(11322706,8,-1527.78,3312.48,108.452,0,0,1,0,100,0), +(11322706,9,-1516.94,3278.18,108.456,0,0,1,0,100,0), +(11322706,10,-1502.84,3232.22,108.308,0,0,1,0,100,0), +(11322706,11,-1499.05,3199.12,94.6383,0,0,1,0,100,0), +(11322706,12,-1506.8,3164.73,85.6659,0,0,1,0,100,0), +(11322706,13,-1512.61,3132.76,85.6753,0,0,1,0,100,0), +(11322706,14,-1504.42,3115.11,83.3742,0,0,1,0,100,0), +(11322706,15,-1491.75,3090.42,72.0224,0,0,1,0,100,0), +(11322706,16,-1472.63,3055.67,53.7606,0,0,1,0,100,0), +(11322706,17,-1463.55,3041.25,48.0012,0,0,1,0,100,0), +(11322706,18,-1443.16,3018.53,36.9585,0,0,1,0,100,0), +(11322707,1,-1426.19,2996.66,37.1142,0,0,1,0,100,0), +(11322707,2,-1426.19,2996.66,37.1142,0,0,1,0,100,0), +(11322707,3,-1420.86,2984.12,35.987,0,0,1,0,100,0), +(11322707,4,-1414.93,2950.44,36.6867,0,0,1,0,100,0), +(11322707,5,-1402.61,2927.92,31.2816,0,0,1,0,100,0), +(11322707,6,-1382.3,2939.2,25.4175,0,0,1,0,100,0), +(11322707,7,-1359.37,2925.01,23.9701,0,0,1,0,100,0), +(11322707,8,-1348.45,2910.15,24.3147,0,0,1,0,100,0), +(11322707,9,-1351.97,2894.62,24.8695,0,0,1,0,100,0), +(11322707,10,-1350.41,2876.29,24.9108,0,0,1,0,100,0), +(11322707,11,-1344.7,2850.11,25.1457,0,0,1,0,100,0), +(11322707,12,-1325.29,2832.26,24.2901,0,0,1,0,100,0), +(11322707,13,-1290.08,2823.92,26.1421,0,0,1,0,100,0), +(11322707,14,-1318.28,2814.4,23.9136,0,0,1,0,100,0), +(11322707,15,-1321.91,2792.99,23.499,0,0,1,0,100,0), +(11322707,16,-1316.96,2769.79,23.9586,0,0,1,0,100,0), +(11322707,17,-1316.57,2747.45,24.3456,0,0,1,0,100,0), +(11322707,18,-1333.14,2704.89,30.3677,0,0,1,0,100,0), +(11322707,19,-1256.09,2718.4,27.7485,0,0,1,0,100,0), +(11322707,20,-1276.42,2664.57,28.3643,0,0,1,0,100,0), +(11322707,21,-1291.27,2637.64,32.6723,0,0,1,0,100,0), +(11322707,22,-1227.07,2659.19,27.5531,0,0,1,0,100,0), +(11322707,23,-1199.42,2661.52,29.5922,0,0,1,0,100,0), +(11322708,1,-850.003,4176.37,751.63,0,0,1,0,100,0), +(11322708,2,-898.993,4094.4,725.061,0,0,1,0,100,0), +(11322708,3,-970.247,3975.18,686.417,0,0,1,0,100,0), +(11322708,4,-1044.75,3879.26,652.558,0,0,1,0,100,0), +(11322708,5,-1151.45,3694.79,592.72,0,0,1,0,100,0), +(11322708,6,-1195.84,3493.36,546.951,0,0,1,0,100,0), +(11322708,7,-1225.72,3286.17,564.777,0,0,1,0,100,0), +(11322708,8,-1194.84,3183.06,550.694,0,0,1,0,100,0), +(11322708,9,-1057.13,3080.14,501.312,0,0,1,0,100,0), +(11322708,10,-1007.26,2974.9,455.169,0,0,1,0,100,0), +(11322708,11,-1019.28,2874.84,399.954,0,0,1,0,100,0), +(11322708,12,-1100.81,2821.6,336.706,0,0,1,0,100,0), +(11322708,13,-1194.32,2756.01,271.367,0,0,1,0,100,0), +(11322708,14,-1242.8,2712.92,285.178,0,0,1,0,100,0), +(11322708,15,-1365.37,2573.14,261.881,0,0,1,0,100,0), +(11322708,16,-1418.23,2507.59,254.915,0,0,1,0,100,0), +(11322708,17,-1495.32,2527.83,191.514,0,0,1,0,100,0), +(11322708,18,-1589.36,2571.82,132.87,0,0,1,0,100,0), +(11322708,19,-1660.26,2642.93,75.1266,0,0,1,0,100,0), +(11322708,20,-1719.28,2723.04,41.4641,0,0,1,0,100,0), +(11322708,21,-1716.3,2809.05,4,0,0,1,0,100,0), +(11322709,1,-1338.13,2800.24,23.0393,0,0,1,0,100,0); diff --git a/sql/sylvania/rivage_1666_donneur.sql b/sql/sylvania/rivage_1666_donneur.sql new file mode 100644 index 0000000..ef1bf1a --- /dev/null +++ b/sql/sylvania/rivage_1666_donneur.sql @@ -0,0 +1,30 @@ +-- ===================================================================== +-- Assaut du Rivage brisé — le donneur manquant de « Begin the Assault » +-- +-- La quête 45102 n'avait NI donneur NI receveur en base : introuvable en +-- jeu, quoi qu'on fasse. C'était l'une des trois quêtes sans donneur +-- relevées à l'audit du 23/08. +-- +-- Le dump de référence (dufernst/LegionCore-7.3.5) donne pour elle +-- l'archimage Khadgar 116302, à la fois donneur et receveur. Ce PNJ est +-- DÉJÀ posé chez nous, sur la carte 1220 à (-1628, 3194) — exactement +-- les coordonnées de la référence — et porte déjà le drapeau de donneur +-- de quêtes (npcflag 3). Rien à faire apparaître : seule la déclaration +-- manquait. +-- +-- 46734 reçoit aussi 116302, comme dans la référence, en plus du 86563 +-- déjà déclaré chez nous. Deux points d'accès, aucun conflit. +-- +-- Rappel de la chaîne, pour mémoire : +-- 46730 « Armies of Legionfall » 86563 (déjà câblée) +-- 45102 « Begin the Assault » 116302 (posée ici) +-- 46734 « Assault on Broken Shore » 86563 + 116302 +-- ===================================================================== + +DELETE FROM `creature_queststarter` WHERE `quest` = 45102 AND `id` = 116302; +DELETE FROM `creature_questender` WHERE `quest` = 45102 AND `id` = 116302; +DELETE FROM `creature_queststarter` WHERE `quest` = 46734 AND `id` = 116302; +DELETE FROM `creature_questender` WHERE `quest` = 46734 AND `id` = 116302; + +INSERT INTO `creature_queststarter` (`id`,`quest`) VALUES (116302, 45102), (116302, 46734); +INSERT INTO `creature_questender` (`id`,`quest`) VALUES (116302, 45102), (116302, 46734); diff --git a/sql/sylvania/rivage_1666_scripts.sql b/sql/sylvania/rivage_1666_scripts.sql new file mode 100644 index 0000000..8098d59 --- /dev/null +++ b/sql/sylvania/rivage_1666_scripts.sql @@ -0,0 +1,39 @@ +-- ===================================================================== +-- Assaut du Rivage brisé (carte 1666) — rattachement des scripts +-- +-- Le C++ porté (scenario_assault_broken_shore.cpp) ne sert à rien tant +-- que ses scripts ne sont liés à aucune entrée : c'est la classe de bug +-- la plus fréquente de ce cœur, des IA complètes jamais appelées. +-- +-- PORTÉE VÉRIFIÉE : les onze entrées ci-dessous n'existent QUE sur la +-- carte 1666 — contrôlé par un GROUP BY sur (id, map). Un ScriptName +-- s'appliquant à toutes les cartes, ce contrôle n'est pas facultatif. +-- +-- NE SONT PAS RATTACHÉES, volontairement : Arganoth (118551), +-- Mephistroth (120746), Illidan (119130) et Lord Kalgorath (116291). +-- L'instance les pilote depuis OnCreatureCreate et OnCreatureKilled ; +-- leur donner un ScriptName ferait double emploi. +-- +-- CONCORDANCE RELEVÉE, pas supposée : +-- 120743 Arcane Bomb → 7 exemplaires posés, critère 57574 × 7 +-- 118558 Legion Portal → 7 exemplaires, dont 3 à fermer (critère 56778) +-- ===================================================================== + +-- Les huit alliés qui accompagnent l'assaut : rotations de sorts et +-- recherche d'adversaire à portée. +UPDATE `creature_template` SET `ScriptName` = 'npc_assaut_allie' + WHERE `entry` IN (119133, 118412, 121232, 118966, 121146, 118969, 118945, 118444); + +-- Le corbeau arcanique : véhicule d'arrivée, sa fin de trajet vaut +-- l'étape 0 du scénario. Aucun spawn en base, il est invoqué par sort. +UPDATE `creature_template` SET `ScriptName` = 'npc_assaut_corbeau_arcanique' + WHERE `entry` = 118517; + +-- Portails de la Légion et bombes arcaniques : activés au clic de sort. +UPDATE `creature_template` SET `ScriptName` = 'npc_assaut_interactif' + WHERE `entry` IN (118558, 120743); + +-- La passerelle démoniaque, objet de type 10 (goober) : notre cœur ne +-- lui offre que le point d'entrée OnGossipHello. +UPDATE `gameobject_template` SET `ScriptName` = 'go_assaut_passerelle_demoniaque' + WHERE `entry` = 267955; diff --git a/src/server/scripts/BrokenIsles/Scenario/scenario_assault_broken_shore.cpp b/src/server/scripts/BrokenIsles/Scenario/scenario_assault_broken_shore.cpp new file mode 100644 index 0000000..264451d --- /dev/null +++ b/src/server/scripts/BrokenIsles/Scenario/scenario_assault_broken_shore.cpp @@ -0,0 +1,772 @@ +/* + * Scenario : Assaut du Rivage brise (ScenarioID 1280, carte 1666) + * + * Contenu de la 7.2, a ne pas confondre avec le scenario d'INTRODUCTION + * de la 7.0 (ScenarioID 786, carte 1460, scenario_broken_shore_intro.cpp) + * ou l'on suit Varian, Vol'jin, Krosus et Gul'dan. Celui-ci est l'assaut + * mene depuis Dalaran, et c'est LUI dont dependent les quetes bloquees + * 45102 « Begin the Assault » et 46734 « Assault on Broken Shore ». + * + * PORTAGE, PAS TRANSCRIPTION + * Source : dufernst/LegionCore-7.3.5 (AssaultBrokenShore.cpp 500 lignes + + * instance_AssaultBrokenShore.cpp 308 lignes), meme lignee uwow que notre + * fork. Son architecture repose sur des extensions que nous n'avons PAS : + * + * onScenarionNextStep(step) / getScenarionStep() hook de progression + * CRITERIA_TYPE_SCRIPT_EVENT_2 type de critere + * FunctionProcessor ordonnanceur + * GetClosestGraveYard(x,y,z) surcharge d'instance + * + * Aucun de ces quatre elements n'existe ici. La progression est donc + * inversee : au lieu d'etre RAPPELE a chaque etape, le script alimente + * les criteres officiels et le moteur avance de lui-meme, exactement + * comme sur la carte 1460. + * + * CRITERES OFFICIELS, releves dans les DB2 du build 7.3.5.26972 + * (ScenarioStep / CriteriaTree / Criteria), et non recopies de la source : + * + * ordre arbre critere type asset montant intitule + * 0 56811 35285 92 56460 1 Into the Fray + * 1 56872 35328 92 56697 3 Vanguard of the Assault + * 2 56874 35329 92 56500 1 Might of the Legion + * 3 57180 35496 92 56779 200 Rifts of Chaos (barre) + * 57185 35495 73 56778 3 ... + les 3 portails + * 4 57183 35497 92 56780 1 The Doomguard's Command + * 5 57293 35551 68 267955 1 Gateway to Ruin + * 6 58303 36178 92 57574 7 Pillar of Fire + * 7 59170 36179 92 57579 1 Mephistroth + * + * type 92 = CRITERIA_TYPE_SEND_EVENT_SCENARIO -> DoSendEventScenario + * type 73 = CRITERIA_TYPE_SEND_EVENT -> UpdateCriteria + * type 68 = CRITERIA_TYPE_USE_GAMEOBJECT -> UpdateCriteria + * + * Les identifiants d'asset de la source coincident avec les officiels : + * verification faite, ils sont conserves. + * + * ECARTS D'API TRAITES, tous constates dans notre code et non supposes : + * DamageTaken(Unit*, uint32&) 2 arguments, pas 3 + * OnSpellClick(Unit*, bool&) 2 arguments, pas 1 + * EnterEvadeMode(EvadeReason) prend un motif + * GameObjectScript::OnGossipHello(Player*, GameObject*) + * MotionMaster::MovePath(id, repeatable) 2 arguments, pas 4 + * PlayerList : GetSource(), pas getSource() + * CreateConversation n'existe QUE sur Player -- la source l'appelle + * sur une creature, cela n'aurait pas compile. + * + * NON PORTE, deliberement : player_scripts_for_start_assault, qui + * sondait CHAQUE joueur a chaque tick pour lui imposer la quete 46730. + * Un donneur de quete en base fait le meme travail sans le cout. + * + * Le nom d'enregistrement « scenario_7.2_broken_shore_intro » est celui + * qu'exige deja instance_template pour la carte 1666. Il contient un + * point, donc ne peut pas passer par la macro RegisterInstanceScript : + * la classe est ecrite a la main pour ne pas avoir a toucher la base. + */ + +#include "ScriptMgr.h" +#include "InstanceScript.h" +#include "Scenario.h" +#include "InstanceScenario.h" +#include "ScriptedCreature.h" +#include "MotionMaster.h" +#include "Player.h" +#include "GameObject.h" +#include "GameObjectAI.h" +#include "TemporarySummon.h" +#include "ObjectAccessor.h" +#include "Conversation.h" + +enum AssautCreatures +{ + // hostiles nommes + NPC_KALGORATH = 116291, // etape 2 + NPC_ARGANOTH = 118551, // etape 4, invisible au depart + NPC_MEPHISTROTH = 120746, // etape 7, invisible au depart + NPC_ILLIDAN = 119130, + + // allies accompagnant l'assaut + NPC_ALLIE_MAIEV = 119133, + NPC_ALLIE_MAGE_KIRIN_TOR = 118412, + NPC_ALLIE_BLOOMER = 121232, + NPC_ALLIE_CHAMAN = 118966, + NPC_ALLIE_GRAND_PRETRE = 121146, + NPC_ALLIE_ILLIDARI = 118969, + NPC_ALLIE_HAUTE_GARDE = 118945, + NPC_ALLIE_GARDIEN_KIRIN_TOR = 118444, + + // vehicule d'arrivee + NPC_CORBEAU_ARCANIQUE = 118517, + + // interactifs + NPC_PORTAIL_LEGION = 118558, // etape 3, a fermer x3 + NPC_BOMBE_ARCANIQUE = 120743, // etape 6, a poser x7 + + NPC_INFERNAL_INTRO = 118416, +}; + +// Elites de la plage : etape 1, trois suffisent. +uint32 const ELITES_PLAGE[] = { 118654, 118453, 118886, 118456 }; + +// Pietaille comptant pour la barre de progression de l'etape 3. +uint32 const PIETAILLE_RIFTS[] = +{ + 120511, 118711, 118644, 118647, 118645, 118656, 118655, 118708, + 118658, 118563, 118659, 118646, 118650, 118648, 118652, 118653, + 118707, 118688, 118457, 118416 +}; + +enum AssautCriteres +{ + ASSET_ARRIVEE = 56460, // etape 0, x1 + ASSET_ELITE_PLAGE = 56697, // etape 1, x3 + ASSET_KALGORATH = 56500, // etape 2, x1 + ASSET_PIETAILLE = 56779, // etape 3, barre de 200 + ASSET_PORTAIL_FERME = 56778, // etape 3, x3 (type 73) + ASSET_ARGANOTH = 56780, // etape 4, x1 + ASSET_PASSERELLE = 267955,// etape 5, x1 (type 68) + ASSET_BOMBE = 57574, // etape 6, x7 + ASSET_MEPHISTROTH = 57579, // etape 7, x1 +}; + +enum AssautConversations +{ + CONV_ARRIVEE = 4828, + CONV_DEBARQUEMENT = 4829, + CONV_PLAGE_1 = 4831, + CONV_PLAGE_2 = 4832, + CONV_KALGORATH = 4887, + CONV_MEPHISTROTH = 4870, + CONV_FINALE = 4892, + CONV_ENTREE = 4526, +}; + +enum AssautSorts +{ + SPELL_ENTREE_AURA = 240155, + SPELL_ENTREE_INVOCATION = 243303, + SPELL_ENTREE_A_RETIRER = 240188, + SPELL_MONTER_CORBEAU = 52391, + SPELL_PASSERELLE = 236671, + SPELL_FINALE = 243824, + SPELL_INFERNAL_1 = 205266, + SPELL_INFERNAL_2 = 239649, + SPELL_MAGE_REACTION = 46604, + SPELL_BOMBE_VISUEL = 144373, +}; + +enum AssautChemins +{ + CHEMIN_CORBEAU = 11322708, + CHEMIN_ALLIES_PLAGE = 11322705, + CHEMIN_ALLIES_APRES_MAIEV = 11322706, + CHEMIN_ALLIES_ARGANOTH = 11322709, + CHEMIN_ALLIES_DEBUT = 11322707, +}; + +// Signaux envoyes a l'instance par les scripts de creature et d'objet. +// On passe par SetData plutot que d'appeler DoSendEventScenario depuis +// l'exterieur : l'instance reste seule maitresse de la progression. +enum AssautSignaux +{ + DATA_ARRIVEE_ACCOMPLIE = 9201, + DATA_PORTAIL_FERME = 9202, + DATA_BOMBE_POSEE = 9203, + DATA_PASSERELLE_UTILISEE = 9204, +}; + +enum AssautQuetes +{ + QUETE_ASSAUT = 46734, + CREDIT_SPIRE_1 = 116253, // « First Legion Spire destroyed » + CREDIT_SPIRE_2 = 116279, // « Second Legion Spire destroyed » + CREDIT_KHADGAR = 120215, // « Speak to Khadgar » +}; + +static bool EstDansListe(uint32 entree, uint32 const* liste, size_t taille) +{ + for (size_t i = 0; i < taille; ++i) + if (liste[i] == entree) + return true; + return false; +} + +class instance_assaut_rivage_brise : public InstanceMapScript +{ +public: + instance_assaut_rivage_brise() : InstanceMapScript("scenario_7.2_broken_shore_intro", 1666) { } + + struct instance_assaut_rivage_brise_InstanceMapScript : public InstanceScript + { + instance_assaut_rivage_brise_InstanceMapScript(InstanceMap* map) : InstanceScript(map) { } + + GuidList allies; + ObjectGuid arganothGuid; + ObjectGuid illidanGuid; + ObjectGuid mephistrothGuid; + + uint32 elitesTues = 0; + uint32 portailsFermes = 0; + uint32 bombesPosees = 0; + bool arganothRevele = false; + bool mephistrothRevele = false; + + void OnCreatureCreate(Creature* creature) override + { + switch (creature->GetEntry()) + { + case NPC_ALLIE_MAIEV: + case NPC_ALLIE_MAGE_KIRIN_TOR: + case NPC_ALLIE_BLOOMER: + case NPC_ALLIE_CHAMAN: + case NPC_ALLIE_GRAND_PRETRE: + case NPC_ALLIE_ILLIDARI: + case NPC_ALLIE_HAUTE_GARDE: + case NPC_ALLIE_GARDIEN_KIRIN_TOR: + allies.push_back(creature->GetGUID()); + break; + case NPC_ARGANOTH: + // Reste hors d'atteinte jusqu'a l'etape 4. + creature->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC | UNIT_FLAG_IMMUNE_TO_PC); + creature->SetVisible(false); + arganothGuid = creature->GetGUID(); + break; + case NPC_ILLIDAN: + illidanGuid = creature->GetGUID(); + break; + case NPC_MEPHISTROTH: + creature->SetVisible(false); + mephistrothGuid = creature->GetGUID(); + break; + default: + break; + } + } + + void OnPlayerEnter(Player* player) override + { + if (!player) + return; + + player->CastSpell(player, SPELL_ENTREE_AURA, true); + + player->AddDelayedEvent(5000, [player]() -> void + { + player->CastSpell(player, SPELL_ENTREE_INVOCATION, true); + + std::list escorte; + GetCreatureListWithEntryInGrid(escorte, player, NPC_ALLIE_MAGE_KIRIN_TOR, 50.0f); + for (Creature* c : escorte) + c->CastSpell(c, SPELL_ENTREE_INVOCATION, true); + + player->AddDelayedEvent(4000, [player]() -> void + { + player->RemoveAura(SPELL_ENTREE_A_RETIRER); + Conversation::CreateConversation(CONV_ENTREE, player, player->GetPosition(), { player->GetGUID() }); + }); + }); + } + + // Diffuse une conversation a tous les joueurs de l'instance. + // La source la jouait pour le PREMIER joueur seulement (break dans + // la boucle) : correct en solo, muet pour les autres en groupe. + void ConversationPourTous(uint32 conversationId) + { + Map::PlayerList const& liste = instance->GetPlayers(); + for (Map::PlayerList::const_iterator i = liste.begin(); i != liste.end(); ++i) + if (Player* player = i->GetSource()) + Conversation::CreateConversation(conversationId, player, player->GetPosition(), { player->GetGUID() }); + } + + void CritereJoueurs(CriteriaTypes type, uint32 asset) + { + Map::PlayerList const& liste = instance->GetPlayers(); + for (Map::PlayerList::const_iterator i = liste.begin(); i != liste.end(); ++i) + if (Player* player = i->GetSource()) + player->UpdateCriteria(type, asset); + } + + void CreditJoueurs(uint32 creditEntry) + { + Map::PlayerList const& liste = instance->GetPlayers(); + for (Map::PlayerList::const_iterator i = liste.begin(); i != liste.end(); ++i) + if (Player* player = i->GetSource()) + player->KilledMonsterCredit(creditEntry); + } + + void DeplacerAllies(uint32 cheminId) + { + for (ObjectGuid const& guid : allies) + if (Creature* allie = instance->GetCreature(guid)) + { + allie->GetMotionMaster()->Clear(); + allie->GetMotionMaster()->MovePath(cheminId, false); + } + } + + void SetData(uint32 type, uint32 /*data*/) override + { + switch (type) + { + case DATA_ARRIVEE_ACCOMPLIE: + // Etape 0 : le corbeau a depose le joueur sur la plage. + DoSendEventScenario(ASSET_ARRIVEE); + ConversationPourTous(CONV_DEBARQUEMENT); + DeplacerAllies(CHEMIN_ALLIES_PLAGE); + break; + + case DATA_PORTAIL_FERME: + // Etape 3 : critere de type 73, trois portails. + CritereJoueurs(CRITERIA_TYPE_SEND_EVENT, ASSET_PORTAIL_FERME); + ++portailsFermes; + + // Les deux premiers portails valent aussi les credits + // de la quete 45102, qui parle de « First » et + // « Second Legion Spire destroyed ». + if (portailsFermes == 1) + CreditJoueurs(CREDIT_SPIRE_1); + else if (portailsFermes == 2) + CreditJoueurs(CREDIT_SPIRE_2); + break; + + case DATA_BOMBE_POSEE: + // Etape 6 : sept bombes. + ++bombesPosees; + DoSendEventScenario(ASSET_BOMBE); + break; + + case DATA_PASSERELLE_UTILISEE: + // Etape 5 : critere de type 68. + CritereJoueurs(CRITERIA_TYPE_USE_GAMEOBJECT, ASSET_PASSERELLE); + break; + + default: + break; + } + } + + void OnCreatureKilled(Creature* creature, Player* /*killer*/) override + { + if (!creature) + return; + + uint32 const entree = creature->GetEntry(); + + if (entree == NPC_KALGORATH) + { + // Etape 2. Le credit de quete porte la meme entree : la + // mort suffit, aucun credit supplementaire a accorder. + DoSendEventScenario(ASSET_KALGORATH); + ConversationPourTous(CONV_KALGORATH); + DeplacerAllies(CHEMIN_ALLIES_APRES_MAIEV); + return; + } + + if (entree == NPC_ARGANOTH) + { + // Etape 4. + DoSendEventScenario(ASSET_ARGANOTH); + RevelerMephistroth(); + return; + } + + if (entree == NPC_MEPHISTROTH) + { + // Etape 7, fin du scenario. + DoSendEventScenario(ASSET_MEPHISTROTH); + Finale(); + return; + } + + if (EstDansListe(entree, ELITES_PLAGE, std::extent::value)) + { + // Etape 1 : trois elites. + ++elitesTues; + DoSendEventScenario(ASSET_ELITE_PLAGE); + + if (elitesTues == 1) + ConversationPourTous(CONV_PLAGE_1); + else if (elitesTues == 2) + ConversationPourTous(CONV_PLAGE_2); + else if (elitesTues >= 3) + RevelerArganoth(); + return; + } + + if (EstDansListe(entree, PIETAILLE_RIFTS, std::extent::value)) + { + // Etape 3 : barre de progression de 200. La source + // envoyait 2 a 3 unites par mort ; on conserve ce rythme, + // faute de source officielle sur la valeur exacte. + uint32 const pas = urand(2, 3); + for (uint32 i = 0; i < pas; ++i) + DoSendEventScenario(ASSET_PIETAILLE); + return; + } + } + + void RevelerArganoth() + { + if (arganothRevele) + return; + arganothRevele = true; + + Creature* arganoth = instance->GetCreature(arganothGuid); + if (!arganoth) + return; + + arganoth->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC | UNIT_FLAG_IMMUNE_TO_PC); + arganoth->SetVisible(true); + arganoth->GetMotionMaster()->Clear(); + + if (Creature* illidan = instance->GetCreature(illidanGuid)) + { + arganoth->GetMotionMaster()->MovePoint(0, illidan->GetPositionX(), illidan->GetPositionY(), illidan->GetPositionZ()); + illidan->AI()->AttackStart(arganoth); + arganoth->AI()->AttackStart(illidan); + } + + DeplacerAllies(CHEMIN_ALLIES_ARGANOTH); + } + + void RevelerMephistroth() + { + if (mephistrothRevele) + return; + mephistrothRevele = true; + + if (Creature* mephistroth = instance->GetCreature(mephistrothGuid)) + mephistroth->SetVisible(true); + + // CreateConversation n'existe que sur Player dans ce coeur : + // la source l'appelait sur la creature, ce qui n'aurait pas + // compile. On la joue pour les joueurs presents. + ConversationPourTous(CONV_MEPHISTROTH); + } + + void Finale() + { + Map::PlayerList const& liste = instance->GetPlayers(); + for (Map::PlayerList::const_iterator i = liste.begin(); i != liste.end(); ++i) + { + Player* player = i->GetSource(); + if (!player) + continue; + + player->AddDelayedEvent(4000, [player]() -> void + { + Conversation::CreateConversation(CONV_FINALE, player, player->GetPosition(), { player->GetGUID() }); + player->CastSpell(player, SPELL_FINALE, true); + player->KilledMonsterCredit(CREDIT_KHADGAR); + }); + } + } + }; + + InstanceScript* GetInstanceScript(InstanceMap* map) const override + { + return new instance_assaut_rivage_brise_InstanceMapScript(map); + } +}; + +// Les allies qui accompagnent l'assaut. Chacun a sa rotation propre, +// reprise telle quelle de la source : ce sont des donnees, pas des choix. +class npc_assaut_allie : public CreatureScript +{ +public: + npc_assaut_allie() : CreatureScript("npc_assaut_allie") { } + + struct npc_assaut_allieAI : public ScriptedAI + { + npc_assaut_allieAI(Creature* creature) : ScriptedAI(creature) + { + instance = me->GetInstanceScript(); + } + + InstanceScript* instance; + EventMap events; + + void Reset() override + { + events.Reset(); + } + + void EnterCombat(Unit* /*who*/) override + { + switch (me->GetEntry()) + { + case NPC_ALLIE_MAIEV: + DoCast(225184); + events.RescheduleEvent(1, 6000); + break; + case NPC_ALLIE_MAGE_KIRIN_TOR: + events.RescheduleEvent(2, 2500); + events.RescheduleEvent(3, 18000); + break; + case NPC_ALLIE_BLOOMER: + DoCast(79833); + events.RescheduleEvent(4, 2300); + events.RescheduleEvent(5, 14000); + break; + case NPC_ALLIE_CHAMAN: + events.RescheduleEvent(6, 4000); + events.RescheduleEvent(7, 35000); + events.RescheduleEvent(8, 11000); + events.RescheduleEvent(9, 14000); + break; + case NPC_ALLIE_GRAND_PRETRE: + events.RescheduleEvent(10, 3000); + events.RescheduleEvent(11, 13000); + events.RescheduleEvent(12, 15000); + break; + case NPC_ALLIE_ILLIDARI: + events.RescheduleEvent(13, 7000); + events.RescheduleEvent(14, 27000); + events.RescheduleEvent(15, 21000); + events.RescheduleEvent(16, 31000); + break; + case NPC_ALLIE_HAUTE_GARDE: + events.RescheduleEvent(17, 4000); + events.RescheduleEvent(18, 9000); + events.RescheduleEvent(19, 39000); + events.RescheduleEvent(20, 31000); + break; + case NPC_ALLIE_GARDIEN_KIRIN_TOR: + events.RescheduleEvent(21, 3000); + events.RescheduleEvent(22, 20000); + break; + default: + break; + } + } + + // Les allies doivent tenir jusqu'au bout de l'assaut : les degats + // recus sont divises d'autant plus fort qu'ils sont bas en vie. + // Formule reprise de la source ; le diviseur vaut au minimum 1, + // donc pas de division par zero. + void DamageTaken(Unit* /*attacker*/, uint32& damage) override + { + uint32 const diviseur = 11 - uint32(me->GetHealthPct() / 10.0f); + damage /= std::max(1, diviseur); + } + + void MovementInform(uint32 type, uint32 /*id*/) override + { + me->SetHomePosition(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), me->GetOrientation()); + + if (type == WAYPOINT_MOTION_TYPE) + ChercherUnAdversaire(); + } + + // Cherche un hostile a portee et l'engage. + bool ChercherUnAdversaire() + { + std::list hostiles; + for (uint32 entree : PIETAILLE_RIFTS) + GetCreatureListWithEntryInGrid(hostiles, me, entree, 40.0f); + for (uint32 entree : ELITES_PLAGE) + GetCreatureListWithEntryInGrid(hostiles, me, entree, 40.0f); + + for (Creature* cible : hostiles) + if (cible->IsAlive()) + { + AttackStart(cible); + return true; + } + + return false; + } + + void UpdateAI(uint32 diff) override + { + if (me->HasUnitState(UNIT_STATE_STUNNED)) + return; + + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + if (uint32 eventId = events.ExecuteEvent()) + { + struct Rotation { uint32 evenement; uint32 sort; uint32 rappel; }; + static Rotation const rotations[] = + { + { 1, 226063, 6000 }, + { 2, 183108, 2500 }, { 3, 183075, 18000 }, + { 4, 182856, 2300 }, { 5, 182854, 14000 }, + { 6, 190330, 4000 }, { 7, 189804, 35000 }, + { 8, 190332, 11000 }, { 9, 190331, 14000 }, + { 10, 183555, 3000 }, { 11, 183553, 13000 }, { 12, 183549, 15000 }, + { 13, 222971, 7000 }, { 14, 223061, 27000 }, { 15, 223109, 21000 }, { 16, 223903, 31000 }, + { 17, 183894, 4000 }, { 18, 183897, 9000 }, { 19, 183898, 39000 }, { 20, 190010, 31000 }, + { 21, 183108, 3000 }, { 22, 13730, 20000 }, + }; + + for (Rotation const& r : rotations) + if (r.evenement == eventId) + { + DoCast(r.sort); + events.RescheduleEvent(r.evenement, r.rappel); + break; + } + } + + DoMeleeAttackIfReady(); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_assaut_allieAI(creature); + } +}; + +// 118517 : le corbeau arcanique qui depose le joueur sur la plage. +// Sa fin de trajet vaut l'etape 0 du scenario. +class npc_assaut_corbeau_arcanique : public CreatureScript +{ +public: + npc_assaut_corbeau_arcanique() : CreatureScript("npc_assaut_corbeau_arcanique") { } + + struct npc_assaut_corbeau_arcaniqueAI : public ScriptedAI + { + npc_assaut_corbeau_arcaniqueAI(Creature* creature) : ScriptedAI(creature) { } + + void IsSummonedBy(Unit* summoner) override + { + SetFlyMode(true); + if (summoner) + summoner->CastSpell(me, SPELL_MONTER_CORBEAU, true); + } + + void PassengerBoarded(Unit* /*passager*/, int8 /*seatId*/, bool /*apply*/) override + { + Creature* moi = me; + me->AddDelayedEvent(4000, [moi]() -> void + { + moi->GetMotionMaster()->MovePath(CHEMIN_CORBEAU, false); + }); + } + + void MovementInform(uint32 moveType, uint32 pointId) override + { + if (moveType != WAYPOINT_MOTION_TYPE || pointId != 20) + return; + + Unit* proprietaire = me->GetAnyOwner(); + if (proprietaire && proprietaire->IsPlayer()) + { + Conversation::CreateConversation(CONV_ARRIVEE, proprietaire, proprietaire->GetPosition(), { proprietaire->GetGUID() }); + + if (InstanceScript* instance = me->GetInstanceScript()) + instance->SetData(DATA_ARRIVEE_ACCOMPLIE, 1); + + // Un infernal s'ecrase a l'arrivee, et un mage du Kirin Tor + // y reagit : mise en scene reprise de la source. + if (Creature* infernal = me->SummonCreature(NPC_INFERNAL_INTRO, me->GetPositionX() + 5.0f, me->GetPositionY() + 5.0f, me->GetPositionZ())) + { + infernal->CastSpell(infernal, SPELL_INFERNAL_1, true); + infernal->CastSpell(infernal, SPELL_INFERNAL_2, true); + + if (Creature* mage = infernal->FindNearestCreature(NPC_ALLIE_MAGE_KIRIN_TOR, 30.0f, true)) + { + mage->CastSpell(mage, SPELL_MAGE_REACTION, true); + mage->AI()->Talk(0); + } + } + } + + me->DespawnOrUnsummon(500); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_assaut_corbeau_arcaniqueAI(creature); + } +}; + +// 118558 : portail de la Legion a fermer (etape 3, trois fois). +// 120743 : bombe arcanique a poser (etape 6, sept fois). +// Les deux s'activent au clic de sort. +class npc_assaut_interactif : public CreatureScript +{ +public: + npc_assaut_interactif() : CreatureScript("npc_assaut_interactif") { } + + struct npc_assaut_interactifAI : public ScriptedAI + { + npc_assaut_interactifAI(Creature* creature) : ScriptedAI(creature) + { + me->SetReactState(REACT_PASSIVE); + me->SetFlag64(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK); + } + + void MoveInLineOfSight(Unit* /*who*/) override { } + void UpdateAI(uint32 /*diff*/) override { } + + void OnSpellClick(Unit* clicker, bool& /*result*/) override + { + if (!clicker || !clicker->IsPlayer()) + return; + + // Un seul usage : on retire le drapeau avant tout traitement. + me->RemoveFlag64(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK); + + InstanceScript* instance = me->GetInstanceScript(); + if (!instance) + return; + + if (me->GetEntry() == NPC_PORTAIL_LEGION) + instance->SetData(DATA_PORTAIL_FERME, 1); + else if (me->GetEntry() == NPC_BOMBE_ARCANIQUE) + { + instance->SetData(DATA_BOMBE_POSEE, 1); + me->RemoveAura(SPELL_BOMBE_VISUEL); + } + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_assaut_interactifAI(creature); + } +}; + +// 267955 : la passerelle demoniaque menant au vaisseau amiral (etape 5). +class go_assaut_passerelle_demoniaque : public GameObjectScript +{ +public: + go_assaut_passerelle_demoniaque() : GameObjectScript("go_assaut_passerelle_demoniaque") { } + + bool OnGossipHello(Player* player, GameObject* go) override + { + if (!player || !go) + return false; + + InstanceScript* instance = go->GetInstanceScript(); + if (!instance) + return false; + + // Tout le groupe franchit la passerelle ensemble. + Map::PlayerList const& liste = go->GetMap()->GetPlayers(); + for (Map::PlayerList::const_iterator i = liste.begin(); i != liste.end(); ++i) + if (Player* p = i->GetSource()) + p->CastSpell(p, SPELL_PASSERELLE, true); + + instance->SetData(DATA_PASSERELLE_UTILISEE, 1); + return true; + } +}; + +void AddSC_scenario_assaut_rivage_brise() +{ + new instance_assaut_rivage_brise(); + new npc_assaut_allie(); + new npc_assaut_corbeau_arcanique(); + new npc_assaut_interactif(); + new go_assaut_passerelle_demoniaque(); +} diff --git a/src/server/scripts/BrokenIsles/brokenisles_script_loader.cpp b/src/server/scripts/BrokenIsles/brokenisles_script_loader.cpp index 60142be..c4b22db 100644 --- a/src/server/scripts/BrokenIsles/brokenisles_script_loader.cpp +++ b/src/server/scripts/BrokenIsles/brokenisles_script_loader.cpp @@ -220,6 +220,7 @@ void AddSC_scenario_the_violet_hold_dh(); void AddSC_scenario_mage_art(); void AddSC_scenario_monk_serenitys_end(); void AddSC_scenario_broken_shore_intro(); +void AddSC_scenario_assaut_rivage_brise(); void AddSC_scenario_artifact_runner(); // The name of this function should match: @@ -422,5 +423,6 @@ void AddBrokenIslesScripts() AddSC_scenario_mage_art(); AddSC_scenario_monk_serenitys_end(); AddSC_scenario_broken_shore_intro(); + AddSC_scenario_assaut_rivage_brise(); AddSC_scenario_artifact_runner(); }