Chantier Rivage brise : materiel de reference et outillage d extraction

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 <noreply@anthropic.com>
This commit is contained in:
SylvaniaCore deploy
2026-08-27 23:33:32 +02:00
parent 1507d1084d
commit 15ac8dbd7a
6 changed files with 1151 additions and 0 deletions
+81
View File
@@ -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`.
@@ -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()
@@ -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)')
@@ -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<Creature*> 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<Creature*>::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<Trinity::AnyFriendlyUnitInObjectRangeCheck> 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();
};
@@ -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);
@@ -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<Creature*> trash;
GetCreatureListWithEntryInGrid(trash, player, 118412, 50.0f);
for (std::list<Creature*>::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();
}