Interface e entrada
Botões e cartões de escolha do canvas ficam clicáveis, com destaque automático ao passar o mouse; registre teclas de atalho, clique no chão para escolher uma posição, leia para onde o mouse aponta e saiba quem o jogador local selecionou. Cliques, teclas de atalho, feitiços lançados, o texto completo do chat e jogadores que saem entram no fluxo de eventos.
O que o canvas desenha agora pode ser clicado. O runtime assume a entrada da janela do jogo, e um programa externo pode:
| Recurso | Em uma frase | O jogo recebe? |
|---|---|---|
| Itens clicáveis do canvas | Botões, cartões de escolha, painéis: o clique gera ui.click, e o destaque ao passar o mouse é automático | O clique sobre o botão não chega ao jogo |
| Teclas de atalho | Registre combinações como F5 ou ctrl+shift+Q; ao pressionar, gera hotkey | Pode ser engolida (junto com o caractere que ela produziria) |
| Clique no chão | Um clique no mundo gera mouse.world, com as coordenadas no chão | Pode ser engolido (“clique numa posição para pôr a torre”) |
| Posição do mouse | Atualizada a cada frame: pixels da tela, ponto do chão sob o cursor, item do canvas sob o cursor | — |
| Seleção | Quem o jogador local selecionou; a cada mudança, gera selection.changed | — |
Tudo é entrada local + desenho local: nada entra no fluxo de comandos do jogo, então é seguro também em partidas multijogador. Mas, se o callback alterar o mundo (criar unidades, mudar atributos), aí só funciona em partida solo.
Python: g.ui
ui = g.ui # no primeiro uso, o runtime assume a entrada da janela
ui.button("shop", "Comprar uma poção (50 de ouro)", screen=(40, 300), on_click=lambda g, ev: buy(g))
c = ui.choice("Subiu de nível! Escolha uma recompensa", [("Força +5", "Aguenta mais"), ("Vel. de ataque +20%", "Bate mais"), ("Invocar lobo", "Mais um ajudante")],
pause=True, on_pick=lambda g, i: give(g, i)) # uma fileira de cartões no meio da tela; pause=True pausa o jogo durante a escolha
i = c.wait(timeout=30) # também dá para esperar bloqueando (os eventos continuam sendo processados, nada se perde)
ui.hotkey("F5", lambda g, ev: g.say(hero, "Entendido!")) # engolida por padrão
ui.hotkey("ctrl+shift+Q", on_press=..., swallow=False)
ui.mouse(on_click, capture=True, buttons=("left", "right")) # captura cliques no chão: informa botão esquerdo e direito, e os engole
xy = ui.pick_point("Clique no chão: onde fica a torre?") # versão bloqueante: próximo clique esquerdo no chão -> (x, y); Esc ou timeout -> None
ui.cursor() # {'screen': (x, y), 'world': (x, y, z) ou None, 'hover': 'shop'}
ui.toast("A onda 3 chegou!", seconds=3)
ui.close() # recolhe os seus controles e teclas de atalho; só devolve a entrada da janela se nenhum outro programa estiver usando a entrada
g.close() # ou desconecta de vez (também dá para escrever with Game(...) as g:)
Os callbacks recebem (g, ev) e disparam quando você chama g.events() — o executor de Bots e o de mods de jogabilidade chamam isso a cada tick. Cliques sem callback vão para ui.clicks. Uma exceção lançada dentro de um callback só vai para o log, sem afetar os outros callbacks nem os eventos.
Também dá para usar o canvas diretamente: g.canvas.text(..., clickable=True, hover=cor); os cliques chegam pelo fluxo de eventos, e ev.key é a key dada ao desenhar. Desenhar um elemento clicável liga a entrada automaticamente, sem precisar mexer em g.ui antes.
Como escrever teclas de atalho: F1 ~ F24, A ~ Z, 0 ~ 9, numpad0 ~ numpad9, space enter esc tab backspace insert delete home end pageup pagedown left up right down, com os prefixos opcionais ctrl+, shift+, alt+.
Letras e números sem tecla modificadora entram em conflito com a digitação no chat e com os atalhos do jogo. Prefira teclas que o jogo não usa, como F5 ~ F8, ou combinações.
Novos eventos
g.events() passa a trazer estes (campos completos em Protocolo W3P):
| kind | Quando | Campos de conveniência |
|---|---|---|
ui.click | Um item interativo do canvas foi clicado | .key key do canvas, .button ('left' / 'right'), .mods teclas modificadoras |
ui.hover | O mouse entra / sai de um item do canvas | .key (None ao sair) |
hotkey | Uma tecla de atalho registrada foi pressionada | .key a tecla como foi registrada, .mods |
mouse.world | Com os cliques no chão ativados, um clique no mundo | .x .y coordenadas no chão, .button, .value (1 = engolido) |
selection.changed | A seleção do jogador local mudou | Use g.selection() para obter as unidades |
spell.cast | Uma unidade lançou uma habilidade (que entrou em recarga) | .spell código de quatro caracteres, .b nível, .value recarga em segundos, .x .y ponto de lançamento |
message | Apareceu uma linha numa caixa de mensagens da tela | .text texto completo, .frame qual caixa, .chat (quando é chat) |
player.left | Um jogador saiu ou foi removido por derrota | .player |
game.ended | Saída da partida | — |
Chat e mensagens na tela
O que o jogador digita no chat é lido direto de .chat no evento message:
for ev in g.events():
if ev.kind == "message" and ev.chat and ev.chat["text"] == "-follow":
... # ev.chat = {'channel': 'Todos', 'sender': 'nome do jogador', 'text': '-follow'}
g.messages() tem um cursor próprio e independente, e nele também aparecem as dicas do jogo (“Você precisa de mais fazendas”, “Não é possível construir aí”). Ao escrever um Bot, use-o para saber por que um comando não foi executado.
Usando a partir de outras linguagens
- Gateway: os métodos
ui.button,ui.choice,ui.hotkey,ui.mouse,ui.cursorestão disponíveis com o mesmo nome no gateway. Um cliente remoto não pode passar funções de callback: cliques e teclas de atalho chegam pelos eventos enviados (o eventoui.clicktraz akey). - Escrevendo direto na memória compartilhada: envie primeiro o comando semântico
input_enable(opcode W3P 74), e o runtime passa a assumir a entrada; no bloco de entradaLocal\War3Input_<pid>, você escreve a tabela de teclas de atalho e as chaves do mouse, e ele devolve a posição do mouse, o ponto do chão sob o cursor e o item sob o cursor. O bit0x40nas flags de um item do canvas significa “interativo”. O layout está em Protocolo W3P.
Vários programas ao mesmo tempo
Mods, Farsight, MCP e cada sessão do gateway podem pôr botões e registrar teclas de atalho na mesma partida ao mesmo tempo, sem um atrapalhar o outro:
- Cada programa registra as próprias teclas de atalho e a própria chave de cliques no chão, e o SDK junta tudo numa tabela só para o runtime. Cada tecla aparece uma vez só, os eventos vão para todos, e cada um reconhece as próprias teclas de atalho pela tecla;
ui.close()só retira o que é seu, e a entrada da janela só é devolvida quando o último programa sai;- Se um programa for encerrado à força, sem tempo de arrumar a casa: o runtime confere a cada 2 segundos e, quando todos os programas registrados já saíram, limpa as teclas de atalho e a interceptação de cliques no chão que eles deixaram, e os botões que desenharam deixam de interceptar cliques.
Medições
2026-09-25, verificação em partidas reais numa instância de teste, 16/16:
- Clicar no botão →
ui.click+ callback, e o contador de interceptações do runtime sobe 1 (o jogo não recebeu esse clique); clicar fora do botão não dispara nada; - F6 →
hotkey; clique no chão →mouse.world(engolido); - Criar um paladino e selecioná-lo →
selection.changed, eg.selection()confere; lançar Escudo Divino →spell.cast('AHds', 1, 35.0); - Texto do mapa →
message; chat →message, com.chatseparando quem falou e o conteúdo; - Derrotar o computador →
player.left; encerrar a partida →game.ended.
Cliques de uma pessoa real nos botões e o destaque ao passar o mouse também foram conferidos um a um.
Limites e cuidados
- A posição vem do mouse real: o próprio jogo lê a posição pelo cursor do sistema, então o destaque e
cursor()refletem o mouse real. A interceptação só cuida dos cliques e das teclas. - Desenhado abaixo do cursor do mouse: o Warcraft desenha o cursor como parte da imagem, a cada frame. O canvas e os balões de fala são desenhados antes do passo em que o jogo desenha o cursor: cobrem a interface do jogo e ficam cobertos pelo cursor. Só quando o cursor não é desenhado num frame (escondido ou em cinemática) o desenho volta a ser feito no último passo.
- Escala do sistema: se você escrever seus próprios testes e enviar cliques por mensagens de janela, as coordenadas enviadas por um processo sem reconhecimento de DPI são ampliadas pelo sistema (medido: ×1.5 com escala de 150%). Declare o programa de teste como DPI-aware antes. Cliques de uma pessoa real não são afetados.
- Na primeira vez, as fontes precisam ser pré-aquecidas, o que leva cerca de 1 segundo. Nesse intervalo, os botões ainda não foram desenhados e não podem ser clicados.
- Fora de uma partida, cliques no chão não são informados: no menu principal e na tela de resultados,
mouse.worldnão é enviado nem engolido. - Se um clique foi engolido ao pressionar e, antes de soltar, você muda para outro programa ou arrasta o mouse para fora da janela, o estado também é reiniciado: a próxima soltura não é engolida junto.
- A 1.27 não tem funções para criar novos frames de interface do jogo (elas só chegaram na 1.31): os botões e cartões daqui são desenhados pelo runtime, com estilo livre, mas não aparecem na hierarquia de menus do próprio jogo.