-
Notifications
You must be signed in to change notification settings - Fork 3
Module Mercenaires
État : actif en production (pbotmerc = 1)
Un joueur solo loue des compagnons — des playerbots — contre de l'or, auprès d'un PNJ dédié. Ils rejoignent son groupe, obéissent à ses ordres, et peuvent dialoguer via un modèle de langage payé par la clé API du joueur.
Portail d'Invocation de Mercenaire — creature_template 1000010 :
| Champ | Valeur |
|---|---|
displayid |
74465 |
rank |
3 (boss) |
faction |
35 (amical avec tous) |
unit_flags |
770 |
ScriptName |
npc_mercenary_portal |
Le PNJ n'est pas spawné par SQL : les portails sont posés à la main en jeu avec
.npc add 1000010(60 spawns en production).
Le gossip enseigne aussi le système d'ordres depuis le commit 9e18ce4c (sous-menu « Comment leur
donner des ordres ? »).
| Règle | Valeur / motif |
|---|---|
| Coût |
100 po (pbotmerc_cost) |
| Plafond |
4 mercenaires (pbotmerc_max) |
| Niveau minimum de l'employeur | 10 (pbotmerc_minlevel) |
| Rôle acheté | protecteur / guérisseur / combattant |
| Niveau du mercenaire | celui de l'employeur |
Pourquoi 4 et pas 5 — un groupe WoW compte cinq places, employeur inclus. Cinq mercenaires
imposeraient un raid, ce qui interdirait les donjons. MERCENARY_HARD_CAP = 4.
Le contrat est jetable, par choix de conception. Déconnexion du joueur, départ ou dissolution du groupe, expulsion d'un mercenaire → le bot quitte le groupe et se déconnecte. Toute nouvelle invocation se repaie. Aucun remboursement, sauf échec d'invocation (aucun bot présenté en 60 s).
Les 10 classes couvertes par AI/PlayerAI/BotGroupAI/ : guerrier, paladin, chasseur, voleur,
prêtre, chevalier de la mort, chaman, mage, démoniste, druide.
❌ Pas de moine ni de chasseur de démons — ils resteraient plantés.
src/server/game/Mercenary/
├── MercenaryMgr.{h,cpp} # 763 lignes — contrats, invocation, rupture
└── MercenaryChat.{h,cpp} # 967 lignes — volet dialogue LLM
src/server/scripts/Mercenary/
Aucun fichier du core n'est modifié. Les ruptures de contrat passent par les hooks existants —
GroupScript::OnRemoveMember, GroupScript::OnDisband, PlayerScript::OnLogout. Seul ajout externe :
CapitalSiegeMgr::IsAccountEngaged(), pour ne pas débaucher un bot engagé dans l'assaut quotidien.
La leçon la plus coûteuse du module (corruption de tas du 16/08/2026, commit 0dbbb73a) :
-
Group::Disband()appellesScriptMgr->OnGroupDisband(this)en première instruction, puis finit pardelete this; -
Group::RemoveMemberappelleDisband()dès que le groupe repasse sous deux membres.
Congédier un mercenaire depuis ces hooks réentre dans Disband() → double libération du Group →
tas corrompu et crash différé dans un malloc sans aucun rapport.
Solution : les hooks se contentent de marquer pendingRelease ; Update() libère au tick suivant.
Le garde-fou m_releasing ne protège que nos propres hooks, pas la réentrance du core.
Le diagnostic complet de cette corruption est détaillé dans Diagnostic des crashs — c'est un bon cas d'école.
PlayerBotMgr::UpdateIdleBotLogout exemptait le siège des capitales mais pas les mercenaires : un
bot loué resté immobile était déconnecté après pbotbg_idlelogout (300 s). Exemption via
IsAccountHired().
En 7.3.5, le texte d'un npc_text vit dans le DB2 BroadcastText (table
dc_hotfixes.broadcast_text). Conséquence : pas de texte de gossip custom sans hotfix. Le boniment
du portail passe donc par creature->Whisper(std::string, LANG_UNIVERSAL, player).
Livré le 18/08/2026 (commit 77c5c68e). Un mercenaire peut converser, en français, dans un registre
médiéval-fantastique, sans jamais rompre l'illusion.
C'est l'exigence qui structure toute l'implémentation.
!api <fournisseur> <modèle> <clé> [adresse]
!api off
La commande est interceptée dès la première instruction de WorldSession::HandleChatMessage —
avant tout journal et toute rediffusion. Sans cela, la clé s'afficherait dans le chat des autres
joueurs et dans les fichiers de log du serveur.
La clé vit en mémoire seule, est écrasée caractère par caractère avant libération, et effacée à la rupture du dernier contrat, à la déconnexion, et à l'arrêt du serveur.
Trois adaptateurs natifs : OpenAI et compatibles, Anthropic, Gemini.
| Fournisseur | État depuis le VPS |
|---|---|
| Mistral (via l'adaptateur OpenAI) | ✅ validé en jeu, canal retenu par le royaume — français natif, quota généreux, sans carte bancaire |
| OpenAI, Anthropic, OpenRouter | ✅ joignables |
| Gemini en direct | ❌ inutilisable depuis un centre de données |
Commande qui fonctionne dans tous les cas :
!api openai mistral-small-latest <clé> https://api.mistral.ai/v1/chat/completions
⚠️ La forme courte!api mistral …n'existe que depuis l'ajout deMERC_LLM_MISTRAL. Avant,mistralétait un alias d'OpenAI et la clé partait chez OpenAI — d'où un 401 trompeur.
Gemini. L'adaptateur fonctionne de bout en bout (payload system_instruction + contents +
generationConfig), modèle validé : gemini-3.5-flash-lite. Mais Google renvoie
400 FAILED_PRECONDITION — "User location is not supported for the API use" pour tout appel venant
d'une IP de centre de données, alors que la même clé fonctionne depuis une machine personnelle.
Ce n'est pas le pays qui est refusé, c'est le type d'IP. Contournement : passer par OpenRouter
en mode compatible OpenAI. Google retire vite ses anciens modèles (gemini-2.0-flash et
gemini-2.5-flash-lite renvoient déjà 404) — pour lister ce qu'une clé permet :
curl -s "https://generativelanguage.googleapis.com/v1beta/models?key=CLE"- Le mercenaire répond dans le canal d'où vient le message.
- Dans le canal de groupe, tout compagnon peut lui parler.
- En chuchotement, seul l'employeur — c'est sa clé qui paie.
- Anti-spam par délai (
pbotmerc_chat_cooldown, 5 s).
Depuis l'ajout du dialogue, tous les ordres prennent le préfixe ! :
!follow !stop !@heal stop !seduce
Sans préfixe, un mot comme « stop » au milieu d'une phrase figeait le mercenaire. Toute documentation ou annonce parlant des anciennes commandes est périmée.
-
dep/rapidjsonne compile plus avec GCC 14 :document.h,GenericStringRef::operator=assigne un membreconst. Le core ne l'utilise que parreader.h/writer.h— n'incluez pasdocument.h; écrivez un extracteur ou passez par le lecteur événementiel. -
libcurlest lié directement danssrc/server/game/CMakeLists.txt(find_package(CURL)), plutôt que d'activerWITH_CPRqui recompilerait curl et cpr en entier. - Jamais d'appel réseau dans le fil du monde : fil dédié + files d'entrée / sortie.
Ne concluez jamais d'un code HTTP nu. Sur le premier échec Mistral : clé crue invalide (elle était bonne), puis payload cru défectueux (il était bon) — seul le message textuel du fournisseur disait la vérité. Le module le remonte désormais au joueur en jeu.
pbotmerc = 1
pbotmerc_cost = 100 # pièces d'or
pbotmerc_max = 4
pbotmerc_minlevel = 10
pbotmerc_chat = 1
pbotmerc_chat_cooldown = 5 # secondes
pbotmerc_chat_timeout = 30 # secondes
pbotmerc_chat_maxtokens= 160
pbotmerc_chat_history = 6
pbotmerc_chat_queue = 32Les valeurs ci-dessus sont celles de la production. Le worldserver.conf.dist versionné livre
les défauts du code, module éteint (pbotmerc = 0, pbotmerc_chat = 0) — voir Configuration.
sql/sylvania/mercenary.sql (créature et gossip) · sql/sylvania/mercenary_title.sql (titre custom).
SylvaniaCore — GPL v2 · La Légion de Sylvania · Discord contributeurs World of Warcraft® et Blizzard Entertainment® sont des marques déposées de Blizzard Entertainment, Inc. Projet non affilié.
Démarrer
Comprendre
Modules
Développer