From a95670ac0a5b395d0378b1a3b2b400a6d8079f15 Mon Sep 17 00:00:00 2001 From: BlaMacfly Date: Mon, 31 Aug 2026 23:34:53 +0200 Subject: [PATCH] Rivage brise : portage de l'Assaut 7.2 (scenario 1280, carte 1666) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La carte 1666 etait declaree mais entierement vide, et instance_template y reclamait un script fantome. Les quetes 45102 « Begin the Assault » et 46734 « Assault on Broken Shore » etaient donc infranchissables : elles relevent de ce scenario, et non de l'introduction 7.0 deja portee sur la carte 1460. PORTAGE, PAS TRANSCRIPTION. La source (dufernst/LegionCore-7.3.5, meme lignee uwow) repose sur quatre extensions que nous n'avons pas : onScenarionNextStep/getScenarionStep, CRITERIA_TYPE_SCRIPT_EVENT_2, FunctionProcessor et une surcharge de GetClosestGraveYard. La progression est donc inversee : au lieu d'etre rappele a chaque etape, le script alimente les criteres officiels et le moteur avance seul -- la meme architecture que sur la carte 1460. Les neuf criteres des huit etapes ont ete releves dans les DB2 du build 7.3.5.26972 (ScenarioStep, CriteriaTree, Criteria) et non recopies de la source ; ils confirment ses identifiants d'asset. Sept sont de type 92 (SEND_EVENT_SCENARIO), un de type 73, un de type 68. Six ecarts d'API traites, tous constates et non supposes : InstanceScript(InstanceMap*), Conversation::CreateConversation (Player n'a pas cette methode -- la source l'appelait sur une creature, cela n'aurait pas compile), UNIT_NPC_FLAGS avec SetFlag64, DamageTaken a 2 arguments, OnSpellClick a 2, MovePath a 2. NON PORTE : player_scripts_for_start_assault, qui sondait chaque joueur a chaque tick pour lui imposer la quete 46730. Un donneur en base fait le meme travail sans ce cout. SQL joint : - rattachement des 5 scripts a leurs 11 entrees, toutes verifiees exclusives a la carte 1666 ; - les 87 points des 5 chemins scriptes. Ils vivaient dans waypoint_data_script, table absente de notre schema, d'ou leur oubli lors de l'extraction du 27/08. Le 11322708 est le vol d'arrivee : l'atteinte de son point 20 declenche l'etape 0, sans lui rien ne demarrait ; - le donneur manquant de 45102, l'archimage Khadgar 116302, deja pose chez nous aux coordonnees de la reference mais jamais declare. Les 593 creatures et 59 objets de la carte etaient generes depuis le 27/08 et n'avaient jamais ete appliques ; ils le sont desormais. RESERVE : rien de tout ceci n'a ete verifie en jeu. Les credits de quete 116253 et 116279 sont en outre cables sur la fermeture des deux premiers portails par deduction -- la quete parle de « First » et « Second Legion Spire destroyed » -- et non sur une donnee. Co-Authored-By: Claude Opus 5 --- .../outils/extraire_chemins_scriptes.py | 98 +++ sql/sylvania/rivage_1666_chemins.sql | 114 +++ sql/sylvania/rivage_1666_donneur.sql | 30 + sql/sylvania/rivage_1666_scripts.sql | 39 + .../scenario_assault_broken_shore.cpp | 772 ++++++++++++++++++ .../BrokenIsles/brokenisles_script_loader.cpp | 2 + 6 files changed, 1055 insertions(+) create mode 100644 chantiers/rivage_brise/outils/extraire_chemins_scriptes.py create mode 100644 sql/sylvania/rivage_1666_chemins.sql create mode 100644 sql/sylvania/rivage_1666_donneur.sql create mode 100644 sql/sylvania/rivage_1666_scripts.sql create mode 100644 src/server/scripts/BrokenIsles/Scenario/scenario_assault_broken_shore.cpp 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(); }