From 15ac8dbd7a19b544da3a436d32ac98cdd3d72d7f Mon Sep 17 00:00:00 2001 From: SylvaniaCore deploy Date: Thu, 27 Aug 2026 23:33:32 +0200 Subject: [PATCH] Chantier Rivage brise : materiel de reference et outillage d extraction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rien n est applique au serveur. Ce commit ne fait que persister le travail de reperage, qui vivait dans /home/ubuntu/tmp et aurait ete perdu. LE BLOCAGE. instance_template reclame pour la carte 1666 un script nomme scenario_7.2_broken_shore_intro qui n existe nulle part dans notre code -- script fantome. La table scenarios n a aucune ligne pour cette carte. Les quetes 45102 et 46734 sont donc infranchissables. RASSEMBLE depuis dufernst/LegionCore-7.3.5 (meme lignee uwow) : - AssaultBrokenShore.cpp (500 lignes) et instance_AssaultBrokenShore.cpp (308 lignes) ; - reperage de 593 placements de creatures (67 entrees) et 59 placements d objets (52 entrees) sur la carte 1666, dans le dump world du depot. NOTRE BASE possede deja les 67 modeles de creatures et les 52 modeles d objets -- aucun manquant. Elle possede aussi les repliques francaises de Vol jin (90708) et Sylvanas (90709), qui n ont ni spawn ni script. DECISIONS DE TRANSPOSITION etablies sur donnees, pas supposees : - spawnMask 4096 = 2^12 -> spawnDifficulties '12', la difficulte 12 etant « Normal Scenario » d apres Difficulty.db2 du build 7.3.5.26972 ; - GUID renumerotes au-dessus de nos maxima (290000114 / 210120986) ; - huit colonnes de la reference sans equivalent chez nous, abandonnees. DEPENDANCES DECOUVERTES : 180 des 593 creatures ont MovementType 2 et suivent donc des chemins a extraire aussi ; 12 portent un AiID renvoyant a une table propre au coeur de reference, comportement a refaire. PIEGE CONSIGNE dans le README : le premier extracteur renvoyait zero ligne, ce qui aurait fait conclure a tort que la reference etait vide. Les instructions insert du dump s etalent sur plusieurs lignes. C est un controle sur cas positif connu qui a revele le bug. Co-Authored-By: Claude Opus 5 --- chantiers/rivage_brise/README.md | 81 +++ .../rivage_brise/outils/extraire_1666_v2.py | 115 ++++ .../rivage_brise/outils/transposer_1666.py | 130 +++++ .../reference/AssaultBrokenShore.cpp | 501 ++++++++++++++++++ .../reference/broken_shore_scenario.sql | 16 + .../reference/instance_AssaultBrokenShore.cpp | 308 +++++++++++ 6 files changed, 1151 insertions(+) create mode 100644 chantiers/rivage_brise/README.md create mode 100644 chantiers/rivage_brise/outils/extraire_1666_v2.py create mode 100644 chantiers/rivage_brise/outils/transposer_1666.py create mode 100644 chantiers/rivage_brise/reference/AssaultBrokenShore.cpp create mode 100644 chantiers/rivage_brise/reference/broken_shore_scenario.sql create mode 100644 chantiers/rivage_brise/reference/instance_AssaultBrokenShore.cpp diff --git a/chantiers/rivage_brise/README.md b/chantiers/rivage_brise/README.md new file mode 100644 index 0000000..cc494da --- /dev/null +++ b/chantiers/rivage_brise/README.md @@ -0,0 +1,81 @@ +# Chantier — Assaut du Rivage brisé (carte 1666) + +État au 27/08/2026. **Rien n'est appliqué au serveur à ce stade.** + +## Le blocage d'origine + +`instance_template` réclame pour la carte 1666 un script nommé +`scenario_7.2_broken_shore_intro` qui **n'existe nulle part dans notre code** — +script fantôme. La table `scenarios` n'a par ailleurs aucune ligne pour cette +carte. Conséquence : les quêtes `45102 Begin the Assault` et +`46734 Assault on Broken Shore` sont infranchissables. + +## Ce qui a été rassemblé + +Source : `dufernst/LegionCore-7.3.5` (même lignée uwow que notre fork). + +| Élément | Volume | Emplacement | +|---|---|---| +| Script du scénario | 500 lignes | `reference/AssaultBrokenShore.cpp` | +| Script d'instance | 308 lignes | `reference/instance_AssaultBrokenShore.cpp` | +| Placements de créatures | **593 spawns**, 67 entrées | à extraire du dump | +| Placements d'objets | **59 spawns**, 52 entrées | à extraire du dump | + +Le dump complet (`LegionCore_world_2020_04_25.zip`, 39 Mo compressés / 232 Mo) +n'est **pas** versionné : il se retélécharge depuis le dépôt de référence, +chemin `sql/base/`. + +## Ce que notre base possède déjà + +- Les **67 modèles de créatures** : présents, aucun manquant. +- Les **52 modèles d'objets** : présents, aucun manquant. +- Les répliques françaises de Vol'jin (90708) et Sylvanas (90709), y compris + la blessure empoisonnée qui déclenche la retraite. Ces deux-là n'ont + cependant **aucun spawn ni script**. + +## Décisions de transposition, établies sur données + +Les deux schémas divergent. Correspondances retenues : + +| Référence | Nous | Décision | +|---|---|---| +| `spawnMask = 4096` | `spawnDifficulties` | **`'12'`** — 4096 = 2¹², et la difficulté 12 est « Normal Scenario » (type instance 5) d'après `Difficulty.db2` du build 7.3.5.26972 | +| `phaseMask = 1` | `phaseUseFlags`, `PhaseGroup` | valeurs neutres : aucun phasage réel à transposer | +| `PhaseId` | `PhaseId` | direct — vaut `''` sauf 3 créatures à `7043` | +| `guid` | `guid` | **renumérotés** au-dessus de nos maxima (créature 290000114, objet 210120986) | +| — | `terrainSwapMap` | `-1`, valeur employée par nos autres instances | +| `npcflag2`, `AiID`, `MovementID`, `MeleeID`, `isActive`, `skipClone`, `personal_size`, `isTeemingSpawn` | — | **abandonnées**, ces colonnes n'existent pas chez nous | + +## Dépendances découvertes, à ne pas oublier + +1. **180 créatures sur 593 ont `MovementType = 2`** : elles suivent des + chemins. Sans extraction de `waypoint_data` pour leurs GUID, elles + apparaîtront figées. +2. **12 créatures portent `AiID = 7424`**, une table d'IA propre au cœur de + référence que nous n'avons pas. Ce comportement sera **perdu** et devra + être refait autrement — probablement en SmartAI. +3. Le dump contient `scenario_data`, `scenario_poi`, `scenario_poi_points` et + `scenario_step_spells` : la déclaration du scénario s'y trouve aussi. +4. Le script s'enregistre sous `instance_AssaultBrokenShore` alors que notre + `instance_template` réclame `scenario_7.2_broken_shore_intro`. À raccorder. + +## Piège rencontré, à retenir + +Le premier extracteur écrit pour ce travail renvoyait **zéro ligne** pour la +carte 1666, ce qui aurait fait conclure à tort que la référence ne contenait +rien. Cause : dans ce dump, une instruction `insert into` s'étale sur +plusieurs lignes, et l'analyse ligne à ligne ne voyait que la première. + +C'est un contrôle de cohérence — vérifier que le parseur trouvait bien des +lignes sur des cartes connues — qui a révélé le bug. **Toujours valider un +extracteur sur un cas positif connu avant de croire un résultat négatif.** + +## Reste à faire + +1. Extraire et transposer les 593 + 59 placements. +2. Extraire les chemins des 180 créatures mobiles. +3. Transposer la déclaration du scénario. +4. Porter les 808 lignes de C++ et les adapter à notre API, qui a divergé + (`MoveJump`, `SetFacingTo`, `GetEffect` diffèrent déjà — constaté ailleurs). +5. Raccorder le nom de script. +6. Refaire autrement le comportement des 12 créatures à `AiID`. diff --git a/chantiers/rivage_brise/outils/extraire_1666_v2.py b/chantiers/rivage_brise/outils/extraire_1666_v2.py new file mode 100644 index 0000000..92de12b --- /dev/null +++ b/chantiers/rivage_brise/outils/extraire_1666_v2.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- +"""Extraction carte 1666 — version corrigee : une instruction INSERT +s'etale sur plusieurs lignes, on accumule jusqu'au point-virgule final.""" +import io, re +from collections import Counter + +CHEMIN = '/home/ubuntu/tmp/abs/LegionCore_world_2020_04_25.sql' +motif = re.compile(r"insert\s+into\s+`(creature|gameobject)`\(([^)]*)\)\s+values\s*", re.I) + + +def decoupe_tuples(bloc): + tuples, courant, prof, chaine, echap = [], [], 0, False, False + for c in bloc: + if chaine: + courant.append(c) + if echap: + echap = False + elif c == '\\': + echap = True + elif c == "'": + chaine = False + continue + if c == "'": + chaine = True + courant.append(c) + elif c == '(': + prof += 1 + if prof == 1: + courant = [] + else: + courant.append(c) + elif c == ')': + prof -= 1 + if prof == 0: + tuples.append(''.join(courant)) + else: + courant.append(c) + elif prof > 0: + courant.append(c) + return tuples + + +def champs(t): + vals, courant, chaine, echap = [], [], False, False + for c in t: + if chaine: + courant.append(c) + if echap: + echap = False + elif c == '\\': + echap = True + elif c == "'": + chaine = False + continue + if c == "'": + chaine = True + courant.append(c) + elif c == ',': + vals.append(''.join(courant).strip()) + courant = [] + else: + courant.append(c) + vals.append(''.join(courant).strip()) + return vals + + +stats = {} + +with io.open(CHEMIN, encoding='utf-8', errors='replace') as f: + ligne = f.readline() + while ligne: + m = motif.match(ligne.lstrip()) + if not m: + ligne = f.readline() + continue + + table = m.group(1).lower() + cols = [c.strip().strip('`') for c in m.group(2).split(',')] + + # accumuler jusqu'au ';' final + morceaux = [ligne[ligne.lower().find('values') + 6:]] + while not morceaux[-1].rstrip().endswith(';'): + suite = f.readline() + if not suite: + break + morceaux.append(suite) + bloc = ''.join(morceaux) + + i_map = cols.index('map') + i_id = cols.index('id') + + s = stats.setdefault(table, {'cols': cols, 'total': 0, 'cartes': Counter(), 'l1666': []}) + for t in decoupe_tuples(bloc): + v = champs(t) + if len(v) != len(cols): + continue + s['total'] += 1 + s['cartes'][v[i_map]] += 1 + if v[i_map] == '1666': + s['l1666'].append(v) + + ligne = f.readline() + +for table, s in stats.items(): + print('=== %s ===' % table) + print(' tuples analyses : %d' % s['total']) + print(' cartes les plus peuplees : %s' % ', '.join('%s(%d)' % c for c in s['cartes'].most_common(6))) + print(' >>> CARTE 1666 : %d lignes' % len(s['l1666'])) + if s['l1666']: + i_id = s['cols'].index('id') + c = Counter(l[i_id] for l in s['l1666']) + print(' %d entrees distinctes ; top 15 :' % len(c)) + for e, n in c.most_common(): + print(' entree %-8s x%d' % (e, n)) + print() diff --git a/chantiers/rivage_brise/outils/transposer_1666.py b/chantiers/rivage_brise/outils/transposer_1666.py new file mode 100644 index 0000000..ed30f9b --- /dev/null +++ b/chantiers/rivage_brise/outils/transposer_1666.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +"""Transpose les placements de la carte 1666 du dump de reference vers +NOTRE schema. Ne touche a rien : produit un rapport et un fichier SQL. + +Les deux schemas divergent, la correspondance est donc explicite : + + reference nous + --------- ---- + guid guid (RENUMEROTE, voir plus bas) + id, map, zoneId, areaId identiques + spawnMask spawnDifficulties (a decider, voir rapport) + phaseMask -- non transposable, on laisse les valeurs neutres + PhaseId PhaseId + modelid .. dynamicflags identiques + unit_flags3 unit_flags3 + npcflag2, AiID, -- ABANDONNES : ces colonnes n'existent pas chez + MovementID, MeleeID, nous. AiID et MovementID renvoient a des tables + isActive, skipClone, propres a ce coeur ; les abandonner fait perdre + personal_size, une part du comportement, a recreer autrement. + isTeemingSpawn + +Les GUID sont renumerotes au-dessus de nos maxima (creature 290000114, +gameobject 210120986) pour ne heurter aucun spawn existant. +""" +import io, re, sys +from collections import Counter + +CHEMIN = '/home/ubuntu/tmp/abs/LegionCore_world_2020_04_25.sql' +SORTIE = '/home/ubuntu/tmp/abs/rivage_brise_spawns.sql' + +GUID_CREATURE_DEPART = 290100000 +GUID_GO_DEPART = 210200000 + +# colonnes de NOTRE schema +NOS_CREATURE = ['guid','id','map','zoneId','areaId','spawnDifficulties','phaseUseFlags','PhaseId', + 'PhaseGroup','terrainSwapMap','modelid','equipment_id','position_x','position_y', + 'position_z','orientation','spawntimesecs','spawndist','currentwaypoint','curhealth', + 'curmana','MovementType','npcflag','unit_flags','unit_flags2','unit_flags3', + 'dynamicflags','ScriptName','movementmode','VerifiedBuild'] + +NOS_GO = ['guid','id','map','zoneId','areaId','spawnDifficulties','phaseUseFlags','PhaseId', + 'PhaseGroup','terrainSwapMap','position_x','position_y','position_z','orientation', + 'rotation0','rotation1','rotation2','rotation3','spawntimesecs','animprogress','state', + 'isActive','ScriptName','VerifiedBuild'] + + +def decoupe_tuples(bloc): + res, cur, prof, chaine, echap = [], [], 0, False, False + for c in bloc: + if chaine: + cur.append(c) + if echap: echap = False + elif c == '\\': echap = True + elif c == "'": chaine = False + continue + if c == "'": + chaine = True; cur.append(c) + elif c == '(': + prof += 1 + if prof == 1: cur = [] + else: cur.append(c) + elif c == ')': + prof -= 1 + if prof == 0: res.append(''.join(cur)) + else: cur.append(c) + elif prof > 0: + cur.append(c) + return res + + +def champs(t): + vals, cur, chaine, echap = [], [], False, False + for c in t: + if chaine: + cur.append(c) + if echap: echap = False + elif c == '\\': echap = True + elif c == "'": chaine = False + continue + if c == "'": + chaine = True; cur.append(c) + elif c == ',': + vals.append(''.join(cur).strip()); cur = [] + else: + cur.append(c) + vals.append(''.join(cur).strip()) + return vals + + +motif = re.compile(r"insert\s+into\s+`(creature|gameobject)`\(([^)]*)\)\s+values\s*", re.I) +recolte = {'creature': {'cols': None, 'lignes': []}, 'gameobject': {'cols': None, 'lignes': []}} + +with io.open(CHEMIN, encoding='utf-8', errors='replace') as f: + ligne = f.readline() + while ligne: + m = motif.match(ligne.lstrip()) + if not m: + ligne = f.readline(); continue + table = m.group(1).lower() + cols = [c.strip().strip('`') for c in m.group(2).split(',')] + morceaux = [ligne[ligne.lower().find('values') + 6:]] + while not morceaux[-1].rstrip().endswith(';'): + s = f.readline() + if not s: break + morceaux.append(s) + i_map = cols.index('map') + recolte[table]['cols'] = cols + for t in decoupe_tuples(''.join(morceaux)): + v = champs(t) + if len(v) == len(cols) and v[i_map] == '1666': + recolte[table]['lignes'].append(v) + ligne = f.readline() + +# ------------------------------------------------------------------ +# RAPPORT : ce que je ne peux pas transposer mecaniquement +# ------------------------------------------------------------------ +print('================= RAPPORT DE TRANSPOSITION =================') +for table in ('creature', 'gameobject'): + r = recolte[table] + print('\n--- %s : %d lignes ---' % (table, len(r['lignes']))) + if not r['lignes']: + continue + cols = r['cols'] + for nom in ('spawnMask', 'phaseMask', 'PhaseId', 'spawntimesecs', 'MovementType', 'AiID', 'MovementID', 'isActive'): + if nom in cols: + i = cols.index(nom) + c = Counter(l[i] for l in r['lignes']) + print(' %-14s : %s' % (nom, ', '.join('%s(x%d)' % x for x in c.most_common(6)))) + +print('\n(la generation SQL n est PAS lancee : on decide d abord de spawnDifficulties)') diff --git a/chantiers/rivage_brise/reference/AssaultBrokenShore.cpp b/chantiers/rivage_brise/reference/AssaultBrokenShore.cpp new file mode 100644 index 0000000..fde8bc3 --- /dev/null +++ b/chantiers/rivage_brise/reference/AssaultBrokenShore.cpp @@ -0,0 +1,501 @@ +/* + BrokenShore +*/ + +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "CreatureTextMgr.h" +#include "GameObjectAI.h" +#include "QuestData.h" +#include "ObjectVisitors.hpp" + +// + Smart: 122105 120515 118444 119130 +// 119133, 118412, 121232, 118966, 121146, 118969, 118945 +class npc_broken_shore_misc : public CreatureScript +{ +public: + npc_broken_shore_misc() : CreatureScript("npc_broken_shore_misc") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_broken_shore_miscAI(creature); + } + + struct npc_broken_shore_miscAI : ScriptedAI + { + npc_broken_shore_miscAI(Creature* creature) : ScriptedAI(creature) + { + instance = me->GetInstanceScript(); + justSawByPlayer = false; // if player didn't saw this npc, then npc is 'freezing' + } + + EventMap events; + InstanceScript* instance; + FunctionProcessor _functions; + bool justSawByPlayer; + + void Reset() override + { + events.Reset(); + } + + void EnterCombat(Unit* who) override + { + switch(me->GetEntry()) + { + case 119133: + events.RescheduleEvent(EVENT_1, 6000); // 226063 + DoCast(225184); + break; + case 118412: + events.RescheduleEvent(EVENT_2, 2500); // 183108 + events.RescheduleEvent(EVENT_3, 18000); // 183075 + break; + case 121232: + events.RescheduleEvent(EVENT_4, 2300); // 182856 + events.RescheduleEvent(EVENT_5, 14000); // 182854 (friendly) + DoCast(79833); + break; + case 118966: + events.RescheduleEvent(EVENT_6, 4000); // 190330 + events.RescheduleEvent(EVENT_7, 35000); // 189804 + events.RescheduleEvent(EVENT_8, 11000); // 190332 + events.RescheduleEvent(EVENT_9, 14000); // 190331 + break; + case 121146: + events.RescheduleEvent(EVENT_10, 3000); // 183555 + events.RescheduleEvent(EVENT_11, 13000); // 183553 + events.RescheduleEvent(EVENT_12, 15000); // 183549 (friendly) + break; + case 118969: + events.RescheduleEvent(EVENT_13, 7000); // 222971 + events.RescheduleEvent(EVENT_14, 27000); // 223061 + events.RescheduleEvent(EVENT_15, 21000); // 223109 + events.RescheduleEvent(EVENT_16, 31000); // 223903 + break; + case 118945: + events.RescheduleEvent(EVENT_17, 4000); // 183894 + events.RescheduleEvent(EVENT_18, 9000); // 183897 + events.RescheduleEvent(EVENT_19, 39000); // 183898 + events.RescheduleEvent(EVENT_20, 31000); // 190010 + break; + case 118444: + events.RescheduleEvent(EVENT_21, 3000); // 183108 + events.RescheduleEvent(EVENT_22, 20000); // 13730 + break; + } + } + + void EnterEvadeMode() override + { + if (instance->getScenarionStep() == 1) + { + if (!DoFindAnyProblem()) + me->GetMotionMaster()->MovePoint(1, me->GetPositionX() + irand(-10, 10), me->GetPositionY() + 10.0f, me->GetPositionZ()); + } + } + + void DamageTaken(Unit* attacker, uint32& damage, DamageEffectType dmgType) override + { + damage /= (11-(me->GetHealthPct()/10)); + } + + void MovementInform(uint32 type, uint32 id) override + { + me->SetHomePosition(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ(), me->GetOrientation()); + if (type == WAYPOINT_MOTION_TYPE) + { + if (instance->getScenarionStep() == 3) // kill some npc after maiev + DoFindAnyProblem(); + } + else if (type == POINT_MOTION_TYPE) + { + if (id == 1) + { + EnterEvadeMode(); + } + } + } + + void MoveInLineOfSight(Unit* who) override + { + if (who->GetTypeId() != TYPEID_PLAYER) + return; + + if (!justSawByPlayer) + { + if (me->GetEntry() == 119133) + if (me->IsWithinDistInMap(who, 40.0f)) + instance->onScenarionNextStep(20); // custom + else + return; + + justSawByPlayer = true; + + switch(instance->getScenarionStep()) + { + case 0: + case 1: + { + if (!DoFindAnyProblem()) + me->GetMotionMaster()->MovePoint(1, me->GetPositionX() + irand(-10, 10), me->GetPositionY() + 10.0f, me->GetPositionZ()); + } + } + } + } + + void UpdateAI(uint32 diff) override + { + if (me->HasUnitState(UNIT_STATE_STUNNED)) // need for this + return; + + _functions.Update(diff); + + if (!UpdateVictim()) + return; + + events.Update(diff); + + if (me->HasUnitState(UNIT_STATE_CASTING)) + return; + + if (uint32 eventId = events.ExecuteEvent()) + { + switch (eventId) + { + case EVENT_1: + DoCast(226063); + events.RescheduleEvent(EVENT_1, 6000); // 226063 + break; + case EVENT_2: + DoCast(183108); + events.RescheduleEvent(EVENT_2, 2500); // 183108 + break; + case EVENT_3: + DoCast(183075); + events.RescheduleEvent(EVENT_3, 18000); // 183075 + break; + case EVENT_4: + DoCast(182856); + events.RescheduleEvent(EVENT_4, 2300); // 182856 + break; + case EVENT_5: + if (Unit* target = FindNearestFriendlyUnit()) + me->CastSpell(target, 182854); + events.RescheduleEvent(EVENT_5, 14000); // 182854 (friendly) + break; + case EVENT_6: + DoCast(190330); + events.RescheduleEvent(EVENT_6, 4000); // 190330 + break; + case EVENT_7: + DoCast(189804); + events.RescheduleEvent(EVENT_7, 35000); // 189804 + break; + case EVENT_8: + DoCast(190332); + events.RescheduleEvent(EVENT_8, 11000); // 190332 + break; + case EVENT_9: + DoCast(190331); + events.RescheduleEvent(EVENT_9, 14000); // 190331 + break; + case EVENT_10: + DoCast(183555); + events.RescheduleEvent(EVENT_10, 3000); // 183555 + break; + case EVENT_11: + DoCast(183553); + events.RescheduleEvent(EVENT_11, 13000); // 183553 + break; + case EVENT_12: + if (Unit* target = FindNearestFriendlyUnit()) + me->CastSpell(target, 183549); + events.RescheduleEvent(EVENT_12, 15000); // 183549 (friendly) + break; + case EVENT_13: + DoCast(222971); + events.RescheduleEvent(EVENT_13, 7000); // 222971 + break; + case EVENT_14: + DoCast(223061); + events.RescheduleEvent(EVENT_14, 27000); // 223061 + break; + case EVENT_15: + DoCast(223109); + events.RescheduleEvent(EVENT_15, 21000); // 223109 + break; + case EVENT_16: + DoCast(223903); + events.RescheduleEvent(EVENT_16, 31000); // 223903 + break; + case EVENT_17: + DoCast(183894); + events.RescheduleEvent(EVENT_17, 4000); // 183894 + break; + case EVENT_18: + DoCast(183897); + events.RescheduleEvent(EVENT_18, 9000); // 183897 + break; + case EVENT_19: + DoCast(183898); + events.RescheduleEvent(EVENT_19, 39000); // 183898 + break; + case EVENT_20: + DoCast(190010); + events.RescheduleEvent(EVENT_20, 31000); // 190010 + break; + case EVENT_21: + DoCast(183108); + events.RescheduleEvent(EVENT_21, 3000); // 183108 + break; + case EVENT_22: + DoCast(13730); + events.RescheduleEvent(EVENT_22, 20000); // 13730 + break; + } + } + DoMeleeAttackIfReady(); + } + + // custom functions + + bool DoFindAnyProblem() // try to find any mobs and start attack this + { + std::list trash; + GetCreatureListWithEntryInGrid(trash, me, 118416, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118456, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118457, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118886, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118453, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118654, 40.0f); + + GetCreatureListWithEntryInGrid(trash, me, 120511, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118711, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118644, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118647, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118645, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118656, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118655, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118708, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118658, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118563, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118659, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118646, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118650, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118648, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118652, 40.0f); + GetCreatureListWithEntryInGrid(trash, me, 118653, 40.0f); + for (std::list::iterator itr = trash.begin(); itr != trash.end(); ++itr) + { + if ((*itr)->isAlive()) + { + AttackStart((*itr)); + return true; + } + } + return false; + } + + Unit* FindNearestFriendlyUnit() // for healers + { + Unit* unit = NULL; + Trinity::AnyFriendlyUnitInObjectRangeCheck u_check(me, me, 20); + Trinity::UnitLastSearcher searcher(me, unit, u_check); + Trinity::VisitNearbyObject(me, 20, searcher); + return unit; + } + + }; +}; + + +// 118517 +class npc_arcane_raven : public CreatureScript +{ +public: + npc_arcane_raven() : CreatureScript("npc_arcane_raven") {} + + struct npc_arcane_ravenAI : ScriptedAI + { + npc_arcane_ravenAI(Creature* creature) : ScriptedAI(creature) {} + + void Reset() override {} + + void IsSummonedBy(Unit* summoner) override + { + SetFlyMode(true); + summoner->CastSpell(me, 52391, true); //Ride Veh + } + + void PassengerBoarded(Unit* who, int8 /*seatId*/, bool apply) override + { + bool isPlayer = who->IsPlayer(); + me->AddDelayedEvent(4000, [this, isPlayer]() -> void + { + if (isPlayer) + me->GetMotionMaster()->MovePath(11322708, false); + else + me->GetMotionMaster()->MovePath(11322708, false, irand(-15, 15), irand(-15, 15)); + }); + } + + void MovementInform(uint32 moveType, uint32 pointId) override + { + if (moveType != WAYPOINT_MOTION_TYPE) + return; + + if (pointId == 20 && me->GetAnyOwner()) + { + if (me->GetAnyOwner()->IsPlayer()) + { + me->GetAnyOwner()->CreateConversation(4828); + me->GetAnyOwner()->ToPlayer()->UpdateAchievementCriteria(CRITERIA_TYPE_SCRIPT_EVENT_2, 56460); + if (Creature* infernal = me->SummonCreature(118416, me->GetPositionX() + 5, me->GetPositionY() + 5, me->GetPositionZ())) + { + infernal->CastSpell(infernal, 205266); + if (Creature* mag = infernal->FindNearestCreature(118412, 30.0f, true)) + { + mag->CastSpell(mag, 46604); + mag->AI()->Talk(0); + } + infernal->CastSpell(infernal, 239649); + } + } + me->DespawnOrUnsummon(500); + } + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_arcane_ravenAI(creature); + } +}; + +// 118558 120743 +class npc_bs_legion_portal : public CreatureScript +{ +public: + npc_bs_legion_portal() : CreatureScript("npc_bs_legion_portal") { } + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_bs_legion_portalAI(creature); + } + + struct npc_bs_legion_portalAI : ScriptedAI + { + npc_bs_legion_portalAI(Creature* creature) : ScriptedAI(creature) + { + me->SetReactState(REACT_PASSIVE); + me->SetFlag(UNIT_FIELD_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK); + } + + void MoveInLineOfSight(Unit* who) override {} + + void OnSpellClick(Unit* clicker) override + { + if (!clicker->IsPlayer()) + return; + + me->RemoveFlag(UNIT_FIELD_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK); + if (me->GetEntry() == 118558) + { + clicker->ToPlayer()->UpdateAchievementCriteria(CRITERIA_TYPE_SCRIPT_EVENT, 56778); + // me->DespawnOrUnsummon(10); + } + else if (me->GetEntry() == 120743) + { + clicker->ToPlayer()->UpdateAchievementCriteria(CRITERIA_TYPE_SCRIPT_EVENT_2, 57574); + me->RemoveAura(144373); + } + } + + void UpdateAI(uint32 diff) override {} + }; +}; + +// 267955 +class go_bs_demoic_gates : public GameObjectScript +{ +public: + go_bs_demoic_gates() : GameObjectScript("go_bs_demoic_gates") { } + + struct go_bs_demoic_gatesAI : public GameObjectAI + { + go_bs_demoic_gatesAI(GameObject* go) : GameObjectAI(go){} + + + bool GossipHello(Player* player) override + { + if (InstanceScript* instance = go->GetInstanceScript()) + { + if (instance->getScenarionStep() < 5) + return false; + } + else + return false; + + Map::PlayerList const &PlList = player->GetMap()->GetPlayers(); + + if (PlList.isEmpty()) + return false; + + for (Map::PlayerList::const_iterator i = PlList.begin(); i != PlList.end(); ++i) + if (Player* pPlayer = i->getSource()) + { + pPlayer->CastSpell(pPlayer, 236671); + pPlayer->UpdateAchievementCriteria(CRITERIA_TYPE_USE_GAMEOBJECT, 267955); + } + return true; + } + }; + + GameObjectAI* GetAI(GameObject* go) const override + { + return new go_bs_demoic_gatesAI(go); + } +}; + +class player_scripts_for_start_assault : public PlayerScript +{ + public: + player_scripts_for_start_assault() : PlayerScript("player_scripts_for_start_assault") + { + checktimer = 1000; + } + uint32 checktimer; + + void OnUpdate(Player* player, uint32 diff) override + { + if (checktimer <= diff) + { + if (player) + if (player->getLevel() == 110) + if (player->GetQuestStatus(46730) == QUEST_STATUS_NONE) + { + Quest const* quest = sQuestDataStore->GetQuestTemplate(46730); + if (quest) + if (player->CanTakeQuest(quest, false) && player->CanAddQuest(quest, true)) + player->AddQuest(quest, NULL); + } + + if (player->GetQuestStatus(46730) != QUEST_STATUS_REWARDED) + checktimer = 1000; + } + else + checktimer -= diff; + } + +}; + + +void AddSC_AssaultBrokenShore() +{ + new npc_broken_shore_misc(); + new npc_arcane_raven(); + new npc_bs_legion_portal(); + new go_bs_demoic_gates(); + new player_scripts_for_start_assault(); +}; \ No newline at end of file diff --git a/chantiers/rivage_brise/reference/broken_shore_scenario.sql b/chantiers/rivage_brise/reference/broken_shore_scenario.sql new file mode 100644 index 0000000..e7b72a6 --- /dev/null +++ b/chantiers/rivage_brise/reference/broken_shore_scenario.sql @@ -0,0 +1,16 @@ +-- remove second LFG queue, already gets triggered by end of scene +-- INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (227058, 'spell_q42740'); +DELETE FROM spell_script_names WHERE spell_id = 227058 AND ScriptName = 'spell_q42740'; + +-- disable custom event (modified uwow Legion Invasion - Westfall/Barrens) +UPDATE game_event SET start_time = '0000-00-00 00:00:00', end_time = '0000-00-00 00:00:00', length = '1' WHERE eventEntry = 811; + +-- disable rep gain from always spawned world quest npc 92128 (Felskorn Pilferer) +-- was: INSERT INTO creature_onkill_reputation (`creature_id`, `RewFaction`, `RewValue`, `MaxStanding`) VALUES (92128, 1948, 75, 7); +DELETE FROM creature_onkill_reputation WHERE creature_id = 92128 AND RewFaction = 1948; + +-- fix droprate of greenies in Legion zones +-- was for world zones: UPDATE zone_loot_template SET ChanceOrQuestChance = 0.05 WHERE item = 463 AND entry IN (7334, 7502, 7503, 7541, 7543, 7558, 7578, 7637, 7656, 7731, 8392); +-- was for dungeons: UPDATE zone_loot_template SET ChanceOrQuestChance = 0.5 WHERE item = 463 AND entry IN (7546, 7588, 7672, 7673, 7787, 7796, 7805, 7812, 7855, 8017, 8025, 8026, 8040, 8079, 8262, 8277, 8443, 8570, 8596, 8625, 8646); +UPDATE zone_loot_template SET ChanceOrQuestChance = 2 WHERE item = 463 AND entry IN (7334, 7502, 7503, 7541, 7543, 7558, 7578, 7637, 7656, 7731, 8392); +UPDATE zone_loot_template SET ChanceOrQuestChance = 4 WHERE item = 463 AND entry IN (7546, 7588, 7672, 7673, 7787, 7796, 7805, 7812, 7855, 8017, 8025, 8026, 8040, 8079, 8262, 8277, 8443, 8570, 8596, 8625, 8646); diff --git a/chantiers/rivage_brise/reference/instance_AssaultBrokenShore.cpp b/chantiers/rivage_brise/reference/instance_AssaultBrokenShore.cpp new file mode 100644 index 0000000..0ad04e1 --- /dev/null +++ b/chantiers/rivage_brise/reference/instance_AssaultBrokenShore.cpp @@ -0,0 +1,308 @@ +/* + BrokenShore +*/ + +#include "ScriptMgr.h" +#include "ScriptedCreature.h" + +class instance_AssaultBrokenShore : public InstanceMapScript +{ +public: + instance_AssaultBrokenShore() : InstanceMapScript("instance_AssaultBrokenShore", 1666) { } + + InstanceScript* GetInstanceScript(InstanceMap* map) const override + { + return new instance_AssaultBrokenShore_InstanceMapScript(map); + } + + struct instance_AssaultBrokenShore_InstanceMapScript : public InstanceScript + { + instance_AssaultBrokenShore_InstanceMapScript(Map* map) : InstanceScript(map) {} + + WorldLocation loc_res_pla; // for respawn + + GuidList FriendlyMisc; + + ObjectGuid Arganot_guid; + ObjectGuid Illidan_guid; + ObjectGuid Mefistrot_guid; + + void Initialize() override + { + Arganot_guid.Clear(); + Mefistrot_guid.Clear(); + Illidan_guid.Clear(); + + FriendlyMisc.clear(); + } + + WorldLocation* GetClosestGraveYard(float x, float y, float z) override + // OnPlayerDeath + { + // Init data + loc_res_pla.Relocate(x, y, z); + loc_res_pla.SetMapId(1666); + + return &loc_res_pla; + } + + void OnUnitDeath(Unit* creature) override + { + switch (creature->GetEntry()) + { + case 116291: + updateCriteriaForPlayers(56500); + break; + case 118551: + updateCriteriaForPlayers(56780); + break; + // 1 + case 118654: + case 118453: + case 118886: + case 118456: + updateCriteriaForPlayers(56697); + break; + // 3 + case 120511: + case 118711: + case 118644: + case 118647: + case 118645: + case 118656: + case 118655: + case 118708: + case 118658: + case 118563: + case 118659: + case 118646: + case 118650: + case 118648: + case 118652: + case 118653: + case 118707: + case 118688: + case 118457: + case 118416: + for (uint8 i = 0; i < urand(2, 3); i++) + updateCriteriaForPlayers(56779); + break; + case 120746: + onScenarionNextStep(8); + break; + } + } + + void OnPlayerEnter(Player* player) override + { + if (!player) + return; + + player->CastSpell(player, 240155); + + player->AddDelayedEvent(5000, [player]() -> void + { + player->CastSpell(player, 243303); // summon + + std::list trash; + GetCreatureListWithEntryInGrid(trash, player, 118412, 50.0f); + for (std::list::iterator itr = trash.begin(); itr != trash.end(); ++itr) + (*itr)->CastSpell(*itr, 243303); // summon + + player->AddDelayedEvent(4000, [player]() -> void + { + player->RemoveAura(240188); + player->CreateConversation(4526); + }); + }); + } + + void updateCriteriaForPlayers(uint32 objective) + { + Map::PlayerList const &PlList = instance->GetPlayers(); + + if (PlList.isEmpty()) + return; + + for (Map::PlayerList::const_iterator i = PlList.begin(); i != PlList.end(); ++i) + if (Player* player = i->getSource()) + { + player->UpdateAchievementCriteria(CRITERIA_TYPE_SCRIPT_EVENT_2, objective); + break; + } + } + + void OnCreatureCreate(Creature* creature) override + { + switch (creature->GetEntry()) + { + case 119133: + case 118412: + case 121232: + case 118966: + case 121146: + case 118969: + case 118945: + case 118444: + FriendlyMisc.push_back(creature->GetGUID()); + break; + case 118551: + creature->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC | UNIT_FLAG_IMMUNE_TO_PC); + creature->SetVisible(false); + Arganot_guid = creature->GetGUID(); + break; + case 119130: + Illidan_guid = creature->GetGUID(); + break; + case 120746: + creature->SetVisible(false); + Mefistrot_guid = creature->GetGUID(); + break; + } + } + + void onScenarionNextStep(uint32 newStep) override + { + switch (newStep) + { + case 4: + { + if (Creature* arganot = instance->GetCreature(Arganot_guid)) + { + arganot->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC | UNIT_FLAG_IMMUNE_TO_PC); + arganot->SetVisible(true); + arganot->GetMotionMaster()->Clear(); + if (Creature* illidan = instance->GetCreature(Illidan_guid)) + { + arganot->GetMotionMaster()->MovePoint(0, illidan->GetPositionX(), illidan->GetPositionY(), illidan->GetPositionZ()); + illidan->AI()->AttackStart(arganot); + arganot->AI()->AttackStart(illidan); + } + } + for (auto guid : FriendlyMisc) + { + if (Creature* cre = instance->GetCreature(guid)) + { + cre->GetMotionMaster()->Clear(); + cre->GetMotionMaster()->MovePath(11322709, false, irand(-8, 8), irand(-8, 8)); + } + } + break; + } + case 20: // custom + { + for (auto guid : FriendlyMisc) + { + if (Creature* cre = instance->GetCreature(guid)) + { + cre->GetMotionMaster()->Clear(); + cre->GetMotionMaster()->MovePath(11322707, false, irand(-8, 8), irand(-8, 8)); + } + } + break; + } + case 2: + { + Map::PlayerList const &PlList = instance->GetPlayers(); + + if (PlList.isEmpty()) + return; + + for (Map::PlayerList::const_iterator i = PlList.begin(); i != PlList.end(); ++i) + if (Player* player = i->getSource()) + { + player->CreateConversation(4829); + player->AddDelayedEvent(5000, [player]() -> void + { + player->CreateConversation(4831); + }); + + player->AddDelayedEvent(7000, [player]() -> void + { + player->CreateConversation(4832); + }); + break; + } + + for (auto guid : FriendlyMisc) + { + if (Creature* cre = instance->GetCreature(guid)) + { + if (cre->GetPositionZ() <= 34.36f && cre->GetPositionX() <= -1524.42f) // only on the shore + { + cre->GetMotionMaster()->Clear(); + cre->GetMotionMaster()->MovePath(11322705, false, irand(-5, 5), irand(-5, 5)); + } + } + } + break; + } + case 3: + { + Map::PlayerList const &PlList = instance->GetPlayers(); + + if (PlList.isEmpty()) + return; + + for (Map::PlayerList::const_iterator i = PlList.begin(); i != PlList.end(); ++i) + if (Player* player = i->getSource()) + { + player->CreateConversation(4887); + break; + } + + for (auto guid : FriendlyMisc) + { + if (Creature* cre = instance->GetCreature(guid)) + { + if (cre->GetPositionY() >= 3144.0f) + { + cre->GetMotionMaster()->Clear(); + cre->GetMotionMaster()->MovePath(11322706, false, irand(-5, 5), irand(-5, 5)); + } + } + } + break; + } + case 7: + { + if (Creature* mefistrot = instance->GetCreature(Mefistrot_guid)) + { + mefistrot->SetVisible(true); + mefistrot->CreateConversation(4870); + } + break; + } + case 8: + { + Map::PlayerList const &PlList = instance->GetPlayers(); + + if (PlList.isEmpty()) + return; + + for (Map::PlayerList::const_iterator i = PlList.begin(); i != PlList.end(); ++i) + if (Player* player = i->getSource()) + { + player->AddDelayedEvent(4000, [player]() -> void + { + player->CreateConversation(4892); + player->CompleteQuest(46734); + }); + + player->AddDelayedEvent(4000, [player]() -> void + { + player->CastSpell(player, 243824); + }); + } + + break; + } + } + } + }; +}; + +void AddSC_instance_AssaultBrokenShore() +{ + new instance_AssaultBrokenShore(); +}