Rivage brise : embarquement sur le navire, escorte de mercenaires, factions

L'exploitant a fourni une seconde video, en 1080p et sans ecran partage,
qui montre le vrai depart cote Alliance. Elle a permis quatre
corrections.

L'EMBARQUEMENT. Le script d'Angelica deposait le joueur en
(443.8, 2076.1, 1.2), le point d'ancrage de la plage -- une coordonnee
inventee. Le sort officiel 199358 porte (441.2, 2023.75, 4.44) dans
spell_target_position, marque VerifiedBuild 27843 : le pont de
l'Alliance Battleship. On y teleporte desormais. La premiere etape
s'intitule « Rendez-vous au rivage Brise » et ne s'acheve qu'une fois
le joueur debarque, au lieu d'une minuterie de douze secondes qui
s'ecoulait pendant le chargement du client.

Sa condition exigeait par ailleurs que la quete 42740 soit EN COURS :
deja rendue ou pas encore prise, l'option restait muette. Elle accepte
maintenant les trois etats et explique le refus au lieu de ne rien
faire.

LES FACTIONS, et la resolution du paradoxe d'aout. On avait bascule les
demons de cette carte en faction 16 parce que la 2780 les rendait
inattaquables, sans comprendre pourquoi. Le prix etait invisible : la
faction 16 porte EnemyGroup = 1, elle n'est hostile QU'AUX JOUEURS. Nos
allies, eux, ont EnemyGroup = 0 et une liste d'ennemis qui designe la
faction 1786. Les deux camps ne se reconnaissaient pas -- d'ou les
soldats qui s'engagent puis n'ont personne a frapper, releve par la
sonde d'evasion : onze fois pour la garde royale gilneenne, dix pour
Jaina, dix pour Varian.

On avait repare le joueur en supprimant la bataille. Les 102 entrees
exclusives a la carte passent en 2898, qui porte la meme faction 1786
mais avec EnemyGroup = 15, hostile a tous. Verifie en jeu : attaquable.

L'ESCORTE. Le scenario officiel se joue en groupe constitue par la file
d'attente, que nous n'avons pas. A l'entree, une escorte est desormais
offerte : un protecteur, un guerisseur et deux combattants, via le
module des mercenaires. Un mode gratuit y est ajoute -- le contrat reste
identique, rupture au premier depart du groupe, mais sans prelevement.
Les invocations sont espacees d'une seconde et demie, faute de quoi
elles se superposaient au meme point.

LES DOUBLONS DE DISTRIBUTION. Quatrieme et cinquieme occurrences du meme
defaut : le script invoquait Genn, Jaina, Mekkatorque, l'escorte et
Arganoth alors que la carte les porte tous. Toutes les invocations de
l'introduction sont supprimees ; c'est Genn qui ouvre la scene cote
Alliance, Varian n'etant pas encore la -- ce qui est tout l'objet de
l'etape « Trouver Varian ».

