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 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
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 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+.
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) :
| 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 :
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.cursoret les autres méthodes sont disponibles sous le même nom sur la passerelle. 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énementui.clickportekey). - É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éesLocal\War3Input_<pid>, 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 bit0x40d’un élément du canevas signifie « interactif ». Pour la disposition, voir Protocole W3P.
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 avecg.selection(); lancer Bouclier divin →spell.cast('AHds', 1, 35.0); - Texte de la carte →
message; chat →message, avec.chatqui 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.worldn’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.