# War3AI / OpenWar3 Documentation complète > Source : https://war3ai.com/fr — interface ouverte de Warcraft III 1.27 pour les agents IA. Pour écrire un Bot, n’utilisez que les méthodes de Game listées dans le « Catalogue de l’API » en fin de document. --- # Vue d’ensemble > Documentation d’OpenWar3 : ce que c’est, ce qu’elle permet ; démarrage rapide, premier Bot, faire écrire l’IA par un LLM, API et protocole, passerelle et MCP — par quelle page commencer selon votre situation. **OpenWar3** est la couche d’interface ouverte de War3AI : un runtime injecté dans Warcraft III 1.27, accompagné d’un SDK Python. - Toutes les **50 ms**, le runtime pousse en mémoire partagée l’état complet de la carte : ressources et nourriture de tous les joueurs ; pour chaque unité, points de vie et mana, ordre en cours, cible attaquée, temps de recharge, buffs, inventaire ; objets au sol, arbres, files de production, cycle jour/nuit. S’y ajoute un **flux d’événements** : apparition et mort des unités, chaque coup porté, fin de production… - Un programme externe envoie des **commandes sémantiques** avec une latence d’**environ une frame** : déplacer, attaquer, récolter, construire, entraîner, lancer un sort, apprendre une compétence, ressusciter, utiliser un objet, acheter… Chaque commande renvoie un **accusé de réception** qui indique si le moteur l’a acceptée et, sinon, avec quel code de raison. - Vous dites seulement « quoi faire » : les unités par leur code à quatre caractères, les sorts par leur nom d’ordre, comme dans le jeu. Le « comment » est l’affaire du runtime. Un LLM n’a donc besoin d’aucune connaissance bas niveau, ni de voir l’écran. Une fois la documentation lue, il peut écrire un Bot qui gère son économie et sait se battre, puis le corriger lui-même en cours de route à partir des accusés de réception et des événements. Et pas seulement pour les matchs : le [canevas](https://war3ai.com/fr/docs/canvas/) permet de dessiner vos propres panneaux et annotations sur l’écran de jeu, [Interface et entrées](https://war3ai.com/fr/docs/ui-input/) rend cliquables les boutons dessinés et fait réagir les raccourcis clavier, le [canal JASS](https://war3ai.com/fr/docs/jass/) permet d’appeler depuis l’extérieur les 1291 fonctions du jeu, et dans les cartes RPG vous pouvez vous faire accompagner d’un [compagnon IA](https://war3ai.com/fr/docs/companion/). Une IA aboutie peut devenir un [schéma](https://war3ai.com/fr/docs/schemes/), à changer en un clic, à exporter et à partager ; tout un nouveau gameplay peut s’écrire sous forme de [mod de jeu](https://war3ai.com/fr/docs/mods/). Pas besoin d’écrire du Python pour se connecter : la [passerelle](https://war3ai.com/fr/docs/gateway/) permet à n’importe quel langage ou page web d’appeler les mêmes interfaces en WebSocket / JSON, et le [serveur MCP](https://war3ai.com/fr/docs/mcp/) permet à un agent comme Claude Code d’appeler directement des outils pour observer la partie et donner des ordres. - [Démarrage rapide](https://war3ai.com/fr/docs/quickstart/): Installez l’environnement, lancez une partie en une commande et regardez le Bot d’exemple prendre la main. - [Écrire un Bot avec un LLM](https://war3ai.com/fr/docs/ai-bot/): Pas besoin de savoir programmer : copiez le prompt, décrivez votre stratégie, confiez le reste à l’agent. - [Modèle mental](https://war3ai.com/fr/docs/concepts/): Instantanés, commandes, accusés de réception, événements, ticks. Cinq minutes de lecture avant d’écrire un Bot. - [Catalogue de l’API](https://war3ai.com/fr/api/): Toutes les interfaces, chacune avec son statut de test, son niveau de latence et son mécanisme sous-jacent. ## Choisissez votre parcours | Vous êtes | Commencez par | Ensuite | |---|---|---| | Joueur de Warcraft, sans notions de programmation | [Démarrage rapide](https://war3ai.com/fr/docs/quickstart/) → [Écrire un Bot avec un LLM](https://war3ai.com/fr/docs/ai-bot/) | En cas de problème, voir la [FAQ](https://war3ai.com/fr/docs/faq/) | | À l’aise en Python | [Premier Bot](https://war3ai.com/fr/docs/first-bot/) → [Modèle mental](https://war3ai.com/fr/docs/concepts/) → [Les quinze règles](https://war3ai.com/fr/docs/rules/) | [Recettes de jeu pro](https://war3ai.com/fr/docs/cookbook/), [Bots d’exemple](https://war3ai.com/fr/docs/examples/) | | En train de construire un agent de code / de l’automatisation | [Itération autonome de l’agent](https://war3ai.com/fr/docs/agent-loop/) | [Accusés de réception et codes de raison](https://war3ai.com/fr/docs/reason-codes/), [`llms-full.txt`](https://war3ai.com/fr/llms-full.txt) | | Intéressé par un LLM qui décide en cours de partie | [Un LLM comme conseiller](https://war3ai.com/fr/docs/llm-coach/) | [Bulles de dialogue et modèles locaux](https://war3ai.com/fr/docs/speech/) | | Envie de laisser un agent agir directement (Claude Code, etc.) | [Un LLM appelle directement des outils (MCP)](https://war3ai.com/fr/docs/mcp/) | [Interface et entrées](https://war3ai.com/fr/docs/ui-input/) | | Utilisateur d’un autre langage (JS, C#, Go, Rust…) | [Passerelle](https://war3ai.com/fr/docs/gateway/) | Plus bas niveau : [Protocole W3P](https://war3ai.com/fr/docs/protocol/) | | Envie de faire s’affronter les IA de plusieurs personnes | [Mode équitable](https://war3ai.com/fr/docs/fair-mode/) | [Arène](https://war3ai.com/fr/arena/) | | Envie de créer votre propre gameplay dans des cartes RPG / personnalisées | [Mods de jeu](https://war3ai.com/fr/docs/mods/) | [Interface et entrées](https://war3ai.com/fr/docs/ui-input/), [Canevas](https://war3ai.com/fr/docs/canvas/), [Canal JASS](https://war3ai.com/fr/docs/jass/), [Compagnon RPG](https://war3ai.com/fr/docs/companion/) | | Envie de partager votre IA avec d’autres | [Schémas d’IA](https://war3ai.com/fr/docs/schemes/) | [Console Farsight](https://war3ai.com/fr/docs/console/) | ## Contenu du dépôt ```text start.bat Le point d'entrée unique : déploiement à partir de zéro + ouverture de Farsight ; stop.bat arrête tout sdk/python/ Couche d'interface. openwar3/ est la façade publique (Game + Bot) : commencez ici brains/ Couche de décision examples/ hello_bot (économie) → rush_bot (armée) → macro_bot (macro) → micro_bot (micro + creeping) ; buddy (compagnon RPG) ; mod_hero_roguelike / mod_endless_defense (mods de jeu) xwar3/ Cerveau de référence : couche stratégique (seconde) + couche réflexe (4 processus) + modèle de victoire console/ Console web Farsight (FastAPI + React) gateway/ Passerelle (WebSocket / JSON) + client JS + page de démonstration dans le navigateur director/ Réalisation automatique, barres de vie au-dessus des unités speech/ Bulles de dialogue + LLM local runtime/ Orchestration multi-instances (chaque partie relancée selon les réglages) data/ order-ids.txt ; outils pour extraire les données de votre propre jeu schemes/ Vos schémas d'IA (mine/) et ceux partagés par d'autres (installed/), hors du dépôt tools/ play.py (une partie en une commande), run_scheme.py (exécuteur de schémas), war3_mcp.py (serveur MCP), run_tests.py, scripts de vérification en jeu docs/ Catalogue de l'API api.json (généré depuis le code), protocole, manuel ``` Entre le runtime et votre code, il n’y a qu’un [protocole W3P](https://war3ai.com/fr/docs/protocol/) versionné : le SDK Python est le plus simple, mais vous pouvez aussi vous connecter depuis un autre langage en suivant le protocole. ## Que signifie le « statut de test » d’une interface Dans le catalogue de l’API, chaque interface porte l’un de ces trois statuts : - **Vérifié en jeu** : le chemin bas niveau (numéro d’action, forme des paramètres, effet relu) a été vérifié dans de vraies parties, et un script de vérification veille dessus. - **Expérimental** : interface récente, déjà fonctionnelle sur une instance de test et en cours de vérification en jeu point par point. Elle est utilisable, mais les détails de l’interface peuvent encore changer. - **Déduit / pas entièrement testé** : le mécanisme sous-jacent reprend la façon de faire du moteur lui-même (par exemple la fonction JASS équivalente), mais n’a pas encore été vérifié point par point en partie. Consultez l’accusé de réception avant de vous y fier. > **Remarque** > > Seul **Warcraft III 1.27** (The Frozen Throne) est pris en charge pour l’instant. Les versions 1.24 à 1.28 partagent la même structure de moteur ; la prise en charge multiversion est prévue à la phase P4 de la [feuille de route](https://war3ai.com/fr/roadmap/). À partir de la 1.29, comme pour Reforged, il s’agit d’un autre moteur, hors du périmètre de nos engagements. --- # Démarrage rapide > Double-cliquez sur start.bat pour tout installer automatiquement, réglez le dossier du jeu dans Farsight, puis lancez une partie et regardez le Bot d'exemple prendre la main. Environ 15 minutes. ## Ce qu'il vous faut | | Exigence | Remarques | |---|---|---| | Système | Windows 10 / 11, 64 bits | Seul Windows est pris en charge pour l'instant | | Jeu | Warcraft III **1.27a** (The Frozen Throne, `Game.dll` 1.27.0.52240) | Un client que vous possédez légalement ; aucun fichier du jeu n'est modifié sur le disque | **Rien d'autre à installer au préalable.** `start.bat` ne télécharge qu'une seule chose : Python 3.13 (le paquet portable officiel, environ 14 MB). Il est placé dans le dossier `bin\env\` du dépôt : pas besoin de droits administrateur, le PATH du système n'est pas modifié, et depuis un réseau en Chine un miroir est utilisé automatiquement ; si votre machine a déjà une version utilisable de Python, elle sert directement. PowerShell utilise celui fourni avec Windows ; les pages web de Farsight sont fournies déjà compilées avec le dépôt, Node.js n'est pas nécessaire. ## Installation 1. **Récupérer le code** ```bash git clone https://github.com/OPENXXAI/OpenWar3AI.git ``` Ou téléchargez [l'archive](https://github.com/OPENXXAI/OpenWar3AI/archive/refs/heads/main.zip) et décompressez-la. Le runtime (DLL injectée et lanceur) est fourni avec le dépôt : rien d'autre à télécharger. 2. **Double-cliquer sur `start.bat`** La première fois, il s'occupe lui-même de : - télécharger Python 3.13 ; - installer les paquets Python, vérifier les fichiers du runtime puis les mettre en place ; - télécharger AMAI et générer les données stratégiques utilisées par le cerveau de référence (AMAI est sous licence personnalisée, les fichiers générés ne sont pas versionnés dans git ; un échec n'affecte que le cerveau de référence) ; - ouvrir la page d'accueil de Farsight, le « Centre de contrôle », `http://127.0.0.1:8866`. Chaque étape affiche son résultat ; pour une étape qui a échoué, il vous indique comment la compléter. Ensuite, chaque double-clic ne fait qu'une vérification d'une ou deux secondes avant d'ouvrir Farsight. La fenêtre noire se ferme d'elle-même au bout de quelques secondes : Farsight continue de tourner en arrière-plan, et fermer le navigateur ne l'arrête pas. 3. **Régler le dossier du jeu dans le Centre de contrôle** Tout en haut du Centre de contrôle, utilisez « Recherche automatique », ou « Parcourir… » pour choisir vous-même le dossier de Warcraft III. Farsight vérifie la version du jeu et **extrait les données depuis votre propre jeu** (table des unités, capacités, objets, table des contres… les fichiers de Blizzard ne sont pas distribués avec le code). Si la version n'est pas la 1.27a, vous en êtes averti. Les cartes et les réglages de la partie suivante sont relatifs à ce dossier (les cartes de tous les sous-dossiers de `\Maps` sont disponibles) ; pour changer de dossier plus tard, passez par la page « Paramètres ». 4. **Lancer une partie et laisser le Bot d'exemple prendre la main** Le plus simple : dans la page « Instances et lancement » de Farsight, cochez un numéro d'instance, choisissez un schéma d'IA et cliquez sur « Lancer le test ». Vous pouvez aussi passer par la ligne de commande : ```bash python tools/play.py --bot brains/examples/hello_bot.py ``` Cette commande démarre une instance du jeu, injecte le runtime, lance automatiquement la partie, puis démarre le Bot. **Si vous voyez les paysans partir à la mine et le hall produire des paysans, c'est gagné.** Le `python` de la commande est celui indiqué dans `openwar3.json` ; celui que `start.bat` a installé se trouve dans `bin\env\python\python.exe`. ## start.bat et stop.bat ```bash start.bat # vérification du déploiement + ouverture de Farsight start.bat setup # vérification complète : réinstalle les paquets Python, relance AMAI start.bat restart # redémarre seulement le serveur de Farsight (le jeu et les services ne sont pas touchés) start.bat node # installe en plus Node.js (seulement utile pour prévisualiser le site, inutile au quotidien) start.bat 5 6 # démarre en plus le test des instances 5 et 6 (jeu + cerveau de référence) stop.bat # arrête tout ; stop.bat --keep-llm garde le modèle local en mémoire vidéo ``` La passerelle, les bulles de dialogue et le LLM local se démarrent et s'arrêtent eux aussi depuis le « Centre de contrôle » de Farsight : plus besoin de chercher d'autres scripts. **Arrêt complet** : double-cliquez sur `stop.bat`, ou cliquez sur « Tout arrêter » en haut à droite du Centre de contrôle — instances du jeu, IA, passerelle, bulles, modèle local utilisé par le système et serveur de Farsight s'arrêtent l'un après l'autre. Le serveur MCP est géré par les clients comme Claude : il n'est pas arrêté. > **Fichier de configuration** > > `openwar3.json` est écrit automatiquement par `start.bat` et Farsight ; il ne contient que des chemins locaux et n'est pas versionné dans git. Pour changer les ports, ou l'adresse et le nom du modèle du LLM local, suivez `openwar3.example.json` et n'y écrivez que les valeurs qui diffèrent. ## Paramètres de play.py ```bash python tools/play.py --bot my_bot.py --inst 9 --race 2 --enemy-race 1 --difficulty 3 --speed 200 python tools/play.py --bot my_bot.py --inst 9 --attach # le jeu est déjà ouvert, ne connecter que le Bot python tools/play.py --bot my_bot.py --fair # mode équitable : seul ce qui est dans le champ de vision est visible ``` | Paramètre | Défaut | Description | |---|---|---| | `--bot` | obligatoire | Chemin du fichier du Bot (il doit contenir une sous-classe de `Bot`) | | `--inst` | `9` | Numéro d'instance. Évitez les numéros déjà utilisés par une instance en cours (la page « Instances et lancement » de Farsight indique ceux qui sont pris) | | `--race` | `1` | Votre race : 1 Humains, 2 Orcs, 3 Morts-vivants, 4 Elfes de la nuit | | `--enemy-race` | `0` | Race de l'adversaire | | `--difficulty` | `2` | Difficulté de l'ordinateur adverse : 2 facile, 3 normal, 4 démentiel | | `--speed` | `100` | Vitesse de jeu (en pourcentage, 200 = vitesse ×2) | | `--map` | `default_map` de la configuration | Carte | | `--attach` | | Ne lance pas le jeu, se connecte seulement à une instance déjà en cours | | `--hz` | `5` | Nombre d'appels à `on_tick` par seconde | | `--minutes` | `60` | Durée maximale d'exécution, en minutes (temps réel) | | `--fair` | | [Mode équitable](https://war3ai.com/fr/docs/fair-mode/) | | `--player` | | Numéro du joueur à commander (pour les matchs IA contre IA) | > **Attention** > > Ne lancez pas le jeu avec `--minimize` : **quand la fenêtre est réduite, la simulation du jeu est arrêtée** (l'horloge n'avance pas), et le Bot attendra indéfiniment le début de la partie. Vous pouvez aussi vous passer de `play.py` et utiliser directement la ligne de commande du SDK pour vous connecter à une instance déjà en cours : ```bash python -m openwar3 run brains/examples/hello_bot.py --inst 5 # exécuter un Bot python -m openwar3 status --inst 5 # se connecter et afficher l'état de l'instantané / de la voie rapide python -m openwar3 catalog # afficher le catalogue de l'API ``` ## Une fois que tout fonctionne - [Écrire votre premier Bot](https://war3ai.com/fr/docs/first-bot/): Partez d'un Bot minimal de 10 lignes et ajoutez pas à pas la production et l'attaque. - [Le faire écrire par un LLM](https://war3ai.com/fr/docs/ai-bot/): Copiez le modèle de prompt et décrivez votre stratégie en langage courant. ## Autovérification ```bash python tools/run_tests.py # SDK / cerveau de référence / couche réflexe / console / bulles / exemples, chaque suite dans un sous-processus ``` Les tests hors ligne n'ont pas besoin de lancer le jeu. Le « Centre de contrôle » de Farsight propose aussi une vérification de l'environnement, qui montre si chaque composant est bien installé. --- # Votre premier Bot > Partez d'un Bot minimal de 10 lignes, ajoutez la production de paysans, la nourriture, l'armée, le héros et l'attaque, puis apprenez à lire les reçus. Un Bot est simplement une classe qui hérite de `openwar3.Bot`. Vous ne surchargez que les hooks dont vous avez besoin ; `g` (`Game`) se charge de « voir » et d'« agir ». ## Le Bot minimal ```python title="my_bot.py" from openwar3 import Bot class MyBot(Bot): def on_start(self, g): # appelé une fois au début de la partie g.message("Me voilà !") def on_tick(self, g): # environ 5 fois par seconde for w in g.idle_workers(): g.gather(w, g.nearest(g.gold_mines(), w)) ``` ```bash python tools/play.py --bot my_bot.py ``` Les paysans inactifs partent vers la mine d'or la plus proche. Les quatre hooks : | Hook | Quand il est appelé | |---|---| | `on_start(g)` | Une fois, au début de la partie, avant le premier tick | | `on_tick(g)` | À chaque tick (5 fois par seconde par défaut). Un tick qui déborde décale automatiquement le suivant, sans accumulation | | `on_event(g, ev)` | Avant chaque `on_tick`, vous transmet un par un les événements survenus depuis le tick précédent | | `on_end(g, reason)` | Une fois, à la fin de la partie (processus du jeu disparu / plus aucune unité de notre côté / arrêt manuel) | > **Astuce** > > Une exception levée dans `on_tick` n'interrompt pas la partie : l'exécuteur affiche la pile d'appels et continue au tick suivant ; il ne s'arrête qu'après **20 ticks d'erreurs consécutives**. ## Ajouter l'économie : paysans et nourriture ```python from openwar3 import Bot class Economy(Bot): def on_tick(self, g): res = g.resources() # une valeur illisible vaut None, pas 0 halls = g.my_buildings({"htow", "hkee", "hcas"}) if res is None or not halls: return home = halls[0] # 1. les paysans inactifs vont récolter de l'or for w in g.idle_workers(): mine = g.nearest(g.gold_mines(), w) if mine: g.gather(w, mine) # 2. produire des paysans : un seul à la fois dans la file (une file pleine immobilise l'argent) if len(g.my_workers()) < 15 and not g.queue(home): g.train(home, "hpea") # 3. nourriture presque au plafond : prendre un paysan qui ne construit pas et bâtir une ferme près du hall if res["food_cap"] - res["food_used"] <= 6: builder = next((w for w in g.my_workers() if not g.is_constructing(w)), None) if builder: g.build_near(builder, "hhou", home.x, home.y) ``` Trois points à noter : - **N'entraîner que si `g.queue(home)` est vide.** Donner l'ordre d'entraînement à chaque tick remplit la file de 7 emplacements et immobilise l'argent (mesuré : 4 paysans en file dans le hall, 300 d'or bloqués, un début de partie nettement plus lent). - **`build_near` plutôt que des coordonnées en dur.** Il cherche lui-même un emplacement libre, du plus proche au plus éloigné, et suit le résultat d'un tick à l'autre ; si l'argent manque, il ne fait rien. Des coordonnées en dur tombent facilement en pleine forêt. - **Ne pas choisir un paysan en train de construire.** Une ferme humaine demande 35 secondes ; si vous retirez l'ouvrier en cours de route, le chantier s'arrête. La version complète, qui fonctionne pour les quatre races, est `brains/examples/hello_bot.py` : 5 ouvriers par mine, les suivants partent couper du bois une fois la mine pleine, et les chantiers arrêtés sont repris. ## Ajouter la caserne, le héros et l'attaque ```python from openwar3 import Bot WAVE = 8 class Rush(Bot): def on_start(self, g): self.attacking = False def on_tick(self, g): halls = g.my_buildings({"htow", "hkee", "hcas"}) if not halls: return home = halls[0] # héros : un autel mais pas de héros -> ressusciter d'abord, sinon entraîner (un héros est unique : en entraîner un autre après sa mort est rejeté) altars = g.my_buildings({"halt"}) if altars and not g.my_heroes(): if not g.revive(altars[0]): g.train(altars[0], "Hpal") for h in g.my_heroes(): info = g.hero_info(h) if info and info["skill_points"]: g.learn(h, "AHhb") # Lumière sacrée # la caserne produit des fantassins en continu (un seul à la fois dans la file) for b in g.my_buildings({"hbar"}): if not g.queue(b): g.train(b, "hfoo") # attaquer quand une vague est prête ; rentrer après de lourdes pertes army = g.my_army() if len(army) >= WAVE: self.attacking = True elif len(army) < WAVE // 2: self.attacking = False if self.attacking: target = g.nearest([e for e in g.enemies() if g.is_building(e)], home) if target: idle = [u for u in army if not g.order_of(u)] # ne donner des ordres qu'aux unités inactives g.attack_move(idle, target.x, target.y) ``` Version complète : `brains/examples/rush_bot.py` (elle hérite de `hello_bot` et construit la caserne / l'autel s'ils manquent). ## Lire les reçus Chaque commande renvoie un reçu. `if r:` signifie « le moteur l'a acceptée » ; sinon, `r.reason` en donne la raison : ```python r = g.train(barracks, "hfoo") if not r: print(r.reason) # rejected(人口不够) (= nourriture insuffisante) print(r.verdict) # 3 ``` Codes de motif courants : `3` nourriture insuffisante, `8` or insuffisant, `9` bois insuffisant, `32` file pleine, `183` prérequis manquant, `221` élément absent / en construction / héros déjà présent, `1001` cible invisible. Liste complète dans [Reçus et codes de motif](https://war3ai.com/fr/docs/reason-codes/). > **Accepté ≠ réussi** > > Un reçu indique seulement que « le moteur a accepté cette commande ». Le moteur accepte aussi sur le moment un emplacement de construction en pleine forêt ; l'ouvrier n'échoue qu'une fois sur place. Un sort peut être interrompu. Pour connaître l'effet réel, regardez l'instantané et les événements : pour construire, utilisez `build_near` (qui vérifie que les fondations apparaissent) ; pour un sort, vérifiez avec `g.cooldown()` qu'il est bien en recharge. ## Étapes suivantes - [Modèle mental](https://war3ai.com/fr/docs/concepts/): Instantané, commande, événement, tick, lot — pourquoi cette conception. - [Recettes des joueurs pros](https://war3ai.com/fr/docs/cookbook/): 21 recettes : récolte saturée, jamais bloqué par la nourriture, tir concentré, retrait des blessés, creeping de nuit… --- # Écrire un Bot avec un LLM > Pas besoin de savoir programmer : vous expliquez clairement comment il doit jouer, le LLM écrit le code. Copiez le modèle de prompt, décrivez votre stratégie, lancez-le, puis demandez des corrections. Cette page s'adresse aux joueurs de Warcraft qui ne savent pas programmer, mais aussi aux développeurs qui veulent gagner du temps. Tout le processus est une conversation : **vous décrivez la stratégie → le modèle écrit le code → vous jouez une partie → vous décrivez au modèle ce que vous avez observé → il corrige**. > **Astuce** > > Installez d'abord l'environnement en suivant le [Démarrage rapide](https://war3ai.com/fr/docs/quickstart/), et faites tourner `hello_bot` jusqu'au bout (les paysans partent récolter). Ainsi, en cas de problème, vous saurez distinguer un problème d'environnement d'un problème de Bot. ## 1. Préparer les documents pour le modèle La qualité du code produit dépend à 80 % du fait que le modèle a lu les bons documents. Choisissez la méthode selon votre outil : | Vous utilisez | Comment fournir les documents | |---|---| | **Un agent de code capable de lire le dépôt** (Claude Code, Cursor, Codex, etc.) | Ouvrez-le dans le dossier du dépôt et demandez-lui de lire d'abord `docs/BOT_HANDBOOK_ZH.md`, `docs/api.json` et un exemple (`brains/examples/macro_bot.py` pour l'économie, `micro_bot.py` pour le combat) | | **Un modèle conversationnel avec accès à Internet** | Demandez-lui de lire d'abord [`https://war3ai.com/llms-full.txt`](https://war3ai.com/fr/llms-full.txt) : toute la documentation du site tient dans ce seul fichier | | **Une conversation web sans accès à Internet** | Collez le manuel, [`api.json`](https://war3ai.com/fr/api.json) et un fichier d'exemple à la suite du prompt | | **Un modèle local** (LM Studio, Ollama) | Même chose. Prévoyez une fenêtre de contexte d'au moins 32K tokens, sinon le manuel et le catalogue de l'API n'y tiennent pas | Pour une technique pro précise, collez en plus la recette correspondante des [Recettes des joueurs pros](https://war3ai.com/fr/docs/cookbook/). ## 2. Copier ce prompt Remplacez la dernière partie, « La stratégie que je veux », par vos propres mots, en étant le plus précis possible : ```text Tu dois écrire une IA (en Python) pour Warcraft III 1.27. Utilise uniquement les méthodes de Game listées dans api.json, n'invente aucune méthode qui n'existe pas. Inspire-toi de rush_bot.py : hérite de openwar3.Bot, implémente on_start(g) et on_tick(g). Règles : - on_tick est appelé environ 5 fois par seconde et doit être rapide (pas de sleep dedans). - Une valeur illisible vaut None, pas 0 : vérifie-la avant de l'utiliser. - Une commande renvoie un reçu (Receipt) ; `if r:` signifie "le moteur l'a acceptée" ; sinon `r.reason` en donne la raison (nourriture insuffisante, or insuffisant, cible invisible, ce héros existe déjà…) : réessaie au tick suivant ou change d'approche. - Pour attaquer un ennemi précis, utilise g.attack(unités, ennemi) ; l'ennemi doit être dans le champ de vision, sinon l'ordre est rejeté. - Un héros mort se ressuscite avec g.revive(autel), on ne peut pas en entraîner un autre. - Pour construire, utilise g.build_near(ouvrier, code_bâtiment, x, y) : il trouve seul un emplacement libre, suit le résultat et ne fait rien si l'argent manque. - Pour savoir "ce qui vient de se passer" (qui est mort, qui a perdu des PV, un héros qui monte de niveau, un objet qui tombe), implémente on_event(g, ev). - Ne redonne pas le même ordre à la même unité à chaque tick (cela interrompt ce qu'elle fait) ; donne des ordres aux unités "inactives". - N'affecte à la récolte que les ouvriers de idle_workers(). 5 ouvriers au maximum par mine d'or. - Une seule unité à la fois dans une file d'entraînement (relance quand g.queue(bâtiment) est vide) ; si la nourriture bloque, regarde g.production(bâtiment).blocked. - Quand un tick doit envoyer beaucoup de commandes, regroupe-les dans with g.batch(): (une seule attente du thread du jeu). - Pour choisir qui attaquer, utilise g.time_to_kill(mon_groupe, ennemi) (contres et armure compris) ; pour choisir où aller, g.path_distance (renvoie None si inaccessible). - En mode équitable, seul ce qui est dans le champ de vision est visible ; pour les ennemis déjà aperçus, utilise g.last_seen(). - Les unités sont désignées par un code à quatre caractères (Paysan humain hpea, Fantassin hfoo, Caserne hbar…), les sorts par leur nom d'ordre (thunderbolt Éclair de tempête, blizzard Blizzard, holybolt Lumière sacrée…, la liste complète est dans data/order-ids.txt), l'apprentissage des compétences par un code à quatre caractères (AHtb, AHbz…). La stratégie que je veux : <Écris-la ici en langage courant, par exemple : "Humains, au départ 5 paysans à l'or et 1 au bois ; l'Archimage en premier ; deux casernes pour des fantassins et des fusiliers ; à 12 unités, partir avec le héros attaquer l'expansion adverse ; si le héros passe sous 30 % de PV, rentrer à la base ; en creeping, attaquer d'abord les camps proches de la base."> ``` ### Comment bien décrire votre stratégie Ce que le modèle redoute le plus, ce sont les consignes floues. Plutôt que « jouer plus agressif », ces informations sont bien plus utiles : - **Race et héros** : quel héros en premier, dans quel ordre monter ses compétences (par exemple pour l'Archimage : Élémentaire d'eau, Blizzard, Élémentaire d'eau…). - **Ordre de construction** : à quel paysan construire la caserne, quand passer de tier, combien de casernes. - **Composition de l'armée** : fantassins + fusiliers ? À partir de combien d'unités sortir ? - **Conditions d'attaque et de repli** : à combien d'unités attaquer, sous quel niveau de PV le héros se replie, rentrer reconstituer l'armée après de lourdes pertes. - **Creeping** : creeper ou non, quand (à la tombée de la nuit ?), uniquement les camps à votre portée ? - **Équitable ou non** : si vous comptez passer un jour sur l'Arène, précisez « n'utiliser que les ennemis visibles dans le champ de vision ». ## 3. Le lancer Enregistrez le code fourni par le modèle dans `brains/my_bot.py`, puis : ```bash python tools/play.py --bot brains/my_bot.py --race 1 --difficulty 2 ``` Pour voir le résultat plus vite, ajoutez `--speed 200` (vitesse ×2). ## 4. Le faire corriger - **En cas d'erreur** : recollez **l'intégralité du message d'erreur** tel quel au modèle et dites « corrige ». - **S'il joue mal** : décrivez **ce que vous voyez en jeu**, pas la cause que vous supposez. Par exemple « le héros reste planté dans la base », « les unités arrivent une par une et se font tuer », « les paysans s'entassent sur une seule mine ». - **Pour ajouter une stratégie** : une seule chose à la fois, jouez une partie pour vérifier que rien n'est cassé, puis passez à la suivante. > **Remarque** > > Un agent de code capable d'exécuter des commandes peut aussi prendre en charge les étapes 3 et 4 : jouer une partie, lire les journaux et les reçus, corriger le code, relancer. Pour lui donner assez d'informations, voir [Itération autonome d'un agent](https://war3ai.com/fr/docs/agent-loop/). ## 5. Problèmes fréquents | Symptôme | Cause probable | |---|---| | Rien ne bouge | Mauvais numéro d'instance (`--inst`), ou la partie n'a pas encore commencé | | Les paysans ne récoltent pas | Des ordres sont donnés à des paysans déjà occupés ; n'affectez que ceux de `idle_workers()` | | Aucune ferme n'est jamais construite | Utilisez `build_near` au lieu de coordonnées en dur ; vérifiez si le `reason` du reçu indique un manque d'argent | | Le héros ne sort pas | Regardez le reçu de `train` : nourriture insuffisante ? Ou le héros est mort (il faut `revive`) ? | | Le héros ne lance pas ses sorts | Compétence non apprise (`learn`) ou mana insuffisant ; après le lancement, vérifiez avec `cooldown()` que le sort est en recharge | | Les unités tressautent à chaque tick | Les ordres sont redonnés à chaque tick ; ne donnez des ordres qu'aux unités inactives | | Aucune unité ne sort, l'argent s'accumule | La nourriture bloque : regardez `g.production(caserne).blocked` | | Le modèle utilise des méthodes inexistantes | Insistez à nouveau dans le prompt sur « uniquement les méthodes de api.json », et collez api.json en entier | ## Pour aller plus loin - Toutes les méthodes et le mécanisme sous-jacent de chacune : [catalogue de l'API](https://war3ai.com/fr/api/) ; - Le cerveau de référence (`brains/xwar3/strategy`) est une IA complète qui prend des expansions, creepe et attaque. Vous pouvez faire lire sa logique au modèle, mais il utilise des interfaces de plus bas niveau : ne le recopiez pas tel quel ; - Sur l'[Arène](https://war3ai.com/fr/arena/), vous ne verrez que les ennemis présents dans votre champ de vision : ajoutez dès maintenant `--fair` pour vous imposer cette contrainte, vous n'aurez rien à modifier plus tard. --- # Itération autonome d'un agent > Laissez un agent de code jouer ses propres parties, lire les résultats, corriger le code et recommencer : il lui faut une commande qui tourne sans surveillance, un rapport de partie structuré et un objectif clair. Dans [Écrire un Bot avec un LLM](https://war3ai.com/fr/docs/ai-bot/), l'étape « jouer une partie → observer → le dire au modèle » vous revient. Un agent de code capable d'exécuter des commandes (Claude Code, Codex, le mode Agent de Cursor, etc.) peut aussi prendre cette étape en charge et boucler la boucle : ```text corriger le code ──► jouer une partie (sans surveillance) ──► lire le rapport ──► trouver le point décisif ──┐ ▲ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────┘ ``` Pour que cette boucle converge vraiment, l'agent a besoin de trois choses. ## 1. Une commande qui tourne sans surveillance ```bash python tools/play.py --bot brains/my_bot.py --speed 200 --minutes 10 --fair ``` - `--minutes` garantit que la partie se termine (en minutes réelles), l'agent ne restera pas bloqué dans une partie ; - `--speed 200` fait gagner du temps en vitesse ×2 — mais dans le Bot, **attendez selon l'horloge du jeu** (`g.clock()`), pas avec un `sleep` en temps réel ; - `--fair` l'oblige dès le premier jour à respecter les règles de l'Arène : il ne voit que ce qui est dans son champ de vision ; - À la fin de l'exécution, le terminal affiche la raison de la fin, par exemple `我方没有单位了` (« plus aucune unité de notre côté ») ou `到时间了` (« temps écoulé ») ; ce que le Bot affiche lui-même avec `print` apparaît aussi dans le terminal. > **Attention** > > Quand la fenêtre est réduite, la simulation du jeu est arrêtée. Faites lancer le jeu par l'agent en mode fenêtré par défaut, et évitez qu'il utilise le même numéro d'instance que celle que vous utilisez (`--inst`). ## 2. Un rapport de partie structuré La sortie du terminal est faite pour les humains. Pour l'agent, il faut un JSON : ce qui s'est passé, ce qui a échoué, et pourquoi. Le SDK vous fournit déjà toute la matière première : les reçus portent des codes de motif, le flux d'événements contient les fins de production et les pertes. Il suffit de les rassembler : ```python title="recorder.py" import collections, json, time from openwar3 import Bot class Recorder(Bot): """Ajoute un rapport de partie au Bot. Héritez-en, puis appelez super() dans vos propres on_start / on_event.""" def on_start(self, g): self.rejects = collections.Counter() # "train hfoo: rejected(人口不够)" (= nourriture insuffisante) -> nombre d'occurrences self.timeline = [] # [secondes de jeu, catégorie, code à quatre caractères] : entraînement / recherche / construction / amélioration terminés self.lost = collections.Counter() # ce que nous avons perdu self.killed = collections.Counter() # ce que nous avons tué def check(self, r, what): """Enveloppe une commande pour noter la raison du rejet : self.check(g.train(b, "hfoo"), "train hfoo")""" if r is not None and not r: self.rejects[f"{what}: {r.reason}"] += 1 return r def on_event(self, g, ev): me = g.me() if ev.kind == "production.done" and ev.owner == me: self.timeline.append([round(ev.clock), ev.done_kind, ev.done_code]) elif ev.kind == "unit.died": (self.lost if ev.owner == me else self.killed)[ev.type] += 1 def on_end(self, g, reason): report = {"reason": reason, "timeline": self.timeline, "lost": self.lost, "killed": self.killed, "rejects": self.rejects.most_common(10)} try: # le jeu est peut-être déjà fermé ; si la lecture échoue, tant pis report |= {"clock": g.clock(), "resources": g.resources(), "army": len(g.my_army()), "workers": len(g.my_workers())} except Exception: pass with open(f"run_{int(time.time())}.json", "w", encoding="utf-8") as f: json.dump(report, f, ensure_ascii=False, indent=1) ``` Les questions auxquelles ce rapport répond : | Signal | Source | Ce qu'il révèle | |---|---|---| | Raisons de rejet les plus fréquentes | `reason` / `verdict` du reçu | Nourriture constamment bloquée (3), ordres donnés sans assez d'argent (8 / 9), attaques sur des cibles dans le brouillard (1001), entraînement d'un héros déjà mort au lieu de le ressusciter (221) | | Chronologie de production | Événements `production.done` (avec le nombre de secondes de jeu nécessaires) | À quelle seconde sort le premier héros, à quelle seconde vous passez de tier, si la caserne produit en continu ; comparable aux ouvertures des joueurs pros | | Pertes des deux camps | Événements `unit.died` | Si vous offrez des unités en continu, combien de fois le héros est mort, si le creeping a été rentable | | Raison de la fin | `on_end(g, reason)` | `我方没有单位了` (plus aucune unité) = défaite ; `到时间了` (temps écoulé) = pas encore de vainqueur | | Armée et ressources finales | Un instantané lu dans `on_end` | De l'argent qui dort = la production ne suit pas ; trop peu d'ouvriers = l'économie n'a pas décollé | > **Remarque** > > La détermination programmatique du vainqueur fait partie des expériences fondatrices de l'[Arène](https://war3ai.com/fr/arena/) et figure encore sur la feuille de route. Pour l'instant, vous pouvez considérer « plus aucune unité de notre côté » comme une défaite, et approcher la victoire par « tous les bâtiments ennemis visibles sont détruits ». ## 3. Un objectif clair et quelques contraintes Confiez le texte suivant à l'agent, en l'adaptant à votre objectif : ```text Objectif : faire en sorte que brains/my_bot.py batte de façon fiable l'ordinateur en difficulté « facile » sur Echo Isles (Humains contre race aléatoire). À chaque itération : 1. Exécute python tools/play.py --bot brains/my_bot.py --speed 200 --minutes 10 --fair 2. Lis la sortie du terminal et le run_*.json le plus récent : raison de la fin, chronologie de production, raisons de rejet les plus fréquentes, pertes des deux camps 3. Trouve « un seul » problème, celui qui pèse le plus sur le résultat, et ne corrige que celui-là ; note en commentaire dans le code la raison du changement et les données sur lesquelles il s'appuie 4. Reviens à l'étape 1. Après 3 parties d'affilée sans progrès, arrête-toi et communique-moi le rapport et ton analyse Contraintes : - Utilise uniquement les méthodes de docs/api.json, n'invente pas d'interface - Ne redonne pas le même ordre à la même unité à chaque tick ; ne donne des ordres qu'aux unités inactives - Garde --fair (uniquement les ennemis visibles dans le champ de vision) - Avant de modifier le code, exécute python tools/run_tests.py pour vérifier que les exemples ne sont pas cassés ``` ## Quelques habitudes pour faire converger la boucle plus vite - **Une seule modification à la fois.** Si vous changez trois choses en même temps, vous ne saurez ni laquelle a fait gagner, ni laquelle a tout cassé. - **Jouez assez de parties pour comparer.** Une même situation comporte beaucoup d'aléa ; deux parties ne révèlent que les très grands écarts. Pour juger d'un progrès, regardez au moins la tendance sur plusieurs parties. - **Corrigez les « rejets » avant d'ajuster la stratégie.** La raison de rejet la plus fréquente dans les reçus est souvent le plus gros bug du Bot. - **Écrivez votre raisonnement dans les commentaires.** L'agent de l'itération suivante (ou de la conversation suivante) comprendra pourquoi le code est ainsi et ne défera pas ce qui a été corrigé. - **Des tests hors ligne comme filet de sécurité.** Écrivez pour la logique critique des tests unitaires qui n'ont pas besoin de lancer le jeu (les tests des Bots d'exemple sont dans `brains/examples/tests/`), et faites-les exécuter par l'agent après chaque modification. --- # Un LLM comme conseiller stratégique > Confiez à un LLM les questions « que faut-il économiser, où affecter les ouvriers, faut-il attaquer ou temporiser cette minute », et laissez la couche de règles se contenter d'exécuter et d'opposer son veto. Le cerveau de référence fonctionne déjà ainsi ; cette page en explique le modèle et les pièges. Passé un certain stade, vous remarquerez que les règles d'économie de votre Bot s'empilent les unes sur les autres : une règle pour le nombre de bûcherons, une pour 5 ouvriers par mine, une pour réduire le bois de moitié quand il y en a trop, une pour envoyer plus de monde à l'or quand l'or manque et que le bois abonde… Chacune est juste prise isolément, mais ensemble elles produisent des situations comme « la mine manque d'ouvriers alors que tous les paysans coupent du bois », dont **aucune règle n'est responsable**. Ce genre de jugement, « regarder l'ensemble et fixer des priorités », ne se prête pas à des `if / else`, mais c'est exactement ce que les LLM font bien. Le cerveau de référence (`brains/xwar3/strategy/brain/coach.py`) utilise le découpage en couches ci-dessous. ## Couches ```text LLM (conseiller) Une fois toutes les 20 secondes de jeu, asynchrone, ne bloque jamais un tick Entrée : un instantané d'une page de la partie (ressources, nourriture, répartition des paysans, mines, unités, technologies, héros, renseignements sur l'ennemi, événements récents) Sortie : JSON strict — un diagnostic en une phrase + répartition des ouvriers + quoi produire en priorité + posture de cette minute + choses à éviter │ ▼ liste blanche + bornage min/max + veto Couche de règles (Bot, à chaque tick) Traduit les conseils en « biais » sur les capacités existantes : répartition des ouvriers, priorités de construction / d'entraînement, posture d'attaque │ ▼ Couche d'exécution (SDK / couche réflexe) Donne les ordres, lit les reçus, fait la micro ``` ## Contrat de sortie Faites en sorte que le modèle ne produise qu'un JSON aux champs fixes, sans ajout ni omission : ```json { "diagnosis": "Une phrase : le plus gros problème de la partie, qui doit s'appuyer sur les données d'entrée", "workers": { "gold": 10, "lumber": 6 }, "priority": ["hpea", "hhou", "hbar"], "posture": "creep", "avoid": ["Ne recherchez pas Iron Plating en premier si le bois manque"] } ``` | Champ | Usage par la couche de règles | Bornage du cerveau de référence | |---|---|---| | `workers` | Nombre cible d'ouvriers à l'or et au bois | Or 2 ~ 25, bois 1 ~ 20 ; la somme ne peut pas dépasser le nombre total de paysans | | `priority` | Ordre de priorité pour l'entraînement / la construction / la recherche | 4 au maximum ; seuls les codes à quatre caractères présents dans la table des « codes autorisés » sont acceptés | | `posture` | Posture de cette minute | Uniquement l'une des valeurs `attack` `defend` `creep` `expand` `recover` `hold` | | `avoid` | Choses à ne pas faire cette minute | 2 au maximum | | `diagnosis` | Sert uniquement aux journaux et à l'affichage dans la console | — | Prévoyez un prompt par race, qui ne couvre que les arbitrages propres à cette race (la construction coopérative et la Milice des Humains, les Terriers des Orcs, la Mine d'or hantée des Morts-vivants, la Mine d'or enchevêtrée des Elfes de la nuit…). Placez les règles générales dans une partie commune, ne les recopiez pas quatre fois. ## Quatre contraintes strictes Le cerveau de référence a appris chacune d'elles à ses dépens : 1. **Le conseiller ne donne jamais d'ordre direct aux unités.** Il ne voit pas ce qui se passe à l'échelle de 150 ms, et il hallucine. Il ne modifie que les objectifs et les priorités ; qui va où et qui attaque qui reste décidé par la couche de règles et la couche réflexe — le commandement ne peut avoir qu'un seul maître. 2. **Asynchrone.** Un appel au conseiller prend environ 1 seconde et tourne dans un thread d'arrière-plan ; le résultat le plus récent s'applique, et il **ne bloque jamais un tick**. Si le modèle n'est pas démarré, dépasse le délai ou répond n'importe quoi, faites comme si cette couche n'existait pas : le comportement revient aux règles pures. Un conseil trop ancien (plus de 3 intervalles) n'est pas utilisé non plus. 3. **Liste blanche + bornage.** Chaque champ doit correspondre à une capacité existante, et les valeurs numériques sont bornées dans une plage raisonnable. Tout contenu non reconnu est **compté puis rejeté**, pas ignoré en silence. 4. **Tout compter.** Combien de requêtes, combien de succès, combien de délais dépassés, combien de bornages, combien de fois chaque champ a été retenu : publiez tout cela, avec la dernière entrée envoyée au modèle. Sinon, « cette couche sert-elle vraiment à quelque chose ? » reste une question sans réponse. > **Ce qui se dégrade sans danger est aussi ce qui se dégrade le plus discrètement** > > Le conseiller est conçu pour que « échec = faire comme si cette couche n'existait pas » ; quand le service du modèle ne tourne pas, le Bot se comporte donc exactement comme avec des règles pures, et rien n'y paraît de l'extérieur. Le cerveau de référence a ainsi passé une journée entière avec le conseiller injoignable sur les 6 instances, sans que personne ne s'en aperçoive. Publiez toujours « l'heure du dernier succès » et « la raison du dernier échec » — c'est précisément le rôle de la page « Conseiller stratégique » de la [console Farsight](https://war3ai.com/fr/docs/console/). ## Implémentation dans votre propre Bot Voici un squelette minimal qui fonctionne avec n'importe quelle API compatible OpenAI (LM Studio, Ollama ou une API cloud) et n'utilise que la bibliothèque standard : ```python title="coached_bot.py" import collections, json, threading, urllib.request from openwar3 import Bot BASE = "http://127.0.0.1:1234/v1" # LM Studio / Ollama / tout service compatible OpenAI MODEL = "your-model" POSTURES = {"attack", "defend", "creep", "expand", "recover", "hold"} SYSTEM = """Tu es un coach de macro pour Warcraft III. Tu ne t'occupes que de l'économie et de la stratégie, pas de la micro. Réponds uniquement en JSON, avec des champs fixes : {"diagnosis": une phrase, "workers": {"gold": entier, "lumber": entier}, "priority": [codes à quatre caractères, 4 au maximum, uniquement parmi allowed], "posture": une valeur parmi six, "avoid": [2 au maximum]} Appuie-toi uniquement sur les données de partie fournies ; n'invente rien qui n'y figure pas.""" def ask(state: dict) -> dict: body = {"model": MODEL, "temperature": 0.3, "max_tokens": 260, "messages": [{"role": "system", "content": SYSTEM}, {"role": "user", "content": json.dumps(state, ensure_ascii=False)}]} req = urllib.request.Request(f"{BASE}/chat/completions", json.dumps(body).encode(), {"Content-Type": "application/json"}) with urllib.request.urlopen(req, timeout=8) as r: text = json.load(r)["choices"][0]["message"]["content"] return json.loads(text[text.index("{"): text.rindex("}") + 1]) class CoachedBot(Bot): EVERY = 20.0 # secondes de jeu : la macro se décide à l'échelle de la minute, inutile de demander à chaque tick allowed = {"hpea", "hfoo", "hrif", "hkni", "hhou", "hbar", "hbla", "Rhme", "Rhar"} def on_start(self, g): self.plan, self.plan_at, self.asked_at, self.busy = {}, -1e9, -1e9, False self.stats = collections.Counter() def summary(self, g) -> dict: # lire l'instantané dans le thread principal ; le thread d'arrière-plan ne touche jamais g res = g.resources() or {} return {"clock": round(g.clock() or 0), "gold": res.get("gold"), "lumber": res.get("lumber"), "food": [res.get("food_used"), res.get("food_cap")], "workers": len(g.my_workers()), "idle_workers": len(g.idle_workers()), "army": collections.Counter(u.type for u in g.my_army()), "enemy_seen": collections.Counter(u.type for u, _t, _age in g.last_seen(max_age=90)), "night": g.is_night(), "allowed": sorted(self.allowed)} def consult(self, state, now): try: p = ask(state) self.stats["ok"] += 1 posture = p.get("posture") if posture not in POSTURES: self.stats["bad_posture"] += 1 # compter puis rejeter, jamais en silence posture = "hold" self.plan = {"gold": min(25, max(2, int(p["workers"]["gold"]))), # bornage "lumber": min(20, max(1, int(p["workers"]["lumber"]))), "priority": [c for c in p.get("priority", []) if c in self.allowed][:4], "posture": posture} self.plan_at = now except Exception as e: # délai dépassé / réponse incohérente : faire comme si cette couche n'existait pas self.stats[f"error:{type(e).__name__}"] += 1 finally: self.busy = False def on_tick(self, g): now = g.clock() or 0.0 if not self.busy and now - self.asked_at >= self.EVERY: self.busy, self.asked_at = True, now threading.Thread(target=self.consult, args=(self.summary(g), now), daemon=True).start() plan = self.plan if now - self.plan_at <= 3 * self.EVERY else {} # ne pas utiliser un conseil trop ancien # ↓ couche de règles : sans plan, suivre les règles par défaut ; avec un plan, n'ajuster que la répartition, les priorités et la posture — les ordres concrets restent décidés par les règles ... ``` ## Choisir un modèle | Situation | Recommandation | |---|---| | En local, besoin de vitesse | Les modèles MoE (qui n'activent qu'une petite partie de leurs paramètres à chaque appel) sont bien plus rapides que les modèles denses de même taille. Le cerveau de référence utilise Qwen3.6-35B-A3B (LM Studio, Q4) : médiane **1.09 s**, pire cas 1.45 s, et 5/5 sorties passent directement dans `json.loads` | | En local, modèle qui « réfléchit » | **Désactivez impérativement la section de réflexion**, sinon tous les tokens partent dans la réflexion et aucun JSON ne sort. LM Studio ignore `/no_think` ; le cerveau de référence est passé à `/v1/completions`, construit lui-même le ChatML et pré-remplit un `` vide suivi d'un `{` | | Modèle cloud | La latence est généralement plus élevée, mais ce découpage est asynchrone par conception ; les décisions de macro se comptent en minutes, quelques secondes de latence sont acceptables | > **Remarque** > > Le même modèle peut aussi donner la parole à vos unités : voir [Bulles de dialogue et modèles locaux](https://war3ai.com/fr/docs/speech/). Si vous voulez que le modèle donne directement les ordres à chaque tick (au lieu de jouer les conseillers), attendez la passerelle JSON de l'[Arène](https://war3ai.com/fr/arena/). --- # Un LLM appelle directement des outils (MCP) > tools/war3_mcp.py est un serveur MCP. Branchez-le sur Claude Code, Claude Desktop ou n’importe quel client compatible MCP : le LLM peut alors observer directement la partie, donner des ordres, parler au joueur à l’écran, lui poser des questions avec des cartes et prendre des captures d’écran, sans écrire de code au préalable. `tools/war3_mcp.py` est un **serveur MCP** (stdio). Claude Code, Claude Desktop, un framework d’agents pour modèles locaux — branchez-le sur n’importe quel client compatible MCP, et le LLM peut **directement** observer la partie, donner des ordres, parler au joueur sur l’écran du jeu, lui poser des questions et prendre des captures d’écran, sans écrire de code au préalable. Après l’écriture de Bots, le rôle de conseiller et les unités qui parlent, c’est une autre façon de se brancher : **le LLM utilise lui-même les outils**. ## Brancher le serveur ```bash claude mcp add war3 -- python \tools\war3_mcp.py --inst 9 # Claude Code ; remplacez par votre dossier openwar3 ``` Pour les autres clients, écrivez la configuration sur ce modèle : ```json {"mcpServers": {"war3": {"command": "python", "args": ["\\tools\\war3_mcp.py", "--inst", "9"]}}} ``` La connexion au jeu n’a lieu qu’au premier appel d’outil : le jeu peut donc être lancé après ; s’il est fermé puis relancé, l’appel suivant se reconnecte automatiquement. Ajoutez `--role` pour limiter ce que le LLM peut faire : | Rôle | Peut utiliser | |---|---| | `dev` (par défaut) | Tous les outils, y compris `war3_jass` | | `player --player N` | Ne commande que les unités du joueur N et ne voit que ce qui est dans sa vision (mode équitable) ; pas de JASS | | `observer` | Lecture seule, ne peut rien dessiner à l’écran ni faire parler les unités ; le runtime rejette directement ses commandes | Le rôle `player` a les mêmes limites que dans la [passerelle](https://war3ai.com/fr/docs/gateway/) : pas de fin de partie, de changement de vitesse ni de pause, pas d’accès aux interfaces qui dévoilent le jeu caché des autres, et les requêtes avec un numéro de joueur ne portent que sur lui-même. Quelques plafonds : un résultat d’outil fait au plus 200 000 caractères, et au-delà il est tronqué avec une indication pour réduire la portée de la requête ; `war3_ask_player` attend au plus 120 secondes ; le `scale` des captures d’écran est compris entre 0.1 et 1. ## Outils | Outil | Ce qu’il fait | |---|---| | `war3_overview` | La partie en une page : temps, ressources, nourriture, effectifs de chacun de nos types d’unités, héros (PV, mana, niveau, recharges), types d’unités ennemies visibles, production. **À appeler en premier** | | `war3_units` | Liste des unités (`owner` vaut me / enemy / creep / all, filtre `types`) ; `addr` sert à donner des ordres | | `war3_events` | Ce qui s’est passé depuis l’appel précédent : morts, montées de niveau, sorts lancés, productions terminées, chat, clics du joueur sur les boutons… (par défaut sans les quelques types qui inondent le flux) | | `war3_call` | Appelle n’importe quelle interface publique (`move`, `attack_move`, `train`, `build`, `cast`, `learn`, `ui.button`, `canvas.text`…) ; une unité s’écrit `{"unit": addr}` | | `war3_api` | Recherche dans les interfaces : par mot-clé, dans les noms et les descriptions | | `war3_toast` / `war3_say` | Une ligne de texte en haut de l’écran / une phrase au-dessus d’une unité | | `war3_ask_player` | Présente quelques cartes de choix au centre de l’écran, attend le clic du joueur et renvoie son choix (peut mettre le jeu en pause) | | `war3_screenshot` | Capture de l’écran du jeu (PNG ; fonctionne même si la fenêtre est masquée, sans lui voler le focus) | | `war3_jass` | Exécute un bout de JASS (réservé à dev ; modification du monde en solo uniquement) | Ce qu’on peut en faire : - **Partenaire de jeu / coach** : `war3_overview` pour lire la partie, `war3_toast` pour afficher des conseils à l’écran ; - **Consulter le joueur en cours de partie** : `war3_ask_player` affiche trois cartes, et on suit celle sur laquelle le joueur clique ; - **Commentaire** : `war3_events` pour lire ce qui s’est passé, `war3_say` pour que les unités le racontent elles-mêmes ; - **Commander directement une armée** : rôle `player` + `war3_call`, qui ne peut déplacer que ses propres unités ; - **Ajuster l’interface en regardant l’image** : une capture avec `war3_screenshot` pour vérifier que les boutons dessinés sont bien placés. ## À quoi ressemble une conversation ```text Vous : regarde où en est la partie, puis demande-moi à l'écran : prochaine étape, expansion, masser des troupes ou monter de tier ? → war3_overview {} ← La partie en une page : temps de jeu, or 500, nourriture 10/12, chez nous htow 1 · hpea 5 · Hpal 1, aucun ennemi en vue, aucune production en cours → war3_ask_player {"question": "Prochaine étape ?", "options": ["Masser des troupes", "Expansion", "Monter de tier"], "pause": true} ← {"picked": 1, "option": "Expansion"} Modèle : vous avez choisi l'expansion. Je cherche d'abord un paysan inactif avec war3_units, puis la mine d'or la plus proche… ``` ## Mesures 2026-09-25 : - Notre propre client MCP connecté à une vraie partie, 7/7 : poignée de main → liste des outils (10) → `war3_overview` (`htow` 1, `hpea` 5) → `war3_units` → `war3_toast` → `war3_screenshot` (PNG d’environ 200 Ko) → `war3_ask_player` (trois cartes, clic simulé sur la deuxième → `{"picked": 1, "option": "Expansion"}`). - Claude Code 2.1 réellement branché : il lance lui-même le serveur et fait la poignée de main, l’état est `connected`, et les 10 outils apparaissent dans sa liste d’outils sous la forme `mcp__war3__*`. ## Implémentation - JSON-RPC 2.0 délimité par des retours à la ligne (`initialize` / `tools/list` / `tools/call` / `ping`), version de protocole 2025-06-18, compatible avec 2025-03-26 et 2024-11-05. - Les erreurs d’outil sont placées dans le résultat, conformément aux règles de MCP (`isError: true`), sans couper la connexion. - Partage avec la [passerelle](https://war3ai.com/fr/docs/gateway/) la même liste blanche de rôles, le même format de paramètres d’unité et la même « partie en une page ». - Les journaux vont sur stderr ; stdout ne contient que le protocole. --- # Modèle mental > Instantané, commande, reçu, événement, tick, lot. Comprenez ces six concepts et vous comprendrez pourquoi l'API a cette forme, et comment écrire du code rapide. ## Instantané : lecture, zéro attente Toutes les **50 ms**, le runtime relève l'ensemble du monde sur le thread du jeu et l'écrit en mémoire partagée. `g.snapshot()` vous donne un monde **complet et cohérent** : - 16 emplacements de joueur : or, bois, nourriture, plafond de nourriture, total récolté, race ; - jusqu'à 1024 unités : type, propriétaire, coordonnées, PV / mana (avec leurs maximums), ordre en cours et cible de l'ordre, **ce qu'elle attaque réellement** (cible de tâche), niveau / expérience / points de compétence des héros, visibilité de l'unité pour chaque joueur ; - jusqu'à 256 fiches de détail d'unité : 12 capacités (niveau, recharge restante), 8 buffs, 6 emplacements d'inventaire ; - objets au sol, arbres (rafraîchis toutes les 2 secondes), table de production (progression des entraînements / recherches / constructions / améliorations), horloge de jeu, heure du jour dans le jeu. Lire un instantané prend environ **0.4 ms** (analyse en Python), sans attendre le thread du jeu. Donc : **lisez sans compter**. Les méthodes comme `g.units()`, `g.my_army()`, `g.cooldown()` ou `g.inventory()` puisent toutes dans le même instantané ; les appeler autant de fois que vous voulez dans un tick ne coûte presque rien. > **Astuce** > > La période de publication est réglable : `g.set_publish_period(ms)`, de 16 à 1000 millisecondes. Un relevé prend environ 0.5 ~ 0.9 ms sur le thread du jeu, 33 ms ne pose donc aucun problème. La valeur est partagée par toute la machine : la dernière écrite l'emporte. ## Commande : écriture, environ une frame `g.move / attack / gather / build / train / cast …` sont exécutées par le thread du jeu. Le runtime exécute par lots les commandes soumises par les clients dans la **distribution d'événements** du thread du jeu ; une commande attend donc environ **une frame** (environ 0.1 ms si elle tombe dans une salve d'événements, sinon jusqu'à la distribution suivante). - Une commande accepte **une unité ou une liste** ; les unités d'une liste reçoivent l'ordre dans la même frame ; - Ajouter `queue='after'` équivaut à Shift : l'unité termine sa tâche en cours avant de faire celle-ci ; - Dans une commande, utilisez simplement les objets unité tirés de l'instantané : le SDK vérifie leur identité par **paire de handles** (une adresse peut être réutilisée par une nouvelle unité, un handle non). ## Reçu : pour chaque commande ```python r = g.build(worker, "hbar", x, y) if r: # le moteur l'a acceptée ... else: r.reason # 'rejected(金不够)' (= or insuffisant) r.verdict # 8 r.exec_us # microsecondes d'exécution de cette commande sur le thread du jeu ``` Le reçu est relu **dans la même frame** : l'ordre de l'unité avant et après la commande, la valeur de retour de la fonction du moteur, le code de motif de la vérification de faisabilité. Il répond à « le moteur a-t-il accepté cette commande, et sinon pourquoi », mais **pas** à « l'action a-t-elle finalement abouti » — pour cela, regardez l'instantané et les événements. Tous les codes d'état et codes de motif sont listés dans [Reçus et codes de motif](https://war3ai.com/fr/docs/reason-codes/). ## Événement : ce qui s'est passé Avant chaque `on_tick`, `on_event(g, ev)` vous transmet un par un les événements survenus depuis le tick précédent : | Événement | Signification | |---|---| | `unit.appeared` / `unit.died` / `unit.removed` | Une unité apparaît, meurt, disparaît (entrer dans une mine d'or, être convertie ou un cadavre qui se décompose comptent aussi comme disparition, ce qui n'est pas une mort) | | `unit.damaged` / `order.changed` / `owner.changed` | Perte de PV, changement d'ordre, changement de propriétaire | | `hero.levelup` | Un héros monte de niveau | | `item.appeared` / `item.removed` | Un objet au sol apparaît, est ramassé ou utilisé | | `damage` | Niveau moteur : **chaque coup** porté. Unité source, type d'attaque, type de dégâts, PV réellement perdus, dégâts avant armure | | `killed` | Niveau moteur : ce coup a tué l'unité, avec le tueur | | `production.done` | Entraînement / recherche / construction / amélioration terminé, avec le code à quatre caractères et le nombre de secondes de jeu nécessaires. Émis aussi pour l'adversaire | | `spell.cast` | Une unité a lancé un sort : code à quatre caractères du sort, niveau, recharge en secondes, point d'incantation | | `message` | Une ligne est apparue dans un cadre de messages à l'écran : indication du jeu (« Il vous faut plus de fermes »), chat (`.chat` contient l'auteur et le texte), message système | | `selection.changed` / `player.left` | La sélection du joueur local a changé / un joueur est parti ou a été retiré après sa défaite | | `game.started` / `game.ended` | Une nouvelle partie commence / on quitte la partie | Les événements d'entrée — clic sur un bouton du canevas, raccourci clavier, clic au sol — sont décrits dans [Interface et entrées](https://war3ai.com/fr/docs/ui-input/). > **Attention** > > Le flux d'événements est **global** : il contient aussi les productions terminées de l'adversaire et la mort des creeps. Filtrez par `ev.owner` ou par handle d'unité. ## Tick : le rythme du Bot Par défaut, `on_tick` est appelé 5 fois par seconde (en temps réel). La durée d'un tick correspond pour l'essentiel à vos propres calculs : l'instantané est sans attente, une commande prend environ une frame. Un tick qui dépasse sa période décale automatiquement le suivant, sans accumulation. - **En vitesse ×2, n'attendez pas en temps réel.** Pour attendre 3 secondes de jeu, vérifiez que `g.clock()` a augmenté de 3, pas `sleep(1.5)`. - **Pas de `sleep` dans `on_tick`.** Pour « faire quelque chose un peu plus tard », notez l'heure de jeu actuelle et vérifiez de nouveau au tick suivant. ## Lot : des dizaines de commandes, une seule attente Quand un tick doit envoyer beaucoup de commandes, regroupez-les dans `with g.batch():` : ```python with g.batch(): g.attack(melee, target_a) g.attack(ranged, target_b) g.move(wounded, home.x, home.y) g.cast(hero, "thunderclap") # à la fin du bloc, tout le lot est soumis : exécuté dans la même frame, une seule attente du thread du jeu ``` - Dans le bloc, les commandes renvoient `Pending`, qui devient un reçu à la fin du bloc ; le lire avant la fin du bloc lève une erreur ; - Si une exception est levée dans le bloc, **tout le lot est annulé** (des commandes à moitié envoyées sont plus dangereuses que rien du tout) ; - Mesuré sur 8 déplacements : 68 ~ 99 ms une par une, **6.5 ~ 10 ms** en un lot. Le même principe vaut pour les requêtes : `g.can_do_many([(u, code), ...])` et `g.tech_many([...])` posent de nombreuses questions d'un coup. ## Relire ce que vous venez d'écrire Dans un même tick, l'instantané ne voit pas encore la commande que vous venez de donner (il ne la rattrape qu'à la publication suivante). Deux parties de votre logique peuvent donc se disputer le même ouvrier : l'une vient de l'envoyer construire une ferme, l'autre le croit encore inactif d'après l'instantané. `g.order_of(u)` règle ce problème : tant que l'instantané n'a pas rattrapé la commande, il se fie au nouvel ordre indiqué dans le reçu. **Pour savoir si une unité est inactive, utilisez `g.order_of(u)`, pas `u.order`.** `g.idle_workers()` exclut déjà les ouvriers « qui viennent de recevoir une tâche pendant ce tick ». ## Niveaux de latence | Niveau | Canal | Latence | Usage | |---|---|---|---| | 0 | Instantané poussé + flux d'événements | Environ 0.4 ms par lecture ; données renouvelées toutes les 50 ms | Toutes les méthodes « d'observation » | | 1 | Voie rapide | Environ 1 frame ; médiane de 0.06 ms avec 6 processus en parallèle | Toutes les commandes et requêtes (par défaut dans le SDK) | | 2 | Canal de contrôle | 20 ~ 40 ms | Solution de repli et quelques opérations d'interface (vitesse de jeu, bulles, messages) | | 3 | [Passerelle](https://war3ai.com/fr/docs/gateway/) (WebSocket / JSON) | Niveau 1 + environ 1 ms | N'importe quel langage, navigateur, LLM, programme sur une autre machine | Le [catalogue de l'API](https://war3ai.com/fr/api/) indique pour chaque méthode le niveau qu'elle utilise. --- # Les quinze règles > Chacune a été apprise dans de vraies parties. Relisez-les en écrivant un Bot : vous économiserez l’essentiel du temps de débogage. > **Astuce** > > Donnez cette page au LLM avec [`api.json`](https://war3ai.com/fr/api.json) : le Bot qu’il écrira s’épargnera bien des détours. ## Lire l’état ### 1. Une valeur illisible vaut `None`, pas 0 `resources()`, `time_of_day()`, `production()` et `cooldown()` peuvent tous renvoyer `None` (chargement en cours, unité sans détails, bâtiment qui ne produit rien…). Vérifiez avant d’utiliser la valeur : ```python res = g.resources() if res is None: return ``` ### 2. Identifiez les unités par leur handle, pas par leur adresse Les adresses sont réutilisées par de nouvelles unités : une ancienne adresse peut pointer vers une unité qui vient d’apparaître. Pour suivre une unité d’un tick à l’autre, stockez `u.handle` et retrouvez-la avec `g.unit(handle)`. ### 3. Le flux d’événements est global `production.done` et `unit.died` concernent aussi l’adversaire et les creeps. Filtrez par `ev.owner` (ou par le handle du bâtiment) : ```python if ev.kind == "production.done" and ev.owner == g.me(): ... ``` ### 4. Les ouvriers dans une mine d’or ne sont pas dans l’instantané Au moment où un ouvrier entre dans la mine, il disparaît de l’instantané (`unit.removed`, il n’est pas mort). Pour savoir combien d’ouvriers travaillent sur chaque mine, **tenez vos propres comptes** au lieu de les corriger d’après l’instantané — sinon vous enverrez trop d’ouvriers dans une mine déjà pleine. ## Donner des ordres ### 5. Accusé « accepté » ≠ réussi Le moteur accepte sur le moment même un point de construction en pleine forêt ; l’échec ne survient qu’à l’arrivée de l’ouvrier. Un sort peut être interrompu. Jugez de l’effet d’après l’instantané et les événements : pour construire, utilisez `build_near` (qui vérifie que les fondations apparaissent) ; pour un sort, vérifiez que `g.cooldown()` est bien en recharge. ### 6. Impossible d’attaquer une cible invisible Un ordre ciblant un ennemi dans le brouillard de guerre est rejeté avec le code de raison **1001**. Pour poursuivre un ennemi dans le brouillard, faites un `attack_move` vers sa dernière position connue. ### 7. Ne donnez d’ordres qu’aux unités inactives Redonner le même ordre à la même unité à chaque tick l’interrompt : les soldats tressautent sur place, le cycle de récolte des paysans repart de zéro. Pour savoir si une unité est « inactive », utilisez `g.order_of(u)` (qui inclut ce que vous venez d’ordonner pendant ce tick), et non `u.order` de l’instantané (qui n’est pas encore à jour). ### 8. Shift ne sait qu’« insérer après l’ordre en cours » Le moteur n’a pas d’« ajout en fin de file » : envoyer B puis C avec `queue='after'` donne A, C, B. Pour parcourir une série de points dans l’ordre, utilisez `g.path(units, liste_de_points)` ; pour qu’un ouvrier enchaîne plusieurs constructions, `g.build_queue(worker, plan)` — ces méthodes insèrent en ordre inverse et règlent le problème pour vous. ### 9. Envoyez les commandes d’un tick en un seul lot Envoyer des dizaines de commandes une par une, c’est attendre des dizaines de fois le thread du jeu ; regroupées dans `with g.batch():`, elles n’attendent qu’une fois. ## Économie et production ### 10. 5 ouvriers au maximum par mine Au-delà, le revenu n’augmente plus. L’objectif d’ouvriers suit le nombre de mines : 5 à l’or par mine, plus quelques bûcherons. ### 11. Une seule unité en file d’entraînement Remplir les 7 places immobilise l’argent dans la file (mesuré : 4 paysans en file au bâtiment principal, 300 d’or bloqués, un début de partie nettement plus lent). N’ajoutez l’unité suivante que lorsque `g.queue(b)` est vide. ### 12. Nourriture bloquée : regardez la table de production `g.production(b).blocked` = une unité est en file mais n’a pas démarré, le plus souvent faute de nourriture. Vous avez ainsi un coup d’avance sur « construire quand la nourriture est presque au maximum » : si vous perdez une partie de l’armée au combat et que la file bloque au moment de la reconstituer, vous le savez tout de suite. ### 13. Les héros sont uniques ; pas de montée de tier tant que la file du bâtiment principal n’est pas vide - Un héros mort ne peut qu’être ressuscité avec `g.revive(autel)` ; essayer de l’entraîner de nouveau est rejeté (221). La résurrection consomme aussi de la nourriture (un héros en occupe 5). - Impossible d’améliorer le bâtiment principal tant que sa file contient quelque chose (code de raison 185, « bâtiment occupé »). ## Temps et espace ### 14. En vitesse 2×, n’attendez pas selon l’horloge murale Pour attendre 3 secondes de jeu, attendez que `g.clock()` ait avancé de 3, et non `sleep(1.5)`. Quand le jeu est accéléré, l’horloge du moteur avance plus vite que l’horloge murale. ### 15. Sur les cartes à îles ou très boisées, oubliez la distance à vol d’oiseau Pour choisir un camp de creeps ou une expansion, utilisez `g.path_distance(a, b)` (A* au sol, qui contourne forêts, falaises et bâtiments) ; il renvoie `None` si le point est inaccessible. Le point le plus proche à vol d’oiseau se trouve peut-être de l’autre côté de la mer. ## Une de plus : écrivez en mode équitable Avec `--fair`, vous ne voyez que les unités, objets, productions et événements situés dans votre champ de vision : c’est la règle de l’arène. Écrivez dès maintenant en mode équitable et vous n’aurez rien à changer pour passer sur l’[Arène](https://war3ai.com/fr/arena/). Voir [Mode équitable](https://war3ai.com/fr/docs/fair-mode/). --- # Mode équitable > Un client injecté dans le jeu peut lire toute la carte. Le mode équitable ne montre au Bot que ce qui se trouve dans son champ de vision — comme pour un joueur humain, et comme le veut la règle de l’arène. Les capacités d’observation de ce projet viennent du fait que « le client détient l’état de tous les joueurs » : l’instantané contient toutes les unités de la carte, y compris les ennemis cachés dans le brouillard de guerre. C’est très pratique pour déboguer, mais injuste en compétition. Le **mode équitable** demande au SDK de filtrer selon votre vision : ```bash python tools/play.py --bot my_bot.py --fair python -m openwar3 run my_bot.py --inst 5 --fair ``` ```python from openwar3 import Game, run g = Game(inst=5, fair=True) # en utilisant Game directement run(MyBot, inst=5, fair=True) # ou en passant par le lanceur ``` ## Ce qui est filtré | Contenu | En mode équitable | |---|---| | Unités | Toutes les vôtres + les unités ennemies et neutres que votre camp voit à cet instant | | Objets au sol | Seulement ceux qui sont dans la vision de vos unités (vision de jour / de nuit calculée séparément d’après les tables de données) | | Table de production | Seulement les bâtiments visibles (impossible de voir ce que l’adversaire entraîne) | | Événements | Les vôtres ; ceux qui sont visibles (ou l’ont été dans la dernière seconde) ; les dégâts infligés par votre camp | ## D’où vient la vision - Chaque unité de l’instantané porte un **masque de visibilité** : le bit p = le joueur p la voit à cet instant (seuls comptent les joueurs 0 à 11 ayant des unités sur la carte ; vos propres unités vous sont toujours visibles). `u.visible_to(g.me())` le lit directement, sans attente. - Pour un point quelconque : `g.visible(x, y)` interroge le moteur (visible / brouillard de guerre / masque noir) via la voie rapide, environ une frame par appel. Si vous devez juger de nombreuses unités pendant un tick, utilisez `u.visible_to()` de l’instantané au lieu d’appeler `g.visible()` pour chacune. ## La mémoire des ennemis : `last_seen` Un joueur humain se souvient d’avoir « vu un groupe de pillards par là tout à l’heure ». Le SDK tient ce registre pour vous : à chaque rafraîchissement de l’instantané, il note les unités ennemies et les creeps que votre camp voit à cet instant (dernière position, points de vie, heure) ; il les efface quand il les voit mourir, et tout est remis à zéro à chaque nouvelle partie. ```python for u, t, age in g.last_seen(max_age=60): # ennemis vus au cours des 60 dernières secondes de jeu print(u.type, u.x, u.y, f"il y a {age:.0f}s") heroes = [r for r in g.last_seen() if r[0].is_hero] # où étaient les héros adverses la dernière fois camps = g.last_seen(owner="creep") # creeps déjà repérés ``` En mode équitable, c’est votre seule « source d’information sur l’adversaire » — comme pour un joueur humain. Le mode normal enregistre lui aussi selon la vision : le même code fonctionne dans les deux cas. ## Jouer en tant que joueur N ```bash python tools/play.py --bot my_bot.py --player 1 --attach ``` `--player N` (ou `Game(player=N)`) fait jouer le Bot en tant que joueur N : il ne peut commander que les unités du joueur N. Pour faire s’affronter deux IA, ouvrez deux canaux de ce type dans la même partie. > **En mode local, l’équité est une convention, pas une barrière de sécurité** > > Sur votre propre machine, rien ne peut empêcher un programme de lire toute la carte. `--fair` est une contrainte que vous vous imposez ; les vrais matchs sont garantis par le processus arbitre de l’[Arène](https://war3ai.com/fr/arena/) : le Bot ne touche jamais à la mémoire partagée, ne reçoit que des observations filtrées par l’arbitre selon la vision, ne peut que soumettre des actions, et chaque action est d’abord contrôlée pour vérifier que l’unité lui appartient. ## Pourquoi l’activer dès maintenant - Ce sera la règle de l’arène : en écrivant dès maintenant en mode équitable, vous n’aurez pas une ligne à changer le moment venu ; - Privé de l’information de toute la carte, votre Bot révèle son vrai niveau (le cerveau de référence dépend encore beaucoup de cette information globale, par exemple du point ciblé par le capitaine de l’ordinateur — c’est justement un bon test) ; - La reconnaissance, la mémoire et le jugement écrits en mode équitable : voilà les capacités d’IA qui ont une vraie valeur. --- # Recettes des joueurs pros > L'avantage des meilleurs joueurs tient surtout à des dizaines de « petites habitudes ». Cette page traduit une à une les techniques pro courantes en code SDK ; chaque extrait peut être copié tel quel dans on_tick. Conventions : `g` est le `Game`, `home` est votre base principale (`g.my_buildings({"htow", "hkee", "hcas"})[0]`), `now = g.clock()`. Le détail des méthodes se trouve dans le [catalogue de l'API](https://war3ai.com/fr/api/), et des exemples complets et exécutables dans [Bots d'exemple](https://war3ai.com/fr/docs/examples/). > **Astuce** > > Quand vous demandez à un LLM d'ajouter une technique, collez-lui la recette correspondante avec son code : c'est bien plus efficace que de lui demander de « jouer un peu plus pro ». ## I. Économie ### 1. Jamais de paysan inactif, 5 par mine ```python for w in g.idle_workers(): # n'affecter que les inactifs (redonner un ordre à un ouvrier occupé interrompt sa récolte) mine = g.nearest([m for m in g.gold_mines() if crew[m.addr] < 5], w) g.gather(w, mine) if mine else g.gather(w, g.trees(w.x, w.y, limit=1)[0]) ``` Tenez vous-même le compte des ouvriers envoyés à chaque mine (`crew`) : un ouvrier entré dans une mine d'or n'apparaît pas dans l'instantané. Exemple complet : `hello_bot.py`. ### 2. Une seule unité en file, l'argent n'est pas bloqué ```python for b in g.my_buildings({"hbar"}): if not g.queue(b): # n'en lancer une autre que quand la file est vide g.train(b, "hfoo") ``` ### 3. Ne jamais être bloqué par la nourriture ```python stuck = any(p.blocked for _b, p in g.all_production("me")) # en file mais pas démarré = nourriture insuffisante res = g.resources() if stuck or res["food_cap"] - res["food_used"] <= 6: g.build_near(builder, "hhou", home.x, home.y) ``` `blocked` réagit un temps avant « presque plein » : après avoir perdu une bonne partie de l'armée en combat, vous le savez dès que la file se bloque au moment de reconstruire. ### 4. Ordre de construction + retour à la mine une fois le bâtiment fini (Shift + mine) ```python spot = g.build_near(w, "hbar", home.x, home.y) if spot: g.gather(w, mine, queue="after") # retourner récolter une fois le bâtiment terminé, inutile de le rechercher au tick suivant ``` Un paysan qui enchaîne plusieurs bâtiments : `g.build_queue(w, [("hhou", x1, y1), ("hhou", x2, y2)])`. L'argent n'est prélevé qu'au début de chaque construction. ### 5. Moment du passage de tier, améliorations d'attaque et d'armure ```python if not g.queue(hall) and g.can_do(hall, "hkee") in (0, 220): # l'amélioration n'est possible que si la file du hall est vide (sinon 185) g.upgrade(hall, "hkee") p = g.production(hall) # progression du passage de tier if p and p.kind == "upgrade": print(f"Hall principal : encore {p.remaining:.0f} s") for sm in g.my_buildings({"hbla"}): if not g.queue(sm): ok = [u for u, v in zip(UPS, g.can_do_many([(sm, u) for u in UPS])) if v in (0, 220)] if ok: g.research(sm, ok[0]) ``` ### 6. Prendre une expansion : choisir la mine la plus proche à pied ```python mines = [m for m in g.gold_mines() if g.dist(m, home) > 1500 and not taken(m)] best = min(mines, key=lambda m: g.path_distance(home, m) or 1e9) # une mine sur une île renvoie None -> classée en dernier ``` ## II. Éclaireurs et renseignement ### 7. Voir ce que fait l'adversaire ```python for b, p in g.all_production("enemy"): # ce que les bâtiments adverses visibles entraînent / recherchent / améliorent print(b.type, p.kind, p.queue, f"{p.progress:.0%}" if p.progress is not None else "bloqué") ``` Avec les événements : `ev.kind == "production.done" and ev.owner != g.me()` — ce que l'adversaire vient de produire. ### 8. Se souvenir de ce qu'on a vu (brouillard de guerre) ```python for u, t, age in g.last_seen(max_age=60): # ennemis vus dans les 60 dernières secondes de jeu (dernière position et derniers PV) ... hero_seen = [r for r in g.last_seen() if r[0].is_hero] # où se trouvait le héros adverse la dernière fois ``` En mode équitable, c'est votre seule source d'information sur l'adversaire, exactement comme pour un joueur humain. ### 9. Où l'ordinateur adverse va attaquer (IA de l'ordinateur uniquement) ```python plan = g.enemy_ai_plan(some_enemy_soldier) # la destination de son capitaine d'IA ``` L'ordinateur fixe son point cible avant même de sortir : ramenez vos troupes là-bas à l'avance. ## III. Creeping ### 10. Creeper la nuit ```python if g.is_night(): # de 18 h à 6 h : les creeps dorment (vous frappez en premier sans être encerclé), la vision de tous diminue ... wait = g.seconds_until(18) # secondes de jeu avant la tombée de la nuit (une journée = 480 s) ``` ### 11. N'attaquer que les camps à votre portée ```python from openwar3 import combat mine = [g.stats(u) for u in army] def ttk(target): return combat.time_to_kill(mine, g.stats(target), target_hp=target.hp) or 1e9 camp = [c for c in g.creeps() if g.dist(c, center) < 600] ours = max(ttk(c) for c in camp) # temps pour vider ce camp (estimation grossière) theirs = g.time_to_kill(camp, weakest_of_mine) or 1e9 # temps qu'il leur faut pour tuer notre unité la plus faible if ours < theirs and g.reachable(center, camp[0]): g.attack_move(army, camp[0].x, camp[0].y) ``` Exemple complet : `_maybe_creep` dans `micro_bot.py`. ## IV. Micro ### 12. Tir concentré : viser « ce qui meurt le plus vite », pas le plus proche ```python target = min(visible_enemies, key=lambda e: g.time_to_kill(fighters, e) or 1e9) g.attack([u for u in fighters if (g.current_target(u) or target).handle != target.handle], target) ``` Ne donnez l'ordre qu'aux unités qui « ne l'attaquent pas déjà » (`current_target`) ; n'interrompez pas celles qui le frappent. ### 13. Retirer les unités blessées ```python for u in army: if u.hp < u.hp_max * 0.35: g.move(u, *toward(home, u, 500)) # reculer de 500 vers la base ; ne pas la retirer à nouveau dans les 3 s ``` Pour détecter un focus : dans les événements `damage`, une même unité touchée par plusieurs sources en peu de temps = elle est encerclée. ### 14. Garder le héros en vie, ne pas offrir d'expérience ```python for h in g.my_heroes(): if h.hp < h.hp_max * 0.4: g.move(h, home.x, home.y) g.use_item(h, slot_of(h, "phea")) # potion de soins : trouver l'emplacement avec inventory(h) ``` ### 15. Contres : les bonnes unités sur les bonnes cibles ```python s = g.stats(u) best = max(enemies, key=lambda e: s.dps_vs(g.stats(e))) # fusiliers contre chevaucheurs de griffon (perforant contre armure légère ×2), griffons contre fantassins (magique contre armure lourde ×2) ``` La table des contres vient des données du jeu : `combat.damage_multiplier("pierce", "small") == 2.0`. ### 16. Prise en tenaille et itinéraires : contourner les tours ```python route = g.walk_path(army_center, target) # points d'inflexion du plus court chemin au sol g.path(army, route, attack=True) # attaque-déplacement par chaque point, dans l'ordre ``` Pour contourner les tours, marquez leurs abords comme infranchissables sur la grille de pathfinding avant de calculer : ```python grid = g.grid().copy() for t in towers: grid.block_area(t.x, t.y, 800) # portée de la tour 700 + marge route = grid.path((army_x, army_y), (target.x, target.y)) ``` ### 17. Envoyer toutes les commandes d'un tick en un lot ```python with g.batch(): g.attack(melee, target_a) g.attack(ranged, target_b) g.move(wounded, *home_xy) g.cast(hero, "thunderclap") ``` Des dizaines de commandes pour une seule attente du thread du jeu (mesuré : 8 déplacements, 68 ms → 6.5 ms). ### 18. Siège : l'artillerie tire au sol ```python g.attack_ground(mortars, tower.x, tower.y) # mortiers / engins de siège tirant sur une zone (derrière des arbres, unités invisibles) ``` ## V. Héros ### 19. Ordre des compétences ```python SKILLS = {"Hamg": ["AHwe", "AHbz", "AHwe", "AHbz", "AHwe", "AHmt"]} # Élémentaire d'eau, Blizzard… ultime au niveau 6 : Téléportation de masse info = g.hero_info(h) if info and info["skill_points"]: g.learn(h, SKILLS[h.type][learned_count]) # si rejeté (niveau insuffisant pour l'ultime), attendre le niveau suivant ``` ### 20. Le sort est-il vraiment parti ? ```python r = g.cast(h, "thunderbolt", target=enemy_hero) # au tick suivant : if g.cooldown(h, "AHtb"): # en recharge = vraiment lancé ; accepté ≠ lancé ... ``` ### 21. Acheter des potions, rentrer en ville ```python g.buy(shop, "phea") # le héros se tient à côté de la boutique g.use_item(hero, slot, x=home.x, y=home.y) # parchemin de téléportation (objet utilisé sur un point) ``` ## VI. Analyse d'après-partie - À chaque tick, écrivez vos décisions dans le journal (`print` s'affiche dans la fenêtre d'exécution), et utilisez `g.say(unité, "Repli !")` pour les voir en jeu ; - L'événement `production.done` indique « combien de secondes cela a pris » : établissez votre propre chronologie de construction (à quelle seconde sort le premier héros, à quelle seconde vous passez de tier) et comparez-la à celle des meilleurs joueurs ; - Laissez l'agent analyser lui-même ses parties : voir le rapport de partie dans [Itération autonome d'un agent](https://war3ai.com/fr/docs/agent-loop/). --- # Bots d’exemple > Quatre exemples, du plus simple au plus complet, tous exécutables tels quels ; chaque bloc de logique correspond à une capacité du SDK. S’y ajoute un cerveau de référence complet. Les exemples se trouvent dans `brains/examples/` ; chacun hérite du précédent et n’ajoute que les nouveautés. Lisez-les dans l’ordre : | Exemple | Ce que vous apprenez | Lancer | |---|---|---| | `hello_bot.py` | Récolte (5 ouvriers par mine, bois une fois la mine pleine), production de paysans (1 seul en file), bâtiments de nourriture, reprise des chantiers à l’arrêt ; fonctionne avec les quatre races | `python tools/play.py --bot brains/examples/hello_bot.py` | | `rush_bot.py` | Caserne et autel (construits avec `build_near` s’il n’y en a pas), héros en premier (ressuscité s’il meurt), compétences apprises dès qu’un point est disponible, attaque-déplacement dès qu’une vague est prête | `… --bot brains/examples/rush_bot.py` | | `macro_bot.py` | Ordre de construction + retour à la mine une fois le bâtiment terminé (Shift), bâtiment de nourriture dès que la nourriture bloque, 1 seule unité en file à la caserne, améliorations d’attaque et d’armure, montée de tier et unités avancées, choix de la cible selon la **vraie distance au sol** et déplacement le long du chemin | `… --bot brains/examples/macro_bot.py --speed 200` | | `micro_bot.py` | Prend en main les combats par-dessus la macro : focus sur l’ennemi le plus vite tuable, retrait des unités blessées, protection du héros, de nuit les camps de creeps à sa portée, défense quand l’ennemi arrive à la base ; les commandes d’un tick partent en un seul lot | `… --bot brains/examples/micro_bot.py --fair` | > **Remarque** > > Les commentaires de `hello_bot` et `rush_bot` consignent les pièges rencontrés en jeu, par exemple « on prenait toujours le premier ouvrier pour construire, résultat : 3 fermes restées à l’état de fondations » ou « les coordonnées de caserne codées en dur tombaient en pleine forêt : en 3 minutes, pas une seule caserne construite ». Les commentaires en apprennent plus que le code. ## hello_bot : l’économie ```python # race -> (ouvrier, bâtiments principaux, bâtiment de nourriture) RACES = { "h": ("hpea", {"htow", "hkee", "hcas"}, "hhou"), "o": ("opeo", {"ogre", "ostr", "ofrt"}, "otrb"), "u": ("uaco", {"unpl", "unp1", "unp2"}, "uzig"), "e": ("ewsp", {"etol", "etoa", "etoe"}, "emow"), } MINE_CAP = 5 # 5 paysans max par mine (au-delà, le revenu n'augmente plus) LUMBER_CREW = 5 # nombre de bûcherons : 5 à l'or par mine + ce nombre au bois = objectif d'ouvriers ``` Trois choses : les ouvriers inactifs vont à l’or (le Bot compte lui-même les ouvriers de chaque mine et envoie le surplus au bois quand la mine est pleine) ; s’il manque des ouvriers, il en produit (1 seul en file) ; quand la nourriture approche du maximum, il prend un ouvrier qui n’est pas déjà en train de construire et bâtit un bâtiment de nourriture près du bâtiment principal (chez les Humains et les Orcs, il renvoie aussi quelqu’un terminer les fondations laissées à l’arrêt). ## rush_bot : produire et attaquer Trois ajouts par rapport à `hello_bot` : construire caserne et autel s’ils manquent ; produire le héros à l’autel (**le ressusciter d’abord s’il est mort**, un héros est unique) et apprendre une compétence dès qu’un point est disponible ; une fois 8 soldats réunis, attaque-déplacement de toute l’armée vers le bâtiment principal ennemi, puis retour à la base pour reconstituer une vague quand l’armée est décimée. Seules les unités inactives reçoivent des ordres, pour ne pas interrompre le combat à chaque tick. ## macro_bot : les bases de la macro ```python TECH = { "h": dict(order=["halt", "hbar", "hbla", "hlum"], altar="halt", hero="Hamg", skills=["AHwe", "AHbz", "AHab"], barracks="hbar", soldiers=["hfoo", "hrif", "hkni"], smith="hbla", upgrades=["Rhme", "Rhar", "Rhra", "Rhla"], tiers=["hkee", "hcas"]), ... } ``` Ce que les joueurs pro font à chaque partie, chaque point correspondant à une capacité du SDK : une table d’ordre de construction + `gather(..., queue="after")` pour retourner à la mine une fois le bâtiment terminé ; `production().blocked` pour repérer un blocage de nourriture ; `g.queue` pour ne garder qu’une unité en file à la caserne ; `can_do` pour demander au moteur si le niveau suivant d’attaque ou d’armure peut être recherché ; montée de tier et unités avancées (leçon tirée d’une vraie partie : resté au tier 1, le Bot s’est fait raser en 23 minutes par des chevaliers et des chevaucheurs de griffon de tier 3) ; choix de la cible avec `path_distance` et déplacement par les points de passage de `path()`. ## micro_bot : une fois le combat engagé ```python def _fight(self, g, army, foes, home, now): ... visible = [e for e in foes if e.visible_to(me)] # une cible invisible est rejetée (1001) atk = [s for s in (g.stats(u) for u in fighters) if s] target = min(visible, key=lambda e: _ttk(g, atk, e)) # la plus vite tuable, pas la plus proche idle_or_other = [u for u in fighters if g.current_target(u) is None or g.current_target(u).handle != target.handle] if idle_or_other: g.attack(idle_or_other, target) ``` En jeu : 5 minutes, 1497 ticks, 3023 commandes, 0 erreur. ## Le cerveau de référence : une IA complète `brains/xwar3/` est une IA complète qui prend des expansions, fait du creeping et attaque. Elle est organisée en trois couches : | Couche | Emplacement | Rythme | Rôle | |---|---|---|---| | Couche stratégique | `strategy/` | De l’ordre de la seconde | Choix et changement de stratégie à la manière d’AMAI, tables de construction, unités de contre, choix des héros ; [coach stratégique LLM](https://war3ai.com/fr/docs/llm-coach/) en option | | Couche réflexe | `reflex/` (4 processus indépendants) | De l’ordre de 100 ms | Survie, sorts, focus, ramassage d’objets | | Modèle de victoire | `worldmodel/` | — | Ce combat est-il gagnable (sous-ensemble d’inférence) | Plusieurs processus partagent les unités via une **table d’arbitrage**, où la priorité décide qui a le dernier mot : ordre manuel 95 > survie 90 > esquive des sorts 85 > sorts 80 > ramassage d’objets 70 > … > stratégie 50 > affectation des ouvriers 45. Votre propre Bot figure dans la table sous l’identité `bot`, avec une priorité par défaut de 50. > **Attention** > > Le cerveau de référence utilise directement les couches bas niveau du SDK (`w3cmd` / `act`) et s’appuie beaucoup sur l’information de toute la carte. Il est utile comme source d’« idées », mais il n’est pas conseillé de le faire recopier tel quel par un LLM. Il a besoin des données AMAI : lors du premier déploiement, `start.bat` les récupère depuis le dépôt public d’AMAI et les génère (AMAI est sous licence personnalisée ; les fichiers générés ne sont pas versionnés dans git ; en cas d’échec, relancez avec `start.bat setup`). Le plus simple pour lancer le cerveau de référence est la [console Farsight](https://war3ai.com/fr/docs/console/) : sur la page « Instances et lancement », cochez les numéros d’instance, puis cliquez sur « Lancer le test ». --- # Débogage et performances > Pourquoi un tick est lent, pourquoi une commande reste sans effet, pourquoi le jeu ne bouge pas. Diagnostiquez à partir du symptôme, puis confirmez avec les scripts de vérification en jeu fournis. ## Lire les accusés de réception L’accusé de réception de chaque commande est l’indice de première main : ```python r = g.cast(hero, "blizzard", x=tx, y=ty) if not r: print(r.reason, r.verdict) # rejected(…) et le code de raison print(r.exec_us, r.engine_us) # durée d'exécution sur le thread du jeu, en µs / dont la fonction d'ordre du moteur elle-même ``` En temps normal, une commande prend de quelques microsecondes à quelques centaines de microsecondes sur le thread du jeu. À la fin d’un bloc de lot, `g.last_receipts` contient l’accusé de réception de chaque commande du lot. ## Observer dans le jeu ```python g.say(unit, "Repli") # une bulle de dialogue au-dessus de l'unité (sans effet sur le jeu) g.message("Début du creeping") # une ligne dans la zone de messages en bas à gauche (visible seulement en local) ``` Ce que vous passez à `print` s’affiche dans le terminal qui exécute le Bot. Afficher les décisions clés de chaque tick, en plus des bulles de dialogue, est bien plus rapide que de relire le code. ## Un tick est lent Vérifiez d’abord s’il s’agit de l’un de ces cas : | Cause | Correction | |---|---| | Commandes envoyées une par une, chacune attend une frame | Les regrouper dans `with g.batch():` : des dizaines de commandes, une seule attente | | Appels à `g.visible()` / `g.can_do()` un par un (chacun passe par la voie rapide et attend une frame) | Visibilité : `u.visible_to()` de l’instantané ; faisabilité : `g.can_do_many([...])` en un seul lot | | `sleep` ou attente dans `on_tick` | Notez l’heure de jeu et vérifiez au tick suivant | | Recalcul coûteux à chaque tick (recherche de chemin, balayage de toute la carte) | Mettez le résultat en cache et recalculez-le tous les quelques ticks. `g.grid()` a un cache intégré de 2 secondes ; les niveaux de technologie de `g.stats()` sont mis en cache 5 secondes | ## Le jeu ne bouge pas / le Bot n’entre jamais en partie | Symptôme | Cause probable | |---|---| | Toujours « en attente de la partie » | Mauvais numéro d’instance ; ou la fenêtre du jeu est **réduite** — tant qu’elle est réduite, la simulation est à l’arrêt (l’horloge n’avance pas) | | Le jeu tourne, mais les ordres du Bot restent sans effet | Ordres donnés aux unités d’un autre joueur (accusé `not_owner`) ; ou le Bot est connecté en tant qu’observer (`forbidden`) | | Commande `held` | Cette unité est tenue par une couche plus prioritaire (couche réflexe du cerveau de référence, ordre manuel de la console) : la commande n’a pas été envoyée | | Les commandes passent encore pendant la pause | Normal : en pause, l’horloge du moteur s’arrête, mais la distribution des événements continue et les commandes s’exécutent normalement | ## Se connecter pour voir l’état ```bash python -m openwar3 status --inst 5 ``` Affiche l’état de la connexion : pid du jeu, période de publication du monde et durée de chaque capture, compteurs de la voie rapide, en partie ou non, nombre d’unités, horloge du jeu. ## Scripts de vérification en jeu Lancez une instance de test et vérifiez point par point que les capacités du SDK fonctionnent sur votre machine : ```bash python tools/sdk_live_check.py --inst 20 # tout python tools/sdk_live_check.py --inst 20 --only prod # une seule section ``` Sections : lots, temps, production, ordres en file, caractéristiques de combat, recherche de chemin, mode équitable. Chaque section donne des ordres dans une vraie partie, relit l’effet obtenu et affiche le nombre de vérifications réussies. Les tests hors ligne ne demandent pas de lancer le jeu : ```bash python tools/run_tests.py ``` ## Les faux bugs fréquents - **Construction acceptée, mais pas de fondations** : le moteur accepte sur le moment même un point en pleine forêt ; l’échec ne survient qu’à l’arrivée de l’ouvrier. Utilisez `build_near`, qui suit la construction et met temporairement sur liste noire les points en échec. - **Sort accepté, mais jamais lancé** : il a été interrompu, ou le mana manquait. Au tick suivant, vérifiez que `g.cooldown()` est bien en recharge. - **Attaque acceptée, mais les soldats frappent autre chose** : pour attaquer une cible précise, utilisez `g.attack(soldats, ennemi)` (sémantique du clic droit). L’ordre d’attaque brut sur une cible ne fait que changer l’ordre sans mémoriser la cible : les unités attaquent autre chose à proximité. - **Le nombre d’ouvriers ne tombe pas juste** : les ouvriers entrés dans une mine d’or ne sont pas dans l’instantané. - **Impossible d’entraîner un héros mort** : un héros est unique, il faut `g.revive(autel)` ; la résurrection consomme de la nourriture et n’est possible qu’environ 3 secondes de jeu après la mort. --- # Compagnon RPG > Donnez au joueur un compagnon IA dans les cartes RPG et personnalisées : il vous suit, combat à vos côtés, vous soigne quand votre vie est basse et vous fait la conversation. Quatre modes ; héritez d’une classe, changez quelques attributs, et vous avez votre propre compagnon. Il n’y a pas que les parties de mêlée. Dans les cartes RPG et personnalisées, vous pouvez vous adjoindre un **compagnon IA** : il vous suit, combat les monstres avec vous, vous soigne quand votre vie est basse et, quand il ne se passe rien, vous fait un brin de conversation — ses répliques peuvent même venir d’un LLM local. **C’est à vous de décider comment vous en servir.** L’ensemble se découpe en trois couches d’interfaces, de la plus basse à la plus haute, et chacune s’utilise directement : | Couche | Ce que c’est | Idéal pour | |---|---|---| | **Canal JASS** `g.jass` | Les 1291 fonctions JASS dont disposent les créateurs de cartes, appelées directement par leur nom (créer des unités, définir des alliances, donner des objets, renommer, afficher du texte, ressusciter des héros…) | Inventer votre propre gameplay | | **Interfaces pratiques** | `g.spawn`, `g.set_alliance`, `g.player_slots`, `g.show_text`, `g.map_data` : les opérations courantes, déjà empaquetées | Écrire vos propres scripts d’assistance | | **Framework de compagnon** | `openwar3.companion.Companion` + `openwar3.talk.Talk` : héritez-en, changez quelques attributs, et vous obtenez un compagnon qui vous suit, combat, soigne et discute | Avoir un compagnon | > **Attention** > > Uniquement pour les parties **en solo ou hébergées vous-même en réseau local**. Créer des unités ou définir des alliances, c’est modifier le monde unilatéralement depuis votre machine : aucun problème en partie solo (contre l’ordinateur) ; en multijoueur, les autres joueurs seraient désynchronisés. En partie multijoueur, le canal JASS n’autorise donc que les fonctions en lecture seule, et le compagnon se replie automatiquement sur « parler seulement ». ## Démarrage le plus rapide : un clic dans Farsight 1. **Choisir la carte** : dans Farsight, page « Instances et lancement » → « Réglages de la partie suivante » → Carte, choisissez une carte RPG (toutes celles des dossiers `Scenario` et `Download` sous `Maps` dans le dossier du jeu sont listées, par exemple `(4)WarChasers`). 2. **Choisir le schéma** : dans la liste déroulante « Schéma d’IA » de la fiche de l’instance, choisissez **Exemple de compagnon (buddy)** → « Sélectionner ». 3. **Lancer le test** : une fois le jeu démarré, **jouez vous-même dans la fenêtre du jeu**. Le compagnon — un paladin nommé « Lumi » — apparaît à côté de vous. Vous pouvez aussi passer par la ligne de commande : ```bash python tools/play.py --bot brains/examples/buddy.py --inst 20 --rpg --map "\Maps\Scenario\(4)WarChasers.w3m" ``` `--rpg` (`"judge": false` dans le manifeste du schéma) signifie que la partie n’est pas jugée selon les règles de mêlée : dans un RPG, les héros peuvent ressusciter, et il n’y a pas de « défaite quand tous les bâtiments sont détruits ». Beaucoup de cartes RPG s’arrêtent après le chargement sur « Appuyez sur une touche pour continuer » ; quand le SDK constate qu’il est « en partie, mais l’horloge du jeu reste à 0 », il appuie lui-même sur Espace (`g.press_to_continue()`, qui envoie seulement un message de touche à la fenêtre du jeu, sans lui donner le focus). ## Écrire votre propre compagnon ```python from openwar3.companion import Companion from openwar3.talk import Talk class MyBuddy(Companion): mode = "ally" # mode, voir le tableau ci-dessous unit = "Hpal" # quoi créer : n'importe quel code à quatre caractères, même ceux propres à la carte nickname = "Lumi" heal = ("holybolt", "AHhb", 0.55) # (nom d'ordre du sort, compétence à apprendre, seuil de vie du maître déclenchant le soin) ; None = pas de soin follow_distance = 350 talk = Talk(persona="Petit paladin plein d'entrain, qui adore encourager son maître") ``` ### Quatre modes | mode | Qui est le compagnon | Remarques | |---|---|---| | `ally` (par défaut) | Occupe un emplacement de joueur libre et devient votre **allié** | A sa propre couleur et son propre nom (le tableau des scores et le panneau des alliés affichent `nickname`) ; vous ne pouvez pas le commander, il se bat seul. Le framework configure automatiquement l’alliance + la vision partagée | | `own` | Créé **sous votre contrôle** | Vous pouvez le commander à la main à tout moment ; quand vous ne vous en occupez pas, l’IA le dirige à votre place | | `adopt` | Prend le contrôle d’une unité **déjà présente** sur la carte | Surchargez `adopt(g)` pour renvoyer cette unité (un familier ou un suivant que la carte vous donne) | | `voice` | Ne crée pas d’unité, **ne fait que parler** | Conversation, rappels ; ne modifie pas le monde, donc utilisable aussi en multijoueur | Sans emplacement libre, `ally` se replie automatiquement sur `own` ; en multijoueur, ou si l’unité ne peut pas être créée, il se replie sur `voice`. > **Remarque** > > Un compagnon `ally` s’attache à « la meilleure unité de cet emplacement à l’instant » (héros en priorité), et non à une unité fixe. En test, une carte a traité le compagnon comme un vrai joueur, supprimé le paladin et distribué un héros de la carte — le compagnon a tout simplement pris le contrôle de ce héros, et apprend aussi les compétences que la carte lui attribue. Quand le héros meurt, il le ressuscite de préférence sur place ; si la carte le ressuscite elle-même, il continue simplement à l’utiliser. ### Ce qu’il fait à chaque tick Il vérifie les conditions dans l’ordre et applique la première qui est remplie : | Rang | Comportement | Condition | Réglage | |---|---|---|---| | 1 | Repli | Sa propre vie est sous 25 % et des ennemis sont proches : il se replie derrière le maître | `retreat_at` | | 2 | Soin | La vie du maître est sous le seuil fixé, la compétence est rechargée, le maître est à 900 au plus | `heal` (None pour désactiver) | | 3 | Assistance | Des ennemis autour du maître : **celui qui attaque le maître > celui que le maître attaque > le plus proche** | `assist_radius`, ou surcharger `pick_target` | | 4 | Suivi | Trop loin du maître, il le rattrape ; au-delà d’une certaine distance, il revient en courant sans s’attarder au combat | `follow_distance`, `leash` | | 5 | Bavardage | Sans ennemi autour, une réplique toutes les 1 à 2.5 minutes | Table des répliques | Les « ennemis » sont déterminés d’après les relations d’alliance dans le jeu (rafraîchies toutes les 20 secondes). Les cartes RPG comptent souvent plusieurs joueurs alliés : on ne peut pas simplement considérer « tous les joueurs sauf moi » comme des ennemis. Hooks que vous pouvez surcharger : `find_master` (qui est le maître ; par défaut, le héros de plus haut niveau du joueur local), `adopt`, `pick_target`, `on_poke` (le maître a fait un clic droit sur le compagnon), ainsi que `on_start` / `on_tick` / `on_event` / `on_end` du Bot. Le nombre de soins, d’assistances, d’éliminations, de suivis, de replis, de répliques et de résurrections est enregistré dans `self.stats` et affiché à la fin. ### Comment l’appeler - **Commandes de chat** : dans la zone de chat, tapez `-follow` (suis-moi), `-stay` (reste ici en garde), `-heal` (soigne-moi tout de suite) ou `-hi` (salut). Pour changer la table des commandes, modifiez `commands` ; pour changer les réactions, surchargez `on_command`. - **Clic droit sur le compagnon** : déclenche `on_poke`. Dans l’exemple, la réaction est la suivante : si le maître n’a pas toute sa vie, il lui donne un soin ; sinon, il dit une phrase. - **Dialogue avec portrait** : les salutations, la chute du maître, sa montée de niveau et le retour du compagnon passent par le dialogue avec portrait du jeu lui-même (le portrait en bas de l’écran devient celui du compagnon, et un sous-titre s’affiche à l’écran) ; tout le reste apparaît en bulle au-dessus de sa tête. - **Panneau d’état** : un panneau sur la gauche de l’écran affiche la barre de vie du compagnon, ce qu’il est en train de faire, son humeur (joyeux / excité / tendu / effrayé / triste), ses éliminations et ses soins. Il est dessiné avec le [canevas](https://war3ai.com/fr/docs/canvas/), donc sans risque en multijoueur. ### Parler, et le LLM local `Talk` choisit les répliques selon les événements et les affiche en bulle au-dessus de la tête ; en mode `voice`, ou quand une bulle ne peut pas s’afficher, elles apparaissent en bas à gauche de l’écran. Chaque réplique est aussi écrite dans le journal du schéma : vous pouvez vérifier après coup ce qu’il a dit. | Événement | Quand | Événement | Quand | |---|---|---|---| | `hello` | À son arrivée | `master_low` | Le maître a peu de vie | | `poke` | Le maître fait un clic droit sur lui | `master_levelup` | Le maître monte de niveau | | `fight` | Début d’un combat | `master_died` / `master_back` | Le maître tombe / ressuscite | | `kill` | Il tue un monstre (et dit son nom) | `buddy_low` / `buddy_died` / `buddy_back` | Le compagnon lui-même a peu de vie / tombe / revient | | `healed` | Il a soigné le maître | `idle` / `item` | Bavardage / objet ramassé | Les répliques peuvent contenir les espaces réservés `{master}`, `{me}`, `{map}`, `{enemy}`, `{level}`, `{item}` ; pour changer les répliques, modifiez directement `talk.lines` ; le temps de recharge se règle dans `talk.cooldown`. **Brancher un LLM local** : `Talk(llm=LocalLLM(url, model))`, avec n’importe quelle API compatible OpenAI (LM Studio, Ollama…). Le modèle répond dans un thread en arrière-plan, et la réplique n’est dite qu’une fois la réponse arrivée ; si le modèle n’est pas lancé, dépasse le délai ou renvoie une erreur, une réplique fixe est utilisée, sans jamais bloquer le jeu. Les requêtes ne partent que vers l’adresse locale que vous indiquez, et contiennent ce qui se passe dans la partie (le nom du maître, les monstres tués). ## Noms des unités des cartes personnalisées La plupart des unités, objets et héros des cartes RPG sont créés par la carte elle-même (codes à quatre caractères comme `HC07`, `I00A`) et ne figurent pas dans la table de noms intégrée. `g.map_data` lit directement le fichier de carte de la partie en cours : ```python md = g.map_data md.name_of("HC07") # 'Optimus Primo' — le nom modifié par la carte est prioritaire md.hero_names("HC07") # liste des noms propres md.hero_skills("OC10") # compétences que la carte attribue à ce héros md.tooltip("I00A") # texte descriptif ``` Les cartes protégées ou optimisées (beaucoup de RPG populaires) n’incluent pas les fichiers standard de données d’objets ; les noms sont alors lus dans les données texte de la carte. En test, les 38 cartes RPG / personnalisées de cette machine ont toutes été analysées avec succès, et les noms d’unités ont été obtenus pour 37 d’entre elles. ## En faire un schéma à partager Un compagnon n’est qu’une sous-classe de `openwar3.Bot` : vous pouvez en faire un [schéma d’IA](https://war3ai.com/fr/docs/schemes/) et le partager. Ajoutez deux champs au manifeste : ```json {"id": "my-buddy", "name": "Mon compagnon", "entry": "my_buddy.py", "fair": false, "judge": false} ``` `"fair": false` : nécessaire pour utiliser le canal JASS (créer des unités, définir des alliances) ; `"judge": false` : ne pas juger la partie selon les règles de mêlée. ## Relevé de test 2026-09-24, instance de test, carte WarChasers, vitesse ×2 : - Les 18 vérifications du canal JASS sont toutes passées : emplacements de joueur, conversions aller-retour entre unités et handles, valeurs de retour de type real, arguments de type chaîne, création d’une unité dans un emplacement libre, définition d’alliances, renommage, suppression d’unités ; les appels depuis une voie `player` et les mauvais nombres d’arguments ont bien été rejetés. - Compagnon : a passé seul l’écran « Appuyez sur une touche pour continuer » → est apparu à côté du maître et l’a salué → l’a suivi dans le cercle de pouvoir de sélection des héros, a reçu un héros de la carte et en a pris le contrôle → suivi (à 200 à 400 du maître) → a combattu des monstres et lancé « Joli ! » après en avoir tué un → s’est replié avec peu de vie → mort, ressuscité par la carte, il a repris le suivi. ## Pas encore fait 1. **Impossible de lire le texte libre que le joueur tape dans le chat.** Les commandes de chat fixes fonctionnent déjà ; pour que le compagnon discute vraiment librement avec vous, il faut encore accéder au texte lui-même. 2. **Le compagnon ne connaît pas le gameplay d’une carte précise** (quêtes, boutiques, scénario). Il fait du générique : suivre, assister, soigner ; pour qu’il comprenne une carte donnée, écrivez-le dans votre sous-classe pour cette carte — `g.map_data` retrouve les noms, `g.jass` appelle n’importe quelle fonction. C’est précisément la part qui vous revient. --- # Canevas > Dessinez sur l’écran du jeu des zones de texte, des panneaux, des barres de progression, des images, des cercles plaqués au sol et des tracés fléchés. Le runtime les dessine lui-même à chaque frame, sans modifier l’état du jeu : sans risque en multijoueur. Depuis Python, en HTTP ou en écrivant directement dans la mémoire partagée. Un programme externe peut dessiner sur l’écran du jeu des **zones de texte, panneaux, barres de progression, images, cercles au sol et tracés au sol (avec flèche)**, que le runtime dessine lui-même à chaque frame. Idéal pour votre propre HUD, des lignes d’aide, des indications, des annotations pédagogiques ou un panneau d’infos pour le streaming. ## Canevas ou fonctions d’affichage JASS : que choisir | | Canevas (cette page) | [Fonctions d’affichage JASS](https://war3ai.com/fr/docs/jass/) | |---|---|---| | Qui dessine | Le runtime lui-même | Le jeu lui-même (texte flottant, effets spéciaux, panneaux, dialogue avec portrait…) | | Multijoueur | **Sans risque** : dessiné uniquement sur l’écran local, sans créer d’objet de jeu ni modifier l’état du jeu | Parties solo uniquement | | Style | Libre : polices au choix (chinois compris), coins arrondis, semi-transparence, bordures, n’importe quelle couleur, images locales | Style natif du jeu | | Suivre quelque chose | Une unité, des coordonnées du monde, une position à l’écran ; les cercles au sol épousent le relief | Selon la fonction | | Coût | Mesuré : 0.2 à 0.35 ms par frame (9 éléments) | Environ 13 ms par appel | Les deux approches se combinent : JASS pour les effets au style natif, le canevas pour les panneaux personnalisés, les lignes d’aide et les indications. ## Python ```python c = g.canvas # au premier usage, le runtime installe le hook de dessin (environ 0.1 s) c.text("title", "Bonjour, ceci est le canevas", screen=(40, 110), color=(255, 220, 80), bg=(0, 0, 0, 170), border="#C49C40", size=22, bold=True) c.panel("status", "Compagnon · Lumi", ["Humeur : joyeux", "Éliminations : 12"], screen=(16, 330)) c.bar("hp", 0.62, unit=hero, lift=260, width=100, height=12, color=(80, 220, 80), text="62%") # suit l'unité c.text("tag", "Le boss prépare son ultime !", unit=boss, lift=320, color=(255, 80, 80), size=22, bold=True) c.circle("danger", (x, y), 300, color=(255, 60, 60), fill=(255, 60, 60, 60), width=3) # zone dangereuse au sol c.circle("aura", hero, 450, color=(80, 200, 255, 220)) # cercle qui suit l'unité c.path("route", [(x1, y1), (x2, y2), (x3, y3)], color=(255, 220, 0), width=5, arrow=True) c.image("icon", "icon.png", screen=(40, 170), width=64, height=64) c.remove("danger"); c.hide("tag"); c.clear() # clear n'efface que ce que vous avez dessiné c.expire("tag", 5) # disparaît tout seul au bout de 5 s with c.batch(): ... # beaucoup de modifications, une seule écriture en mémoire partagée c.stats() # drawnFrames augmente = le dessin a bien lieu ``` Chaque élément est identifié par une `key` : redessiner avec la même key revient à le mettre à jour. **Cliquable** : ajoutez `clickable=True` à une zone de texte ou à un panneau (la couleur de survol se règle avec `hover=`) ; un clic dessus envoie un `ui.click` dans le flux d’événements, avec `ev.key` égal à cette key, et le jeu ne reçoit pas ce clic. Boutons, cartes de choix, raccourcis clavier et clics au sol prêts à l’emploi : voir [Interface et entrées](https://war3ai.com/fr/docs/ui-input/). **Position** (une par élément) : - `screen=(x, y)` : pixels à l’écran ; une valeur négative compte à partir du bord droit / bas ; `center=True` aligne sur le centre ; - `frac=(0.5, 0.1)` : fraction de l’écran ; - `world=(x, y)` : coordonnées du monde ; - `unit=unité` : suit l’unité. Pour le texte et les barres placés sur le monde ou sur une unité, le milieu du bord inférieur est aligné sur ce point ; `lift` les remonte. Par défaut, les éléments placés sur le monde ou sur une unité évitent le panneau de commande en bas et l’horloge jour/nuit en haut (`over_ui=True` pour passer par-dessus). Les **couleurs** s’écrivent `(r, g, b)`, `(r, g, b, a)`, `"#RRGGBB"` ou `"#RRGGBBAA"`. | Méthode | Ce qu’elle dessine | Paramètres courants | |---|---|---| | `text(key, texte, ...)` | Zone de texte, plusieurs lignes avec `\n` | `color`, `bg` couleur de fond (transparent si omis), `border`, `size`, `bold`, `shadow`, `width` (retour à la ligne à cette largeur), `radius` coins arrondis | | `panel(key, titre, [lignes...], ...)` | Panneau (fond sombre semi-transparent, bordure dorée) | Comme `text` | | `bar(key, 0..1, ...)` | Barre de progression : vie, recharge, incantation | `width`, `height`, `color`, `bg`, `border`, `text` | | `image(key, chemin, ...)` | Image locale (png / jpg / bmp / gif) | `width`, `height` (taille d’origine si omis) | | `circle(key, unité ou point, rayon, ...)` | Cercle au sol, qui épouse le relief | `color` couleur du trait, `fill` remplissage (avec transparence), `width` épaisseur du trait | | `path(key, [points...], ...)` | Ligne brisée au sol | `color`, `width`, `arrow` flèche à l’extrémité ; les points peuvent être des coordonnées ou des unités | ## HTTP (tout langage) Serveur de Farsight (écoute uniquement en local) : ```http POST /api/instances/20/canvas {"set": [ {"key": "banner", "kind": "text", "text": "Canevas envoyé par HTTP", "frac": [0.5, 0.12], "center": true, "color": "#FFDC50", "bg": [0, 0, 0, 180]}, {"key": "hp", "kind": "bar", "value": 0.8, "unit": 596125988, "lift": 260, "text": "80%"}, {"key": "zone", "kind": "circle", "center": 596125988, "radius": 600, "color": [255, 200, 0], "width": 4}, {"key": "route", "kind": "path", "points": [[-4587, -9092], [-5387, -8792]], "color": "#50C8FF"} ], "remove": ["old"], "clear": false} GET /api/instances/20/canvas éléments actuellement dessinés + nombre de frames dessinées ``` `kind` est le nom de la méthode Python, et les noms de paramètres sont les mêmes ; une unité s’indique par son adresse `addr` dans l’instantané. ## Écrire directement dans la mémoire partagée On peut aussi se passer de Python et de Farsight : envoyez d’abord une fois la commande sémantique `canvas_enable` (opcode W3P 73) ; le runtime crée alors le bloc de mémoire partagée `Local\War3Canvas_` : en-tête de 64 octets + 256 entrées × 112 octets + réserve de 64 KB pour les textes / points. L’écriture suit le seqlock (numéro de séquence impair → écriture des entrées et de la réserve → numéro de séquence pair) ; le runtime lit le bloc une fois par frame, reprend la frame précédente s’il tombe sur une écriture à moitié faite, et renvoie le nombre de frames dessinées, le nombre d’éléments et le compteur d’exceptions. L’implémentation de référence en Python est `sdk/python/w3canvas.py` ; les structures sont définies dans l’en-tête du protocole, voir [Protocole W3P](https://war3ai.com/fr/docs/protocol/). ## Plusieurs programmes qui dessinent en même temps Mods, Farsight, MCP et la passerelle peuvent dessiner en même temps dans la même partie, alors qu’il n’y a qu’un seul canevas. La règle : **chaque programme ne touche qu’à ses propres éléments**. - Avant d’écrire, prendre un verrou nommé, lire les éléments existants, garder ceux des autres, remplacer les siens, puis réécrire le tout ; - Chaque élément retient qui l’a dessiné (identifiant de processus + numéro dans ce processus) ; si le programme qui l’a dessiné s’est arrêté, l’élément est nettoyé à la prochaine écriture, quel qu’en soit l’auteur, et ses boutons n’interceptent plus les clics ; - Les numéros d’éléments sont attribués par un compteur partagé, donc sans collision. Le SDK Python le fait déjà, et `clear()` n’efface lui aussi que ses propres éléments. Si vous écrivez vous-même directement dans la mémoire partagée, suivez ces règles, sinon vous écraserez ce que les autres ont dessiné. Pour le détail de la structure en mémoire, voir [Protocole W3P](https://war3ai.com/fr/docs/protocol/). ## Mesures et précautions - Mesuré le 2026-09-25 (1920×1080, vitesse ×2) : 0.27 à 0.34 ms par frame pour 9 éléments, environ 63 frames par seconde, 0 exception ; écrire 9 entrées prend 6 ms ; quand le héros se déplace, le cercle, le texte et la barre de vie qui suivent l’unité restent bien calés. La texture n’est redessinée que si le contenu change ; un simple déplacement ne la redessine pas. - Le dessin a lieu après l’interface du jeu et avant le curseur de la souris : il passe par-dessus les barres de vie, les unités et l’interface du jeu, et le curseur de la souris passe par-dessus lui. Il évite le panneau de commande en bas et l’horloge jour/nuit en haut, mais **ne contourne pas les panneaux propres à la carte** (classement, compte à rebours en haut à droite) — ne placez pas vos panneaux en haut à droite. - Hors partie (menu principal, écran de fin de partie), les éléments placés sur des coordonnées du monde ou sur une unité ne sont pas dessinés ; ceux placés à une position de l’écran le sont normalement. - Un cercle au sol est obtenu en projetant sur le sol chacun des 64 points de sa circonférence : si le terrain a du relief, la forme ondule avec lui — c’est voulu, il est dessiné sur le vrai sol. - La première ouverture installe le hook et préchauffe les polices, environ 1 seconde ; pendant ce temps, les éléments de texte ne sont pas encore dessinés, mais les cercles et les lignes le sont. - À la moindre exception pendant le dessin, plus rien n’est dessiné pour le reste de la session (même protection que pour les bulles au-dessus des unités) ; `faults` dans `stats()` passe alors à 1. - Textes, chemins d’images et points partagent 64 KB au total, avec 256 éléments au maximum ; les chemins d’images doivent être des chemins locaux lisibles par le processus du jeu. Le panneau d’état du [compagnon IA](https://war3ai.com/fr/docs/companion/) est dessiné avec le canevas : barre de vie, ce qu’il est en train de faire, humeur, éliminations et soins. --- # Interface et entrées > Les boutons et cartes de choix du canevas sont cliquables et s’illuminent automatiquement au survol ; enregistrez des raccourcis clavier, choisissez une position d’un clic au sol, lisez ce que pointe la souris et sachez ce que le joueur local a sélectionné. Clics, raccourcis clavier, sorts lancés, texte intégral du chat et départs de joueurs : tout arrive dans le flux d’événements. Ce que dessine le [canevas](https://war3ai.com/fr/docs/canvas/) est désormais **cliquable**. Le runtime prend en charge les entrées de la fenêtre du jeu, et un programme externe peut utiliser : | Capacité | En une phrase | Le jeu le reçoit-il ? | |---|---|---| | **Éléments de canevas cliquables** | Boutons, cartes de choix, panneaux : un clic envoie `ui.click`, et le survol les met automatiquement en surbrillance | Le clic qui tombe sur le bouton **n’est pas reçu** | | **Raccourcis clavier** | Enregistrez des combinaisons comme `F5` ou `ctrl+shift+Q` ; chaque pression envoie `hotkey` | Absorption facultative (avec le caractère produit par la touche) | | **Clics au sol** | Un clic dans le monde envoie `mouse.world`, avec les coordonnées au sol | Absorption facultative (« cliquez à un endroit pour poser une tour ») | | **Position de la souris** | Mise à jour à chaque frame : pixels à l’écran, point du sol sous le curseur, élément du canevas survolé | — | | **Sélection** | Ce que le joueur local a sélectionné ; chaque changement envoie `selection.changed` | — | Tout cela n’est qu’**entrée locale + dessin local** : rien ne passe dans le flux d’ordres de la partie, c’est donc sans risque en multijoueur. Mais si vos callbacks modifient le monde (faire apparaître des unités, changer des caractéristiques), cette partie reste réservée aux parties solo. ## Python : g.ui ```python ui = g.ui # au premier usage, le runtime prend en charge les entrées de la fenêtre ui.button("shop", "Acheter une potion (50 or)", screen=(40, 300), on_click=lambda g, ev: buy(g)) c = ui.choice("Niveau supérieur ! Choisissez une récompense", [("Force +5", "Plus résistant"), ("Vitesse d'attaque +20%", "Frappe plus vite"), ("Invoquer un loup", "Un allié de plus")], pause=True, on_pick=lambda g, i: give(g, i)) # une rangée de cartes au centre de l'écran ; pause=True met le jeu en pause pendant le choix i = c.wait(timeout=30) # ou attendre en bloquant (les événements continuent d'être pompés, rien n'est perdu) ui.hotkey("F5", lambda g, ev: g.say(hero, "Compris !")) # absorbé par défaut ui.hotkey("ctrl+shift+Q", on_press=..., swallow=False) ui.mouse(on_click, capture=True, buttons=("left", "right")) # capte les clics au sol : gauche et droit signalés, et absorbés xy = ui.pick_point("Cliquez au sol : où poser la tour ?") # version bloquante : prochain clic gauche au sol -> (x, y) ; Échap ou délai dépassé -> None ui.cursor() # {'screen': (x, y), 'world': (x, y, z) ou None, 'hover': 'shop'} ui.toast("La vague 3 arrive !", seconds=3) ui.close() # retire vos propres widgets et raccourcis ; ne rend les entrées de la fenêtre que si aucun autre programme ne les utilise g.close() # ou tout déconnecter (on peut aussi écrire with Game(...) as g:) ``` Les callbacks reçoivent `(g, ev)` et se déclenchent quand vous appelez `g.events()` — les exécuteurs des Bots et des [mods de jeu](https://war3ai.com/fr/docs/mods/) l’appellent à chaque tick. Les clics sans callback vont dans `ui.clicks`. Une exception levée dans un callback est seulement journalisée, sans effet sur les autres callbacks ni sur les événements. On peut aussi utiliser directement la couche du canevas : `g.canvas.text(..., clickable=True, hover=couleur)` ; les clics arrivent alors par le flux d’événements, et `ev.key` est la key donnée au moment du dessin. Dessiner un élément cliquable active automatiquement les entrées, sans passer d’abord par `g.ui`. **Syntaxe des raccourcis** : `F1` à `F24`, `A` à `Z`, `0` à `9`, `numpad0` à `numpad9`, `space enter esc tab backspace insert delete home end pageup pagedown left up right down`, éventuellement précédés de `ctrl+`, `shift+` ou `alt+`. > **Attention** > > Les lettres et les chiffres sans touche de modification entrent en conflit avec la saisie du chat et les raccourcis du jeu. Préférez des touches que le jeu n’utilise pas, comme F5 à F8, ou des combinaisons. ## Nouveaux événements `g.events()` renvoie désormais aussi ceux-ci (tous les champs dans [Protocole W3P](https://war3ai.com/fr/docs/protocol/)) : | kind | Quand | Champs pratiques | |---|---|---| | `ui.click` | Un élément interactif du canevas a été cliqué | `.key` key du canevas, `.button` (`'left'` / `'right'`), `.mods` touches de modification | | `ui.hover` | La souris entre sur un élément du canevas / en sort | `.key` (`None` à la sortie) | | `hotkey` | Un raccourci enregistré a été pressé | `.key` syntaxe du raccourci, `.mods` | | `mouse.world` | Clics au sol activés : un clic est tombé dans le monde | `.x .y` coordonnées au sol, `.button`, `.value` (1 = absorbé) | | `selection.changed` | La sélection du joueur local a changé | Récupérez les unités avec `g.selection()` | | `spell.cast` | Une unité a lancé un sort (la recharge du sort a commencé) | `.spell` code à quatre caractères, `.b` niveau, `.value` recharge en secondes, `.x .y` point d’incantation | | `message` | Une ligne est apparue dans un cadre de messages à l’écran | `.text` texte intégral, `.frame` quel cadre, `.chat` (si c’est du chat) | | `player.left` | Un joueur est parti ou a été retiré après sa défaite | `.player` | | `game.ended` | On a quitté la partie | — | ## Chat et messages à l’écran Ce que le joueur tape dans la boîte de chat se lit directement dans le `.chat` de l’événement `message` : ```python for ev in g.events(): if ev.kind == "message" and ev.chat and ev.chat["text"] == "-follow": ... # ev.chat = {'channel': 'Tous', 'sender': 'nom du joueur', 'text': '-follow'} ``` `g.messages()` a son propre curseur, indépendant : les indications du jeu (« Il vous faut plus de fermes », « Impossible de construire ici ») s’y trouvent aussi. En écrivant un Bot, c’est là que vous saurez pourquoi une commande n’a pas abouti. ## Depuis d’autres langages - **Passerelle** : `ui.button`, `ui.choice`, `ui.hotkey`, `ui.mouse`, `ui.cursor` et les autres méthodes sont disponibles sous le même nom sur la [passerelle](https://war3ai.com/fr/docs/gateway/). Un client distant ne peut pas transmettre de fonction de rappel : les clics et les raccourcis arrivent par les événements poussés (l’événement `ui.click` porte `key`). - **Écrire directement dans la mémoire partagée** : envoyez d’abord la commande sémantique `input_enable` (opcode W3P 74), et le runtime commence à prendre en charge les entrées. Dans le bloc d’entrées `Local\War3Input_`, vous écrivez la table des raccourcis et les interrupteurs de la souris ; le runtime y écrit en retour la position de la souris, le point du sol sous le curseur et l’élément survolé. Le bit `0x40` d’un élément du canevas signifie « interactif ». Pour la disposition, voir [Protocole W3P](https://war3ai.com/fr/docs/protocol/). ## Plusieurs programmes en même temps Les mods, Farsight, MCP et chaque session de la passerelle peuvent poser des boutons et enregistrer des raccourcis dans la même partie en même temps, sans se gêner : - Chaque programme enregistre ses propres raccourcis et son propre interrupteur de clics au sol ; le SDK fusionne ceux de tous en une seule table qu’il transmet au runtime. Une même touche n’y figure qu’une fois, l’événement est envoyé à tout le monde, et chacun reconnaît ses propres raccourcis à la touche ; - `ui.close()` ne retire que ce qui appartient au programme appelant ; les entrées de la fenêtre ne sont rendues qu’au départ du dernier programme ; - Un programme arrêté de force n’a pas le temps de faire le ménage : le runtime vérifie toutes les 2 secondes et, quand tous les programmes enregistrés sont partis, efface les raccourcis et l’interception des clics au sol qu’ils ont laissés ; leurs boutons n’interceptent plus les clics non plus. ## Mesures 2026-09-25, vérification en jeu sur une instance de test, 16/16 : - Clic sur un bouton → `ui.click` + callback, et le compteur d’interceptions du runtime augmente de 1 (le jeu n’a pas reçu ce clic) ; un clic hors du bouton ne déclenche rien ; - F6 → `hotkey` ; clic au sol → `mouse.world` (absorbé) ; - Faire apparaître un paladin et le sélectionner → `selection.changed`, cohérent avec `g.selection()` ; lancer Bouclier divin → `spell.cast('AHds', 1, 35.0)` ; - Texte de la carte → `message` ; chat → `message`, avec `.chat` qui en extrait l’auteur et le contenu ; - Défaite de l’ordinateur → `player.left` ; fin de la partie → `game.ended`. Les vrais clics de souris sur les boutons et la surbrillance au survol ont aussi été vérifiés un par un. ## Limites et précautions - **La position vient de la vraie souris** : le jeu lit la position sur le curseur du système, donc le survol et `cursor()` reflètent la vraie souris. L’interception ne concerne que les clics et les touches. - **Dessiné sous le curseur de la souris** : Warcraft dessine le curseur dans l’image, à chaque frame. Le canevas et les bulles au-dessus des unités sont dessinés juste avant l’étape où le jeu dessine le curseur : ils couvrent l’interface du jeu, et le curseur passe par-dessus eux. Ce n’est que lorsqu’une frame n’a pas de curseur (masqué, ou pendant une cinématique) qu’ils reviennent à un dessin en toute dernière étape. - **Mise à l’échelle du système** : si vous écrivez vos propres tests et envoyez des clics par messages de fenêtre, les coordonnées envoyées par un processus qui ne gère pas le DPI sont agrandies par le système (×1.5 mesuré avec une mise à l’échelle de 150 %). Faites d’abord déclarer à votre programme de test qu’il gère le DPI. Les vrais clics ne sont pas concernés. - **Les polices doivent être préchauffées la première fois**, ce qui prend environ 1 seconde. Pendant ce temps, les boutons ne sont pas encore dessinés et ne peuvent pas être cliqués. - **Pas de clics au sol hors partie** : dans le menu principal et sur l’écran de fin de partie, `mouse.world` n’est ni envoyé ni absorbé. - Si un clic a été absorbé à l’appui et que, avant le relâchement, on bascule vers un autre programme ou la souris sort de la fenêtre, l’état est quand même réinitialisé : le relâchement suivant n’est pas absorbé lui aussi. - La 1.27 n’a pas de fonction pour créer de nouveaux cadres d’interface du jeu (ils sont arrivés avec la 1.31) : les boutons et cartes décrits ici sont dessinés par le runtime ; leur style est libre, mais ils n’apparaissent pas dans la hiérarchie des menus du jeu. --- # Canal JASS > Les 1291 fonctions JASS dont disposent les créateurs de cartes s’appellent désormais par leur nom depuis l’extérieur du jeu : créer des unités, modifier des attributs, effets, panneaux, boîtes de dialogue, sons, caméra, brouillard… Quatre usages : console Farsight, ligne de commande, HTTP, Python. Les **1291 natives JASS** que les créateurs de cartes peuvent utiliser dans leurs scripts s’appellent désormais toutes par leur nom depuis l’extérieur du jeu : créer des unités, modifier des attributs, afficher des effets, ouvrir des panneaux et des boîtes de dialogue, jouer des sons, déplacer la caméra, modifier le brouillard… De quoi personnaliser le jeu plus avant — assistance pour RPG, [compagnon IA](https://war3ai.com/fr/docs/companion/), mini-jeux maison, outils de débogage. | Usage | Idéal pour | Point d’entrée | |---|---|---| | **Page « Console JASS » de Farsight** | Essayer à la main, modifier en voyant le résultat | Barre latérale « Système → Console JASS » : écrivez un script et cliquez sur Exécuter ; à droite, parcourez les fonctions par catégorie, un clic en insère une dans le script | | **Ligne de commande** | Essayer à la main, ou en faire un fichier de script à relancer | `python -m openwar3 jass --inst 20` (interactif), `-e "code"`, `my_script.j`, `--list mot-clé` | | **HTTP** | Programmes externes, dans n’importe quel langage | `POST /api/instances/{n}/jass`, etc. (voir plus bas) ; le serveur de Farsight n’écoute qu’en local | | **Python** | Écrire des schémas, des compagnons, des outils | `g.jass.NomDeFonction(...)` ; les effets visuels et interactions courants sont regroupés dans `openwar3.visual` | > **Attention** > > Trois limites, toutes dictées par le mécanisme : > > - Seules les **parties solo** (contre l’ordinateur, sur votre machine) peuvent modifier le monde. Créer des objets ou modifier des unités unilatéralement depuis votre machine désynchroniserait les autres joueurs d’une partie multijoueur — en multijoueur, seules les fonctions en lecture seule sont autorisées (`Get*`, `Is*`, `Count*`…). > - Réservé aux outils locaux : les appels passés via une connexion en tant que joueur (`Game(player=N)`) ou en mode équitable sont rejetés. > - Uniquement pour les parties en solo ou hébergées vous-même en réseau local. > > Pour ajouter des éléments à l’écran en multijoueur, utilisez le [canevas](https://war3ai.com/fr/docs/canvas/) : c’est le runtime qui le dessine lui-même, sans modifier l’état du jeu. ## Écrire un script La console, la ligne de commande et HTTP utilisent le même format de script. Une instruction par ligne ; **vous pouvez coller du JASS tel quel** (`call` / `set` / `local`, `true` / `false` / `null`, codes à quatre caractères `'Hpal'`, commentaires `//`), ou écrire à la manière de Python : ```text set h = hero() // intégré : héros principal de notre camp local texttag t = CreateTextTag() call SetTextTagText(t, "|cffffcc00+128 critique !|r", 0.024) call SetTextTagPosUnit(t, h, 60) call SetTextTagVelocity(t, 0, 0.03) call SetTextTagPermanent(t, false) call SetTextTagLifespan(t, 4) call SetTextTagVisibility(t, true) call PingMinimapEx(h.x, h.y + 300, 3, 255, 0, 0, false) set u = CreateUnit(Player(0), 'hfoo', h.x + 200, h.y, 270) print("créé", u, "niveau du héros", GetHeroLevel(h)) ``` - **Les variables sont mémorisées** : pour une même instance et une même partie, les variables définies par `set` dans un bloc restent utilisables dans le suivant ; elles sont effacées automatiquement à chaque nouvelle partie, et peuvent aussi l’être à la main. - **Fonctions intégrées** : `hero()` héros principal de notre camp, `me()` joueur local, `unit('hfoo')` trouve une unité, `unit_at(x, y)`, `wait(secondes)`, `print(...)`. Sur une unité, on peut lire `.x`, `.y`, `.hp`, `.hp_max`, `.mana`, `.type`, `.owner`, `.level` ; les quatre opérations arithmétiques et les comparaisons sont prises en charge. - **Pas de** `if`, `loop` ni `function` — pour écrire de la logique, utilisez `g.jass` en Python (ce sont de simples appels de fonction), ou écrivez un [schéma](https://war3ai.com/fr/docs/schemes/). - En cas d’erreur, vous saurez à quelle ligne et pourquoi (fonction inexistante, mauvais nombre d’arguments, variable non définie…) ; les instructions qui précèdent l’erreur ont déjà pris effet. Arguments et valeurs de retour : | Dans la signature | Quoi passer | Remarques | |---|---|---| | Entier | Un nombre ; les codes à quatre caractères `'Hpal'` sont convertis automatiquement | | | Réel | Un nombre | Le runtime le convertit au format attendu par le moteur | | Booléen | `true` / `false` | | | Chaîne | `"..."` | Le chinois et les codes couleur du jeu sont pris en charge ; les chaînes que le jeu conserve (texte flottant, panneaux, boutons, commandes de chat) sont copiées sur le moment : aucun risque | | Handle | Un handle stocké dans une variable, ou une unité (ce que renvoie `hero()` est converti automatiquement en handle) | | | Fonction (code) | Uniquement `null` | Impossible de fournir une fonction JASS depuis l’extérieur ; `TimerStart(t, 60, false, null)` fonctionne | | Retour de type chaîne | — | Le moteur renvoie un index dans la table des chaînes : le texte ne peut pas être relu. Pour les noms d’unités, utilisez `g.map_data.name_of` | ## Catégories Les fonctions sont classées d’après leur nom ; la partie droite de la console et `--list` suivent ce classement : | Catégorie | Nombre | Exemples | |---|---|---| | Effets visuels | 80 | Texte flottant, éclairs reliant deux points, effets spéciaux, images au sol, marques au sol, couleur / taille / animation des unités | | Panneaux d’interface | 146 | Panneaux multilignes, classements, fenêtres de compte à rebours, boîtes de dialogue, quêtes, texte à l’écran, signaux sur la mini-carte, dialogue avec portrait, filtres plein écran | | Caméra | 44 | Champs de caméra, panoramique, tremblement de caméra | | Sons et musique | 50 | Créer et jouer des sons, lancer une musique | | Brouillard et vision | 25 | Zones visibles, activer / désactiver le brouillard | | Objets / héros / unités | 63 / 32 / 161 | Créer des objets, définir le niveau d’un héros, changer de propriétaire, ajouter des compétences | | Joueurs / alliances / ressources | 71 | Définir les alliances, modifier l’or et le bois | | Déclencheurs / événements / minuteries | 62 | Créer des déclencheurs, enregistrer des événements, minuteries | | Terrain / météo / destructibles | 45 | Effets météo, modifier le terrain, créer des destructibles | | Déroulement de la partie | 57 | Vitesse de jeu, pause, heure du jour | | Autres | … | Groupes d’unités et régions, stockage, scripts d’IA de l’ordinateur, conversions de types et maths, réponses aux événements… | Le 2026-09-24, **94 d’entre elles** ont été appelées une par une en partie réelle, avec vérification visuelle de l’effet ; les autres passent par le même chemin, simplement sans vérification individuelle de leur effet. > **Remarque** > > Les fonctions de la catégorie « réponses aux événements » (`GetTriggerUnit`, `GetClickedButton`…) n’ont de valeur qu’à l’instant où un déclencheur s’exécute ; appelées depuis l’extérieur, elles renvoient 0 ou une valeur vide. Pour savoir « si c’est arrivé », utilisez les compteurs d’événements décrits plus bas. ## HTTP Serveur de Farsight (`127.0.0.1:8866` par défaut, écoute uniquement en local) : ```http GET /api/jass/natives?q=TextTag&cat=visual POST /api/instances/20/jass {"code": "set h = hero()\ncall PingMinimapEx(h.x, h.y, 3, 255, 0, 0, false)"} -> {"ok": true, "rows": [...], "printed": [...], "vars": {...}} -> en cas d'erreur : {"ok": false, "error": "第 2 行:...", "line": 2} POST /api/instances/20/jass/call {"name": "SetUnitScale", "args": [{"unit": 599669636}, 1.4, 1.4, 1.4]} POST /api/instances/20/jass/reset efface les variables mémorisées ``` Un argument unité s’écrit `{"unit": adresse}`, l’adresse étant le `addr` de l’unité dans l’instantané. Mesuré : 60 à 90 ms par requête. ## Python : g.jass et openwar3.visual ```python j = g.jass t = j.CreateTextTag() j.SetTextTagText(t, "Bonjour", 0.024) # mêmes règles d'arguments que dans les scripts ; les unités et objets de l'instantané se passent directement j.signature("CreateImage") # consulter la signature ``` `openwar3.visual.Visual(g)` regroupe les effets visuels courants déjà testés, une ligne par effet (appelez `v.tick()` à chaque tick : ce qui a expiré est supprimé, les liens et cercles qui suivent des unités sont déplacés ; `v.clear()` supprime tout) : | Méthode | Effet | |---|---| | `float_text(texte, unité ou point, ...)` | Texte flottant : chiffres de dégâts, indications au-dessus de la tête, chinois et couleurs acceptés | | `link(a, b, kind)` | Un lien entre deux unités, qui les suit : traction / lien spirituel / drain de vie / vague de soins | | `effect(modèle, unité ou point, ...)` | Modèle d’effet spécial : au-dessus de la tête, sous les pieds, ou joué une seule fois (explosion, colonne de lumière) | | `ring(unité ou point, rayon, color)` | Cercle au sol : portée d’un sort, zone dangereuse, point de ralliement ; peut suivre une unité | | `ping(point, color)` | Signal sur la mini-carte | | `board(titre, lignes...)` | Panneau multiligne en haut à droite (avec icônes), modifiable case par case | | `countdown(titre, secondes)` | Fenêtre de compte à rebours en haut à droite, décomptée par le jeu lui-même | | `scene(nom, texte, portrait)` | Dialogue avec portrait : le portrait du bas devient une unité qui parle, et un sous-titre « nom : texte » s’affiche à l’écran | | `screen_tint(color, alpha)` | Filtre plein écran (par défaut, bords rougis : alerte de vie basse) | | `sound(chemin)` / `reveal(point, rayon, secondes)` / `look(unité, ...)` | Jouer un son / dissiper le brouillard sur une zone / changer la couleur d’une unité, l’agrandir, lui faire jouer une animation, la faire clignoter | ## Interactions : savoir ce que fait le joueur sans écrire de fonction JASS En JASS, réagir aux actions du joueur exige d’écrire une fonction de déclencheur, et on ne peut pas fournir de fonction depuis l’extérieur. La solution : **créer un déclencheur vide, sans condition ni action, y enregistrer seulement l’événement, puis compter combien de fois il s’exécute.** En test, un déclencheur vide compte bel et bien. | Méthode | Usage | |---|---| | `chat_commands(["-follow", "-stay"])` → `.poll()` | Commandes tapées par le joueur dans le chat (correspondance exacte, ou sur le début) | | `menu(titre, [boutons...])` → `.clicked()` | Menu de boutons au centre de l’écran : lequel a été cliqué | | `hotkeys(("left", "right", "up", "down", "esc"))` → `.poll()` | Nombre d’appuis sur les flèches et sur Échap | | `on("TriggerRegister...Event", arguments...)` → `.poll()` | Nombre d’occurrences de n’importe quel événement JASS : mort d’une unité, entrée dans une région, dégâts subis, minuterie… | La limite : on sait seulement « combien de fois », pas « qui, ni quel texte ». Pour distinguer les auteurs, créez un compteur par objet. C’est ainsi que sont branchées les commandes de chat du [compagnon IA](https://war3ai.com/fr/docs/companion/). ## Précautions - **Supprimez vous-même ce que vous créez** : textes flottants, liens, images, panneaux, déclencheurs… sinon ils restent indéfiniment (`Visual.clear()` supprime ce qu’il a lui-même créé). Le jeu affiche au maximum environ 100 textes flottants simultanés. - **Les fonctions BJ ne sont pas des natives** : `CreateTextTagUnitBJ` et consorts sont assemblées à partir de natives dans le script de la carte, et ne sont pas disponibles ici — reproduisez leur implémentation avec des natives. - **Certaines constantes doivent d’abord être converties** : par exemple `ConvertPlayerColor(1)`, `ConvertFogState(4)` (valeurs possibles dans common.j). - Environ 13 ms par appel (conversion des handles comprise) ; au niveau du protocole, ce sont les opcodes W3P 70 à 72, voir [Protocole W3P](https://war3ai.com/fr/docs/protocol/). --- # Mods de jeu > Un schéma n’est pas forcément une IA qui joue à votre place : ce peut aussi être un ensemble de règles. Vous jouez vous-même dans la fenêtre du jeu ; le mod prépare le début de partie, fait apparaître les monstres, distribue les récompenses, vous présente boutons et cartes de choix à l’écran et décide de l’issue. Héritez de openwar3.Mod : un fichier, un gameplay complet. Les [schémas d’IA](https://war3ai.com/fr/docs/schemes/) sont de deux sortes : `kind: bot` est une IA qui joue à votre place ; `kind: mod` est **un ensemble de règles** — vous jouez vous-même dans la fenêtre du jeu, et le mod vous lance les défis : comment la partie est préparée, quand les monstres apparaissent (selon le temps ou les événements), quelles récompenses sont données, quels boutons et cartes de choix s’affichent à l’écran, quand la partie est gagnée. Un mod n’utilise que des capacités existantes : [Interface et entrées](https://war3ai.com/fr/docs/ui-input/) (boutons et cartes cliquables, raccourcis clavier, clics au sol), le [canevas](https://war3ai.com/fr/docs/canvas/) (panneaux, barres de progression, tracés), le [canal JASS](https://war3ai.com/fr/docs/jass/) (faire apparaître des unités, modifier des caractéristiques, donner des objets) et le flux d’événements (morts, montées de niveau, sorts lancés, chat). ## Deux exemples À choisir dans Farsight, sous « Schémas d’IA » → « Intégrés » : | Mod | Principe | Capacités utilisées | |---|---|---| | **Roguelike de héros** `builtin/hero-roguelike` | Vous n’avez qu’un paladin, et les monstres arrivent par vagues de tous les côtés ; à chaque niveau gagné, choisissez un bonus parmi trois au centre de l’écran (le jeu est en pause pendant le choix) ; tenez 10 vagues pour gagner ; si le héros meurt, c’est perdu | `g.ui.choice` (cartes cliquables + pause), événements `hero.levelup` / `killed` / `spell.cast`, `-help` dans le chat, JASS pour modifier les caractéristiques du héros et donner des objets | | **Défense sans fin** `builtin/endless-defense` | Les monstres partent du point de départ opposé et foncent vers votre bâtiment principal en suivant une ligne rouge tracée au sol ; chaque vague repoussée rapporte de l’or ; cliquez sur le bouton à l’écran ou appuyez sur F7 pour appeler la vague suivante plus tôt, avec une récompense ×1.5 ; appuyez sur F8 puis faites un clic gauche au sol pour poser une tour de garde gratuite (clic droit pour annuler) | `g.ui.button`, `g.ui.hotkey`, `g.ui.mouse` (capture des clics au sol), panneau / barre de progression / tracé du canevas, JASS pour faire apparaître les monstres et ajouter de l’or | Chacun des deux exemples fait environ 150 lignes ; le code se trouve dans `brains/examples/mod_hero_roguelike.py` et `brains/examples/mod_endless_defense.py`. ```bash python tools/play.py --bot brains/examples/mod_hero_roguelike.py --inst 9 # lance une partie, le mod prend la main, vous jouez dans la fenêtre du jeu ``` ## Écrire un mod ```python from openwar3 import Mod class Survive(Mod): name = "survive" def on_start(self, g): super().on_start(g) # vérifie qu'on est en solo + neutralise l'ordinateur adverse self.foe = self.wave_player(g) # un emplacement libre devient le « joueur des vagues » : allié de personne, sans IA d'ordinateur self.every(30, self.wave) # une vague toutes les 30 secondes de jeu (rien ne s'écoule pendant la pause) g.ui.hotkey("F7", lambda g, ev: self.wave(g)) def wave(self, g): self.spawn_ring(g, self.foe, "ugho", 6, self.home(g), 1400, attack_to=self.home(g)) def on_event(self, g, ev): if ev.kind == "unit.died" and ev.type == "htow": self.finish("loss", "L'hôtel de ville est tombé") ``` Par rapport à `Bot`, `Mod` ajoute : | Méthode / attribut | Description | |---|---| | `on_start / on_tick / on_event / on_end` | Comme pour Bot ; si vous surchargez `on_start` / `on_tick`, appelez d’abord `super()` | | `every(secondes, fn, first=)` / `after(secondes, fn)` | Minuteurs qui suivent le **temps de jeu** ; le callback est `fn(g)` | | `finish(result, reason)` | Termine la partie (`'win'` / `'loss'` / `'unknown'`) : l’exécuteur s’arrête au tick suivant, un panneau de résultat est dessiné au centre de l’écran, et le bilan du schéma est enregistré d’après ce résultat | | `wave_player(g)` | Le premier emplacement de joueur libre, à utiliser comme joueur des vagues | | `spawn_ring(g, joueur, unité, nombre, centre, rayon, attack_to=)` | Fait apparaître des unités sur un cercle, sans à-coups même par dizaines à chaque vague ; renvoie les handles JASS | | `alive_of(g, joueur)` / `attack_move_all(g, joueur, point)` | Les unités vivantes d’un joueur / toutes en attaque-déplacement vers un point (appelez-la toutes les quelques secondes pour que les monstres vous poursuivent) | | `home(g)` / `hud(g, titre, lignes)` | Position de notre bâtiment principal / panneau d’informations en haut à droite | | `neutralize_ai = True` | Neutralise l’ordinateur adverse en début de partie : ses unités sont mises en pause toutes les 5 secondes, son or et son bois remis à zéro. Une carte de mêlée a toujours un ordinateur ; quand le mod fixe ses propres règles, il ne doit pas venir perturber la partie | | `single_player_only = True` | Refuse de s’exécuter s’il y a d’autres joueurs humains (le JASS qui modifie le monde les désynchroniserait) | | `linger_s = 6` | Une fois l’issue décidée, nombre de secondes passées sur l’écran de résultat avant de terminer | `finish()` fonctionne aussi sur `Bot` : un Bot ordinaire peut lui aussi annoncer lui-même la fin de la partie. ## En faire un schéma et le partager Écrivez `"kind": "mod"` dans `scheme.json`, et définissez une sous-classe de `Mod` dans le fichier d’entrée : ```json {"id": "survive", "name": "Tenir 10 vagues", "kind": "mod", "entry": "survive.py", "class": "Survive"} ``` Un mod **n’est jamais en mode équitable** (c’est l’arbitre qui pose les défis : il doit voir toute la carte et modifier le monde), et **son issue n’est jamais jugée selon les règles de mêlée** (c’est `finish` qui la signale) ; `fair` / `judge` dans le manifeste sont sans effet. Export en zip, import, confiance et bilan fonctionnent exactement comme pour les schémas de Bot : voir [Schémas d’IA](https://war3ai.com/fr/docs/schemes/). Un mod, c’est aussi du code : avant la première exécution du mod de quelqu’un d’autre, il faut également confirmer votre confiance. ## Mesures 2026-09-25, sur une instance de test : - **Roguelike de héros** : la première vague apparaît, le panneau en haut à droite se met à jour ; héros monté au niveau 3 → des cartes s’affichent au centre de l’écran et l’horloge du jeu s’arrête ; deux clics sur des cartes → deux bonus appliqués (force 22 → 27), l’horloge repart. - **Défense sans fin** : panneau, tracé au sol et bouton sont bien là ; F8 + clic au sol → une tour de défense apparaît à côté du bâtiment principal ; clic sur le bouton alors que la vague en cours n’est pas terminée → message « cette vague n’est pas encore terminée ». ## Limites - **Parties solo uniquement** : faire apparaître des unités et modifier des caractéristiques passe par le canal JASS, ce qui désynchronise une partie multijoueur. C’est une conséquence du modèle lockstep ; un gameplay multijoueur devra attendre un canal de synchronisation (voir la [feuille de route](https://war3ai.com/fr/roadmap/)). - Le mod voit toute la carte — c’est lui qui pose les défis, ce n’est pas un joueur. - Dans une carte de mêlée, l’ordinateur adverse est seulement « neutralisé », pas retiré (le retirer déclencherait la victoire selon les règles de mêlée). --- # Console Farsight > Console web locale et point d’entrée unique : régler le dossier du jeu, démarrer et arrêter les services comme les instances du jeu, régler la partie suivante, voir ce que pense l’IA, donner des ordres manuels, réalisation, historique des parties. Farsight est une console web qui tourne sur votre machine et **n’écoute que sur 127.0.0.1**. C’est aussi le point d’entrée unique de tout le système : lancer une partie, changer d’IA, la passerelle, les bulles de dialogue, le LLM local, tout se fait ici, sans avoir à chercher d’autres scripts. ```bash start.bat # vérification du déploiement, puis ouverture de Farsight sur http://127.0.0.1:8866 start.bat 5 6 # démarre en plus le test des instances 5 et 6 (jeu + cerveau de référence) start.bat restart # redémarre seulement le serveur de Farsight (après une modification du code côté serveur ; le jeu et les services ne sont pas touchés) stop.bat # arrête tout ``` Le port se règle dans `ports.console` de `openwar3.json` (8866 par défaut). ## Centre de contrôle La page d’accueil de Farsight. - **Dossier du jeu** : recherche automatique ou choix manuel, vérification de la version du jeu, extraction des données depuis votre jeu. - **Services locaux** : [passerelle](https://war3ai.com/fr/docs/gateway/), [bulles de dialogue](https://war3ai.com/fr/docs/speech/), LLM local (LM Studio), aperçu local du site ; chaque carte permet de démarrer, arrêter, redémarrer et consulter les journaux. On voit aussi si le serveur [MCP](https://war3ai.com/fr/docs/mcp/) a été branché par un client. - **Vérification de l’environnement** : Python, fichiers du runtime, données du jeu, données AMAI… chaque composant est-il bien installé. - **Tout arrêter** (en haut à droite) : instances du jeu, IA, passerelle, bulles, modèle local utilisé par le système et serveur de Farsight s’arrêtent l’un après l’autre ; c’est l’équivalent d’un double-clic sur `stop.bat`. Le serveur MCP est géré par les clients comme Claude : il n’est pas arrêté ; LM Studio lui-même n’est pas fermé non plus. ## Pages | Groupe | Page | Rôle | |---|---|---| | Pilotage | Centre de contrôle | Voir la section précédente | | Partie | Vue d’ensemble | Résumé de la partie de l’instance courante | | | Commandement | Vue de la carte ; ordres manuels possibles (les ordres manuels ont la priorité la plus haute de la table d’arbitrage : 95) | | | Données des unités | Ordre, cible de la tâche, mana, niveau et expérience des héros, temps de recharge des compétences et inventaire de chaque unité | | | Décisions de l’IA / décisions de combat | Ce que pense le cerveau de référence pendant ce tick, détail de chaque décision de combat | | | Coach stratégique | État du [conseiller LLM](https://war3ai.com/fr/docs/llm-coach/) : service de modèle disponible ou non, connexion de chaque instance, dernière recommandation et entrées qu’il a reçues | | | Régie | Réalisation automatique, barres de vie au-dessus des unités | | | Bulles de dialogue | Faire parler les unités, dialoguer avec le modèle local, pause-café des paysans, dialogues à la caméra, déclenchement par la partie, réglages du modèle. Voir [Bulles de dialogue](https://war3ai.com/fr/docs/speech/) | | | Vitesse des ordres | APM et débit de commandes | | | Événements et entrées | Ce qui s’est passé dans la partie : sorts lancés, chat et messages à l’écran, clics sur les boutons, raccourcis clavier, clics au sol, sélection, départs de joueurs, avec filtre par catégorie ; à côté, la position de la souris, l’élément survolé et la sélection locale. Voir [Interface et entrées](https://war3ai.com/fr/docs/ui-input/) | | Historique | Journaux / historique des parties | Sources de journaux de chaque instance ; issue, durée et effectif maximal de chaque partie | | | Mémo des problèmes | En jeu, appuyez sur Pause/Break pour mettre en pause et noter l’instant ; complétez la description ici plus tard | | Système | Instances et lancement | Démarrer / arrêter les instances ; régler pour la **partie suivante** la carte (carte de mêlée, ou carte RPG / personnalisée), les races des deux camps, la difficulté et la vitesse ; choisir un schéma d’IA par instance ; « Lancer le test » démarre le jeu + l’IA en un clic | | | Schémas d’IA | Importer, exporter, copier, supprimer des schémas et leur accorder votre confiance ; changer le schéma d’une instance (le nouveau schéma peut même reprendre immédiatement la partie en cours) ; voir les résultats de chaque schéma. Voir [Schémas d’IA](https://war3ai.com/fr/docs/schemes/) | | | Console JASS | Écrivez un script JASS et cliquez sur Exécuter ; à droite, parcourez les 1291 fonctions par catégorie et insérez-en une dans le script d’un clic ; les variables sont conservées pendant toute la partie. Voir [Canal JASS](https://war3ai.com/fr/docs/jass/) | | | Connexion et extensions | État de la passerelle et démarrage en un clic ; adresses de connexion générées par rôle (dev / joueur / observateur), commandes et configuration pour brancher le MCP ; exemples JS et Python. Voir [Passerelle](https://war3ai.com/fr/docs/gateway/) et [MCP](https://war3ai.com/fr/docs/mcp/) | | | Données et disque | Espace disque occupé par chaque type de données d’exécution (enregistrements, historique des parties, journaux…), volume ajouté au cours du dernier jour, et ce qui peut être supprimé (Farsight ne supprime jamais rien automatiquement) | | | Paramètres | Dossier du jeu, langue de l’interface, apparence (sombre / clair, moderne / style Warcraft), etc. | | | Retours et suggestions | Un problème, une suggestion : envoyez-les-nous directement ; les informations de diagnostic ne sont jointes que si vous cochez la case, et vous pouvez les prévisualiser avant l’envoi | Appuyez sur Ctrl + K pour ouvrir la palette de commandes : changer de page, changer d’instance, terminer la partie en cours, lancer un nouveau cerveau. En bas de la barre latérale, « Nouveautés » liste ce qui a récemment été ajouté à Farsight et à la plateforme. Au démarrage (puis toutes les 6 heures), Farsight demande à War3AI.com s’il existe une nouvelle version et vous prévient si c’est le cas. Quand Farsight est mis à jour, un bandeau apparaît en haut de la page : enregistrez ce que vous êtes en train de saisir, puis cliquez sur « Actualiser ». ## Instances multiples `runtime/farm.py` gère le multi-instances (Farsight l’appelle pour vous quand il démarre ou arrête des instances) : il copie le lanceur d’origine `War3.exe` sous le nom `War3-.exe` (sans modifier aucun fichier du jeu) ; chaque instance a son numéro et son dossier (`bin/inst/`). À la fin d’une partie, la suivante démarre automatiquement selon `next_game.json` (c’est ce fichier que modifie la page « Instances et lancement » de la console). > **Astuce** > > Votre Bot se connecte à une instance donnée avec `--inst N`. La page « Instances et lancement » de la console indique quels numéros sont utilisés : évitez de prendre le même que le cerveau de référence. ## Page de diffusion `http://127.0.0.1:8866/live` est une page de journal défilant, conçue pour une « source navigateur » d’OBS, qui affiche les décisions de l’IA et le déroulement de la partie. ## API Le serveur de la console expose un ensemble d’API REST + WebSocket locales (état des instances, réglages de la partie suivante, détail des unités, ordres manuels, journaux, historique des parties, réalisation, schémas d’IA, appels JASS, canevas…) ; la page web n’est qu’un client parmi d’autres, et un programme écrit dans n’importe quel langage peut les appeler directement. La liste des API figure dans l’en-tête du fichier `console/server/app.py` ; l’utilisation des trois groupes schémas, JASS et canevas est décrite respectivement dans [Schémas d’IA](https://war3ai.com/fr/docs/schemes/), [Canal JASS](https://war3ai.com/fr/docs/jass/) et [Canevas](https://war3ai.com/fr/docs/canvas/). --- # Schémas d’IA > Un schéma est une IA complète. Changez-en en un clic dans Farsight, y compris pour reprendre sur-le-champ la partie en cours ; exportez-le en zip pour le partager, importez celui d’un autre pour le tester ; le bilan de chaque schéma est compilé automatiquement. Un **schéma** = une IA complète : un dossier + un manifeste `scheme.json` + du code. Chaque instance du jeu utilise un schéma ; dans Farsight, on en change en un clic, et **la partie en cours peut être reprise immédiatement par le nouveau schéma**. Les schémas partagés par d’autres sont importés dans **un espace à part**, sans interférer avec les vôtres ; pour en modifier un, utilisez « Copier dans mes schémas ». ```text schemes/ mine// Mes schémas : écrits par vous, ou copiés d'un autre schéma pour être modifiés (modifiables à volonté, effet à la partie suivante) installed// Installés : les zip partagés par d'autres sont décompressés ici (confiance à confirmer avant la première exécution) brains/xwar3/ Intégré : cerveau de référence (IA complète) brains/examples/ Intégrés : quatre exemples pédagogiques hello / rush / macro / micro, l'exemple de compagnon buddy, deux mods de jeu (Roguelike de héros, Défense sans fin) ``` Un schéma n’est pas forcément une IA qui joue à votre place : un schéma `kind: mod` est un ensemble de **règles de jeu** — vous jouez vous-même, il vous lance les défis. Voir [Mods de jeu](https://war3ai.com/fr/docs/mods/). ## Dans Farsight Page « Schémas d’IA » (barre latérale « Système → Schémas d’IA ») : | Action | Effet | |---|---| | Importer un schéma (zip) | L’installe dans `installed/` ; si le même id est déjà installé, demande s’il faut le remplacer (après remplacement, la confiance doit être redonnée) | | Appliquer à une instance… | Choisir l’instance + « Effet immédiat » (arrête l’IA actuelle, le nouveau schéma reprend la partie en cours) ou « Effet au prochain lancement du test » | | Copier dans mes schémas | En fait une copie dans `mine/`, avec « moi » pour auteur et la version 0.1.0, en notant de quel schéma et de quelle version elle provient | | Exporter en zip | Crée `-.zip` : l’envoyer à quelqu’un, c’est le partager | | Ouvrir le dossier | Ouvre le dossier du schéma dans l’Explorateur de fichiers, pour modifier directement le code | | Faire confiance | Obligatoire avant la première exécution d’un schéma d’autrui (voir « Confiance et sécurité » plus bas) | | Bilan récent | Victoire ou défaite, durée et cause de fin de chaque partie de ce schéma | | Supprimer | Uniquement pour « Mes schémas » et « Installés » ; impossible si une instance l’utilise | La fiche de chaque instance comporte aussi une ligne « Schéma d’IA » : choisissez le schéma dans la liste → « Changer (effet immédiat) ». Quand l’instance ne tourne pas, le bouton s’appelle « Sélectionner », et le prochain « Lancer le test » démarrera l’IA avec ce schéma. ## Le manifeste scheme.json ```json { "format": 1, "id": "fast-rush", "name": "Rush en trois minutes", "version": "1.2.0", "author": "Untel", "description": "Une phrase qui résume le style de jeu de cette IA", "entry": "rush_bot.py", "class": "RushBot", "fair": true, "hz": 5, "races": ["human", "orc"], "license": "MIT" } ``` | Champ | Obligatoire | Description | |---|---|---| | `id` | ✔ | Lettres minuscules, chiffres, `-`, `_`, de 2 à 41 caractères | | `entry` | ✔ | Un fichier `.py` du dossier du schéma (pas de chemin absolu, pas de `..`) | | `kind` | | `bot` par défaut (sous-classe de `openwar3.Bot`, joue à votre place) ; `mod` = [mod de jeu](https://war3ai.com/fr/docs/mods/) (sous-classe de `openwar3.Mod`, jamais en mode équitable, issue jamais jugée selon les règles de mêlée) | | `class` | | Nom de la sous-classe de Bot (ou de Mod) dans le fichier d’entrée ; à défaut, la dernière sous-classe de `openwar3.Bot` du fichier d’entrée est utilisée | | `fair` | | `true` par défaut : ne voit que ce qui est dans le champ de vision, même règle que sur l’Arène. `false` = toute la carte visible, et c’est aussi la seule façon d’utiliser le [canal JASS](https://war3ai.com/fr/docs/jass/) (nécessaire aux compagnons) | | `judge` | | `true` par défaut : la partie est jugée selon les règles de mêlée. Les schémas RPG / compagnon mettent `false` | | `hz` | | Nombre d’appels à `on_tick` par seconde, 5 par défaut | | `format` | | Version du format du manifeste, actuellement 1 ; un format plus récent que votre OpenWar3 est refusé, avec une invitation à mettre à jour | | Autres | | `name`, `version`, `author`, `description`, `races`, `license`, `homepage`, `forked_from` ne servent qu’à l’affichage | Le dossier du schéma est ajouté au chemin de recherche des modules Python : le fichier d’entrée peut importer (`import`) les autres fichiers du même dossier. Les paquets tiers (numpy, torch…) ne sont pas installés automatiquement — indiquez clairement dans `description` ce qui est nécessaire. **Le plus petit schéma tient en deux fichiers** : ```python # my_bot.py from openwar3 import Bot class MyBot(Bot): def on_tick(self, g): for w in g.idle_workers(): mine = g.nearest(g.gold_mines(), w) if mine: g.gather(w, mine) ``` ```json {"id": "my-first", "name": "Ma première IA", "entry": "my_bot.py"} ``` Placez-les dans `schemes/mine/my-first/`, rafraîchissez Farsight et le schéma apparaît. Point de départ encore plus simple : choisissez un exemple dans « Intégrés » et cliquez sur « Copier dans mes schémas ». ## Exécution et bilan Les schémas sont exécutés par l’**exécuteur de schémas** (c’est lui que démarrent « Lancer le test / Changer » dans Farsight) : ```bash python tools/run_scheme.py --inst 20 --scheme builtin/micro --hours 6 ``` - Chaque instance a un processus superviseur permanent, qui **lance un sous-processus à chaque partie** pour exécuter le schéma : si le code du schéma plante, le superviseur n’est pas entraîné dans sa chute ; si vous modifiez le code d’un de « Mes schémas », la partie suivante utilise automatiquement la nouvelle version. - À la fin de chaque partie, une ligne de bilan est enregistrée : schéma, version, auteur, issue, cause, durée de jeu, nombre d’erreurs. Les taux de victoire affichés dans Farsight sont calculés à partir de là. Comment l’issue est déterminée : | Situation | Résultat enregistré | |---|---| | Tous les bâtiments adverses sont détruits | Victoire | | Tous nos bâtiments sont détruits (même s’il reste des troupes — c’est ainsi qu’une partie de mêlée juge la défaite) | Défaite | | Toutes nos unités sont détruites | Défaite | | Fin / arrêt manuel depuis Farsight | Indéterminé | | Lors du changement, la partie durait déjà depuis plus de 60 secondes de jeu (reprise en cours de route) | Compté à part, **exclu du taux de victoire** | | L’horloge du jeu reste longtemps bloquée | Indéterminé | Une fois l’issue connue, l’exécuteur ferme l’écran des scores, lance la partie suivante selon les « Réglages de la partie suivante », et le schéma reprend la main — vous pouvez le laisser tourner toute la nuit pour accumuler un bilan. Une pause n’est pas une fin : pendant la pause, le Bot continue de tourner normalement, seule l’horloge du jeu s’arrête. ## Confiance et sécurité **Un schéma, c’est du code, qui s’exécute avec exactement les mêmes droits que vous** (il peut lire et écrire des fichiers, accéder au réseau). Par conséquent : - les schémas de `installed/` ne sont **pas de confiance** par défaut : Farsight et l’exécuteur refusent de les lancer tant que vous n’avez pas cliqué sur « Faire confiance » ; - remplacer l’installation d’un schéma de même id **réinitialise la confiance** (nouvelle version = nouveau code) ; - l’import vérifie : zip de 50 MB au plus, 2000 fichiers au plus ; ni chemin absolu ni `..` (pour empêcher toute écriture hors du dossier du schéma) ; un manifeste invalide ou un fichier d’entrée absent entraîne un refus immédiat. > **Attention** > > Avant de faire confiance, cliquez sur « Ouvrir le dossier » et lisez le code. Ne prenez de schémas qu’auprès de personnes en qui vous avez confiance. ## API (pour les scripts) | Interface | Description | |---|---| | `GET /api/schemes` | Liste des schémas + bilans + schéma choisi et schéma en cours d’exécution pour chaque instance | | `GET /api/schemes/results?ref=` | Les 30 dernières parties d’un schéma | | `POST /api/schemes/import` | Importer un zip | | `GET /api/schemes/export?ref=` | Télécharger le zip | | `POST /api/schemes/fork` | Copier dans mes schémas | | `POST /api/schemes/trust` | Faire confiance | | `DELETE /api/schemes?ref=` | Supprimer (refusé si une instance l’utilise) | | `POST /api/instances/{n}/scheme` | Changer le schéma d’une instance : reprise immédiate de la partie en cours, ou effet au prochain lancement du test | En Python, utilisez directement la bibliothèque : `from openwar3 import schemes` (`list_schemes`, `install_zip`, `export_zip`, `fork`, `trust`, `stats`…). ## À venir : le site de schémas Le zip exporté est l’unité de partage ; le site n’a qu’à ajouter une couche autour : envoi en un clic depuis Farsight ; téléchargement depuis le site, avec exactement les mêmes vérifications que « Importer un schéma » et la même confirmation de confiance ; envoi facultatif des bilans, le site agrégeant les taux de victoire par version. Le bouton « Partager sur le site de schémas » a déjà sa place réservée dans Farsight. Avancement : voir la [feuille de route](https://war3ai.com/fr/roadmap/). --- # Bulles de dialogue et modèles locaux > Faites parler n’importe quelle unité du jeu, sous n’importe quelle identité, dans une bulle au-dessus de sa tête. Branchez un LLM local : une phrase entre, une réponse s’affiche au-dessus de l’unité. Les bulles sont une couche destinée au spectacle : elles n’influent pas sur l’issue de la partie, et se prêtent au streaming, au commentaire et au débogage. - n’importe quelle unité peut parler, sous n’importe quelle identité ; plusieurs unités peuvent parler en même temps ; - taille de police, couleur, largeur, pointe, transparence et vitesse de frappe se règlent bulle par bulle ; - branchement direct sur un LLM local (LM Studio), avec sortie en streaming : la bulle se met à jour au fil de la génération. ## Depuis un Bot Le plus simple est le `say` fourni avec le SDK : ```python g.say(hero, "Avec moi, chargez !", seconds=4) ``` ## Démarrage et interface **Le plus simple : le « Centre de contrôle », page d’accueil de Farsight** — cliquez d’abord sur « LLM local → Démarrer et charger le modèle » (serveur local LM Studio + chargement du modèle configuré en mémoire vidéo), puis sur « Bulles de dialogue → Démarrer ». Les cartes permettent aussi de consulter les journaux, d’arrêter et de redémarrer. L’interface se trouve dans la page « Bulles de dialogue », à gauche dans Farsight : faire parler les unités (choisir une unité, écrire le texte, régler le style, dialoguer avec le modèle), pause-café des paysans, dialogues à la caméra, déclenchement par la partie, réglages du modèle ; tout s’applique à l’instance choisie dans la barre du haut. Également en ligne de commande : ```bash python speech/speak_launch.py # lance le serveur de modèle local + charge et préchauffe le modèle + lance l'API des bulles python speech/speak_launch.py --restart # relance l'API après une modification du code python speech/speak_launch.py --stop # arrête l'API et décharge le modèle de la mémoire vidéo ``` Chaque étape est ignorée si elle est déjà faite : relancer la commande n’a aucun effet de bord. ## API HTTP Adresse par défaut : `http://127.0.0.1:8872/` (le port est défini par `ports.speech` dans `openwar3.json`) ; n’importe quel programme peut l’appeler. ### Faire parler une unité `POST /api/say` ```json { "inst": 16, "bubbles": [ { "unit": "0x14A12614", "name": "Roi de la montagne", "text": "Avec moi, chargez !" }, { "unit": "0x14A12924", "name": "Archimage", "text": "Je m'occupe du Blizzard.", "style": { "font_px": 26, "text_color": "#FFE080", "bg_color": "#C0102040" } }, { "screen": [960, 110], "key": 1, "name": "Narrateur", "text": "Première vague d'orcs dans 30 secondes.", "style": { "tail": false, "type_ms": 0 } }, { "world": [-4684, 2644], "key": 2, "text": "Point de ralliement", "style": { "font_px": 16 } } ] } ``` | Champ | Description | |---|---| | `unit` / `world` / `screen` | Un seul des trois : suivre une unité (collée juste au-dessus de sa barre de vie si elle en a une) / coordonnées sur la carte / pixels à l’écran (pour la narration) | | `name` | Nom de l’orateur affiché en première ligne ; libre, pas forcément celui de l’unité | | `text` | Texte principal, avec retour à la ligne automatique | | `duration_ms` | Durée d’affichage ; 0 = automatique, 3 à 5 secondes | | `key` | Identifiant d’une bulle monde / écran : un nouveau message avec la même key remplace l’ancien | | `update` | Si la même bulle existe déjà, ne remplace que le texte, sans réinitialiser la minuterie (pour le streaming) | | `style` | `font_px`, `max_width_px`, `text_color`, `bg_color`, `border_color`, `tail`, `side_px`, `opacity`, `type_ms`, `font`… | 32 bulles au maximum en même temps ; coût moyen d’environ 0.1 à 0.2 ms par frame. ### Dialoguer avec le modèle local `POST /api/chat` ```json { "inst": 16, "unit": "0x14A12614", "name": "Roi de la montagne", "persona": "Vous incarnez Muradin, le Roi de la montagne de Warcraft : jovial, amateur de bière. Une ou deux phrases parlées, pas plus de 40 caractères.", "message": "Il y a une bande d'ogres devant, on charge ou pas ?", "stream": true } ``` Renvoie `{"reply": "...", "first_token_ms": 283, "total_ms": 342}` ; au même moment, la réponse s’affiche déjà au-dessus de l’unité. Chaque unité garde en mémoire ses 6 derniers échanges. ### Autres | Interface | Description | |---|---| | `GET /api/instances` | Parties en cours | | `GET /api/units?inst=16&mine=true&heroes=true` | Liste des unités (avec nom chinois, coordonnées, points de vie) | | `POST /api/clear` | Efface une bulle ou toutes les bulles | | `GET /api/llm`, `POST /api/llm` | Consulter / modifier la configuration du modèle (`base_url`, `model`, `max_tokens`, `temperature`) | | `POST /api/banter` | Pause-café des paysans : les ouvriers de la base râlent à tour de rôle selon leur personnage, et annoncent le début de la partie (toutes les infos de jeu sont réelles) | | `POST /api/camtalk` | Dialogues à la caméra : les héros et leur escorte visibles à l’écran dialoguent selon leur rôle | | `POST /api/events` | Déclenché par la partie : début de combat, fin de combat, mort d’un héros, montée de tier, bâtiment détruit… on ne parle que lorsqu’il se passe quelque chose | ## Choisir un modèle local Mesuré sur une RTX 5090 (5 répliques de jeu) : | Modèle | VRAM | Vitesse | Une réponse | Verdict | |---|---|---|---|---| | **Qwen3.6-35B-A3B** (MoE, seulement 3B actifs à chaque pas), Q4, réflexion désactivée | 20.6 GB | Environ 142 token/s | **Environ 0.3 seconde** (premier token vers 0.27 seconde) | Recommandé : rapide, jeu de rôle naturel en chinois | | gpt-oss-20b (MXFP4), raisonnement low | 11.3 GB | Environ 280 token/s | 0.3 à 0.8 seconde | Quand la VRAM est juste ; chinois un peu plat | | Qwen3.6-27B (dense), Q4 | 17.2 GB | Environ 39 token/s | Toujours en réflexion après 5.5 secondes | Inadapté au dialogue en temps réel | - **La vitesse dépend des « paramètres actifs à chaque pas », pas du total** : le MoE de 35B n’en active que 3B et va 3 à 4 fois plus vite que le 27B dense. - **Désactivez impérativement la « réflexion »** : sinon tous les tokens partent dans la réflexion, et pas un mot de réponse ne sort. - La bulle s’écrit caractère par caractère, à environ 22 caractères par seconde : la vitesse de génération n’est plus le goulot d’étranglement. Ce qui compte vraiment pour l’expérience, c’est la **latence du premier token**. > **Des répliques qui sonnent « vrai »** > > Ne donnez au modèle que des données réelles sur la partie (nombre de parties, victoires et défaites, effectifs, stocks), et précisez explicitement « n’utilisez que ces faits ». En test, sans cette contrainte, le modèle invente des combats qui n’ont jamais eu lieu. --- # Passerelle > Passerelle WebSocket / JSON : les interfaces publiques que le SDK Python peut appeler le sont aussi depuis JS, C#, Go, Rust, une page web ou un programme sur une autre machine. Trois rôles, un client JS et une page de démonstration dans le navigateur fournis ; la latence est celle de la voie rapide, plus environ 1 ms. La passerelle enveloppe la voie rapide et l’état poussé en **WebSocket / JSON**. Les interfaces publiques du [catalogue de l’API](https://war3ai.com/fr/api/) que le SDK Python peut appeler sont aussi accessibles depuis JS, C#, Go, Rust, une page web, un programme sur une autre machine ou un LLM, avec les mêmes noms de méthodes et les mêmes paramètres. La latence est celle de la voie rapide, plus environ 1 ms. **Le plus simple : page d’accueil de Farsight, « Centre de contrôle » → Passerelle → Démarrer** (arrêter, redémarrer, consulter les journaux et ouvrir la page de démonstration se font aussi depuis cette carte). En ligne de commande : ```bash python gateway/server.py # ws://127.0.0.1:8870/ws (port défini par ports.gateway dans openwar3.json) python gateway/server.py --open # idem, puis ouvre la page de démonstration http://127.0.0.1:8870/demo dès que le port écoute python gateway/server.py --host 0.0.0.0 # pour le réseau local : jeton exigé automatiquement (bin/gateway/token.txt) python gateway/server.py --allow-origin http://localhost:5173 # pour que votre propre page web puisse aussi se connecter ``` ## Connexion et rôles Adresse de connexion : `ws://127.0.0.1:8870/ws?inst=9&role=dev` (`pid=` peut remplacer `inst=` ; si un jeton est exigé, ajoutez `&token=`). | Rôle | Peut appeler | Idéal pour | |---|---|---| | `dev` | Tout : observation, commandes, contrôle du jeu, bac à sable (JASS qui modifie le monde), dessin d’interface | Outils locaux, [mods de jeu](https://war3ai.com/fr/docs/mods/), compagnons | | `player` (avec `&player=N`) | Observation, commandement des unités du joueur N, dessin d’interface ; **mode équitable par défaut** : ne voit que ce qui est dans la vision du joueur N (`&fair=0` pour le désactiver) | Un Bot ou un LLM qui joue à la place d’un joueur donné | | `observer` | Lecture seule (le runtime rejette directement ses commandes) | Spectateurs, commentaire, collecte de données | `player` n’a pas accès : au contrôle du jeu (terminer la partie, changer la vitesse, mettre en pause), à `players` et `enemy_ai_plan`, qui dévoilent le jeu caché des autres, à `canvas.image`, qui ferait ouvrir un fichier local au processus du jeu, ni à JASS. Les requêtes qui prennent un numéro de joueur, comme `resources`, `tech` ou `stats`, ne portent que sur lui-même. Une connexion = une session, qui occupe une voie rapide (le runtime en a 16 au total). La passerelle accepte au plus 12 sessions simultanées, pour en laisser quelques-unes aux Bots, aux mods et à Farsight. À la déconnexion, seuls les éléments dessinés et les raccourcis clavier de cette session sont retirés ; ce que d’autres programmes ont dessiné reste en place. ## Messages Une fois connecté, vous recevez d’abord `hello` : version du protocole, rôle, identifiant du processus du jeu, liste des méthodes que ce rôle peut appeler. Ensuite, chaque requête porte un `id`, et la réponse porte le même `id` : ```json → {"id": 1, "op": "call", "method": "units", "args": ["me"]} ← {"id": 1, "ok": true, "result": [{"addr": 123456, "type": "hpea", "owner": 0, "x": -4480, "y": 2120, "hp": 220, "hp_max": 220}]} → {"id": 2, "op": "call", "method": "move", "args": [[{"unit": 123456}], 100, 200]} → {"id": 3, "op": "call", "method": "ui.button", "args": ["shop", "Acheter une potion"], "kwargs": {"screen": [40, 300]}} → {"id": 4, "op": "subscribe", "state": {"hz": 4, "units": "mine"}, "events": true} ← {"type": "state", ...} {"type": "events", ...} puis poussés en continu → {"id": 5, "op": "overview"} la partie en une page : ressources, effectifs par type, héros, ennemis visibles, production → {"id": 6, "op": "jass", "code": "call PingMinimapEx(0, 0, 3, 255, 0, 0, false)"} réservé à dev → {"id": 7, "op": "api"} catalogue des méthodes (il y a aussi ping / unsubscribe) ``` - **Paramètre d’unité** : `{"unit": adresse}`, l’adresse étant le `addr` du JSON de l’unité ; on peut y ajouter `"handle": [lo, hi]` pour vérifier que cette adresse n’a pas été réutilisée par une autre unité. - **Noms de méthode** : les méthodes publiques de Game, plus `ui.*` (button / choice / toast / hotkey / mouse / cursor…), `canvas.*` (text / panel / bar / image / circle / path / remove…) et `jass.` (réservé à dev). - Un client distant ne peut pas transmettre de fonction de rappel : les clics et les raccourcis clavier arrivent par les événements poussés, et l’événement `ui.click` porte `key`. Voir [Interface et entrées](https://war3ai.com/fr/docs/ui-input/). - Une erreur dans un appel ne concerne que cet appel (`ok: false` plus `error`) ; la connexion reste ouverte ; il en va de même si ce que vous envoyez n’est pas du JSON. - Les champs JSON des événements sont les mêmes que dans le [protocole W3P](https://war3ai.com/fr/docs/protocol/), avec en plus des champs pratiques (`spell`, `key`, `text`, `chat`, `button`, `player`, `mods`). HTTP fonctionne aussi, pratique pour des appels ponctuels et pour curl : `GET /api?role=player` liste le catalogue des méthodes, `POST /call` avec `inst`, `role`, `method`, `args`, `kwargs` effectue un appel. `/call` réutilise les sessions : si le jeu est relancé et change de processus, une nouvelle session est ouverte automatiquement ; celles inactives depuis 10 minutes sont fermées. ## Clients **JS** (navigateur ou Node 22+, sans dépendance) : `gateway/clients/js/openwar3.mjs` ```js import { OpenWar3, unit } from "./openwar3.mjs"; const ow = new OpenWar3("ws://127.0.0.1:8870/ws", { inst: 9, role: "dev" }); await ow.connect(); const mine = await ow.api.units("me"); await ow.api.move(mine.slice(0, 3).map(unit), 100, 200); await ow.api.ui.button("hi", "Cliquez-moi", { screen: [40, 300] }); // dernier objet simple = arguments nommés ow.on("event:ui.click", (e) => console.log("Clic sur", e.key)); await ow.subscribe({ state: { hz: 2, units: "mine" }, events: true }); ``` Node 20 / 21 nécessite `--experimental-websocket`. Exemple complet dans `gateway/clients/js/example.mjs`. **Page de démonstration dans le navigateur** `http://127.0.0.1:8870/demo` : la partie, la liste de nos unités, un bouton à poser dans le jeu, le flux d’événements — tout sur une seule page. **Autres langages** : n’importe quelle bibliothèque WebSocket + le JSON ci-dessus suffisent, sans toucher à la mémoire partagée. **LLM** : utilisez directement le [serveur MCP](https://war3ai.com/fr/docs/mcp/), qui fournit les opérations courantes sous forme d’outils prêts à l’emploi. ## Mesures 2026-09-25, vérification point par point sur une vraie partie, 16/16 (9 pour la passerelle + 7 pour MCP) : poignée de main (rôle dev, 121 méthodes), `units('me')`, la partie en une page, notification à l’écran, pose d’un bouton ; après abonnement, clic sur ce bouton dans le jeu → `ui.click` poussé au client ; JASS ; une unité invalide ne produit d’erreur que pour cet appel ; HTTP `/call` (rôle observer). Le client JS (Node) et la page de démonstration ont aussi été testés : le bouton posé depuis la page web a été cliqué dans le jeu, et le journal d’événements de la page a reçu `ui.click`. ## Sécurité - Par défaut, la passerelle n’écoute qu’en local sur `127.0.0.1`, sans jeton (comme Farsight). Si `--host` n’est pas une adresse locale, un jeton est exigé automatiquement ; `--auth` l’exige aussi en local. - **Les autres sites ouverts dans le navigateur ne peuvent pas se connecter** : toute connexion lancée par un navigateur porte son origine (`Origin`), et la passerelle n’accepte que sa propre page de démonstration et les adresses passées à `--allow-origin` ; les programmes comme Python, Node ou curl n’envoient pas d’origine et se connectent normalement. Quand elle n’écoute qu’en local, elle vérifie aussi `Host`, ce qui bloque les attaques consistant à faire résoudre un nom de domaine externe vers la machine locale. - Le rôle est déclaré par le client à la connexion : en mode local, c’est une convention, pas une frontière de sécurité. Sur l’Arène, c’est le processus arbitre qui décidera qui reçoit quel rôle : voir [Arène](https://war3ai.com/fr/arena/). --- # Protocole W3P > L'intégralité du contrat entre le runtime et les programmes externes : huit blocs de mémoire partagée, lecture de l'état du monde, lecture des événements, envoi de commandes, reçus, rôles des voies, canevas, interface et entrées. Lisez cette page si vous vous connectez depuis un autre langage que Python. Le runtime et les programmes externes échangent des données **uniquement via la mémoire partagée**. Tout ce qui suit constitue l'ensemble du contrat. - L'implémentation de référence est en Python : `sdk/python/w3world.py` (lecture) et `sdk/python/w3fast.py` (écriture) ; la taille et les offsets de chaque structure y sont définis et verrouillés par des tests ; - **Le protocole ne décrit que la sémantique et ne dépend pas de la version du jeu.** Quand la version du jeu change, le runtime s'adapte lui-même et le protocole reste identique ; les nouveaux champs sont toujours ajoutés en fin de bloc, les anciens clients continuent donc de fonctionner. > **Remarque** > > La plupart des gens n'ont pas besoin de cette page : utilisez simplement le SDK Python. Elle ne vous sert que si vous voulez vous connecter directement en C++ / C# / Rust / Go ou dans un autre langage, ou si vous voulez savoir ce qui se passe sous le SDK. ## 1. Huit blocs de mémoire partagée `` est l'identifiant du processus du jeu. | Nom | Sens | Contenu | Synchronisation | |---|---|---|---| | `Local\War3World_` | runtime → vous | État du monde : en-tête + 16 joueurs + jusqu'à 1024 unités + 256 fiches de détail d'unité + 256 objets au sol + zone d'extension + table de production | seqlock | | `Local\War3Trees_` | runtime → vous | Jusqu'à 4096 destructibles (arbres, etc.), rafraîchis toutes les 2 secondes | seqlock | | `Local\War3Events_` | runtime → vous | Anneau d'événements, 8192 entrées | chaque entrée porte son propre numéro de séquence | | `Local\War3Map_` | runtime → vous | Carte : cases de terrain (128 par case, jusqu'à 256×256) + limites de la zone jouable + points de départ ; calculée par lots pendant les premières secondes de la partie | seqlock (ne change plus une fois calculée) | | `Local\War3Fast_` | bidirectionnel | Voies de commande : 16 voies × 16 emplacements ; chaque emplacement contient une commande + son reçu ; chaque voie a un rôle | un seul écrivain et un seul lecteur par emplacement | | `Local\War3Canvas_` | vous → runtime | [Canevas](https://war3ai.com/fr/docs/canvas/) : en-tête de 64 octets + 256 éléments × 112 octets + réserve de 64 Ko pour le texte / les points ; créé seulement après un premier envoi de `canvas_enable` | seqlock (vous écrivez, le runtime lit à chaque frame) | | `Local\War3Msgs_` | runtime → vous | Anneau des messages à l'écran : texte intégral des indications du jeu, du chat et des messages système, 128 entrées × 256 octets | chaque entrée porte son propre numéro de séquence | | `Local\War3Input_` | bidirectionnel | [Interface et entrées](https://war3ai.com/fr/docs/ui-input/) : le runtime y écrit la position de la souris, le point du sol sous le curseur et l'élément survolé ; vous y écrivez la table des raccourcis clavier et les interrupteurs de la souris ; le runtime ne prend en charge les entrées qu'après un premier envoi de `input_enable` | seqlock pour la table des raccourcis | **Plusieurs clients qui utilisent le canevas et les entrées en même temps** : ces deux blocs n'existent qu'en un seul exemplaire, et si chacun écrit de son côté, ils s'écrasent mutuellement. Voici les conventions, à respecter aussi si vous écrivez votre propre client : - **Canevas** : lecture - modification - écriture en tenant le mutex nommé `Local\War3CanvasMutex_` ; ne remplacez que vos propres éléments et laissez ceux des autres tels quels (en recalculant les offsets dans la réserve) ; supprimez ceux dont le processus propriétaire s'est terminé et ceux qui n'ont pas de propriétaire. Dans un élément, `reserved[1]` = pid du processus propriétaire, `reserved[2]` = numéro de séquence dans ce processus ; les numéros d'éléments sont attribués par le compteur situé à l'offset 60 de l'en-tête du bloc (à partir de `0x10000`). - **Entrées** : chaque client inscrit ses raccourcis et ses interrupteurs de souris dans `Local\War3InputClients_` (en-tête de 16 octets + 16 clients × 528 octets) ; en tenant `Local\War3InputMutex_`, il met à jour sa propre entrée, puis fusionne les clients encore vivants et écrit le résultat dans le bloc d'entrées : les raccourcis sont dédoublonnés par « code de touche + touches de modification », les interrupteurs de souris sont réunis (union). Les événements sont envoyés à tous les clients, et chacun reconnaît ses raccourcis par « code de touche + touches de modification ». Tant que la table d'inscription contient d'autres clients vivants, n'envoyez pas `input_enable 0`. - **Runtime** : les éléments cliquables dont le processus propriétaire s'est terminé n'interceptent plus les clics ; toutes les 2 secondes, le runtime consulte la table d'inscription et, quand tous les clients inscrits se sont terminés, remet à zéro la table des raccourcis et les interrupteurs de souris du bloc d'entrées. ## 2. Lire l'état du monde (seqlock) ```text loop: s1 = block.seq (offset 8, int32) if s1 est impair : réessayer (le runtime est en train d'écrire) copier en-tête + players + units[unitCount] + details[detailCount] + items[itemCount] if block.seq != s1: réessayer ``` - **En-tête** : compteur de publications (s'il n'augmente plus, la publication est interrompue), horloge de jeu du moteur, epoch incrémenté de 1 à chaque partie, numéro du joueur local, partie en cours ou non, vitesse de jeu, période de publication, microsecondes passées à collecter cette copie sur le thread du jeu, numéro de séquence des événements, durées par étape. Les clients peuvent écrire `requestedPeriodMs` pour demander une période de publication (16 ~ 1000 ms). - **Unité** (112 octets) : paire de handles (**identifiez les unités par leur paire de handles**, les adresses sont réutilisées), code à quatre caractères du type, propriétaire, drapeaux, coordonnées, PV / mana (avec leurs maximums), ordre en cours + cible de l'ordre, cible de tâche (ce qu'elle attaque réellement), niveau / expérience / points de compétence du héros, index de détail, `visibleTo` (bit p = le joueur p la voit en ce moment). - **Détails** (288 octets ; héros > unités des joueurs > creeps, 256 au maximum) : 12 capacités (code / niveau / drapeaux / secondes de recharge restantes), 8 codes de buff, 6 emplacements d'inventaire. - **Extensions en fin de bloc** (ajout uniquement, les offsets précédents ne bougent jamais, les anciens clients continuent de fonctionner) : zone d'extension `EXT1` (heure du jour dans le jeu, vitesse du cycle jour/nuit, nombre d'entrées de la table de production) et table de production `prods[128]` (bâtiments en train d'entraîner / rechercher / construire / améliorer, file d'attente, durée totale, temps écoulé, bloqué ou non). **Ne les utilisez que si le magic correspond.** ## 3. Lire les événements ```text head = ring.writeSeq (offset 8) for seq in (cursor, head]: e = ring.events[(seq - 1) % 8192] if e.seq > seq: une entrée perdue (écrasée parce que la lecture est trop lente) elif e.seq != seq: pas encore entièrement écrite, relire la prochaine fois else: traiter e ``` La structure d'un événement fait 64 octets : `seq kind clockMs addr handleLo handleHi typeId owner a b x y value extra`. - Obtenus en comparant deux publications consécutives (précision = période de publication) : `unit.appeared / unit.died / unit.removed / unit.damaged / order.changed / hero.levelup / owner.changed / item.appeared / item.removed / game.started` ; - Niveau moteur (le runtime les enregistre sur le thread du jeu au moment où ils se produisent, il y en a donc un pour **chaque coup**) : `damage` (source, type de dégâts, type d'attaque, position, PV réellement perdus, dégâts avant armure), `killed` (tueur) ; - Obtenus en suivant la table de production : `production.done` (code à quatre caractères de ce qui est terminé, catégorie, secondes de jeu nécessaires ; émis aussi pour les adversaires) ; - Vérifiés par le runtime à chaque publication : `spell.cast` (début de la recharge du sort : `a` code à quatre caractères du sort, `b` niveau, `value` recharge en secondes, `x/y` point d'incantation), `player.left` (`a` numéro du joueur, `b` nouvel état de l'emplacement), `selection.changed` (sélection du joueur local ; la liste complète est dans la zone d'extension du bloc monde), `game.ended` (on quitte la partie) ; - Messages à l'écran : `message` (`a` = numéro du message, dont le texte intégral se lit dans la mémoire partagée `Local\War3Msgs_` : 128 entrées × 256 octets, indications du jeu, chat et messages système compris ; `b` = numéro du cadre de messages) ; - Interface et entrées (une fois `input_enable` activé) : `ui.click` (`a` id de l'élément du canevas, `b` 1 clic gauche / 2 clic droit), `ui.hover`, `hotkey` (`a` id du raccourci, `b` code de touche virtuelle), `mouse.world` (`x/y` coordonnées au sol, `value` = 1 si le clic a été absorbé) ; les touches de modification sont toutes dans `extra`. ## 4. Envoyer des commandes 1. **Un objet client occupe une voie** : prenez `Local\War3FastMutex_`, trouvez une voie libre (ou dont le processus propriétaire est mort), puis écrivez le rôle, le numéro de joueur et votre propre pid. Si un même processus a besoin de deux rôles, ouvrez deux voies ; 2. Remplissez les emplacements : drapeau de commande sémantique, opcode, `args[11]`, échéance `deadlineMs` ; 3. Une fois tous les emplacements écrits, marquez-les comme soumis et incrémentez de 1 le `submitSeq` de la voie ; 4. Attendez l'événement `Local\War3FastDone__` (ou interrogez en boucle), lisez les reçus, puis rendez les emplacements. Le runtime exécute les commandes par lots dans la distribution d'événements du thread du jeu : chaque vidage dispose d'un budget de **4 ms** (minuterie haute résolution réelle) ; ce qui dépasse est reporté à la distribution suivante. **Un emplacement dont l'échéance est passée n'est jamais exécuté** : vous ne verrez donc jamais « d'anciennes commandes réexécutées après la reprise d'une pause ». Index de `args` : `0..2` unité (adresse, handle lo, handle hi), `3` identifiant d'ordre ou code à quatre caractères, `4..6` cible, `7/8` x / y (bits du float), `9` extra (numéro de joueur / numéro d'emplacement / interrupteur / mise en file), `10` mode (0 sans cible / 1 sur un point / 2 sur une cible). ### Opcodes | Opcode | Nom | Description | |---|---|---| | 1 | `point` | Ordre sur un point pour une unité (déplacement / attaque-déplacement / patrouille / attaque au sol / sort ciblé sur un point). extra bit0 = mise en file (insérée après l'ordre en cours) | | 2 | `target` | Ordre sur une cible pour une unité (attaque au clic droit / récolte / réparation / sort ciblé sur une unité / ramassage d'objet) ; la cible doit être visible | | 3 | `immediate` | Commande sans cible (arrêt / tenir la position / entraînement / recherche / amélioration / sort sans cible) | | 4 | `build` | Un ouvrier construit un bâtiment (coordonnées alignées sur 32) | | 5 | `learn` | Un héros apprend une compétence | | 6 | `use_item` | Utilise l'emplacement d'inventaire extra | | 7 | `revive` | Ressuscite un héros à l'autel | | 8 | `rally` | Point de ralliement (sur un point / sur une cible) | | 9 | `buy` | Une boutique vend un objet à un héros à proximité | | 10 | `item_drop` | L'objet quitte l'inventaire : donné à un allié, vendu à une boutique (`code` = unité qui le reçoit), ou lâché au sol | | 20 ~ 25 | Requêtes | `q_tech` décompte des technologies, `q_feasible` faisabilité, `q_visible` visibilité, `q_mine_gold` or restant dans une mine, `q_captain` capitaine de l'ordinateur, `q_dead_heroes` liste des héros morts | | 30 | `pause` | Pause / reprise | | 40 ~ 50 | Caméra | Lire l'état de la caméra, définir un champ, regarder un point, suivre, réinitialiser, pivoter, limites, lissage, affichage de l'interface, image épurée, brouillard | | 60 ~ 63 | HUD | Texte du bouton de quêtes, titre et description du panneau de quêtes, rafraîchissement, lire si le panneau est ouvert | | 70 | `jass` | Appelle une native JASS par son nom (1291 natives) : le nom et les paramètres chaîne vont dans la zone annexe de l'emplacement, les autres paramètres dans `args` selon la signature ; la valeur de retour est dans `value[0]`. Réservé aux voies des outils locaux ; tout appel avec un paramètre de type fonction ou susceptible de suspendre le thread de script est refusé. Voir [Canal JASS](https://war3ai.com/fr/docs/jass/) | | 71 / 72 | `jass_handle_of` / `jass_unit_of` | Conversion unité de l'instantané ↔ handle JASS (la paire de handles de l'instantané n'est pas un handle JASS) | | 73 | `canvas_enable` | Crée la mémoire partagée du canevas et installe le hook de dessin ; n'importe quelle voie peut l'envoyer (le canevas ne dessine que sur l'écran local). Le premier appel doit installer le hook : prévoyez un délai d'au moins 2 secondes | | 74 | `input_enable` | `extra` = 1 prend en charge les entrées de la fenêtre du jeu (clic / survol des éléments du canevas, raccourcis clavier, clics au sol), 0 = les rend au jeu. Bloc d'entrées `Local\War3Input_` : en-tête de 128 octets + 32 raccourcis × 16 octets ; vous écrivez la table des raccourcis et les interrupteurs de la souris, le runtime y écrit la position de la souris, le point du sol sous le curseur et l'élément survolé. N'importe quelle voie peut l'envoyer (seules les entrées locales sont concernées). Voir [Interface et entrées](https://war3ai.com/fr/docs/ui-input/) | ## 5. Reçus Un reçu fait 52 octets (+8 octets de chronométrage) : `status`, `engineReturn`, `verdict` (code de motif du rejet), `orderBefore / orderAfter` (l'ordre de l'unité relu dans la même frame), `value[8]` (résultats des requêtes), `execUs` (microsecondes d'exécution de cette commande sur le thread du jeu), `engineUs` (la part passée dans la fonction d'ordre du moteur elle-même). Tous les codes d'état et codes de motif sont listés dans [Reçus et codes de motif](https://war3ai.com/fr/docs/reason-codes/). ## 6. Rôles des voies | Rôle | Ce qu'il peut faire | |---|---| | `dev` | Outils locaux : commandes sémantiques (sur les unités du joueur local) + canal JASS | | `player` | Uniquement des commandes sémantiques, et uniquement sur les unités du joueur auquel appartient la voie (celles des autres = `not_owner`) | | `observer` | Uniquement les requêtes, la caméra, la lecture de l'état des panneaux du HUD, l'activation du canevas et des entrées locales ; tout le reste = `forbidden` | Deux IA qui s'affrontent = deux voies `player` dans la même partie (player 0 / player 1). > **Attention** > > En mode local, le rôle est déclaré par le client lui-même (c'est une convention, pas une frontière de sécurité). Sur l'[Arène](https://war3ai.com/fr/arena/), c'est le processus arbitre qui crée les voies et ne remet à chaque participant que la voie `player`. ## 7. Sémantique vérifiée en parties réelles - Clic droit (smart) sur un ennemi = attaquer **celui-ci** (la cible de l'ordre et la cible de tâche sont toutes deux cette unité) ; un ordre d'attaque brut envoyé comme commande sur cible ne fait que passer à l'ordre d'attaque sans mémoriser la cible, et l'unité part attaquer autre chose à proximité ; - Le moteur refuse les commandes sur cible visant des unités que vous ne voyez pas : à la tombée de la nuit, les camps éloignés passent dans le brouillard de guerre et tous les clics droits sont rejetés (1001) ; - Une construction « acceptée » signifie seulement que l'ouvrier a pris l'ordre : un emplacement en pleine forêt est lui aussi accepté sur le moment, et l'ouvrier n'échoue qu'une fois sur place ; un emplacement manifestement occupé est rejeté immédiatement ; - Un héros ne peut être ressuscité qu'environ 3 secondes de jeu après sa mort ; la résurrection est aussi rejetée si la nourriture manque (les héros consomment de la nourriture) ; - Les objets dans un inventaire ne comptent pas comme objets au sol ; en ramasser un émet `item.removed` ; - Pendant une pause, l'horloge du moteur s'arrête, mais les commandes peuvent toujours être envoyées ; - Une partie lancée en fenêtre réduite a sa simulation arrêtée (l'horloge n'avance pas). --- # Accusés de réception et codes de raison > Chaque accusé de réception de commande porte un code d’état et un code de raison. C’est sur eux que les Bots et les agents s’appuient pour se corriger : le « pourquoi ça n’a pas marché » devient un nombre lisible par une machine. ```python r = g.train(barracks, "hfoo") bool(r) # False r.status # 1 -> rejected r.verdict # 3 -> nourriture insuffisante r.reason # 'rejected(人口不够)' (= nourriture insuffisante) r.exec_us # durée d'exécution de cette commande sur le thread du jeu, en microsecondes ``` `if r:` équivaut à `r.status == 0` (le moteur a accepté). ## Code d’état `status` | Code | Nom | Signification | Causes fréquentes | |---|---|---|---| | 0 | `accepted` | Accepté par le moteur | — (mais accepté ≠ réussi, voir plus bas) | | 1 | `rejected` | Refusé par le moteur | Voir `verdict` | | 2 | `bad_unit` | L’unité n’existe pas ou le handle ne correspond pas | Unité déjà morte ; objet unité périmé | | 3 | `not_owner` | Ce n’est pas votre unité | Ordres donnés aux unités d’un autre joueur en tant que `player` | | 4 | `fault` | Exception à l’exécution (interceptée par le runtime, sans conséquence pour le jeu) | Signalez-la avec les étapes pour la reproduire | | 5 | `bad_args` | Paramètres invalides | Coordonnées, numéro de case ou code à quatre caractères erroné | | 6 | `unsupported` | Non pris en charge | Cette version du runtime n’a pas cette capacité | | 7 | `bad_target` | Cible invalide | La cible n’existe plus ; mauvais type de cible | | 8 | `forbidden` | Interdit par le rôle de la voie | Ordre donné en tant qu’`observer` | | 97 | `cancelled` | Exception levée dans le bloc de lot : aucune commande du lot n’a été envoyée | Erreur dans le code du bloc `with g.batch():` | | 98 | `held` | Unité tenue par une couche plus prioritaire : commande non envoyée | La couche réflexe du cerveau de référence ou un ordre manuel de la console tient cette unité | | 99 | `timeout` | Délai dépassé | Échéance dépassée pendant une pause ou un ralentissement du jeu (une commande expirée n’est jamais exécutée) | ## Code de raison `verdict` En cas de refus, le runtime en donne la raison à l’aide des propres contrôles de faisabilité du moteur. Vous pouvez aussi poser la question avant de donner l’ordre : `g.can_do(unité, code)` renvoie les mêmes codes. | Code | Signification | Que faire | |---|---|---| | 0 / 220 | Possible | — | | 3 | Nourriture insuffisante | Construire un bâtiment de nourriture ; repérer le blocage à l’avance avec `g.production(b).blocked` | | 8 | Or insuffisant | Attendre l’argent ; vérifier avec `g.can_afford(code)` avant de donner l’ordre | | 9 | Bois insuffisant | Envoyer plus d’ouvriers au bois | | 32 | File d’entraînement pleine (7 places) | Une seule unité en file : n’en ajouter une que lorsque `g.queue(b)` est vide | | 183 | Technologie / bâtiment prérequis manquant | Construire d’abord le bâtiment prérequis, monter de tier | | 185 | Bâtiment occupé | L’autel est en train de ressusciter un héros ; impossible d’améliorer le bâtiment principal tant que sa file n’est pas vide | | 221 | Élément inexistant / en construction / en amélioration / déjà présent | Le héros existe déjà (s’il est mort, utilisez `revive`) ; cette boutique ne vend pas cet objet | | 89 | Boutique pas encore approvisionnée | En début de partie, les objets arrivent selon le délai de mise en vente de la table des objets ; pour une boutique qu’on vient de construire, le compte démarre à la fin de la construction | | 1001 | Cible invisible | La cible est dans le brouillard de guerre ou le masque noir ; faites un `attack_move` vers sa position | ## Accepté ≠ réussi L’accusé de réception indique seulement que « le moteur a accepté la commande » ; il est relu dans la même frame. Il ne couvre pas ce qui peut se produire ensuite : | Commande | Peut encore échouer après acceptation | Comment vérifier | |---|---|---| | Construire | Un point en pleine forêt est lui aussi accepté sur le moment ; l’échec ne survient qu’à l’arrivée de l’ouvrier | Utiliser `build_near` (qui vérifie que les fondations apparaissent), ou attendre `production.done` | | Lancer un sort | Sort interrompu, mana insuffisant | Au tick suivant, vérifier que `g.cooldown(u, sort)` est bien en recharge | | Entraîner | Placé en file, mais faute de nourriture ne démarre jamais | `g.production(b).blocked` | | Déplacer / attaquer | Remplacé par une autre logique (ou par une couche plus prioritaire) | `g.current_target(u)`, `g.order_of(u)` | ## Interfaces de requête Ces interfaces ne donnent aucun ordre, elles ne font qu’interroger le moteur ; le résultat est placé dans le champ `value` de l’accusé de réception (le SDK renvoie directement la valeur) : | Interface | Renvoie | |---|---| | `g.can_do(u, code)` / `g.can_do_many([(u, code), ...])` | Le code de raison du tableau ci-dessus | | `g.tech(code, player=None)` / `g.tech_many([...])` | Niveau de recherche / nombre de bâtiments terminés (chaîne d’amélioration comprise) | | `g.visible(x, y)` | Ce point est-il visible pour votre camp | | `g.gold_left(mine)` | Quantité d’or restant dans la mine | | `g.enemy_ai_plan(unité_ennemie)` | Où le capitaine de l’IA de l’ordinateur compte emmener ses troupes (uniquement pour l’IA de l’ordinateur) | --- # D’où viennent les données > L’origine et la précision de chaque type de données. Quand quelque chose « semble faux », commencez par cette page. | Données | Source | Précision | |---|---|---| | Unités, ressources, ordres, compétences, buffs, inventaire | Bloc monde poussé par le runtime toutes les 50 ms | Période de publication (réglable jusqu’à 16 ms) | | Événements de dégâts et d’élimination | Enregistrés par le runtime sur le thread du jeu, à chaque coup | Immédiate | | Autres événements (apparition, mort, changement d’ordre, montée de niveau…) | Comparaison de deux publications consécutives | Période de publication | | Table de production (entraînement / recherche / construction / amélioration) | Champs de minuterie des compétences de production du moteur + temps écoulé cumulé par le runtime | Environ ±0.2 seconde de jeu | | Caractéristiques de combat, table des contres | Tables de données du jeu (extraites du jeu installé sur votre machine) | Sans les modificateurs des objets, auras et buffs | | Recherche de chemin | Praticabilité du terrain selon le moteur (cases de 128) + arbres + emprise des bâtiments, A* côté SDK | Une case ; un passage plus étroit qu’une case est considéré comme bloqué | | Heure de jeu | JASS `GetFloatGameState(GAME_STATE_TIME_OF_DAY)` | Période de publication | | Visibilité | Masque de visibilité calculé par le runtime pour chaque unité et chaque joueur | Période de publication | | Compteurs de technologie, faisabilité, or restant dans les mines | Requête par la voie rapide, directement auprès du moteur | Immédiate | ## Les données de jeu ne sont pas distribuées avec le code Les tables d’unités, de compétences, d’objets, de héros et de buffs, la table des contres de dégâts, etc. proviennent des fichiers du jeu de Blizzard et **ne figurent pas dans le dépôt**. Une fois le dossier du jeu réglé dans le « Centre de contrôle » de Farsight, elles sont extraites automatiquement de votre propre jeu ; vous pouvez aussi lancer l’extraction à la main : ```bash python data/tools/extract_game_data.py ``` Le résultat est placé dans `data/game/` (hors git) : les `.slk` / `.txt` bruts, ainsi que les fichiers mis en forme `units.json`, `names.json`, `skills.json`, `items.json`, `heroes.json` et `buffs.json`. ## Quelques chiffres concrets | Grandeur | Valeur | |---|---| | Une journée | 480 secondes de jeu (240 secondes de jour, 240 de nuit) ; une heure = 20 secondes de jeu ; la partie commence à 8 h du matin | | Jour | De 6:00 à 18:00 | | Coefficient d’armure | 0.06 (tiré des tables de données du jeu) | | Capacité du bloc monde | 16 joueurs, 1024 unités, 256 fiches de détails d’unité, 256 objets au sol, 128 productions | | Arbres | 4096 destructibles au maximum, rafraîchis toutes les 2 secondes | | Anneau d’événements | 8192 entrées ; si la lecture est trop lente, des événements sont perdus (le SDK le détecte) | | Cases de la carte | 128 unités de jeu par case, 256 × 256 au maximum | ## Exemples calibrés par des mesures - Durées de production : paysan 14.9, ferme 34.9, Épées en fer forgé 59.9 secondes de jeu, identiques aux valeurs poussées par le runtime (écart inférieur à 0.2 seconde) ; - Caractéristiques de combat comparées au panneau du jeu : Paladin 650 PV, 255 mana, 3.9 d’armure, attaque 24 à 34 ; fantassin avec une amélioration d’attaque : 13 à 15 ; - Les dégâts avant armure des événements de dégâts du moteur (14 / 15 / 15) tombent dans l’intervalle calculé par `stats()` ; - Recherche de chemin : Echo Isles fait 116 × 88 cases, distance au sol jusqu’au bâtiment principal adverse 10642 (9856 à vol d’oiseau), construction de la grille en 18 ms, environ 1 ms par A*. --- # Questions fréquentes > Est-ce un logiciel de triche ? Quelles versions sont prises en charge ? Que peut voir et faire l’IA ? Peut-on s’en servir sans savoir programmer ?… ## Est-ce un logiciel de triche ? Non. C’est une interface de développement destinée à la recherche et au divertissement autour de l’IA, à utiliser uniquement avec **un client que vous possédez légalement**, en local, en réseau local ou dans des parties que vous hébergez, contre l’ordinateur ou contre d’autres IA. Elle **ne doit pas être utilisée sur Battle.net ni sur aucun serveur doté d’un anti-triche**, et n’offre aucune fonctionnalité visant les parties contre des humains. Voir [Limites d’utilisation](https://war3ai.com/fr/docs/legal/). ## Quelles versions du jeu sont prises en charge ? Seul **Warcraft III 1.27** (The Frozen Throne) est pris en charge pour l’instant. Les versions 1.24 à 1.28 partagent la même structure de moteur ; la prise en charge multiversion (table de symboles par version, repli par recherche de signatures, autotest au démarrage produisant la liste des capacités) est prévue à la phase P4 de la [feuille de route](https://war3ai.com/fr/roadmap/). Les versions 1.29 et suivantes, comme Reforged, reposent sur un autre moteur et demanderaient une adaptation à part : aucun engagement pour l’instant. ## Mes fichiers de jeu sont-ils modifiés ? Non. Le runtime est injecté pendant l’exécution du jeu et **ne modifie ni le Game.dll sur le disque** ni aucun autre fichier du jeu. Pour lancer plusieurs instances, le lanceur d’origine `War3.exe` est simplement copié tel quel sous un autre nom. Les données de jeu (tables d’unités, etc.) sont extraites de votre propre jeu et ne sont pas distribuées avec le code. ## Que peut voir l’IA ? En gros, tout ce qu’un joueur pro voudrait savoir, mis à jour toutes les 50 ms : - l’or, le bois et la nourriture de tous les joueurs ; pour toutes les unités : position, points de vie et mana, ordre en cours, **cible attaquée**, niveau et expérience ; - niveaux et temps de recharge restants des compétences des héros et des unités, buffs actifs, inventaire ; - ce que chaque bâtiment entraîne / recherche / construit / améliore, l’avancement, et s’il est bloqué faute de nourriture ; - objets au sol, arbres, grilles de déplacement et de construction de la carte, points de départ, heure de jeu (jour/nuit) ; - le flux d’événements : apparition et mort des unités, **chaque coup porté** (auteur, type d’attaque, dégâts avant armure), éliminations, fins de production, montées de niveau des héros… - et des questions directes au moteur : telle action est-elle possible maintenant, et sinon pourquoi ; à quel niveau en est telle technologie ; tel point est-il visible ; combien d’or reste-t-il dans une mine ; où l’IA de l’ordinateur compte-t-elle envoyer ses troupes. En plus de cela, le SDK calcule les caractéristiques de combat (contres, armure, améliorations d’attaque et d’armure), le « temps nécessaire pour tuer » et la recherche de chemin au sol. Toutes les interfaces sont dans le [catalogue de l’API](https://war3ai.com/fr/api/). ## Que peut faire l’IA ? Presque toutes les actions d’un joueur : déplacer, attaque-déplacement, attaquer une cible, arrêter, tenir la position, patrouiller, attaquer le sol, récolter, réparer, construire (avec recherche automatique d’emplacement), entraîner / rechercher / améliorer, annuler, apprendre une compétence, lancer un sort (sur une unité / sur un point / sans cible), point de ralliement, ressusciter un héros, ramasser / utiliser / lâcher / donner / vendre un objet, acheter, Appel aux armes ; file d’attente Shift, marche par points de passage, un ouvrier qui enchaîne plusieurs constructions ; sans oublier la vitesse de jeu, la pause et les bulles de dialogue. Chaque commande renvoie un accusé de réception. Au-delà des actions du joueur, elle peut aussi dessiner ses propres panneaux et annotations sur l’écran de jeu ([canevas](https://war3ai.com/fr/docs/canvas/)) et, en partie solo, appeler les 1291 fonctions JASS dont disposent les créateurs de cartes ([canal JASS](https://war3ai.com/fr/docs/jass/)). ## Peut-on l’utiliser dans des cartes RPG / personnalisées ? Oui. Choisissez une carte RPG et attribuez à l’instance le schéma « Exemple de compagnon » ; une fois la partie lancée, vous jouez vous-même, accompagné d’un compagnon IA qui combat avec vous, vous soigne et vous fait la conversation : voir [Compagnon RPG](https://war3ai.com/fr/docs/companion/). `g.map_data` lit les noms des unités personnalisées de la carte ; le [canal JASS](https://war3ai.com/fr/docs/jass/) permet de créer des unités, de définir des alliances, d’afficher des panneaux… À vous de décider comment jouer. Les opérations qui modifient le monde ne sont disponibles qu’en partie solo (en multijoueur, elles provoqueraient une désynchronisation) ; le canevas, lui, reste sûr même en multijoueur. ## Peut-on s’en servir sans savoir programmer ? Oui. Installez l’environnement en suivant le [Démarrage rapide](https://war3ai.com/fr/docs/quickstart/), puis lisez [Écrire un Bot avec un LLM](https://war3ai.com/fr/docs/ai-bot/) : vous décrivez la stratégie avec vos mots, le LLM écrit le code ; en cas de problème à l’exécution, transmettez-lui le message d’erreur ou ce que vous voyez en jeu, et demandez-lui de corriger. ## Uniquement en Python ? Le SDK est en Python. Entre le runtime et les programmes externes, il n’y a qu’un protocole en mémoire partagée ([W3P](https://war3ai.com/fr/docs/protocol/)) : tout langage capable de lire et d’écrire la mémoire partagée de Windows peut s’y connecter. Plus simple encore, la [passerelle](https://war3ai.com/fr/docs/gateway/) (WebSocket / JSON) : JS, C#, Go, Rust, une page web ou un programme sur une autre machine peuvent tous appeler les mêmes interfaces ; un agent LLM peut se brancher directement sur [MCP](https://war3ai.com/fr/docs/mcp/). ## Quel LLM choisir ? Tous les modèles courants capables d’écrire du code conviennent. L’essentiel n’est pas le modèle, mais **les bons documents à lui fournir** (le manuel + `api.json` + un exemple), en exigeant qu’il n’utilise que les méthodes présentes dans le catalogue de l’API. Les décisions en temps réel pendant la partie (conseiller, doublage) sont sensibles à la latence ; les modèles MoE locaux s’en sortent très bien. Voir [Un LLM comme conseiller](https://war3ai.com/fr/docs/llm-coach/) et [Bulles de dialogue et modèles locaux](https://war3ai.com/fr/docs/speech/). ## Le jeu est-il ralenti ? Une capture de l’état du monde prend en médiane 0.5 à 0.9 ms sur le thread du jeu (100 à 120 unités), toutes les 50 ms. Chaque commande coûte quelques microsecondes sur le thread du jeu ; chaque vidage de la file dispose d’un budget de 4 ms, et ce qui ne tient pas dans ce budget est reporté au vidage suivant, sans jamais bloquer le jeu. Tous les appels au jeu sont protégés contre les exceptions : si un Bot plante, seul son camp s’arrête, sans entraîner le jeu dans sa chute. ## Peut-on lancer plusieurs parties en même temps ? Oui. `runtime/farm.py` orchestre les instances multiples, chacune avec son numéro ; vous les démarrez et les arrêtez depuis la [console Farsight](https://war3ai.com/fr/docs/console/). Votre Bot se connecte à une instance donnée avec `--inst N`. ## Peut-on faire s’affronter deux IA ? Ouvrez deux canaux `player` dans la même partie (`--player 0` / `--player 1`) : c’est de l’IA contre IA. En mode local, l’équité repose sur une convention ; les vrais matchs, avec arbitre, filtrage selon la vision et contrôle de propriété, se jouent sur l’[Arène](https://war3ai.com/fr/arena/) (phase P6). ## Mac et Linux sont-ils pris en charge ? Seuls Windows 10 / 11 sont pris en charge pour l’instant. ## Sous quelle licence ? La licence sera publiée avec la version officielle. Les composants tiers conservent leur propre licence (par exemple MinHook, sous BSD-2) ; AMAI est sous licence personnalisée : ses données dérivées ne sont pas distribuées avec le projet, elles sont récupérées depuis le dépôt public d’AMAI et générées à l’installation. ## Où signaler un problème ? Un canal de signalement ouvrira avec la version officielle. Joignez à votre signalement le numéro d’instance, la sortie de `python -m openwar3 status` et les étapes pour reproduire le problème. Vérifiez d’abord si [Débogage et performances](https://war3ai.com/fr/docs/debugging/) ne résout pas déjà la question. --- # Limites d’utilisation > Ce qui est permis, ce qui ne l’est pas, les statistiques de visite de ce site, et les mentions relatives aux marques et aux licences tierces. En utilisant ce projet, vous acceptez de respecter ces limites. ## Autorisé - Utiliser le projet sur un client Warcraft III 1.27 **que vous possédez légalement** ; - Faire jouer l’IA contre l’ordinateur ou contre d’autres IA, en local, hors ligne, en réseau local ou dans des parties que vous hébergez ; - La recherche, l’enseignement, le divertissement et la diffusion en direct de vos propres parties d’IA ; - Développer des projets dérivés à partir du SDK, du cerveau de référence, des exemples et des outils, dans le respect de leurs licences. ## Interdit - **Toute utilisation sur Battle.net, ou sur tout serveur ou plateforme doté d’un anti-triche**, ainsi que toute utilisation en même temps qu’une session anti-triche active ; - Toute utilisation visant à obtenir un avantage indu dans des parties contre des humains ; - La distribution des fichiers du jeu de Blizzard ou des données qui en sont extraites (ce projet ne les distribue pas non plus : chaque utilisateur extrait les données de son propre jeu) ; - Tout manquement à la licence d’utilisation du runtime. ## Nos engagements techniques - Ne modifier ni le `Game.dll` sur le disque, ni aucun fichier du jeu : toutes les modifications ont lieu pendant l’exécution ; - Pour le multi-instances, le lanceur d’origine `War3.exe` est simplement copié tel quel sous un autre nom ; - Le projet ne contient aucun code ni aucun fichier de jeu de Blizzard. ## Votre responsabilité Les lois sur la rétro-ingénierie et la modification de jeux varient d’un pays à l’autre. **Il vous appartient de vérifier que l’utilisation de ce projet est légale là où vous vous trouvez, et vous en assumez seul les conséquences.** Ce projet est fourni « en l’état », sans aucune garantie, expresse ou implicite. ## Statistiques de visite de ce site Ce site (war3ai.com) utilise Microsoft Clarity pour mesurer sa fréquentation : pages consultées, provenance des visiteurs, durée de la visite, clics et défilement, ainsi que des enregistrements de navigation et des cartes de chaleur anonymes. Nous nous en servons uniquement pour améliorer la documentation et les pages. - Aucune inscription n’est nécessaire, et aucune information d’identité comme un nom ou une adresse e-mail n’est collectée ; le texte saisi dans les champs est masqué par défaut et n’est pas enregistré ; - Clarity stocke des cookies dans le navigateur pour distinguer les visites successives d’un même visiteur ; les données sont traitées par Microsoft, voir la [déclaration de confidentialité de Microsoft](https://privacy.microsoft.com/privacystatement) ; - Pour ne pas être comptabilisé : ouvrez une fois n’importe quelle adresse de ce site en y ajoutant `?stats=off`, et ce navigateur ne sera plus comptabilisé par la suite (`?stats=on` pour rétablir) ; vous pouvez aussi bloquer `clarity.ms` avec la protection contre le pistage de votre navigateur, le site fonctionne normalement. Farsight, le SDK et le runtime installés sur votre machine n’embarquent aucune statistique de ce type. Farsight ne se connecte à war3ai.com que dans deux cas : au démarrage puis toutes les 6 heures, pour lire la liste des versions et voir s’il en existe une nouvelle ; et quand vous cliquez pour soumettre dans la page « Retours et suggestions », pour envoyer le retour que vous avez écrit ainsi qu’un identifiant de la machine (obtenu par hachage salé de l’identifiant du système, dont on ne peut pas retrouver la valeur d’origine ; il sert à empêcher les envois abusifs). Les informations de diagnostic ne sont jointes que si vous cochez la case, et vous pouvez les prévisualiser avant l’envoi. Quand ces deux types de requêtes arrivent sur war3ai.com, le serveur enregistre l’adresse IP, le pays ou la région déterminée par Cloudflare, et la version du client (User-Agent), afin de prévenir les abus et de compter combien d’instances de Farsight sont utilisées. Les enregistrements des vérifications de version sont automatiquement supprimés au bout de 90 jours ; les retours sont conservés avec ces informations jusqu’à ce que le mainteneur les traite et les supprime. Ces données ne sont visibles que par le mainteneur du projet, depuis l’interface d’administration, et ne sont fournies à personne d’autre. ## Marques Warcraft® (魔兽争霸® en chinois) est une marque commerciale ou une marque déposée de Blizzard Entertainment, Inc. War3AI / OpenWar3 est un projet communautaire indépendant, sans lien avec Blizzard Entertainment, et n’est ni approuvé ni parrainé par celle-ci. Les autres noms de produits cités (Claude, GPT, Gemini, Qwen, etc.) appartiennent à leurs propriétaires respectifs et ne sont mentionnés que pour indiquer la compatibilité. ## Composants et données tiers | Composant / données | Licence | Traitement | |---|---|---| | MinHook | BSD-2-Clause | Utilisé avec le runtime, avec sa mention de licence conservée | | AMAI | Licence personnalisée | Non distribué avec le projet ; lors du déploiement, `start.bat` le récupère depuis le dépôt public d’AMAI, puis un outil génère les données utilisées par le cerveau de référence | | Données de jeu (unités, compétences, objets, etc.) | Blizzard | Non distribuées avec le projet ; chaque utilisateur les extrait de son propre jeu | | Données factuelles extraites de replays de matchs publics (emplacements des bâtiments, ordres d’ouverture) | — | Données purement factuelles, utilisées par le cerveau de référence | --- # Catalogue de l’API (api.json) Statut : verified = chemin bas niveau vérifié en jeu ; experimental = nouvelle interface, déjà fonctionnelle, en cours de vérification en jeu point par point ; inferred = déduit / pas entièrement testé. Latence :Instantané poussé(Lit la mémoire partagée, sans attendre le thread du jeu (~0.05 ms)); Voie rapide(~1 frame : exécution par lots sur le thread du jeu); Canal de contrôle(20~40 ms (ancien chemin des opérations d’interface)); Écriture directe(Sans passer par la file du thread du jeu : écrit la mémoire partagée (canevas) ou envoie un message à la fenêtre du jeu); Calcul local(Calcul pur ou lecture de fichier, sans toucher au jeu) ## Observation Lire l’état sans modifier le jeu. La plupart lisent directement l’instantané poussé, sans attente. - `snapshot(max_age: 'float' = 0.05)` [verified] [Instantané poussé] État complet de toute la carte (WorldState) : .units .players .items .clock .me ; des appels répétés dans un délai de max_age secondes renvoient la même copie. ⚠ Les ouvriers entrés dans une mine d'or ne figurent pas dans la liste ; par défaut toute la carte est visible (en modèle lockstep, tout est présent en local), seul Game(fair=True) filtre selon le champ de vision. (Mécanisme: Bloc du monde W3P Local\War3World_ (poussé par le runtime toutes les 50 ms, seqlock)) - `last_seen(owner: 'str | int' = 'enemy', max_age: 'float | None' = None) -> 'list'` [verified] [Instantané poussé] Unités ennemies (ou creeps avec 'creep', ou unités d'un numéro de joueur) vues pour la dernière fois : [(état de l'unité à ce moment-là, horloge de jeu à ce moment-là, secondes écoulées depuis)], les plus récentes en premier. Une unité vue en train de mourir est retirée de la liste. En mode équitable comme en mode normal, l'enregistrement se fait selon « ce que votre camp voit en ce moment » — c'est la carte que le joueur a en tête : les forces repérées, la dernière position connue du héros adverse, le moment où l'adversaire a pris son expansion. max_age ne garde que les unités vues dans ce nombre de secondes de jeu. (Mécanisme: visibleTo de l'instantané poussé (à chaque rafraîchissement de l'instantané, les unités ennemies/creeps visibles sont enregistrées)) - `map()` [verified] [Instantané poussé] Table du terrain de la partie, MapInfo : .walkable(x,y) .buildable(x,y) .at(x,y) .bounds (zone jouable) .starts (points de départ) .cells (bit0 non praticable, bit1 non constructible). Le calcul prend quelques secondes après le début de la partie ; tant qu'il n'est pas terminé, renvoie None. Les arbres n'y figurent pas (utilisez trees()). (Mécanisme: Bloc de carte W3P Local\War3Map_ (calculé par lots par le runtime après le début de la partie, IsTerrainPathable déplacement/construction)) - `me() -> 'int | None'` [verified] [Instantané poussé] Votre numéro de joueur (0~11). (Mécanisme: En-tête du bloc du monde) - `resources(player: 'int | None' = None) -> 'dict | None'` [verified] [Instantané poussé] {'gold','lumber','food_used','food_cap','gold_gathered','lumber_gathered'} ; player vaut par défaut votre joueur, et n'importe quel joueur peut être lu. Renvoie None si la lecture échoue : ne le confondez pas avec 0. (Mécanisme: players[16] du bloc du monde) - `players() -> 'list'` [verified] [Instantané poussé] Les 16 emplacements de joueur : Player(id, gold, lumber, food_used, food_cap, gold_gathered, lumber_gathered, race, known). (Mécanisme: players[16] du bloc du monde) - `units(owner: 'str | int' = 'all', types=None, alive: 'bool' = True) -> 'list'` [verified] [Instantané poussé] Filtre les unités par propriétaire / type. owner : 'me' / 'enemy' / 'creep' / 'all' / numéro de joueur. types : ensemble de codes à quatre caractères. (Mécanisme: units[] du bloc du monde) - `unit(handle) -> 'object | None'` [verified] [Instantané poussé] Trouve une unité par sa paire de handles (lo, hi) (la cible d'ordre, la cible de tâche et les événements fournissent tous des paires de handles). (Mécanisme: by_handle du bloc du monde) - `is_building(u) -> 'bool'` [verified] [Instantané poussé] Indique s'il s'agit d'un bâtiment (tours comprises). Déterminé par une vitesse de déplacement nulle dans la table des unités ; l'emprise au sol du hall mort-vivant vaut 0, ne vous fiez donc pas à l'emprise. (Mécanisme: Instantané + units.json (spd==0 = bâtiment)) - `my_workers() -> 'list'` [verified] [Instantané poussé] Vos ouvriers (paysans / péons / acolytes / feux follets). (Mécanisme: Instantané poussé) - `idle_workers() -> 'list'` [verified] [Instantané poussé] Ouvriers sans travail : ni ordre ni tâche (ceux à qui vous venez de donner du travail pendant ce tick ne comptent pas). ⚠ Redonner un ordre de récolte à un ouvrier qui a une tâche interrompt son cycle de récolte (revenu nul). (Mécanisme: Instantané poussé (emplacement d'ordre + emplacement de tâche)) - `my_heroes() -> 'list'` [verified] [Instantané poussé] Vos héros vivants (les héros morts sont dans la liste de résurrection de l'autel, voir revive). (Mécanisme: Instantané poussé) - `my_army() -> 'list'` [verified] [Instantané poussé] Vos unités de combat : ni ouvriers ni bâtiments. (Mécanisme: Instantané poussé + units.json) - `my_buildings(types=None) -> 'list'` [verified] [Instantané poussé] Vos bâtiments (tours et fondations en construction comprises) ; types permet de n'en garder que certains, par exemple {'hbar'}. (Mécanisme: Instantané poussé) - `is_constructing(worker) -> 'bool'` [verified] [Instantané poussé] Indique si cet ouvrier est en train de construire (ou se rend sur un chantier / aide à réparer ; y compris s'il vient d'y être envoyé pendant ce tick). Ignorez-le quand vous choisissez un constructeur, sinon le chantier précédent s'arrête. (Mécanisme: Instantané poussé (ordre = code à quatre caractères d'un bâtiment, ou ordre de construction / de réparation)) - `under_construction(building) -> 'bool'` [verified] [Instantané poussé] Ce bâtiment n'est pas terminé (PV incomplets). ⚠ Un bâtiment endommagé n'a pas non plus tous ses PV — suffisant en début de partie, mais une fois les combats engagés, tenez aussi compte du temps. (Mécanisme: Instantané poussé (les PV des fondations montent d'une valeur très basse jusqu'au maximum)) - `gold_mines() -> 'list'` [verified] [Instantané poussé] Les mines d'or de la carte. ⚠ La mine d'or enchevêtrée des Elfes de la nuit et la mine neutre ont chacune une unité aux mêmes coordonnées ; envoyez la récolte vers la vôtre. (Mécanisme: Instantané poussé (ngol/egol/ugol)) - `enemies(fighters_only: 'bool' = False) -> 'list'` [verified] [Instantané poussé] Unités des joueurs ennemis (creeps exclus). fighters_only : exclut les ouvriers et les bâtiments. (Mécanisme: Instantané poussé) - `creeps() -> 'list'` [verified] [Instantané poussé] Creeps (neutres hostiles). ⚠ La nuit, la vision diminue ; une fois les camps éloignés passés dans le brouillard, les commandes sur cible qui les visent sont rejetées (code de motif 1001). (Mécanisme: Instantané poussé (owner 12 = neutre hostile)) - `life_mana(u) -> 'dict | None'` [verified] [Instantané poussé] {'hp','hp_max','mana','mana_max'} (flottants, valeurs brutes du moteur). Pour u, une unité issue de l'instantané suffit (elle est remplacée par la copie la plus récente). (Mécanisme: hp/hpMax/mana/manaMax des unités du bloc du monde) - `hero_info(hero) -> 'dict | None'` [verified] [Instantané poussé] {'level','xp','skill_points'}. (Mécanisme: level/xp/skillPoints des unités du bloc du monde) - `abilities(u) -> 'list'` [verified] [Instantané poussé] [{code, level, cooldown, flags}] ; les buffs sont dans buffs(u). Disponible uniquement pour les unités « avec détails » (héros > unités des joueurs > creeps, 256 au maximum). (Mécanisme: Détails du bloc du monde : capacités (code/niveau/drapeaux/recharge restante)) - `buffs(u) -> 'list'` [verified] [Instantané poussé] Codes des buffs portés par l'unité (par exemple 'BHds' Bouclier divin, 'Bslo' Ralentissement). L'effet de chaque code est décrit dans data/game/buffs.json. (Mécanisme: Détails du bloc du monde : objets de capacité dont le code commence par B) - `cooldown(u, ability: 'str') -> 'float | None'` [verified] [Instantané poussé] Secondes de recharge restantes pour cette capacité (secondes de jeu) ; 0 = utilisable ; renvoie None si l'unité n'a pas cette capacité (ou n'a pas de détails). (Mécanisme: Détails du bloc du monde : recharge restante des capacités (minuteur de capacité)) - `inventory(hero) -> 'list | None'` [verified] [Instantané poussé] Codes à quatre caractères des 6 emplacements d'objets (None pour un emplacement vide) ; renvoie None si l'unité n'a pas d'inventaire. (Mécanisme: Détails du bloc du monde : 6 emplacements d'inventaire) - `current_order(u) -> 'dict | None'` [verified] [Instantané poussé] {'order','target','x','y'} : l'ordre que l'unité est en train d'exécuter (order vaut 0x000D00xx ou le code à quatre caractères d'un bâtiment, 0 = inactive). target est une paire de handles ; convertissez-la en unité avec g.unit(target). (Mécanisme: order / cible d'ordre / point cible d'ordre des unités du bloc du monde) - `current_target(u)` [verified] [Instantané poussé] L'unité que cette unité **attaque ou poursuit réellement** (None s'il n'y en a pas). ⚠ Après un ordre d'attaque, l'emplacement d'ordre se vide rapidement et l'attaque est portée par la tâche — pour savoir « qui elle attaque », utilisez cette méthode, pas current_order. (Mécanisme: Cible de tâche des unités du bloc du monde) - `clock() -> 'float | None'` [verified] [Instantané poussé] Horloge de jeu du moteur (en secondes de jeu, 0 pendant le chargement). À vitesse accélérée, elle avance plus vite que le temps réel. (Mécanisme: clockMs de l'en-tête du bloc du monde (horloge de jeu du moteur)) - `production(building)` [verified] [Instantané poussé] Ce que produit ce bâtiment : Production(kind, queue, duration, elapsed, blocked, progress, remaining…), ou None s'il ne produit rien. kind 'queue' (entraînement/recherche/héros ; queue compte 7 emplacements au maximum, [0] est en cours) / 'construction' (en construction) / 'upgrade' (amélioration du hall/d'une tour) ; blocked = en file mais pas démarré (le plus souvent nourriture insuffisante — il est temps de construire une ferme) ; progress 0..1. Les bâtiments adverses sont aussi consultables (en mode équitable, uniquement ceux qui sont visibles). (Mécanisme: Table de production du bloc du monde (objets de capacité Aque/ABnP/AUnP + temps écoulé suivi par le runtime ; écart mesuré < 0.2 seconde de jeu)) - `queue(building) -> 'list'` [verified] [Instantané poussé] Codes à quatre caractères de la file d'entraînement/recherche ([0] en cours) ; [] si le bâtiment est inactif ou n'est pas un bâtiment de production. (Mécanisme: Table de production du bloc du monde) - `all_production(owner: 'str | int' = 'me') -> 'list'` [verified] [Instantané poussé] Toutes les productions en cours [(bâtiment, Production)]. owner comme pour units() : 'me' / 'enemy' / numéro de joueur / 'all'. Usage pro : voir quelles unités l'adversaire entraîne, quelles technologies il recherche, quand il passe de tier (quand vous repérez ses bâtiments). (Mécanisme: Table de production du bloc du monde) - `path_distance(a, b) -> 'float | None'` [verified] [Instantané poussé] Distance à parcourir par une unité terrestre de a à b (a et b : unités ou (x,y)) ; None si inaccessible. Sur les cartes à îles, utilisez-la pour savoir « si ce camp de creeps / cette expansion est accessible par voie terrestre » ; elle est plus fiable que la distance à vol d'oiseau (elle contourne forêts, falaises et bâtiments). Précision d'une case de 128 ; un passage plus étroit qu'une case est considéré comme bloqué. (Mécanisme: Bloc de carte (IsTerrainPathable du moteur) + bloc des arbres + emprise des bâtiments, A* côté SDK (cases de 128)) - `reachable(a, b) -> 'bool | None'` [verified] [Instantané poussé] Accessible ou non par voie terrestre (None si le bloc de carte n'est pas encore calculé). (Mécanisme: Idem) - `walk_path(a, b) -> 'list | None'` [verified] [Instantané poussé] Points d'inflexion du chemin [(x,y)...] (le dernier point est b) ; avec path(units, liste de points), faites suivre ce chemin à vos troupes (contourner les tours, emprunter des chemins détournés). (Mécanisme: Idem) - `upkeep(player: 'int | None' = None) -> 'dict | None'` [inferred] [Instantané poussé] Palier d'entretien : {'level': 'none'/'low'/'high', 'income': 1.0/0.7/0.4, 'next_at': nourriture du palier suivant (aucun = None)}. Règle bien connue des pros : restez à 50 de nourriture pendant le passage au tier 3 et les améliorations d'attaque/armure, et ne montez à 80 qu'avant la bataille décisive. (Mécanisme: Règle fixe de la 1.27 : de 0 à 50 de nourriture, pas d'entretien ; de 51 à 80, revenu ×0.7 ; de 81 à 100, ×0.4) - `xp_to_next(hero) -> 'int | None'` [verified] [Instantané poussé] Expérience qu'il manque au héros pour atteindre le niveau suivant (niveau 10 = 0). (Mécanisme: level/xp du bloc du monde + formule NeedHeroXP de MiscGame) - `creep_camps(link: 'float' = 600.0) -> 'list'` [verified] [Instantané poussé] Regroupe les creeps (visibles) en camps : [{'x','y','units','level','hp','max_level'}], du plus proche au plus éloigné de votre base principale. level = niveau total du camp (la mesure courante de la difficulté du creeping), hp = PV totaux. À combiner avec time_to_kill / path_distance pour choisir un camp. (Mécanisme: Instantané poussé (creeps regroupés dans un rayon de 600) + niveaux de units.json) - `buff_info(code: 'str') -> 'dict | None'` [verified] [Calcul local] Ce qu'est un code de buff : {'ability','effect','dur','hero_dur','targets'} (ex. 'Bslo' -> Ralentissement). Si un code a plusieurs lignes, la première est renvoyée. (Mécanisme: data/game/buffs.json (BuffID de AbilityData.slk -> capacité/effet/durée)) - `stats(u, player: 'int | None' = None)` [verified] [Instantané poussé] Caractéristiques de combat de l'unité, combat.UnitStats : PV/mana max, armure (améliorations d'attaque/armure et agilité du héros comprises), type d'armure, vitesse de déplacement, vision de jour/de nuit, armes (ce qu'elles peuvent toucher, portée, intervalle d'attaque, plage de dégâts, type d'attaque, dégâts de zone). u : une unité (la technologie de son propriétaire et le niveau du héros sont appliqués automatiquement) ou un code à quatre caractères (player vaut par défaut votre joueur). À combiner avec .dps_vs(adversaire) / .hits_to_kill(adversaire) / combat.time_to_kill(groupe, adversaire). ⚠ Objets, auras et buffs non pris en compte. (Mécanisme: Tables de données (UnitBalance/UnitWeapons/UpgradeData/MiscGame) + niveaux de technologie en temps réel + niveau du héros) - `time_to_kill(attackers, target) -> 'float | None'` [verified] [Instantané poussé] Secondes de jeu nécessaires à ce groupe d'unités pour tuer target ensemble (selon les PV actuels de target ; contres, armure et améliorations d'attaque/armure pris en compte ; déplacements, dégâts de zone et soins ignorés). Usage pro : en tir concentré, frappez d'abord l'unité « qui meurt le plus vite » (time_to_kill minimal), pas la plus proche. Impossible à atteindre = None. (Mécanisme: stats() + PV en temps réel) - `time_of_day() -> 'float | None'` [verified] [Instantané poussé] Heure du jour dans le jeu (en heures, 0~24). La partie commence à 8 h du matin ; une journée complète = 480 secondes de jeu (240 s de jour et 240 s de nuit, mises à l'échelle par la vitesse du cycle jour/nuit). Renvoie None si la lecture échoue (ancien runtime / hors partie). (Mécanisme: Zone d'extension du bloc du monde : GetFloatGameState(GAME_STATE_TIME_OF_DAY)) - `is_night() -> 'bool | None'` [verified] [Instantané poussé] Indique s'il fait nuit (18:00~6:00). Usage pro : la nuit, les creeps dorment (en frappant le premier, vous n'êtes pas encerclé), la vision de toutes les unités diminue (bon moment pour une attaque surprise), et les sentinelles/unités des Elfes de la nuit deviennent invisibles près des arbres. Renvoie None si la lecture échoue. (Mécanisme: Zone d'extension du bloc du monde (jour de 6 h à 18 h)) - `seconds_until(hour: 'float') -> 'float | None'` [verified] [Instantané poussé] Secondes de jeu restantes avant qu'il soit hour heures dans le jeu (par exemple seconds_until(18) = temps restant avant la tombée de la nuit, pour planifier un creeping nocturne). (Mécanisme: Zone d'extension du bloc du monde + journée de 480 secondes (mesuré : 20 secondes de jeu par heure)) - `items_on_ground() -> 'list'` [verified] [Instantané poussé] Objets au sol [Item(addr, handle_lo, handle_hi, type, x, y, life)]. Ramasser ou utiliser un objet émet l'événement item.removed. (Mécanisme: items[] du bloc du monde (uniquement les objets au sol : handle du porteur entièrement à FF)) - `trees(x: 'float | None' = None, y: 'float | None' = None, limit: 'int' = 60) -> 'list'` [verified] [Instantané poussé] Arbres vivants (ceux dont le targType dans DestructableData contient tree) ; si (x,y) est fourni, triés du plus proche au plus éloigné, limit arbres au maximum. Chaque arbre est un Tree(addr, handle_lo, handle_hi, type, x, y, life), que vous pouvez passer directement à gather pour couper du bois. (Mécanisme: Bloc des arbres Local\War3Trees_ (rafraîchi toutes les 2 secondes)) - `events() -> 'list'` [verified] [Instantané poussé] Ce qui s'est passé depuis le dernier appel : unit.appeared / unit.died / unit.removed / unit.damaged / order.changed / hero.levelup / owner.changed / item.appeared / item.removed / game.started (obtenus en comparant les publications, précision = période de publication de 50 ms), ainsi que les événements de niveau moteur damage / killed (le runtime les enregistre sur le thread du jeu au moment où ils se produisent : il y en a un pour **chaque coup**) : damage : handle = l'unité touchée, .source_addr = celle qui frappe (convertissez-la en unité avec snapshot().unit_by_addr), .value = PV réellement perdus, .raw_damage = dégâts avant armure, .attack_type (normal/pierce/siege/magic/chaos/hero/spell), .damage_type killed : ce coup a tué l'unité, .source_addr = le tueur ainsi que production.done, obtenu par le suivi de la table de production par le runtime (précision = période de publication) : unité = le bâtiment, .done_code = code à quatre caractères de ce qui est terminé, .done_kind = 'training' (unités/héros/résurrection) / 'research' / 'construction' (bâtiment terminé) / 'upgrade' (passage de tier / amélioration de tour), .value = secondes de jeu nécessaires Ajouts du 09-25 : spell.cast : unité = le lanceur, .spell code à quatre caractères du sort, b niveau, value recharge en secondes, x,y point d'incantation (détecté quand la recharge du sort commence, précision = période de publication) player.left : .player numéro du joueur parti / retiré après sa défaite ; game.ended : on quitte la partie selection.changed : la sélection du joueur local a changé (g.selection() pour obtenir les unités) message : une ligne d'un cadre de messages à l'écran (indication du jeu, chat, système) : .text texte intégral, .frame numéro du cadre de messages, .chat = {'channel', 'sender', 'text'} (quand c'est du chat ; c'est là qu'on lit ce que le joueur tape dans la boîte de chat) ui.click / ui.hover / hotkey / mouse.world : interface et entrées (g.ui), .key est la key du canevas / la syntaxe du raccourci Chaque entrée est un Event(seq, kind, clock, addr, handle, type, owner, a, b, x, y, value, extra). En mode équitable (fair=True), seuls sont fournis : les événements de vos propres unités, ceux des unités visibles en ce moment (ou encore visibles dans la dernière seconde), les dégâts subis ou infligés par votre camp, ainsi que les événements locaux d'interface / de messages / de partie. (Mécanisme: Anneau d'événements Local\War3Events_ (comparaison des publications + événements de dégâts capturés par le runtime)) - `selection() -> 'list'` [verified] [Instantané poussé] Unités actuellement sélectionnées par le joueur local (l'unité principale en premier ; 12 au maximum). Tout changement de sélection émet l'événement selection.changed. (Mécanisme: Zone d'extension du bloc du monde W3P, selAddrs (à chaque publication, le runtime y joint la sélection du joueur local)) - `messages() -> 'list'` [verified] [Instantané poussé] Messages apparus dans les cadres de messages à l'écran depuis le dernier appel : [{'text', 'frame', 'repeat', 'seq', 'game_ms'}]. Les indications du jeu (« Il vous faut plus de fermes », « Impossible de construire ici »), le chat et les messages système s'y trouvent tous ; frame indique de quel cadre de messages il s'agit. Ce sont les mêmes messages que les événements message du flux d'événements (chacun avec son propre curseur). (Mécanisme: Mémoire partagée Local\War3Msgs_ (messages à l'écran capturés par le runtime)) - `tech(code: 'str', player: 'int | None' = None) -> 'int | None'` [verified] [Voie rapide] Niveau de recherche / nombre de bâtiments terminés (les chaînes d'amélioration comptent : un château compte aussi comme htow). player vaut par défaut votre joueur ; n'importe quel joueur peut être interrogé. (Mécanisme: Requête W3P q_tech (décompte des technologies du joueur par le moteur)) - `can_do(u, code: 'str') -> 'int | None'` [verified] [Voie rapide] Verdict de faisabilité du moteur : 0/220 possible ; 3 nourriture, 8 or manquant, 9 bois manquant, 32 file pleine, 183 prérequis manquant, 185 autel en cours de résurrection, 221 élément absent/en construction. ⚠ Vaut toujours 221 pour un ouvrier qui construit un bâtiment : inutilisable pour valider un emplacement (utilisez build_near). (Mécanisme: Requête W3P q_feasible (vérification de faisabilité du moteur)) - `can_do_many(pairs) -> 'list'` [verified] [Voie rapide] Pose de nombreuses questions can_do d'un coup : pairs = [(unité, code à quatre caractères), ...], renvoie la liste des codes de verdict dans le même ordre (None pour une question sans réponse). Pour planifier ce qu'un tick doit construire/entraîner, interrogez d'abord tout en bloc : c'est N fois plus rapide que des can_do un par un (cerveau de référence, 09-23 : planification des constructions 76 -> 25 ms). (Mécanisme: Requête W3P q_feasible × N, soumise en un lot) - `tech_many(codes, player: 'int | None' = None) -> 'dict'` [verified] [Voie rapide] Interroge d'un coup de nombreux décomptes de technologies/bâtiments : {code à quatre caractères: nombre ou None}. (Mécanisme: Requête W3P q_tech × N, soumise en un lot) - `visible(x: 'float', y: 'float') -> 'bool | None'` [verified] [Voie rapide] Indique si ce point est visible par votre camp en ce moment (ni dans le brouillard ni dans le masque noir). Un bot en mode équitable ne devrait utiliser que les ennemis visibles. (Mécanisme: Requête W3P q_visible (visible / brouillard / masque noir)) - `gold_left(mine) -> 'int | None'` [inferred] [Voie rapide] Or restant dans la mine d'or. (Mécanisme: Requête W3P q_mine_gold (or restant dans la mine selon le moteur)) - `enemy_ai_plan(enemy_unit) -> 'dict | None'` [verified] [Voie rapide] Le capitaine de l'IA de l'ordinateur : où il emmène ses troupes (vous savez avant même son départ quelle partie de votre base il va attaquer). Fonctionne uniquement contre un adversaire ordinateur ; renvoie None si l'unité ne suit pas de capitaine. (Mécanisme: Requête W3P q_captain (le capitaine de l'ordinateur que suivent les unités ennemies)) - `order_of(u) -> 'int | None'` [verified] [Instantané poussé] L'ordre actuel de l'unité, **y compris celui que vous venez de donner pendant ce tick** (tant que l'instantané n'a pas rattrapé, le nouvel ordre du reçu est utilisé). ⚠ Constaté en partie réelle le 09-23 : hello_bot venait d'envoyer un paysan construire une ferme ; au même tick, rush_bot le voyait « inactif » dans l'instantané et l'envoyait construire une caserne, si bien que la ferme était abandonnée à mi-chemin, encore et encore. Pour choisir des unités « inactives / qui ne construisent pas », utilisez cette méthode plutôt que u.order. (Mécanisme: Ordre de l'instantané + commandes de ce processus qui viennent d'être acceptées (reçus)) - `can_afford(code: 'str') -> 'bool'` [verified] [Instantané poussé] Indique si l'or et le bois actuels suffisent pour acheter code (unité, bâtiment ; selon les prix de units.json). Tout ce qui est absent de la table des prix est considéré comme abordable. ⚠ Pour les codes de passage de tier, la table contient le prix cumulé : le résultat est donc prudent ; c'est le reçu du moteur qui fait foi. (Mécanisme: Ressources de votre camp dans l'instantané poussé + prix de units.json) - `map_data()` [verified] [Calcul local] Données de la carte en cours (openwar3.mapdata.MapData) : name_of('HC07') pour le nom d'une unité / d'un objet / d'une capacité personnalisés, hero_names, tooltip. Dans les cartes RPG, la plupart des unités sont créées par la carte elle-même et absentes de la table de noms intégrée ; si la partie n'a pas été lancée par le lanceur (fichier de carte introuvable), renvoie None. (Mécanisme: Fichier de carte (chemin --map du lanceur) : w3u/w3t/w3a + wts ; pour une carte protégée, lit les TXT contenus dans la carte) ## Commandes Faire agir les unités. Appliquées en ~1 frame, chacune avec accusé de réception. - `batch() -> 'Batch'` [verified] [Voie rapide] Regroupe les commandes d'un tick en un lot : with g.batch() as b: g.attack(archers, target) # renvoie Pending, qui ne devient un reçu qu'à la fin du bloc g.move(wounded, *home) g.cast(hero, "thunderclap") print(b.sent, b.wait_ms, [r.reason for r in b.receipts]) Envoyée seule, chaque commande attend un passage du thread du jeu (environ 10 ms) ; un lot n'attend qu'une fois — c'est ainsi que le cerveau de référence (09-23) est passé de 48 -> 26 ms par cycle. * L'arbitrage s'applique toujours commande par commande (une unité réservée reçoit immédiatement un reçu held et n'entre pas dans le lot) ; * Dans le bloc, les commandes renvoient Pending : lire son .ok avant la fin du bloc lève une erreur (le reçu n'existe pas encore) ; après la fin du bloc, il s'utilise comme un Receipt ; * Une exception levée dans le bloc = tout le lot est annulé (status 97 cancelled), et les unités réservées sont libérées ; * Les requêtes (can_do / tech / visible …), build_near et buy n'entrent pas dans le lot et restent posées sur-le-champ — leur résultat est nécessaire immédiatement ; pour poser de nombreuses questions d'un coup, utilisez can_do_many / tech_many ; * Un with g.batch() imbriqué est fusionné dans le lot le plus externe ; au-delà de 16 commandes, le runtime découpe automatiquement en plusieurs segments (une attente par segment). (Mécanisme: Les commandes du bloc sont accumulées en un lot, soumis en une fois à la fin du bloc (exécuté dans la même frame, une seule attente du thread du jeu)) - `move(units, x: 'float', y: 'float', queue: 'str | None' = None)` [verified] [Voie rapide] Se rend en (x,y) sans attaquer en chemin (à utiliser pour battre en retraite). Accepte une unité ou une liste (l'ordre est donné dans la même frame). queue='after' : termine d'abord la tâche en cours (inséré après l'ordre actuel). values[0] du reçu = nombre d'ordres en file pour cette unité après l'ordre (celui en cours compris). (Mécanisme: W3P point : move (bit extra = mode de mise en file)) - `attack_move(units, x: 'float', y: 'float', queue: 'str | None' = None)` [verified] [Voie rapide] Attaque-déplacement (A + clic au sol) : attaque les ennemis rencontrés en chemin. queue comme pour move. (Mécanisme: W3P point : attack sur un point) - `attack(units, target, force: 'bool' = False, queue: 'str | None' = None)` [verified] [Voie rapide] Attaque target. Utilise par défaut le clic droit (sur un ennemi = attaquer celui-ci ; mesuré le 09-23 : la cible d'ordre et la cible de tâche sont toutes deux cette unité). ⚠ La cible doit être dans le champ de vision ; une cible invisible est rejetée (code de motif 1001). force=True utilise l'ordre d'attaque 0x0F (nécessaire pour attaquer une unité alliée / un petit animal neutre) — mesuré : il ne fait que changer l'ordre en attaque sans mémoriser la cible, et l'unité part attaquer d'autres ennemis à proximité ; ne l'utilisez pas pour une cible précise. (Mécanisme: W3P target : commande sur cible (clic droit smart)) - `stop(units)` [verified] [Voie rapide] Arrête tout (ordre 0x000D0004) et vide aussi les ordres en file. (Mécanisme: W3P immediate : stop) - `hold(units, queue: 'str | None' = None)` [verified] [Voie rapide] Tenir la position (ne poursuit pas, n'attaque que ce qui est à portée). (Mécanisme: W3P immediate : holdposition) - `patrol(units, x: 'float', y: 'float', queue: 'str | None' = None)` [inferred] [Voie rapide] Patrouille entre la position actuelle et (x,y). (Mécanisme: W3P point : patrol) - `attack_ground(units, x: 'float', y: 'float', queue: 'str | None' = None)` [verified] [Voie rapide] Attaque au sol : l'artillerie tire sur une zone (unités invisibles, ennemis derrière des arbres, blocage d'un passage). Seules les unités capables d'attaquer le sol acceptent l'ordre. (Mécanisme: W3P point : attackground (unités de siège / mortiers / catapultes)) - `cancel(building)` [verified] [Voie rapide] Annuler : le dernier emplacement de la file d'entraînement/recherche (remboursé), un bâtiment en construction (75 % remboursés), un hall en cours d'amélioration. (Mécanisme: W3P immediate : cancel) - `path(units, points, attack: 'bool' = False)` [verified] [Voie rapide] Parcourt une suite de points dans l'ordre (points enchaînés avec Shift : points de passage, contournement de tours, itinéraire d'éclaireur). attack=True fait de chaque segment une attaque-déplacement. Soumis en une fois ; un reçu par point (dans l'ordre de points). (Mécanisme: Un lot : le premier segment s'exécute immédiatement, les autres sont insérés en ordre inverse avec queue='after' (le moteur ne sait insérer qu'après l'ordre en cours)) - `gather(workers, target, queue: 'str | None' = None)` [verified] [Voie rapide] Récolte de l'or / coupe du bois (target est une mine d'or ou un arbre de trees()). ⚠ N'affectez que des ouvriers inactifs (idle_workers) : redonner l'ordre à un ouvrier qui a une tâche interrompt son cycle de récolte. Usage pro : retourner à la mine après une construction = gather(worker, mine, queue='after') après build(...). (Mécanisme: W3P target : harvest (mine d'or ou arbre)) - `repair(workers, building, queue: 'str | None' = None)` [verified] [Voie rapide] Réparer / aider à construire (un chantier humain ou orc s'arrête si personne n'y travaille). (Mécanisme: W3P target : repair) - `build(worker, code: 'str', x: 'float', y: 'float', queue: 'str | None' = None)` [verified] [Voie rapide] Ordonne à un ouvrier de construire code en (x,y) (coordonnées alignées sur une grille de 32). Reçu accepté = l'ordre de l'ouvrier est déjà ce bâtiment (ou l'ordre de démarrage du chantier) ; avec queue='after' = ajouté à la file d'ordres de l'ouvrier (values[0] du reçu = nombre en file). ⚠ Accepté ≠ construit : le moteur accepte aussi sur le moment un emplacement en pleine forêt, et l'ouvrier n'échoue qu'une fois sur place (mesuré le 09-23) ; si l'argent est dépensé ailleurs entre-temps, les fondations n'apparaissent pas non plus. Si vous ne savez pas où le bâtiment peut être placé, utilisez build_near (il suit le résultat et met sur liste noire les emplacements en échec). Pour en enchaîner plusieurs, utilisez build_queue. (Mécanisme: W3P build : ordre de construction, confirmé en relisant l'ordre de l'ouvrier dans la même frame) - `build_queue(worker, plan)` [verified] [Voie rapide] Un ouvrier construit plusieurs bâtiments d'affilée (construction enchaînée avec Shift) : plan = [(code à quatre caractères, x, y), ...]. Soumis en une fois ; reçus dans l'ordre de plan. ⚠ L'argent n'est prélevé qu'au début de chaque construction (pas lors de la mise en file) — avec 3 bâtiments en file mais de l'argent pour un seul, les deux suivants échouent quand l'ouvrier arrive sur place. (Mécanisme: Un lot : le premier immédiatement, les autres en ordre inverse avec queue='after') - `build_near(worker, code: 'str', x: 'float', y: 'float', min_r: 'float' = 450, max_r: 'float' = 1500, max_tries: 'int' = 24)` [verified] [Voie rapide] Cherche autour de (x,y), du plus proche au plus éloigné, un emplacement libre pour construire code. **Non bloquant**, peut être appelé à chaque tick : * une construction de ce type est encore en cours (l'ouvrier est en chemin) -> renvoie cet emplacement, sans redonner l'ordre ; * la précédente a réussi (les fondations sont apparues) -> cherche au besoin un nouvel emplacement ; * la précédente a échoué (l'ouvrier a découvert sur place que le bâtiment ne pouvait pas être posé, le moteur a retiré l'ordre, aucune fondation) -> cet emplacement est mis sur liste noire pendant 45 secondes, on passe au suivant ; * argent insuffisant -> renvoie directement None (sans essayer ni mettre sur liste noire) ; si tous les emplacements ont été essayés, renvoie None. ⚠ Pourquoi ce suivi : constaté en partie réelle le 09-23, le moteur **accepte sur le moment** un emplacement en pleine forêt, et l'ouvrier n'échoue qu'une fois sur place (le reçu de la même frame ne permet pas de le détecter) ; de plus, la vérification d'emplacement du moteur renvoie toujours 221 pour un ouvrier qui construit, on ne peut donc pas « vérifier » avant de construire. Seul un emplacement manifestement occupé (le centre du hall) est rejeté sur-le-champ. (Mécanisme: build point par point + suivi (fondations apparues = réussite ; ouvrier qui abandonne l'ordre sans fondations = emplacement mis sur liste noire)) - `train(building, code: 'str')` [verified] [Voie rapide] Entraîne une unité / recherche une technologie / améliore le hall (passer de tier = donner au hall lui-même le code à quatre caractères du hall cible, par exemple 'hkee'). En cas de rejet, le reason du reçu en donne la raison (nourriture insuffisante, or manquant, bois manquant, file pleine, prérequis manquant…). (Mécanisme: W3P immediate : code à quatre caractères, avec le code de motif de faisabilité en cas de rejet) - `learn(hero, ability: 'str')` [verified] [Voie rapide] Le héros apprend une compétence (code à quatre caractères, par exemple 'AHbz' Blizzard). (Mécanisme: W3P learn : la compétence n'est considérée comme apprise que si les points de compétence diminuent) - `cast(u, spell, target=None, x: 'float | None' = None, y: 'float | None' = None)` [verified] [Voie rapide] Lance un sort. spell est le nom d'ordre ('thunderbolt' Éclair de tempête, 'blizzard', 'holybolt' Lumière sacrée…, voir data/order-ids.txt) ou l'identifiant d'ordre. Avec target = sur une unité ; avec x,y = sur le sol ; sans l'un ni l'autre = sans cible (Coup de tonnerre, Bouclier divin, Invocation d'élémentaire d'eau). Un reçu accepté signifie seulement que le moteur a pris l'ordre ; pour savoir si le sort est vraiment parti, vérifiez que cooldown() indique une recharge ou que le buff apparaît dans buffs(). (Mécanisme: W3P target / point / immediate (choisi selon les paramètres)) - `rally(building, x: 'float | None' = None, y: 'float | None' = None, target=None)` [verified] [Voie rapide] Définit le point de ralliement (sur un point, ou sur une unité / une mine d'or). (Mécanisme: W3P rally) - `revive(altar, hero=None)` [verified] [Voie rapide] Ressuscite à l'autel un héros mort (sans hero, le premier de la liste de résurrection). Causes de rejet fréquentes (indiquées dans le reason du reçu) : nourriture insuffisante (les héros consomment aussi de la nourriture), argent insuffisant, mort trop récente (résurrection possible environ 3 secondes de jeu après la mort), résurrection déjà en cours (le moteur libère cet emplacement sur-le-champ quand il accepte l'ordre). (Mécanisme: W3P revive : liste des héros morts -> l'autel lance la résurrection sur le héros mort) - `pick_up(hero, item)` [verified] [Voie rapide] Le héros va ramasser un objet au sol (item provient de items_on_ground). Une fois ramassé, l'objet apparaît dans l'inventaire et l'événement item.removed est émis pour le sol. (Mécanisme: W3P target : clic droit sur l'objet) - `use_item(hero, slot: 'int', target=None, x: 'float | None' = None, y: 'float | None' = None)` [verified] [Voie rapide] Utilise l'objet de l'emplacement slot (0~5) de l'inventaire ; peut prendre une unité cible ou un point cible. ⚠ Pour un objet utilisé sur un point (par exemple une tour d'ivoire), le moteur renvoie 0 même en cas de succès, et le reçu est toujours compté comme accepté — vérifiez si l'emplacement d'inventaire s'est vidé. (Mécanisme: W3P use_item (par numéro d'emplacement)) - `drop_item(hero, slot: 'int', x: 'float', y: 'float')` [verified] [Voie rapide] Dépose en (x,y) l'objet de l'emplacement slot de l'inventaire (le héros s'y rend pour le poser). (Mécanisme: W3P item_drop (calqué sur JASS UnitDropItemPoint : dropitem 0xD0021 sur un point + objet en cible immédiate)) - `give_item(hero, slot: 'int', to)` [verified] [Voie rapide] Donne l'objet de l'emplacement slot à to (un autre héros / une unité ; le héros se déplace pour le remettre). Donner à une boutique = vendre (voir sell_item). (Mécanisme: W3P item_drop (calqué sur JASS UnitDropItemTarget : dropitem sur une unité)) - `sell_item(hero, slot: 'int', shop)` [verified] [Voie rapide] Vend à une boutique l'objet de l'emplacement slot de l'inventaire (le héros doit se rendre près de la boutique ; seuls les objets vendables sont acceptés, repris à moitié prix). (Mécanisme: Comme give_item, avec la boutique pour cible (mesuré : un Staff of Sanctuary se vend 125 d'or)) - `move_item(hero, slot: 'int', to_slot: 'int')` [verified] [Voie rapide] Change un objet d'emplacement dans l'inventaire (de l'emplacement slot vers to_slot ; si les deux sont occupés, ils sont échangés). Utile pour organiser les raccourcis clavier. (Mécanisme: W3P target : ordre 0xD0022+numéro d'emplacement, cible = l'objet (calqué sur JASS UnitDropItemSlot)) - `buy(shop, item_code: 'str')` [inferred] [Voie rapide] Achète un objet dans une boutique (pour le héros qui se tient à côté). S'il manque un prérequis technologique, le moteur renvoie 0 et ne prélève rien. (Mécanisme: W3P buy : la boutique vend au héros voisin) - `call_to_arms(hall, on: 'bool' = True)` [verified] [Voie rapide] Appel aux armes des Humains : les paysans deviennent des miliciens (l'hôtel de ville de tier 1 n'a pas cette capacité ; valable uniquement pour le donjon/château). (Mécanisme: W3P immediate : townbellon/off) ## Contrôle du jeu Vitesse de jeu, pause, période de publication, bulles de dialogue, canevas, interface et entrées, messages. - `ui()` [verified] [Écriture directe] Interface et entrées (openwar3.ui.UI) : boutons et cartes de choix cliquables, raccourcis clavier, choix d'une position par un clic au sol, ce que pointe la souris. Le jeu ne reçoit pas le clic qui tombe sur un bouton ; uniquement de l'entrée locale + du dessin local, donc sûr même en multijoueur. (Mécanisme: W3P 74 input_enable + mémoire partagée Local\War3Input_ (le runtime reçoit les entrées de la fenêtre)) - `set_speed(percent: 'int') -> 'bool'` [verified] [Canal de contrôle] Vitesse de jeu (100 = vitesse normale). (Mécanisme: Action 47 (25~800 %)) - `pause(on: 'bool' = True)` [verified] [Voie rapide] Met en pause / reprend la partie. Pendant la pause, l'horloge du moteur s'arrête, mais la voie rapide accepte toujours les ordres (la distribution d'événements continue de tourner). (Mécanisme: W3P pause) - `set_publish_period(ms: 'int') -> 'None'` [verified] [Instantané poussé] Période de publication de l'état du monde (16~1000 millisecondes, 50 par défaut). Un relevé prend environ 0.5 ms, 33 ms ne pose donc aucun problème ; la valeur est partagée par toute la machine, la dernière écrite l'emporte. (Mécanisme: requestedPeriodMs du bloc du monde) - `say(u, text: 'str', seconds: 'float' = 4.0) -> 'bool'` [verified] [Canal de contrôle] Affiche une bulle de dialogue au-dessus d'une unité (pour le streaming / le débogage, sans effet sur la partie). Renvoie False si la bulle n'est pas apparue ; la raison est dans g.last_say_error. (Mécanisme: Action 56) - `message(text: 'str') -> 'bool'` [inferred] [Canal de contrôle] Affiche une ligne dans la zone de messages en bas à gauche de l'écran de jeu (visible uniquement sur cette machine). Il faut attendre que le jeu ait lui-même affiché une notification (la DLL récupère la zone de messages à ce moment-là). (Mécanisme: Action 45) - `end_game() -> 'bool'` [verified] [Canal de contrôle] Termine ce processus de jeu (farm.py --keep lance automatiquement la partie suivante d'après next_game.json). (Mécanisme: Action 22) - `canvas()` [verified] [Écriture directe] Canevas : dessine sur l'écran de jeu des zones de texte, panneaux, barres de progression, images, cercles au sol et itinéraires (openwar3.canvas.Canvas). Le runtime dessine lui-même, sans créer de handle de jeu ni modifier l'état du jeu — sûr même en multijoueur ; style libre (caractères chinois, coins arrondis, transparence). (Mécanisme: W3P 73 canvas_enable + mémoire partagée Local\War3Canvas_ (dessinée par le runtime à chaque frame, juste avant que le jeu ne dessine le curseur de la souris ; le curseur passe par-dessus)) - `press_to_continue() -> 'bool'` [verified] [Écriture directe] Appuie une fois sur Espace sur l'écran de chargement « Appuyez sur une touche pour continuer ». Beaucoup de cartes RPG / scénarisées attendent une touche à la fin du chargement pour démarrer (mesuré le 09-24 sur WarChasers : sans appui, le jeu reste sur l'écran de chargement, horloge de jeu à 0, voie rapide jamais vidée). openwar3.run appuie de lui-même en attendant l'entrée en partie ; en général, inutile de l'appeler à la main. (Mécanisme: PostMessage WM_KEYDOWN/UP de la touche Espace vers la fenêtre du jeu (sans lui donner le focus)) ## Bac à sable Canal JASS : créer des unités, définir des alliances, renommer des joueurs, afficher du texte… pour les assistants RPG et les compagnons ; ne peut modifier le monde qu’en partie solo et depuis les outils locaux. - `jass()` [verified] [Voie rapide] Appelle n'importe quelle native JASS par son nom : g.jass.CreateUnit(g.jass.Player(1), "Hpal", x, y, 270.0). Paramètres I/R/B/S/H convertis automatiquement (unités et objets se passent directement) ; en multijoueur, seules les natives en lecture seule peuvent être appelées. Détails dans openwar3/jass.py et docs/COMPANION_ZH.md. (Mécanisme: W3P 70 jass (le runtime cherche la native par son nom dans sa table de 1291 natives)) - `player_slots() -> 'list[dict]'` [verified] [Voie rapide] Les 16 emplacements de joueur : controller (user = humain / computer / neutral…), state (empty / playing / left), human, me, ally (allié ou non de votre joueur). Sert dans les cartes RPG à trouver un emplacement libre pour le compagnon, et à savoir si la partie est en solo. (Mécanisme: JASS GetPlayerController / GetPlayerSlotState / IsPlayerAlly) - `spawn(code: 'str', x: 'float', y: 'float', player: 'int | None' = None, facing: 'float' = 270.0)` [verified] [Voie rapide] Crée une unité en (x,y) (player vaut par défaut le joueur local) et renvoie l'unité de l'instantané (après la publication suivante du monde, environ 50 ms) ; renvoie None si la création échoue. L'unité renvoyée a un attribut supplémentaire, jass_handle. ⚠ Uniquement en partie solo (en multijoueur, cela provoque une désynchronisation). (Mécanisme: JASS CreateUnit + W3P 72 handle -> unité) - `set_alliance(a: 'int', b: 'int', allied: 'bool' = True, vision: 'bool' = True, control: 'bool' = False, xp: 'bool' = False, both: 'bool' = True) -> 'None'` [verified] [Voie rapide] Définit la relation d'alliance du joueur a envers b : allied = pas d'attaque mutuelle + demande d'aide mutuelle ; vision = vision partagée ; control = contrôle partagé des unités (b peut commander les unités de a) ; xp = expérience partagée. both=True règle les deux sens à la fois (control seulement de a -> b). (Mécanisme: JASS SetPlayerAlliance) - `set_player_name(player: 'int', name: 'str') -> 'None'` [verified] [Voie rapide] Change le nom d'un joueur (celui affiché dans le tableau des scores, le chat et le panneau des alliances). Sert à donner un nom au compagnon. (Mécanisme: JASS SetPlayerName) - `show_text(text: 'str', seconds: 'float' = 6.0, player: 'int | None' = None) -> 'None'` [verified] [Voie rapide] Affiche une ligne de texte en bas à gauche de l'écran (le texte qu'utilisent les déclencheurs des cartes), par défaut pour le joueur local. Prend en charge les codes couleur |cffRRGGBB. (Mécanisme: JASS DisplayTimedTextToPlayer) ## Connexion et outils État de la connexion et outils de calcul pur. - `status() -> 'dict'` [verified] [Calcul local] État de la connexion : pid, publication du monde (période, durée de collecte), compteurs de la voie rapide. (Mécanisme: Bloc du monde + voie rapide + table des réservations) - `nearest(candidates, to)` [verified] [Calcul local] L'élément le plus proche de to (une unité ou (x,y)) ; renvoie None s'il n'y a aucun candidat. (Mécanisme: Calcul pur)