Corrige aussi : Gul'dan Talk(1) n'existait pas, le script l'appelait
dans la finale et rien ne se jouait. 263 allies passent en arme
degainee, 131 recoivent un equipement recupere de la reference et la
posture EMOTE_STATE_READY1H.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
BlaMacfly
2026-09-05 09:42:29 +02:00
parent 346bd9c200
commit bd22c62631
7 changed files with 519 additions and 60 deletions
+5 -4
View File
@@ -361,7 +361,7 @@ bool MercenaryMgr::FindCandidate(Player* owner, uint8 role, uint32& accountId, u
return false;
}
MercenaryResult MercenaryMgr::Summon(Player* owner, uint8 role, Creature* portal)
MercenaryResult MercenaryMgr::Summon(Player* owner, uint8 role, Creature* portal, bool freeOfCharge)
{
if (!m_enabled)
return MERC_ERR_DISABLED;
@@ -396,7 +396,7 @@ MercenaryResult MercenaryMgr::Summon(Player* owner, uint8 role, Creature* portal
return MERC_ERR_GROUP_FULL;
}
if (!owner->HasEnoughMoney(uint64(m_cost)))
if (!freeOfCharge && !owner->HasEnoughMoney(uint64(m_cost)))
return MERC_ERR_NO_MONEY;
uint32 accountId = 0;
@@ -410,8 +410,9 @@ MercenaryResult MercenaryMgr::Summon(Player* owner, uint8 role, Creature* portal
if (!session)
return MERC_ERR_NO_BOT;
// Le mercenaire existe : on encaisse.
owner->ModifyMoney(-int64(m_cost));
// Le mercenaire existe : on encaisse, sauf recrutement offert.
if (!freeOfCharge)
owner->ModifyMoney(-int64(m_cost));
uint32 const level = PlayerBotSetting::CheckMaxLevel(owner->getLevel());
+6 -1
View File
@@ -110,7 +110,12 @@ class TC_GAME_API MercenaryMgr
// Verifie, encaisse et lance l invocation. Le prelevement n a lieu que
// si un mercenaire du role demande a effectivement ete reserve.
// "portal" est la structure invoquante : le mercenaire en sortira.
MercenaryResult Summon(Player* owner, uint8 role, Creature* portal = nullptr);
// « freeOfCharge » saute la verification et le prelevement : il sert
// aux recrutements que le jeu offre, comme l escorte fournie a
// l entree d un scenario. Le contrat reste identique par ailleurs,
// rupture au premier depart du groupe comprise.
MercenaryResult Summon(Player* owner, uint8 role, Creature* portal = nullptr,
bool freeOfCharge = false);
void Update(uint32 diff);
+24 -1
View File
@@ -180,7 +180,30 @@ WorldSession::~WorldSession()
while (_recvQueue.next(packet))
delete packet;
LoginDatabase.PExecute("UPDATE account SET online = 0 WHERE id = %u;", GetAccountId()); // One-time query
// =================================================================
// SylvaniaCore : ne plus ecrire en base une fois l'arret engage.
//
// Chaque arret du serveur produisait un vidage memoire de 120 Mo :
// #4 World::~World()
// #3 PlayerBotSession::~PlayerBotSession()
// #2 WorldSession::~WorldSession()
// #1 DatabaseWorkerPool<LoginDatabaseConnection>::Execute()
// #5 __run_exit_handlers
// Le monde est detruit par les gestionnaires de sortie, donc APRES
// le demontage du reservoir de connexions : la requete ci-dessous
// ecrivait dans un objet deja liberé.
//
// Ces faux plantages noient les vrais -- on a passe du temps cette
// semaine a trier des vidages qui n'etaient que des arrets propres.
//
// Sauter la requete est sans consequence : Main.cpp remet DEJA tous
// les comptes et personnages du royaume hors ligne au demarrage.
//
// IsStopped() est statique et lit un booleen statique : il reste
// consultable pendant la destruction, contrairement a sWorld.
// =================================================================
if (!World::IsStopped())
LoginDatabase.PExecute("UPDATE account SET online = 0 WHERE id = %u;", GetAccountId());
}
bool WorldSession::PlayerDisconnected() const
@@ -17,6 +17,9 @@
#include "GameObject.h"
#include "ObjectMgr.h"
#include "TaskScheduler.h"
#include "MercenaryMgr.h"
#include "Chat.h"
#include "Group.h"
enum BrokenShoreData
{
@@ -288,6 +291,54 @@ struct scenario_broken_shore_intro : public InstanceScript
// objectif « embarquement » (Alliance) : credite aussi ici au cas ou
player->KilledMonsterCredit(NPC_CREDIT_SHIP);
// =============================================================
// ESCORTE_MERCENAIRE
//
// DEMANDE : « cree le systeme de groupe avec playerbot pour le
// scenario, avec un groupe complet heal tank dps », en reprenant
// le principe des mercenaires mais declenche a l entree.
//
// Le scenario officiel se joue en groupe, constitue par la file
// d attente. Faute de ce systeme, on offre une escorte : un
// protecteur, un guerisseur et deux combattants, gratuitement.
//
// Le contrat reste celui des mercenaires -- rupture au premier
// depart du groupe -- mais sans prelevement : le jeu la fournit,
// le joueur ne l a pas louee.
//
// On ne complete que ce qui manque : un joueur deja accompagne
// garde ses compagnons.
// =============================================================
if (sMercenaryMgr->IsEnabled())
{
uint32 place = MERCENARY_GROUP_SIZE - 1;
if (Group* groupe = player->GetGroup())
place = (groupe->GetMembersCount() < MERCENARY_GROUP_SIZE)
? MERCENARY_GROUP_SIZE - groupe->GetMembersCount() : 0;
static uint8 const composition[] = { ROLE_TANK, ROLE_HEALER, ROLE_DAMAGE, ROLE_DAMAGE };
// SIGNALE EN JEU : « une se superpose a la meme position ».
// On espace les arrivees d'une seconde et demie : le module
// pose chaque mercenaire aupres de son employeur, et deux
// invocations simultanees se retrouvent au meme point.
uint8 recrutes = 0;
for (uint8 i = 0; i < 4 && recrutes < place; ++i)
{
uint8 const role = composition[i];
Player* employeur = player;
scheduler.Schedule(Milliseconds(1500 * (i + 1)), [employeur, role](TaskContext)
{
sMercenaryMgr->Summon(employeur, role, nullptr, true);
});
++recrutes;
}
if (recrutes)
ChatHandler(player->GetSession()).PSendSysMessage(
"Une escorte de %u combattants se joint a vous pour l'assaut.", recrutes);
}
if (!introDone)
{
introDone = true;
@@ -343,6 +394,10 @@ struct scenario_broken_shore_intro : public InstanceScript
{
case NPC_KING_VARIAN: placedVarianGUID = creature->GetGUID(); break;
case NPC_VOLJIN: placedVoljinGUID = creature->GetGUID(); break;
case NPC_JAINA: jainaGUID = creature->GetGUID(); break;
case NPC_SYLVANAS: sylvanasGUID = creature->GetGUID(); break;
case NPC_GENN: gennGUID = creature->GetGUID(); break;
case NPC_THRALL: thrallGUID = creature->GetGUID(); break;
case NPC_TIRION_POSE: tirionGUID = creature->GetGUID(); break;
case NPC_GULDAN_POSE: guldanGUID = creature->GetGUID(); break;
case NPC_KROSUS: krosusGUID = creature->GetGUID(); break;
@@ -420,34 +475,71 @@ struct scenario_broken_shore_intro : public InstanceScript
void StartIntro()
{
FactionAnchors const& a = Anchors();
// =============================================================
// CAST DEJA POSE SUR LA CARTE : plus aucune invocation.
//
// SIGNALE EN JEU : « je n'avais pas remarque mais a cette
// position il y a un 2e Genn Grisetete et une 2e Jaina ».
//
// Troisieme fois que ce defaut se manifeste -- apres Varian,
// puis Tirion et Krosus. La carte porte TOUTE la distribution,
// a l'endroit qui lui revient :
// Genn Grisetete (487, 2052) a 49 m du debarquement
// Jaina (491, 2047) a 56 m
// Vol'jin, Thrall (568-572, 1882-1887) plage Horde
// Baine, Sylvanas (992-1000, 1874-1881)
// Mekkatorque, Varian (1117-1120, 2471-2484)
//
// Le script en fabriquait des copies au point d'ancrage, d'ou
// les doublons. On retient les vraies dans OnCreatureCreate et
// on ne convoque plus personne.
//
// Le chef de faction n'ouvre plus la scene : sur la plage, ce
// sont Genn et Jaina cote Alliance, Vol'jin cote Horde. Varian
// est ailleurs -- c'est tout l'objet de l'etape « Trouver
// Varian » d'aller le chercher.
// =============================================================
leaderGUID = (team == TEAM_HORDE) ? placedVoljinGUID : placedVarianGUID;
// debarquement : chef de faction + escorte
if (Creature* leader = Summon(LeaderEntry(), a.beach))
{
leaderGUID = leader->GetGUID();
leader->AI()->Talk(0);
}
if (team == TEAM_HORDE)
{
if (Creature* sylvanas = SummonAt(NPC_SYLVANAS, a.beach, 4.0f, 4.0f))
sylvanasGUID = sylvanas->GetGUID();
SummonAt(NPC_BAINE, a.beach, -4.0f, 4.0f);
SummonAt(NPC_THRALL, a.beach, 4.0f, -4.0f);
}
else
{
if (Creature* jaina = SummonAt(NPC_JAINA, a.beach, 4.0f, 4.0f))
jainaGUID = jaina->GetGUID();
SummonAt(NPC_GENN, a.beach, -4.0f, 4.0f);
SummonAt(NPC_MEKKATORQUE, a.beach, 4.0f, -4.0f);
}
for (uint8 i = 0; i < 4; ++i)
SummonAt(TroopEntry(), a.beach, -8.0f + i * 5.0f, -8.0f);
if (Creature* orateur = instance->GetCreature(team == TEAM_HORDE ? placedVoljinGUID : gennGUID))
orateur->AI()->Talk(0);
// etape 0 « The Broken Shore » : courte mise en scene puis assaut
scheduler.Schedule(Seconds(12), [this](TaskContext /*context*/)
// =============================================================
// ATTEINDRE_LA_PLAGE
//
// L'etape 1 s'intitule « Rendez-vous au rivage Brise » : elle se
// joue depuis le pont du navire, et ne s'acheve qu'une fois le
// joueur debarque. Une minuterie de douze secondes la validait
// sans lui -- et comme le client mettait ce temps a charger la
// zone, il se retrouvait deja en phase 2 a son arrivee.
//
// On attend desormais qu'il pose le pied sur le sable, a moins
// de 40 metres du point de debarquement.
// =============================================================
scheduler.Schedule(Seconds(3), [this](TaskContext context)
{
if (stage != STAGE_INTRO)
return;
// SIGNALE EN JEU : « je reste bloque en p1 ». Le pont du
// navire est a 52 metres du point de debarquement, donc hors
// du cercle de 40 que j'exigeais : selon l'endroit ou le
// joueur descendait, il n'y entrait jamais. Porte a 90, ce
// qui couvre toute la greve devant le navire.
bool debarque = false;
Position const& plage = Anchors().beach;
DoOnPlayers([&debarque, &plage](Player* player)
{
if (player->GetExactDist2d(plage.GetPositionX(), plage.GetPositionY()) < 90.0f)
debarque = true;
});
if (!debarque)
{
context.Repeat(Seconds(3));
return;
}
// PLUS AUCUNE VAGUE. Le script a ete ecrit quand la carte
// etait VIDE : il fabriquait ses propres ennemis partout.
// Elle porte desormais 748 creatures posees, et ces
@@ -584,7 +676,10 @@ struct scenario_broken_shore_intro : public InstanceScript
// switche jusqu'a la 5 sans rien faire »).
stage = STAGE_COMMANDER;
if (Creature* arganoth = Summon(NPC_ARGANOTH, Anchors().commander))
// Arganoth est POSE sur la carte en (613, 2085) : quatrieme
// occurrence du meme defaut, apres Varian, Tirion, Krosus et la
// distribution de la plage. On emploie celui de la base.
if (Creature* arganoth = instance->GetCreature(arganothGUID))
{
arganoth->AI()->Talk(0);
arganoth->SetInCombatWithZone();
@@ -991,6 +1086,8 @@ private:
ObjectGuid tirionGUID;
ObjectGuid krosusGUID;
ObjectGuid arganothGUID;
ObjectGuid gennGUID;
ObjectGuid thrallGUID;
ObjectGuid guldanGUID;
TaskScheduler scheduler;
};
@@ -15,6 +15,7 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Chat.h"
#include "LFGMgr.h"
#include "LFGQueue.h"
#include "LFGPackets.h"
@@ -340,12 +341,56 @@ public:
player->PlayerTalkClass->ClearMenus();
player->PlayerTalkClass->SendCloseGossip();
// Embarquement pour la Rive Brisee : credit du bateau puis entree dans le scenario 786 (map 1460)
if (player->GetQuestStatus(42740) == QUEST_STATUS_INCOMPLETE)
// =============================================================
// SylvaniaCore : on embarque, on n'atterrit pas sur le sable.
//
// SIGNALE EN JEU : « quand on rejoint le Rivage brise on spawn
// sur un bateau en mouvement qui se dirige vers la plage, on
// n'est pas teleporte directement sur la plage ». Confirme par
// la video : la premiere phase s'intitule « Rendez-vous au
// rivage Brise » et se joue depuis le pont d'un navire.
//
// Ce script deposait le joueur en (443.8, 2076.1, 1.2), le
// point d'ancrage de la plage -- une coordonnee inventee.
//
// Le sort 199358 porte la destination OFFICIELLE dans
// spell_target_position : (441.2, 2023.75, 4.44), c'est-a-dire
// le pont de l'Alliance Battleship, pose en (440, 2025). La
// ligne est marquee VerifiedBuild 27843 : c'est de la donnee
// Blizzard, pas une deduction.
// =============================================================
// SIGNALE EN JEU : « le gossip d'Angelica ne declenche plus
// rien ». Deux causes, corrigees ensemble.
//
// Le lancement du sort 199358 n'aboutissait pas -- rien dans le
// journal, donc il n'etait meme pas tente. On teleporte
// directement aux coordonnees que ce sort porte dans
// spell_target_position, ce qui revient au meme sans dependre
// de la presence du sort cote serveur.
//
// Et la condition exigeait que la quete 42740 soit EN COURS :
// deja rendue ou pas encore prise, l'option restait muette.
// On accepte desormais aussi la quete achevee mais non rendue,
// et on laisse repasser ceux qui l'ont terminee -- refaire le
// scenario ne doit pas etre interdit.
float const PONT_DU_NAVIRE_X = 441.2f;
float const PONT_DU_NAVIRE_Y = 2023.75f;
float const PONT_DU_NAVIRE_Z = 4.44f;
QuestStatus const etat = player->GetQuestStatus(42740);
if (etat == QUEST_STATUS_INCOMPLETE || etat == QUEST_STATUS_COMPLETE ||
etat == QUEST_STATUS_REWARDED)
{
player->KilledMonsterCredit(creature->GetEntry());
player->TeleportTo(1460, 443.8f, 2076.1f, 1.2f, 0.4f);
if (etat == QUEST_STATUS_INCOMPLETE)
player->KilledMonsterCredit(creature->GetEntry());
player->TeleportTo(1460, PONT_DU_NAVIRE_X, PONT_DU_NAVIRE_Y,
PONT_DU_NAVIRE_Z, 0.4f);
}
else
ChatHandler(player->GetSession()).PSendSysMessage(
"Prenez d'abord la quete La bataille du rivage Brise.");
return true;
};