# War3AI / OpenWar3 Documentação completa > Fonte https://war3ai.com/pt . Interface aberta de Warcraft III 1.27 para agentes de IA. Ao escrever um Bot, use apenas os métodos de Game listados no “Catálogo da API”, no final. --- # Visão geral da documentação > Documentação do OpenWar3: o que é, o que faz; Início rápido, Seu primeiro Bot, Escreva um Bot com um LLM, API e protocolo, Gateway e MCP — por onde começar depende do seu caso. **OpenWar3** é a camada de interface aberta do War3AI: um runtime injetado no Warcraft III 1.27, mais um SDK em Python. - A cada **50 ms**, o runtime envia para a memória compartilhada o estado completo do mapa inteiro: recursos e comida de todos os jogadores; vida e mana, ordem atual, quem cada unidade está atacando, recargas de habilidades, buffs e inventário de todas as unidades; itens no chão, árvores, filas de produção, dia e noite. Há também um **fluxo de eventos**: unidades aparecendo e morrendo, cada golpe de dano, produção concluída… - Programas externos enviam **comandos semânticos** com latência de **cerca de um frame**: mover, atacar, coletar, construir, treinar, lançar feitiços, aprender habilidades, reviver, usar itens, comprar… Cada comando tem um **recibo** que diz se o motor aceitou e, se não aceitou, o código de motivo. - Você só diz “o que fazer”: unidades por código de quatro caracteres, habilidades por nome de ordem, os mesmos nomes usados no jogo; o “como” fica por conta do runtime. Por isso o LLM não precisa de nenhum conhecimento de baixo nível nem precisa ver a tela. Depois de ler a documentação, ele consegue escrever um Bot que cuida da economia e sabe lutar, e depois ajustá-lo sozinho com base nos recibos e eventos da partida. E não é só para partidas competitivas: o [canvas](https://war3ai.com/pt/docs/canvas/) desenha seus próprios painéis e marcações na tela do jogo, [Interface e entrada](https://war3ai.com/pt/docs/ui-input/) torna clicáveis os botões desenhados e faz as teclas de atalho responderem, o [canal JASS](https://war3ai.com/pt/docs/jass/) chama de fora as 1291 funções do jogo, e em mapas RPG você ainda pode ter um [companheiro de IA](https://war3ai.com/pt/docs/companion/) ao seu lado. Uma IA pronta pode virar um [esquema](https://war3ai.com/pt/docs/schemes/), que você troca com um clique e exporta para compartilhar; uma jogabilidade nova inteira pode virar um [mod de jogabilidade](https://war3ai.com/pt/docs/mods/). Também dá para conectar sem escrever Python: o [gateway](https://war3ai.com/pt/docs/gateway/) deixa qualquer linguagem ou página de navegador chamar as mesmas APIs via WebSocket / JSON, e o [servidor MCP](https://war3ai.com/pt/docs/mcp/) deixa agentes como o Claude Code chamarem ferramentas diretamente para ver a partida e dar comandos. - [Início rápido](https://war3ai.com/pt/docs/quickstart/): Prepare o ambiente, inicie uma partida com um comando e veja o Bot de exemplo assumir. - [Escreva um Bot com um LLM](https://war3ai.com/pt/docs/ai-bot/): Não precisa saber programar: copie o prompt, descreva a estratégia e entregue ao agente. - [Modelo mental](https://war3ai.com/pt/docs/concepts/): Snapshot, comando, recibo, evento, tick. Cinco minutos de leitura antes de escrever um Bot. - [Catálogo da API](https://war3ai.com/pt/api/): Todas as APIs, cada uma com status de teste, faixa de latência e mecanismo interno. ## Escolha um caminho para o seu caso | Você | Comece por aqui | Depois | |---|---|---| | Joga Warcraft, mas não programa | [Início rápido](https://war3ai.com/pt/docs/quickstart/) → [Escreva um Bot com um LLM](https://war3ai.com/pt/docs/ai-bot/) | Se algo der errado, veja as [Perguntas frequentes](https://war3ai.com/pt/docs/faq/) | | Sabe Python | [Seu primeiro Bot](https://war3ai.com/pt/docs/first-bot/) → [Modelo mental](https://war3ai.com/pt/docs/concepts/) → [As quinze regras](https://war3ai.com/pt/docs/rules/) | [Receitas de jogadas profissionais](https://war3ai.com/pt/docs/cookbook/), [Bots de exemplo](https://war3ai.com/pt/docs/examples/) | | Está criando um agente de programação / automação | [Iteração autônoma do agente](https://war3ai.com/pt/docs/agent-loop/) | [Recibos e códigos de motivo](https://war3ai.com/pt/docs/reason-codes/), [`llms-full.txt`](https://war3ai.com/pt/llms-full.txt) | | Quer que um LLM tome decisões durante a partida | [LLM como coach de estratégia](https://war3ai.com/pt/docs/llm-coach/) | [Balões de fala e modelos locais](https://war3ai.com/pt/docs/speech/) | | Quer que um agente opere o jogo diretamente (Claude Code etc.) | [LLM usando ferramentas (MCP)](https://war3ai.com/pt/docs/mcp/) | [Interface e entrada](https://war3ai.com/pt/docs/ui-input/) | | Usa outra linguagem (JS, C#, Go, Rust…) | [Gateway](https://war3ai.com/pt/docs/gateway/) | Mais baixo nível: [Protocolo W3P](https://war3ai.com/pt/docs/protocol/) | | Quer pôr IAs de pessoas diferentes para se enfrentar | [Modo justo](https://war3ai.com/pt/docs/fair-mode/) | [Arena](https://war3ai.com/pt/arena/) | | Quer criar sua própria jogabilidade em mapas RPG / personalizados | [Mods de jogabilidade](https://war3ai.com/pt/docs/mods/) | [Interface e entrada](https://war3ai.com/pt/docs/ui-input/), [Canvas](https://war3ai.com/pt/docs/canvas/), [Canal JASS](https://war3ai.com/pt/docs/jass/), [Companheiro de RPG](https://war3ai.com/pt/docs/companion/) | | Quer compartilhar a sua IA com outras pessoas | [Esquemas de IA](https://war3ai.com/pt/docs/schemes/) | [Console Farsight](https://war3ai.com/pt/docs/console/) | ## O que há no repositório ```text start.bat Único ponto de entrada: instalação do zero + abre o Farsight; stop.bat para tudo sdk/python/ Camada de interface. openwar3/ é a fachada pública (Game + Bot); comece por aqui brains/ Camada de decisão examples/ hello_bot (economia) → rush_bot (tropas) → macro_bot (macro) → micro_bot (micro + creeps); buddy (companheiro de RPG); mod_hero_roguelike / mod_endless_defense (mods de jogabilidade) xwar3/ Cérebro de referência: camada de estratégia (segundos) + camada reflexa (4 processos) + modelo de vitória console/ Console web Farsight (FastAPI + React) gateway/ Gateway (WebSocket / JSON) + cliente JS + página de demonstração no navegador director/ Câmera automática, barras de vida sobre as unidades speech/ Balões de fala sobre as unidades + LLM local runtime/ Orquestração de várias instâncias (cada partida reiniciada conforme a configuração) data/ order-ids.txt; ferramentas para extrair dados do seu próprio jogo schemes/ Seus esquemas de IA (mine/) e os compartilhados por outras pessoas (installed/); fora do repositório tools/ play.py (inicia uma partida com um comando), run_scheme.py (executor de esquemas), war3_mcp.py (servidor MCP), run_tests.py, scripts de verificação em partidas reais docs/ Catálogo da API api.json (gerado a partir do código), protocolo, manual ``` Entre o runtime e o seu código há apenas o [protocolo W3P](https://war3ai.com/pt/docs/protocol/), que é versionado: o SDK em Python é o caminho mais fácil, mas você também pode integrar a partir de outra linguagem seguindo o protocolo. ## O que significa o “status de teste” de uma API Cada API do catálogo tem um de três status: - **Verificado em partidas reais**: o caminho interno (número da ação, formato dos parâmetros, efeito lido de volta) foi verificado em partidas reais e é protegido por um script de verificação. - **Experimental**: API nova, que já funciona na instância de teste e ainda está sendo verificada item por item em partidas reais. Pode ser usada, mas os detalhes da interface ainda podem mudar. - **Inferido / não totalmente testado**: o mecanismo interno copia o que o próprio motor faz (por exemplo, a função JASS equivalente), mas ainda não foi verificado item por item numa partida. Confira o recibo antes de depender dele. > **Nota** > > No momento, só há suporte ao **Warcraft III 1.27** (The Frozen Throne). As versões 1.24 ~ 1.28 compartilham a mesma estrutura de motor; o suporte a várias versões está na fase P4 do [roadmap](https://war3ai.com/pt/roadmap/). A 1.29 em diante e o Reforged usam outro motor e ficam fora do escopo. --- # Início rápido > Dê dois cliques em start.bat para instalar tudo automaticamente, defina a pasta do jogo no Farsight e inicie uma partida para ver um Bot de exemplo assumir o controle. Cerca de 15 minutos. ## O que você precisa | | Requisito | Observações | |---|---|---| | Sistema | Windows 10 / 11, 64 bits | Por enquanto só há suporte a Windows | | Jogo | Warcraft III **1.27a** (The Frozen Throne, `Game.dll` 1.27.0.52240) | Um cliente que você possui legalmente; nenhum arquivo do jogo em disco é modificado | **Não é preciso instalar mais nada antes.** O `start.bat` baixa só uma coisa: o Python 3.13 (pacote portátil oficial, com cerca de 14 MB), colocado em `bin\env\` dentro do repositório, sem precisar de permissão de administrador nem alterar o PATH do sistema; em redes da China continental, troca automaticamente para espelhos. Se o computador já tiver um Python que funcione, ele é usado diretamente. O PowerShell usado é o que já vem com o Windows; a página web do Farsight já vem compilada junto com o repositório, sem precisar do Node.js. ## Instalação 1. **Obtenha o código** ```bash git clone https://github.com/OPENXXAI/OpenWar3AI.git ``` Ou baixe o [arquivo compactado](https://github.com/OPENXXAI/OpenWar3AI/archive/refs/heads/main.zip) e extraia. O runtime (a DLL de injeção e o lançador) vem junto com o repositório; não é preciso baixá-lo à parte. 2. **Dê dois cliques em `start.bat`** Na primeira vez, ele faz sozinho: - Baixa o Python 3.13; - Instala os pacotes Python e instala os arquivos do runtime depois de conferi-los; - Baixa os dados de estratégia do AMAI usados para gerar o cérebro de referência (o AMAI tem licença própria e os arquivos gerados não entram no git; uma falha aqui só afeta o cérebro de referência); - Abre a página inicial do Farsight, a “Central de controle”, em `http://127.0.0.1:8866`. Cada passo imprime o resultado; se algum não der certo, ele explica como completar. Das próximas vezes, cada clique duplo só faz uma verificação de um ou dois segundos e abre o Farsight. A janela preta se fecha sozinha depois de alguns segundos: o Farsight continua rodando em segundo plano, e fechar o navegador não o interrompe. 3. **Defina a pasta do jogo na Central de controle** No topo da Central de controle, use “Localizar automaticamente” ou “Procurar…” para escolher você mesmo a pasta do Warcraft III. O Farsight verifica a versão do jogo e **extrai os dados da sua própria cópia do jogo** (tabela de unidades, habilidades, itens, tabela de vantagens de tipo… os arquivos da Blizzard não são distribuídos com o código). Se a versão não for 1.27a, ele avisa. Os mapas e as configurações da próxima partida são relativos a essa pasta (dá para escolher mapas de qualquer pasta dentro de `\Maps`); para trocar a pasta depois, use a página “Configurações”. 4. **Inicie uma partida e deixe o Bot de exemplo assumir** O jeito mais fácil é, na página “Instâncias e partidas” do Farsight, marcar um número de instância, escolher um esquema de IA e clicar em “Iniciar teste”. Também dá para usar a linha de comando: ```bash python tools/play.py --bot brains/examples/hello_bot.py ``` Esse comando inicia uma instância do jogo, injeta o runtime, começa a partida automaticamente e então sobe o Bot. **Quando você vir os camponeses indo minerar e a sede começando a treinar camponeses, deu certo.** O `python` do comando é o que está registrado no `openwar3.json`; o que o `start.bat` instala fica em `bin\env\python\python.exe`. ## start.bat e stop.bat ```bash start.bat # verificação da instalação + abre o Farsight start.bat setup # verificação completa: reinstala os pacotes Python, tenta o AMAI de novo start.bat restart # reinicia só o backend do Farsight (jogos e serviços não são afetados) start.bat node # também instala uma cópia do Node.js (só é necessária para a prévia do site; no dia a dia não precisa) start.bat 5 6 # também inicia os testes nas instâncias 5 e 6 (jogo + cérebro de referência) stop.bat # para tudo; stop.bat --keep-llm mantém o modelo local na VRAM ``` O gateway, os balões de fala e o LLM local também são iniciados e parados na “Central de controle” do Farsight; não é preciso procurar outros scripts. **Para parar tudo**: dê dois cliques em `stop.bat` ou clique em “Parar tudo” no canto superior direito da Central de controle — as instâncias do jogo, a IA, o gateway, os balões, o modelo local usado por este sistema e o backend do Farsight param, nessa ordem. Os servidores MCP são gerenciados pelos clientes, como o Claude, e não são parados. > **Arquivo de configuração** > > O `openwar3.json` é escrito automaticamente pelo `start.bat` e pelo Farsight, guarda só caminhos locais e não entra no git. Para mudar as portas ou o endereço e o nome do modelo do LLM local, siga o `openwar3.example.json` e escreva só os itens que forem diferentes dele. ## Parâmetros do 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 # o jogo já está aberto; só conecta o Bot python tools/play.py --bot my_bot.py --fair # modo justo: só vê o que está no campo de visão ``` | Parâmetro | Padrão | Descrição | |---|---|---| | `--bot` | Obrigatório | Caminho do arquivo do Bot (o arquivo precisa conter uma subclasse de `Bot`) | | `--inst` | `9` | Número da instância. Não repita o número de uma instância que já está rodando (a página “Instâncias e partidas” do Farsight mostra quais números estão em uso) | | `--race` | `1` | Nossa raça: 1 Humanos, 2 Orcs, 3 Mortos-vivos, 4 Elfos Noturnos | | `--enemy-race` | `0` | Raça do adversário | | `--difficulty` | `2` | Dificuldade do computador: 2 Fácil, 3 Normal, 4 Insano | | `--speed` | `100` | Velocidade do jogo (porcentagem, 200 = 2×) | | `--map` | `default_map` da configuração | Mapa | | `--attach` | | Não inicia o jogo; só se conecta a uma instância que já está rodando | | `--hz` | `5` | Quantas vezes por segundo `on_tick` é chamado | | `--minutes` | `60` | Tempo máximo de execução em minutos (relógio real) | | `--fair` | | [Modo justo](https://war3ai.com/pt/docs/fair-mode/) | | `--player` | | Com qual número de jogador comandar (para IA contra IA) | > **Atenção** > > Não inicie o jogo com `--minimize`: **a simulação do jogo para enquanto a janela está minimizada** (o relógio não anda), e o Bot vai ficar esperando para sempre a partida começar. Você também pode dispensar o `play.py` e usar a linha de comando do SDK para se conectar direto a uma instância que já está rodando: ```bash python -m openwar3 run brains/examples/hello_bot.py --inst 5 # roda um Bot python -m openwar3 status --inst 5 # conecta e imprime o estado do snapshot / da via rápida python -m openwar3 catalog # imprime a referência da API ``` ## Depois que funcionar - [Escreva seu primeiro Bot](https://war3ai.com/pt/docs/first-bot/): Comece com um Bot mínimo de 10 linhas e adicione, passo a passo, treino de unidades e ataques. - [Deixe um LLM escrever para você](https://war3ai.com/pt/docs/ai-bot/): Copie o modelo de prompt e descreva sua estratégia em linguagem simples. ## Autoverificação ```bash python tools/run_tests.py # SDK / cérebro de referência / camada reflexa / console / balões de fala / exemplos, um subprocesso por suíte ``` Os testes offline não precisam do jogo aberto. A “Central de controle” do Farsight também tem uma verificação do ambiente, que mostra se cada parte está instalada. --- # Seu primeiro Bot > Comece com um Bot mínimo de 10 linhas, depois adicione treino de camponeses, construções para comida, exército, heróis e ataques — e, por fim, aprenda a ler os recibos. Um Bot é uma classe que herda de `openwar3.Bot`. Você só sobrescreve os hooks de que precisa; `g` (`Game`) cuida de "observar" e de "agir". ## O Bot mínimo ```python title="my_bot.py" from openwar3 import Bot class MyBot(Bot): def on_start(self, g): # chamado uma vez depois que a partida começa g.message("Cheguei") def on_tick(self, g): # cerca de 5 vezes por segundo for w in g.idle_workers(): g.gather(w, g.nearest(g.gold_mines(), w)) ``` ```bash python tools/play.py --bot my_bot.py ``` Os camponeses ociosos vão para a mina de ouro mais próxima. Os quatro hooks: | Hook | Quando é chamado | |---|---| | `on_start(g)` | Uma vez depois que a partida começa, antes do primeiro tick | | `on_tick(g)` | A cada tick (5 vezes por segundo por padrão). Se um tick passar do tempo, o próximo é adiado automaticamente — eles nunca se acumulam | | `on_event(g, ev)` | Antes de cada `on_tick`; entrega a você, um por um, todos os eventos desde o último tick | | `on_end(g, reason)` | Uma vez quando a partida termina (o processo do jogo sumiu / não temos mais unidades / parada manual) | > **Dica** > > Uma exceção no `on_tick` não encerra a partida: o executor imprime o stack trace e segue para o próximo tick; ele só para depois de **20 ticks seguidos com erro**. ## Adicionando economia: treinar camponeses, construir para ter comida ```python from openwar3 import Bot class Economy(Bot): def on_tick(self, g): res = g.resources() # None se não der para ler, não 0 halls = g.my_buildings({"htow", "hkee", "hcas"}) if res is None or not halls: return home = halls[0] # 1. Camponeses ociosos vão minerar ouro for w in g.idle_workers(): mine = g.nearest(g.gold_mines(), w) if mine: g.gather(w, mine) # 2. Treinar camponeses: só 1 na fila por vez (fila cheia prende o ouro nela) if len(g.my_workers()) < 15 and not g.queue(home): g.train(home, "hpea") # 3. Comida quase no limite: ache um camponês que não esteja construindo e faça uma Fazenda perto da sede 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) ``` Três detalhes que valem atenção: - **Só treine quando `g.queue(home)` estiver vazia.** Dar ordem de treino a cada tick enche a fila de 7 espaços e prende seu ouro (medido: a sede enfileirou 4 camponeses, com 300 de ouro presos na fila, e a abertura ficou muito mais lenta). - **`build_near` em vez de coordenadas fixas.** Ele procura, do mais perto para o mais longe, um lugar que caiba e acompanha o resultado ao longo dos ticks; não faz nada quando falta dinheiro. Coordenadas fixas podem muito bem cair no meio de uma floresta. - **Não escolha camponeses que já estão construindo.** Uma Fazenda humana leva 35 segundos para ficar pronta; se você tirar o trabalhador no meio, a obra para. A versão completa, que funciona para as quatro raças, é `brains/examples/hello_bot.py`: 5 por mina, os trabalhadores extras cortam árvores quando a mina está cheia, e ele retoma obras paradas. ## Adicionando Quartel, herói e ataques ```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] # Herói: tem altar mas não tem herói -> reviva primeiro, só treine se não der para reviver (heróis são únicos; treinar de novo depois da morte é rejeitado) 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") # Luz Sagrada # O Quartel produz Soldados sem parar (só 1 na fila por vez) for b in g.my_buildings({"hbar"}): if not g.queue(b): g.train(b, "hfoo") # Ataca quando uma leva estiver pronta; volta para casa depois de perdas pesadas 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)] # só dá ordens às unidades ociosas g.attack_move(idle, target.x, target.y) ``` Para a versão completa, veja `brains/examples/rush_bot.py` (ele herda de `hello_bot` e constrói o Quartel / altar se não existirem). ## Lendo os recibos Todo comando retorna um recibo. `if r:` significa "o engine aceitou"; quando não foi aceito, `r.reason` diz o motivo: ```python r = g.train(barracks, "hfoo") if not r: print(r.reason) # rejected(人口不够) (= falta comida) print(r.verdict) # 3 ``` Códigos de motivo comuns: `3` falta comida, `8` falta ouro, `9` falta madeira, `32` fila cheia, `183` falta pré-requisito, `221` item inexistente / em construção / você já tem este herói, `1001` alvo não visível. Para a tabela completa, veja [Recibos e códigos de motivo](https://war3ai.com/pt/docs/reason-codes/). > **Aceito ≠ feito** > > O recibo só diz que "o engine aceitou este comando". O engine também aceita na hora um ponto de construção dentro de uma floresta, e o trabalhador só falha quando chega lá; magias podem ser interrompidas. Para ver o efeito real, olhe os snapshots e os eventos: para construções, use `build_near` (ele acompanha se a fundação aparece), e para magias, veja se `g.cooldown()` mostra a recarga. ## Próximos passos - [Modelo mental](https://war3ai.com/pt/docs/concepts/): Snapshots, comandos, eventos, ticks, lotes — por que foi projetado assim. - [Receitas de jogadas profissionais](https://war3ai.com/pt/docs/cookbook/): 21 receitas: mineração saturada, nunca travar por comida, focar fogo, recuar unidades feridas, creepar à noite… --- # Escreva um Bot com um LLM > Não precisa saber programar: você explica como quer que ele jogue, e o LLM escreve o código. Copie o modelo de prompt, descreva sua estratégia, rode e depois peça ao modelo para ajustar. Serve para quem joga Warcraft mas não programa, e também para desenvolvedores que querem ganhar tempo. O processo todo é uma conversa: **você descreve a estratégia → o modelo escreve o código → você joga uma partida → conta ao modelo o que aconteceu → ele ajusta**. > **Dica** > > Primeiro prepare o ambiente com o [Início rápido](https://war3ai.com/pt/docs/quickstart/) e faça o `hello_bot` funcionar (você vai ver os camponeses indo minerar ouro). Assim, quando algo der errado, você consegue saber se o problema é do ambiente ou do Bot. ## 1. Prepare o material para o modelo A qualidade do código que o modelo escreve depende, em grande parte, de ele ter lido o material certo. Escolha a opção que combina com a sua ferramenta: | O que você usa | Como entregar o material | |---|---| | **Um agente de programação que lê o repositório** (Claude Code, Cursor, Codex etc.) | Abra-o no diretório do repositório e peça que leia primeiro `docs/BOT_HANDBOOK_ZH.md`, `docs/api.json` e um exemplo (`brains/examples/macro_bot.py` para economia, `micro_bot.py` para combate) | | **Um modelo de chat com acesso à web** | Peça que leia primeiro [`https://war3ai.com/llms-full.txt`](https://war3ai.com/pt/llms-full.txt) — toda a documentação do site está nesse único arquivo | | **Chat na web sem acesso à internet** | Cole o manual, o [`api.json`](https://war3ai.com/pt/api.json) e um arquivo de exemplo depois do seu prompt | | **Um modelo local** (LM Studio, Ollama) | Igual ao anterior. Recomenda-se uma janela de contexto de 32K tokens ou mais; caso contrário, o manual e a referência da API não cabem | Se você quer uma técnica profissional específica, cole também a receita correspondente das [Receitas de jogadas profissionais](https://war3ai.com/pt/docs/cookbook/). ## 2. Copie este prompt Troque "A estratégia que eu quero", no final, pelas suas próprias palavras — quanto mais específico, melhor: ```text Você vai escrever uma IA (em Python) para Warcraft III 1.27. Use apenas os métodos de Game listados em api.json; não invente métodos que não existem. Siga o estilo de rush_bot.py: herde de openwar3.Bot e implemente on_start(g) e on_tick(g). Regras: - on_tick é chamado cerca de 5 vezes por segundo e precisa ser rápido (não use sleep dentro dele). - Valores que não puderam ser lidos são None, não 0 — verifique antes de usar. - Comandos retornam um recibo (Receipt); `if r:` significa "o engine aceitou"; quando não foi aceito, `r.reason` diz o motivo (falta comida, falta ouro, alvo não visível, você já tem este herói…) — tente de novo no próximo tick ou mude de abordagem. - Para atacar um inimigo específico, use g.attack(unidades, inimigo); o inimigo precisa estar no campo de visão — os que você não vê são rejeitados. - Um herói morto precisa ser revivido com g.revive(altar); não dá para treinar outro. - Para construir, use g.build_near(trabalhador, código_da_construção, x, y): ele encontra um lugar que caiba, acompanha o resultado e não faz nada quando falta dinheiro. - Para saber "o que acabou de acontecer" (quem morreu, quem levou dano, herói subiu de nível, item caiu), implemente on_event(g, ev). - Não repita o mesmo comando para a mesma unidade a cada tick (isso interrompe o que ela está fazendo); dê ordens às unidades "ociosas". - Só mande para a coleta trabalhadores de idle_workers(). No máximo 5 trabalhadores por mina de ouro. - Coloque só 1 unidade por vez na fila de treino (enfileire a próxima quando g.queue(construção) estiver vazia); para saber se travou por comida, veja g.production(construção).blocked. - Quando um tick envia muitos comandos, coloque-os dentro de with g.batch(): (espera a thread do jogo uma única vez). - Para escolher quem atacar, use g.time_to_kill(meu_grupo, inimigo) (considera vantagens de tipo e armadura); para escolher aonde ir, use g.path_distance (retorna None se não houver caminho). - No modo justo você só vê o que está no campo de visão; para inimigos que você viu antes, use g.last_seen(). - Unidades são códigos de quatro caracteres (Camponês humano hpea, Soldado hfoo, Quartel hbar…), magias são strings de ordem (thunderbolt Storm Bolt, blizzard Nevasca, holybolt Luz Sagrada…, tabela completa em data/order-ids.txt), e aprender habilidades usa códigos de quatro caracteres (AHtb, AHbz…). A estratégia que eu quero: ``` ### Como descrever sua estratégia com clareza O que os modelos mais têm dificuldade é com pedidos vagos. Em vez de "jogue de forma mais agressiva", estas informações são muito mais úteis: - **Raça e heróis**: qual herói primeiro e a ordem de habilidades (por exemplo, Arquimago: Elemental da Água, Nevasca, Elemental da Água…). - **Ordem de construção**: com quantos camponeses construir o Quartel, quando subir de tier, quantos Quartéis. - **Composição do exército**: Soldados + Fuzileiros? Com quantas unidades sair para atacar? - **Condições de ataque e recuo**: quantas unidades antes de atacar, com quanto de vida o herói recua, voltar para casa e reconstruir depois de perdas pesadas. - **Creeping**: creepar ou não, quando (depois de anoitecer?), só acampamentos que você consegue vencer? - **Justo ou não**: se você pretende entrar na Arena depois, diga "use só inimigos visíveis no campo de visão". ## 3. Rode Salve o código do modelo como `brains/my_bot.py` e então: ```bash python tools/play.py --bot brains/my_bot.py --race 1 --difficulty 2 ``` Para ver os resultados mais rápido, adicione `--speed 200` (velocidade 2×). ## 4. Peça ajustes - **Deu erro**: cole o **erro inteiro**, exatamente como apareceu, de volta para o modelo e diga "corrija isso". - **Joga mal**: descreva **o que você viu no jogo**, não a causa que você imagina. Por exemplo: "o herói fica parado em casa", "as unidades chegam uma de cada vez", "os camponeses se amontoam em uma mina só". - **Quer adicionar uma tática nova**: adicione uma coisa de cada vez, jogue uma partida para confirmar que nada quebrou e só então adicione a próxima. > **Nota** > > Um agente de programação que roda comandos sozinho pode assumir também os passos 3 e 4: jogar uma partida, ler os logs e os recibos, mudar o código e rodar de novo. Para saber como dar a ele informação suficiente, veja [Iteração autônoma do agente](https://war3ai.com/pt/docs/agent-loop/). ## 5. Problemas comuns | Sintoma | Causa mais provável | |---|---| | Nada se mexe | Número de instância errado (`--inst`), ou a partida ainda não começou | | Camponeses não mineram | Ordens foram dadas a camponeses que já estavam trabalhando; só designe a partir de `idle_workers()` | | As casas nunca são construídas | Use `build_near` em vez de coordenadas fixas; veja se o `reason` do recibo diz que falta dinheiro | | O herói nunca sai | Veja o recibo do `train`: falta comida? Ou o herói morreu (use `revive`)? | | O herói não lança magias | A habilidade não foi aprendida (`learn`) ou falta mana; depois de lançar, veja se `cooldown()` mostra a recarga | | Unidades se contorcem tick após tick | Os comandos estão sendo repetidos a cada tick; só dê ordens às unidades ociosas | | Nenhuma unidade sai e o ouro só aumenta | Travou por comida: veja `g.production(quartel).blocked` | | O modelo usou métodos que não existem | Reforce no prompt "use só métodos que estão em api.json" e cole o api.json completo | ## Indo além - Todas as APIs e o mecanismo por trás de cada uma: [referência da API](https://war3ai.com/pt/api/); - O cérebro de referência (`brains/xwar3/strategy`) é uma IA completa que expande, creepa e ataca. Você pode pedir ao modelo que leia o código para tirar ideias, mas ele usa APIs de nível mais baixo, então não é recomendável copiá-lo diretamente; - Quando você entrar na [Arena](https://war3ai.com/pt/arena/), só vai conseguir ver inimigos no campo de visão — adicione `--fair` desde já para se impor essa regra, e não vai precisar mudar nada depois. --- # Iteração autônoma do agente > Deixe um agente de programação jogar partidas, ler os resultados, mudar o código e jogar de novo sozinho. Ele precisa de um comando que rode sem supervisão, de um relatório de partida estruturado e de um objetivo claro. Em [Escreva um Bot com um LLM](https://war3ai.com/pt/docs/ai-bot/), o passo "jogar uma partida → observar → contar ao modelo" fica por sua conta. Um agente de programação que executa comandos (Claude Code, Codex, o modo agente do Cursor etc.) pode assumir esse passo também e fechar o ciclo: ```text muda o código ──► joga uma partida (sem supervisão) ──► lê o relatório ──► acha o ponto que mais pesa ──┐ ▲ │ └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ ``` Para esse ciclo convergir de verdade, o agente precisa de três coisas. ## 1. Um comando que rode sem supervisão ```bash python tools/play.py --bot brains/my_bot.py --speed 200 --minutes 10 --fair ``` - `--minutes` garante que a partida termina (em minutos de relógio real), então o agente nunca fica preso numa partida; - `--speed 200` usa velocidade 2× para ganhar tempo — mas dentro do Bot, **espere pelo relógio do jogo** (`g.clock()`), não com um `sleep` de relógio real; - `--fair` faz o Bot seguir as regras da Arena desde o primeiro dia: ele só vê o que está no campo de visão; - Quando a execução termina, o terminal imprime o motivo do fim, por exemplo `我方没有单位了` ("não temos mais unidades") ou `到时间了` ("acabou o tempo"); o que o próprio Bot imprime com `print` também aparece no terminal. > **Atenção** > > A simulação do jogo para enquanto a janela está minimizada. Faça o agente iniciar o jogo no modo janela padrão e garanta que ele não use o mesmo número de instância que você está usando (`--inst`). ## 2. Um relatório de partida estruturado A saída do terminal é feita para humanos. O que o agente deve ler é um JSON: o que aconteceu, o que não deu certo e por quê. O SDK já entrega toda a matéria-prima — os recibos trazem códigos de motivo, e o fluxo de eventos traz produções concluídas e baixas. Basta juntar tudo: ```python title="recorder.py" import collections, json, time from openwar3 import Bot class Recorder(Bot): """Adiciona um relatório de partida a um Bot. Herde dele e chame super() nos seus próprios on_start / on_event.""" def on_start(self, g): self.rejects = collections.Counter() # "train hfoo: rejected(人口不够)" -> contagem (= falta comida) self.timeline = [] # [segundos de jogo, categoria, código de quatro caracteres]: treino / pesquisa / construção / melhoria concluídos self.lost = collections.Counter() # o que nós perdemos self.killed = collections.Counter() # o que nós matamos def check(self, r, what): """Envolve um comando para registrar motivos de rejeição: 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: # o jogo pode já ter fechado; se não der para ler, deixa para lá 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) ``` Perguntas que esse relatório responde: | Sinal | De onde vem | O que revela | |---|---|---| | Motivos de rejeição mais frequentes | `reason` / `verdict` do recibo | Travado por comida o tempo todo (3), dando ordens sem ter dinheiro (8 / 9), atacando alvos na névoa de guerra (1001), treinando um herói que já morreu (221) | | Linha do tempo de produção | Eventos `production.done` (com os segundos de jogo gastos) | Em que segundo saiu o primeiro herói, em que segundo você subiu de tier, se o Quartel ficou produzindo sem parar; compare com as aberturas de jogadores profissionais | | Baixas dos dois lados | Eventos `unit.died` | Se você está entregando unidades o tempo todo, quantas vezes o herói morreu, se o creeping compensou | | Motivo do fim | `on_end(g, reason)` | `我方没有单位了` ("não temos mais unidades") = derrota; `到时间了` ("acabou o tempo") = ainda sem vencedor | | Exército e recursos finais | Um snapshot lido no `on_end` | Ouro acumulado sem gastar = a produção não acompanha; poucos trabalhadores = a economia não decolou | > **Nota** > > Detectar vitória e derrota por programa é um dos experimentos de fundação da [Arena](https://war3ai.com/pt/arena/) e ainda está no roadmap. Por enquanto, você pode tratar `我方没有单位了` ("não temos mais unidades") como derrota e aproximar a vitória como "todas as construções inimigas visíveis foram destruídas". ## 3. Um objetivo claro e algumas restrições Entregue o texto abaixo ao agente, ajustado ao seu objetivo: ```text Objetivo: fazer brains/my_bot.py vencer com consistência o computador no nível "Fácil" em Echo Isles (Humanos contra raça aleatória). A cada rodada: 1. Rode python tools/play.py --bot brains/my_bot.py --speed 200 --minutes 10 --fair 2. Leia a saída do terminal e o run_*.json mais recente: motivo do fim, linha do tempo de produção, motivos de rejeição mais frequentes, baixas dos dois lados 3. Encontre O problema que mais afeta o resultado e mude só esse ponto; escreva num comentário do código o motivo da mudança e os dados em que ela se baseia 4. Volte ao passo 1. Se não houver melhora por 3 partidas seguidas, pare e me mostre o relatório e a sua avaliação Restrições: - Use apenas métodos de docs/api.json; não invente APIs - Não repita o mesmo comando para a mesma unidade a cada tick; só dê ordens a unidades ociosas - Mantenha --fair (use só inimigos visíveis no campo de visão) - Antes de mudar o código, rode python tools/run_tests.py para confirmar que os exemplos não quebraram ``` ## Hábitos que fazem o ciclo convergir mais rápido - **Mude uma coisa por vez.** Se você muda três coisas ao mesmo tempo e ganha, não sabe qual ajudou; se perde, não sabe qual estragou. - **Jogue partidas suficientes para comparar.** O mesmo confronto tem muita aleatoriedade; duas partidas só revelam diferenças muito grandes. Avalie "se melhorou" pela tendência de, no mínimo, várias partidas. - **Corrija as rejeições antes de ajustar a estratégia.** O motivo de rejeição mais frequente nos recibos costuma ser o maior bug do Bot. - **Escreva seu raciocínio nos comentários.** O agente da próxima rodada (ou a próxima conversa) consegue ler nos comentários por que o código está assim e não desfaz as correções. - **Testes offline como rede de segurança.** Escreva testes unitários para a lógica principal que não precisem do jogo aberto (os testes dos Bots de exemplo ficam em `brains/examples/tests/`) e faça o agente rodá-los depois de cada mudança. --- # LLM como coach de estratégia > Entregue a um LLM as decisões "para o que economizar, onde colocar trabalhadores, atacar ou segurar neste minuto" e deixe a camada de regras apenas executar e vetar. O cérebro de referência já funciona assim; esta página explica o padrão e as armadilhas. Quando seu Bot passa de certo tamanho, você percebe que as regras de economia vão sendo empilhadas umas sobre as outras: uma regra para quantos lenhadores, uma para 5 trabalhadores por mina, uma para cortar a madeira pela metade quando sobra, uma para mandar mais gente ao ouro quando falta ouro e sobra madeira… Cada regra está certa isoladamente, mas juntas produzem situações como "a mina está sem trabalhadores enquanto todos os camponeses cortam árvores" — situações pelas quais **nenhuma regra é responsável**. Julgamentos do tipo "olhar o quadro geral e definir prioridades" nunca se encaixaram bem em `if / else`, mas são exatamente o que LLMs fazem bem. O cérebro de referência (`brains/xwar3/strategy/brain/coach.py`) usa as camadas abaixo. ## Camadas ```text LLM (conselheiro) Uma vez a cada 20 segundos de jogo, assíncrono, nunca bloqueia um tick Entrada: um snapshot de uma página da partida (recursos, comida, distribuição de camponeses, minas, tipos de unidade, tecnologia, heróis, informações do inimigo, eventos recentes) Saída: JSON estrito — um diagnóstico de uma frase + divisão de trabalhadores + o que produzir primeiro + a postura deste minuto + o que evitar │ ▼ lista de permissões + limites mín./máx. + veto Camada de regras (Bot, a cada tick) Traduz o conselho em "vieses" sobre capacidades existentes: divisão de trabalhadores, prioridade de construção / treino, postura de ataque │ ▼ Camada de execução (SDK / camada reflexa) Dá ordens, lê recibos, faz micro ``` ## Contrato de saída Faça o modelo produzir JSON com um conjunto fixo de campos — sem acrescentar nem remover nenhum: ```json { "diagnosis": "Uma frase: o maior problema da partida, que precisa ter base nos dados de entrada", "workers": { "gold": 10, "lumber": 6 }, "priority": ["hpea", "hhou", "hbar"], "posture": "creep", "avoid": ["Não pesquise Placas de Ferro primeiro quando faltar madeira"] } ``` | Campo | Como a camada de regras usa | Limites do cérebro de referência | |---|---|---| | `workers` | Número alvo de trabalhadores no ouro e na madeira | Ouro 2 ~ 25, madeira 1 ~ 20; a soma não pode passar do total de camponeses | | `priority` | Ordem de prioridade de treino / construção / pesquisa | No máximo 4; só aceita códigos de quatro caracteres que aparecem na tabela de "códigos permitidos" | | `posture` | A postura deste minuto | Precisa ser um de `attack` `defend` `creep` `expand` `recover` `hold` | | `avoid` | O que não fazer neste minuto | No máximo 2 | | `diagnosis` | Usado só para logs e para exibição no console | — | Use um prompt por raça, cobrindo apenas as escolhas específicas daquela raça (a construção cooperativa e a Milícia dos Humanos, as Tocas dos Orcs, a Mina de Ouro Assombrada dos Mortos-vivos, a Mina de Ouro Enredada dos Elfos Noturnos…). Coloque as regras comuns em uma parte compartilhada — não copie tudo quatro vezes. ## Quatro restrições rígidas O cérebro de referência aprendeu cada uma delas do jeito difícil: 1. **O conselheiro nunca dá ordens diretas a unidades.** Ele não enxerga o que acontece na escala de 150 ms e alucina. Ele só muda metas e prioridades; quem vai para onde e quem ataca o quê continua sendo decidido pela camada de regras e pela camada reflexa — o comando só pode ter um dono. 2. **Assíncrono.** Uma consulta ao conselheiro leva cerca de 1 segundo e roda em uma thread em segundo plano; vale o resultado mais recente, e ela **nunca bloqueia um tick**. Se o modelo não subiu, estourou o tempo ou respondeu lixo, aja como se essa camada não existisse e volte às regras puras. Conselhos antigos demais (mais de 3 intervalos) também não são usados. 3. **Lista de permissões + limites.** Todo campo precisa corresponder a uma capacidade existente, e os valores numéricos são limitados a uma faixa razoável. Conteúdo não reconhecido é **contado e depois descartado**, não ignorado em silêncio. 4. **Conte tudo.** Quantas vezes você perguntou, quantas deram certo, quantas estouraram o tempo, quantas foram limitadas, quantas vezes cada campo foi adotado — publique tudo isso junto com a última entrada enviada ao modelo. Caso contrário, "essa camada serve para alguma coisa?" vira uma pergunta sem resposta. > **O que degrada com segurança é o que mais degrada em silêncio** > > O conselheiro foi projetado para que "falha = agir como se essa camada não existisse"; por isso, quando o serviço do modelo não está no ar, o Bot se comporta exatamente como as regras puras e nada parece errado visto de fora. O cérebro de referência já passou um dia inteiro com o conselheiro sem conseguir se conectar em todas as 6 instâncias, sem que ninguém percebesse. Sempre publique "horário da última chamada bem-sucedida" e "motivo da última falha" — é exatamente para isso que existe a página "coach de estratégia" no [console Farsight](https://war3ai.com/pt/docs/console/). ## Implementando no seu próprio Bot Abaixo está um esqueleto mínimo que funciona com qualquer API compatível com OpenAI (LM Studio, Ollama ou uma API na nuvem) e usa só a biblioteca padrão: ```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 / qualquer serviço compatível com OpenAI MODEL = "your-model" POSTURES = {"attack", "defend", "creep", "expand", "recover", "hold"} SYSTEM = """Você é um coach de macro de Warcraft III. Cuida só de economia e estratégia, não de micro. Produza apenas JSON, com campos fixos: {"diagnosis": uma frase, "workers": {"gold": inteiro, "lumber": inteiro}, "priority": [códigos de quatro caracteres, no máximo 4, só os de allowed], "posture": um dos seis, "avoid": [no máximo 2]} Baseie tudo nos dados da partida que você recebeu; não invente nada que não esteja nos dados.""" 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 # segundos de jogo: decisões de macro acontecem na escala de minutos, não precisa perguntar a cada 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: # lê o snapshot na thread principal; a thread em segundo plano nunca toca em 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 # conta e descarta — nunca em silêncio posture = "hold" self.plan = {"gold": min(25, max(2, int(p["workers"]["gold"]))), # limita "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: # timeout / resposta lixo: age como se a camada não existisse 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 {} # não usa conselhos antigos demais # ↓ camada de regras: com plan vazio, segue as regras padrão; com plan, só ajusta divisão, prioridades e postura — as ordens em si continuam decididas pelas regras ... ``` ## Como escolher o modelo | Situação | Recomendação | |---|---| | Local, precisa ser rápido | Modelos MoE (que ativam só uma pequena parte dos parâmetros por chamada) são muito mais rápidos que modelos densos do mesmo tamanho. O cérebro de referência usa Qwen3.6-35B-A3B (LM Studio, Q4): mediana de **1.09 s**, a mais lenta 1.45 s, e 5/5 saídas passam direto por `json.loads` | | Modelos locais que "pensam" | **Você precisa desligar a seção de raciocínio**, senão todos os tokens vão para o raciocínio e nenhum JSON sai. O LM Studio ignora `/no_think`; o cérebro de referência passou a usar `/v1/completions`, monta o ChatML por conta própria e preenche antecipadamente um `` vazio seguido de um `{` | | Modelos na nuvem | A latência costuma ser maior, mas essas camadas já são assíncronas por design; decisões de macro se medem em minutos, então alguns segundos de latência são aceitáveis | > **Nota** > > O mesmo modelo também pode dar voz às suas unidades: veja [Balões de fala e modelos locais](https://war3ai.com/pt/docs/speech/). Se você quer que o modelo dê ordens diretamente a cada tick (em vez de atuar como conselheiro), aguarde o gateway JSON da [Arena](https://war3ai.com/pt/arena/). --- # LLM usando ferramentas (MCP) > tools/war3_mcp.py é um servidor MCP. Conecte-o ao Claude Code, ao Claude Desktop ou a qualquer cliente com suporte a MCP, e o LLM passa a ver a partida, dar comandos, falar com o jogador na tela, perguntar ao jogador com cartões e tirar capturas de tela diretamente, sem escrever código antes. `tools/war3_mcp.py` é um **servidor MCP** (stdio). Claude Code, Claude Desktop, frameworks de agentes para modelos locais — conecte-o a qualquer cliente com suporte a MCP, e o LLM passa a **diretamente** ver a partida, dar comandos, falar com o jogador na tela do jogo, fazer perguntas ao jogador e tirar capturas de tela, sem escrever código antes. Além de escrever Bots, aconselhar e dar voz às unidades, esta é mais uma forma de conexão: **o próprio LLM é quem usa as ferramentas**. ## Conectando ```bash claude mcp add war3 -- python \tools\war3_mcp.py --inst 9 # Claude Code; troque pela sua pasta do openwar3 ``` Em outros clientes, escreva a configuração neste formato: ```json {"mcpServers": {"war3": {"command": "python", "args": ["\\tools\\war3_mcp.py", "--inst", "9"]}}} ``` A conexão com o jogo só acontece na primeira chamada de ferramenta, então o jogo pode ser aberto depois; se o jogo for fechado e aberto de novo, a próxima chamada reconecta sozinha. Acrescente `--role` para limitar o que o LLM pode fazer: | Papel | O que pode usar | |---|---| | `dev` (padrão) | Todas as ferramentas, inclusive `war3_jass` | | `player --player N` | Só comanda as unidades do jogador N e só enxerga a visão dele (modo justo); sem JASS | | `observer` | Só leitura; não pode desenhar na tela nem fazer unidades falarem; o runtime rejeita direto os comandos que ele enviar | O papel `player` tem as mesmas limitações do [gateway](https://war3ai.com/pt/docs/gateway/): não pode encerrar a partida, mudar a velocidade nem pausar, não tem acesso às APIs que mostram as cartas dos outros, e consultas que levam número de jogador só podem consultar o próprio jogador. Alguns limites: o resultado de uma ferramenta tem no máximo 200 mil caracteres; o que passar disso é cortado, com uma dica de como restringir a consulta; `war3_ask_player` espera no máximo 120 segundos; o `scale` da captura de tela fica entre 0.1 e 1. ## Ferramentas | Ferramenta | O que faz | |---|---| | `war3_overview` | Resumo da partida numa página: tempo, recursos, comida, contagem de cada tipo de unidade nossa, heróis (vida, mana, nível, recargas), tipos de unidades inimigas visíveis, produção. **Chame esta primeiro** | | `war3_units` | Lista de unidades (`owner` aceita me / enemy / creep / all, `types` filtra); o `addr` serve para dar comandos | | `war3_events` | O que aconteceu desde a última chamada: mortes, subidas de nível, feitiços, produção concluída, chat, o jogador clicou num botão… (por padrão, remove alguns tipos que inundam o log) | | `war3_call` | Chama qualquer API pública (`move`, `attack_move`, `train`, `build`, `cast`, `learn`, `ui.button`, `canvas.text`…); unidades são escritas como `{"unit": addr}` | | `war3_api` | Consulta a API: busca nomes e descrições por palavra-chave | | `war3_toast` / `war3_say` | Uma linha de texto no alto da tela / uma frase sobre a cabeça de uma unidade | | `war3_ask_player` | Mostra alguns cartões de escolha no meio da tela, espera o jogador clicar e retorna qual foi escolhido (pode pausar o jogo) | | `war3_screenshot` | Captura da tela do jogo (PNG; funciona mesmo com a janela coberta, sem roubar o foco) | | `war3_jass` | Executa um trecho de JASS (só para dev; alterações no mundo só em partida solo) | O que dá para fazer: - **Parceiro de jogo / coach**: `war3_overview` para ver a partida, `war3_toast` para dar conselhos na tela; - **Perguntar ao jogador durante a partida**: `war3_ask_player` mostra três cartões, e o que o jogador clicar é o que vale; - **Narração**: `war3_events` para ler o que aconteceu, `war3_say` para as próprias unidades contarem; - **Comandar uma tropa diretamente**: papel `player` + `war3_call`, só com as próprias unidades; - **Ajustar a interface olhando a imagem**: `war3_screenshot` tira uma captura, e o modelo confere se os botões que desenhou estão no lugar certo. ## Uma conversa típica ```text Você: Veja como está a partida e depois me pergunte na tela: próximo passo é expandir, fazer mais tropas ou subir de tier? → war3_overview {} ← Resumo da partida: tempo de jogo, ouro 500, comida 10/12, nossas htow 1 · hpea 5 · Hpal 1, nenhum inimigo visível, nada em produção → war3_ask_player {"question": "Próximo passo?", "options": ["Mais tropas", "Expandir", "Subir de tier"], "pause": true} ← {"picked": 1, "option": "Expandir"} Modelo: Você escolheu expandir. Primeiro vou usar war3_units para achar um camponês ocioso e depois ver onde fica a mina de ouro mais próxima… ``` ## Medições 2026-09-25: - Nosso próprio cliente MCP conectado a uma partida real, 7/7: handshake → listar ferramentas (10) → `war3_overview` (`htow` 1, `hpea` 5) → `war3_units` → `war3_toast` → `war3_screenshot` (PNG de cerca de 200 mil bytes) → `war3_ask_player` (três cartões, clique simulado no segundo → `{"picked": 1, "option": "开矿"}`, ou seja, “expandir”). - Conectado de verdade no Claude Code 2.1: ele mesmo iniciou o servidor e fez o handshake, com status `connected`, e as 10 ferramentas apareceram na lista de ferramentas dele como `mcp__war3__*`. ## Implementação - JSON-RPC 2.0 separado por quebras de linha (`initialize` / `tools/list` / `tools/call` / `ping`), versão do protocolo 2025-06-18, compatível com 2025-03-26 e 2024-11-05. - Erros de ferramenta vão no resultado, como manda o MCP (`isError: true`), sem desconectar. - Usa a mesma lista de permissões por papel, o mesmo formato de parâmetros de unidade e o mesmo “resumo da partida” do [gateway](https://war3ai.com/pt/docs/gateway/). - Os logs vão para stderr; stdout só tem o protocolo. --- # Modelo mental > Snapshots, comandos, recibos, eventos, ticks e lotes. Entenda esses seis conceitos e você vai entender por que a API tem esse formato e como escrever código rápido. ## Snapshots: leitura sem espera A cada **50 ms**, o runtime coleta o mundo inteiro na thread do jogo e grava tudo em memória compartilhada. O que `g.snapshot()` entrega é um mundo **completo e consistente**: - 16 slots de jogador: ouro, madeira, comida, limite de comida, total coletado, raça; - Até 1024 unidades: tipo, dono, coordenadas, vida / mana (com os máximos), ordem atual e alvo da ordem, **o que ela está atacando de fato** (alvo da tarefa), nível / XP / pontos de habilidade do herói e a visibilidade dela para cada jogador; - Até 256 detalhes de unidade: 12 habilidades (nível, recarga restante), 8 buffs, 6 espaços de inventário; - Itens no chão, árvores (atualizadas a cada 2 segundos), tabela de produção (progresso de treino / pesquisa / construção / melhoria), relógio do jogo, hora do dia no jogo. Ler um snapshot leva cerca de **0.4 ms** (parse em Python), sem esperar a thread do jogo. Então: **leia à vontade**. APIs como `g.units()`, `g.my_army()`, `g.cooldown()` e `g.inventory()` tiram tudo do mesmo snapshot, então chamá-las muitas vezes em um tick custa pouco. > **Dica** > > O período de publicação é ajustável: `g.set_publish_period(ms)`, de 16 a 1000 ms. Uma coleta leva cerca de 0.5 ~ 0.9 ms na thread do jogo, então até 33 ms funciona. Há um único valor compartilhado pela máquina inteira; vale a última escrita. ## Comandos: escrita, cerca de um frame `g.move / attack / gather / build / train / cast …` são entregues à thread do jogo para execução. O runtime executa em lotes os comandos enviados pelos clientes dentro do **despacho de eventos** da thread do jogo, então um comando espera cerca de **um frame** (cerca de 0.1 ms quando cai num grupo de eventos; caso contrário, espera o próximo despacho). - Comandos aceitam **uma unidade ou uma lista**; as unidades de uma lista recebem a ordem no mesmo frame; - Adicionar `queue='after'` equivale ao Shift: faz isto depois de terminar o que está fazendo; - Você pode passar os objetos de unidade que pegou do snapshot; o SDK confere a identidade pelo **par de handles** (endereços são reutilizados por unidades novas, handles não). ## Recibos: todo comando tem um ```python r = g.build(worker, "hbar", x, y) if r: # o engine aceitou ... else: r.reason # 'rejected(金不够)' (= falta ouro) r.verdict # 8 r.exec_us # quantos microssegundos este comando levou na thread do jogo ``` Os recibos são lidos **no mesmo frame**: a ordem da unidade antes e depois do comando, o valor de retorno da função do engine e o código de motivo da verificação de viabilidade. Um recibo responde "o engine aceitou este comando e, se não, por quê", mas **não responde** "no fim deu certo?" — para isso, olhe os snapshots e os eventos. Para todos os códigos de status e códigos de motivo, veja [Recibos e códigos de motivo](https://war3ai.com/pt/docs/reason-codes/). ## Eventos: o que aconteceu `on_event(g, ev)` roda antes de cada `on_tick` e entrega a você, um por um, todos os eventos desde o último tick: | Evento | Significado | |---|---| | `unit.appeared` / `unit.died` / `unit.removed` | Uma unidade apareceu, morreu ou sumiu (entrar numa mina de ouro, ser convertida ou um cadáver se decompor também contam como sumir — não é o mesmo que morrer) | | `unit.damaged` / `order.changed` / `owner.changed` | Perdeu vida, mudou de ordem, mudou de dono | | `hero.levelup` | Um herói subiu de nível | | `item.appeared` / `item.removed` | Um item no chão apareceu, foi pego ou foi usado | | `damage` | Nível de engine: **cada golpe**. Unidade de origem, tipo de ataque, tipo de dano, vida realmente perdida, dano antes da armadura | | `killed` | Nível de engine: este golpe a matou, com quem matou | | `production.done` | Treino / pesquisa / construção / melhoria concluído, com o código de quatro caracteres e os segundos de jogo gastos. Também emitido para os adversários | | `spell.cast` | Uma unidade lançou uma habilidade: código de quatro caracteres da habilidade, nível, recarga em segundos, ponto de lançamento | | `message` | Apareceu uma linha numa caixa de mensagens da tela: dicas do jogo (“Você precisa de mais fazendas”), chat (`.chat` traz quem falou e o conteúdo), mensagens do sistema | | `selection.changed` / `player.left` | A seleção do jogador local mudou / um jogador saiu ou foi removido por derrota | | `game.started` / `game.ended` | Uma nova partida começou / saída da partida | Eventos de entrada, como cliques em botões do canvas, teclas de atalho e cliques no chão, estão em [Interface e entrada](https://war3ai.com/pt/docs/ui-input/). > **Atenção** > > O fluxo de eventos é **global**: as produções concluídas dos adversários e as mortes de creeps estão todas nele. Filtre por `ev.owner` ou pelo handle da unidade. ## Ticks: o ritmo do Bot Por padrão, `on_tick` é chamado 5 vezes por segundo (relógio real). O custo de um tick é basicamente só a sua própria computação: snapshots não têm espera e comandos levam cerca de um frame. Se um tick passar do período, o próximo é adiado automaticamente — eles nunca se acumulam. - **Na velocidade 2×, não espere pelo relógio real.** Para esperar 3 segundos de jogo, observe `g.clock()` avançar 3 — não use `sleep(1.5)`. - **Não use `sleep` dentro do `on_tick`.** Se precisar "fazer daqui a pouco", anote o tempo de jogo atual e confira de novo no próximo tick. ## Lotes: dezenas de comandos, uma espera Quando um tick envia muitos comandos, coloque-os dentro de `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") # o lote inteiro é enviado quando o bloco termina: executado no mesmo frame, esperando a thread do jogo uma única vez ``` - Comandos dentro do bloco retornam `Pending`, que vira um recibo quando o bloco termina; lê-lo antes disso lança um erro; - Se uma exceção for lançada dentro do bloco, **o lote inteiro é descartado** (meio conjunto de comandos é mais perigoso do que nenhum); - Medido com 8 movimentos: 68 ~ 99 ms um por vez, **6.5 ~ 10 ms** em lote. A mesma ideia vale para consultas: `g.can_do_many([(u, code), ...])` e `g.tech_many([...])` perguntam sobre muitas coisas de uma vez. ## Lendo o que você acabou de escrever No mesmo tick, o snapshot ainda não reflete os comandos que você acabou de enviar (ele só alcança na próxima publicação). Duas partes da lógica podem acabar disputando o mesmo trabalhador: uma acabou de mandá-lo construir uma fazenda, enquanto a outra olha o snapshot e acha que ele ainda está ocioso. `g.order_of(u)` resolve isso: até o snapshot alcançar, ele usa a nova ordem do recibo. **Para decidir se uma unidade está ociosa, use `g.order_of(u)`, não `u.order`.** `g.idle_workers()` já exclui os trabalhadores que receberam uma tarefa neste tick. ## Faixas de latência | Faixa | Canal | Latência | Usado para | |---|---|---|---| | 0 | Snapshot enviado + fluxo de eventos | Cerca de 0.4 ms por leitura; dados novos a cada 50 ms | Todas as APIs de "observação" | | 1 | Via rápida | Cerca de 1 frame; mediana de 0.06 ms com 6 processos em paralelo | Todos os comandos e consultas (padrão do SDK) | | 2 | Canal de controle | 20 ~ 40 ms | Plano B e algumas operações de interface (velocidade do jogo, balões de fala, mensagens) | | 3 | [Gateway](https://war3ai.com/pt/docs/gateway/) (WebSocket / JSON) | Faixa 1 + cerca de 1 ms | Qualquer linguagem, navegadores, LLMs, programas em outra máquina | Cada API na [referência da API](https://war3ai.com/pt/api/) indica qual faixa ela usa. --- # As quinze regras > Cada uma foi aprendida na prática, em partidas reais. Confira a lista ao escrever um Bot e você economiza a maior parte do tempo de investigação. > **Dica** > > Entregue esta página ao LLM junto com o [`api.json`](https://war3ai.com/pt/api.json), e o Bot que ele escrever vai tropeçar muito menos. ## Ler o estado ### 1. Sem leitura é `None`, não 0 `resources()`, `time_of_day()`, `production()` e `cooldown()` podem retornar `None` (carregando, unidade sem detalhes, construção sem produzir…). Verifique antes de usar: ```python res = g.resources() if res is None: return ``` ### 2. Identifique unidades pelo handle, não pelo endereço Endereços são reaproveitados por unidades novas: um endereço antigo pode apontar para uma unidade recém-criada. Para lembrar de uma unidade entre ticks, guarde `u.handle` e recupere com `g.unit(handle)`. ### 3. O fluxo de eventos é global `production.done` e `unit.died` incluem eventos do adversário e dos creeps. Filtre por `ev.owner` (ou pelo handle da construção): ```python if ev.kind == "production.done" and ev.owner == g.me(): ... ``` ### 4. Trabalhadores dentro da mina de ouro não estão no snapshot No momento em que entra na mina, o trabalhador some do snapshot (`unit.removed`, ele não morreu). Para contar quantos há em cada mina, **mantenha a sua própria contagem**, não a recalcule pelo snapshot — senão você manda gente demais para uma mina cheia. ## Dar comandos ### 5. Recibo “aceito” ≠ concluído O motor aceita na hora até um ponto de construção dentro da floresta, e a falha só vem quando o trabalhador chega; habilidades podem ser interrompidas. Confira o efeito pelo snapshot e pelos eventos: para construir, use `build_near` (ele acompanha se a fundação aparece); para habilidades, veja se `g.cooldown()` entrou em recarga. ### 6. Não dá para atacar o que não se vê Comandos com alvo em inimigos na névoa de guerra são rejeitados com o código de motivo **1001**. Para perseguir um inimigo na névoa, use `attack_move` na última posição em que ele apareceu. ### 7. Só dê ordens a unidades ociosas Repetir o mesmo comando para a mesma unidade a cada tick a interrompe: os soldados ficam tremendo no lugar e o ciclo de coleta dos camponeses volta a zero. Para saber se ela está “ociosa”, use `g.order_of(u)` (que inclui o que você acabou de mandar neste tick), não o `u.order` do snapshot (o snapshot ainda não se atualizou). ### 8. Shift só “insere logo depois da atual” O motor não tem “adicionar ao final”: enviar B e C seguidos com `queue='after'` resulta em A, C, B. Para percorrer uma série de pontos em ordem, use `g.path(units, lista_de_pontos)`; para um trabalhador construir várias em sequência, use `g.build_queue(worker, plano)` — eles inserem em ordem inversa e cuidam disso por você. ### 9. Os comandos de um tick saem em um lote Enviar dezenas de comandos um a um significa esperar a thread do jogo dezenas de vezes; dentro de `with g.batch():`, a espera é uma só. ## Economia e produção ### 10. No máximo 5 trabalhadores por mina Mais que isso não aumenta a renda. A meta de trabalhadores acompanha o número de minas: 5 no ouro por mina, mais alguns na madeira. ### 11. Só 1 na fila de treino Encher as 7 vagas prende o dinheiro na fila (nos testes, 4 camponeses na fila do edifício principal prenderam 300 de ouro, e o início ficou bem mais lento). Coloque o próximo quando `g.queue(b)` esvaziar. ### 12. Comida travada: olhe a tabela de produção `g.production(b).blocked` = há algo na fila, mas não começou; quase sempre é falta de comida. Isso avisa um passo antes de “construir quando a comida estiver perto do limite”: você perde um monte de tropas numa luta, a fila trava na hora de repor, e você fica sabendo na hora. ### 13. O herói é único; com a fila do edifício principal ocupada, não dá para subir de tier - Se o herói morrer, só dá para usar `g.revive(altar)`; treinar de novo é rejeitado (221); reviver também exige comida (o herói ocupa 5). - Com algo na fila do edifício principal, não dá para melhorá-lo (código de motivo 185, “construção ocupada”). ## Tempo e espaço ### 14. Na velocidade 2×, não se guie pelo relógio de parede Para esperar 3 segundos de jogo, veja `g.clock()` subir 3, não use `sleep(1.5)`. Com o jogo acelerado, o relógio do motor anda mais rápido que o relógio de parede. ### 15. Em mapas de ilhas ou com florestas, não use distância em linha reta Para escolher acampamentos de creeps e expansões, use `g.path_distance(a, b)` (A* por terra, contornando florestas, penhascos e construções); se não houver caminho, retorna `None`. O ponto mais próximo em linha reta pode estar do outro lado do mar. ## Mais uma: escreva para o modo justo Com `--fair`, você só vê unidades, itens, produção e eventos dentro da sua visão — essa é a regra da Arena. Escreva para o modo justo desde já e, quando for para a [Arena](https://war3ai.com/pt/arena/), não vai precisar mudar nada. Veja [Modo justo](https://war3ai.com/pt/docs/fair-mode/). --- # Modo justo > Um cliente injetado no jogo consegue ler o mapa inteiro. O modo justo faz o Bot enxergar só o que está na sua visão — como um jogador humano, e como na regra da Arena. A capacidade de observação deste projeto vem do fato de que “o cliente guarda o estado de todos os jogadores”: o snapshot traz todas as unidades do mapa inteiro, inclusive inimigos na névoa de guerra. Isso é ótimo para depurar, mas injusto numa competição. O **modo justo** faz o SDK filtrar tudo pela sua visão: ```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) # usando Game diretamente run(MyBot, inst=5, fair=True) # ou deixando com o executor ``` ## O que é filtrado | Conteúdo | No modo justo | |---|---| | Unidades | Todas as suas + as unidades inimigas e neutras que o seu lado vê neste momento | | Itens no chão | Só os que estão na visão das suas unidades (visão de dia / de noite calculada separadamente, pela tabela de dados) | | Tabela de produção | Só construções visíveis (você não vê o que o adversário está treinando) | | Eventos | Os seus; os visíveis (ou vistos no último 1 segundo); o dano causado pelo seu lado | ## De onde vem a visão - Cada unidade no snapshot traz uma **máscara de visibilidade**: bit p = o jogador p a vê neste momento (só contam os jogadores 0 ~ 11 com unidades em campo; suas próprias unidades são sempre visíveis para você). `u.visible_to(g.me())` lê isso direto, sem espera. - Para qualquer ponto: `g.visible(x, y)` pergunta ao motor (visível / névoa de guerra / máscara preta), pela via rápida, cerca de um frame por chamada. Quando precisar checar muitas unidades num tick, use `u.visible_to()` do snapshot em vez de chamar `g.visible()` uma a uma. ## A memória dos inimigos: `last_seen` Um jogador humano lembra que “agora há pouco viu um grupo de Wolf Riders ali”. O SDK também lembra por você: a cada atualização do snapshot, registra as unidades inimigas e os creeps que o seu lado vê naquele momento (última posição, vida, tempo); quando vê um deles morrer, apaga o registro; ao trocar de partida, limpa tudo. ```python for u, t, age in g.last_seen(max_age=60): # inimigos vistos nos últimos 60 segundos de jogo print(u.type, u.x, u.y, f"há {age:.0f}s") heroes = [r for r in g.last_seen() if r[0].is_hero] # onde os heróis inimigos estavam da última vez camps = g.last_seen(owner="creep") # creeps já vistos ``` No modo justo, esta é a sua única “fonte de informação sobre o adversário” — como para um jogador humano. O modo normal também registra pela visão, então o mesmo código serve para os dois. ## Comandar como um jogador específico ```bash python tools/play.py --bot my_bot.py --player 1 --attach ``` `--player N` (ou `Game(player=N)`) faz o Bot comandar como o jogador N, e ele só pode comandar unidades do jogador N. Duas IAs se enfrentando são dois canais desses na mesma partida. > **No modo local, o jogo limpo é um acordo, não uma barreira de segurança** > > Na sua própria máquina, não há como impedir um programa de ler o mapa inteiro. `--fair` é uma restrição que você impõe a si mesmo; partidas de verdade são garantidas pelo processo árbitro da [Arena](https://war3ai.com/pt/arena/): o Bot nunca toca na memória compartilhada, só recebe as observações que o árbitro filtrou pela visão, só pode enviar ações, e cada ação tem a propriedade das unidades verificada antes. ## Por que ativar desde já - Quando você for para a Arena, a regra vai ser esta; escrevendo para o modo justo agora, você não vai precisar mudar uma linha depois; - Só sem a informação do mapa inteiro você descobre o nível real do seu Bot (hoje, o cérebro de referência depende muito da informação do mapa inteiro, como o ponto-alvo do capitão do computador — e isso é um bom teste); - Reconhecimento, memória e julgamento escritos no modo justo são a capacidade de IA que realmente tem valor. --- # Receitas de jogadas profissionais > A maior parte da vantagem de um jogador de alto nível vem de dezenas de pequenos hábitos. Esta página traduz, uma a uma, técnicas comuns dos profissionais em código do SDK — cada trecho pode ser colado direto no on_tick. Convenções: `g` é o `Game`, `home` é a nossa base principal (`g.my_buildings({"htow", "hkee", "hcas"})[0]`), `now = g.clock()`. Para detalhes das APIs, veja a [referência da API](https://war3ai.com/pt/api/); exemplos completos e executáveis estão em [Bots de exemplo](https://war3ai.com/pt/docs/examples/). > **Dica** > > Quando pedir a um LLM para adicionar uma técnica, cole para ele a receita correspondente junto com o código. Isso funciona muito melhor do que dizer "jogue mais como um profissional". ## I. Economia ### 1. Trabalhadores nunca ociosos, 5 por mina ```python for w in g.idle_workers(): # só designa os ociosos (dar nova ordem a quem está ocupado interrompe a coleta) 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]) ``` Anote você mesmo quantos mandou para cada mina (`crew`): trabalhadores dentro de uma mina de ouro não aparecem no snapshot. Exemplo completo em `hello_bot.py`. ### 2. Só 1 na fila, sem travar o ouro ```python for b in g.my_buildings({"hbar"}): if not g.queue(b): # só enfileira o próximo quando a fila esvaziar g.train(b, "hfoo") ``` ### 3. Nunca travar por comida ```python stuck = any(p.blocked for _b, p in g.all_production("me")) # na fila mas não começou = falta comida res = g.resources() if stuck or res["food_cap"] - res["food_used"] <= 6: g.build_near(builder, "hhou", home.x, home.y) ``` `blocked` avisa um passo antes de "quase cheio": quando você perde parte do exército numa luta e a fila trava na hora de repor, você fica sabendo na hora. ### 4. Ordem de construção + voltar à mina ao terminar (Shift para voltar a minerar) ```python spot = g.build_near(w, "hbar", home.x, home.y) if spot: g.gather(w, mine, queue="after") # volta a minerar ao terminar, sem precisar procurá-lo de novo no próximo tick ``` Um camponês construindo várias em sequência: `g.build_queue(w, [("hhou", x1, y1), ("hhou", x2, y2)])`. O ouro só é descontado quando a obra começa. ### 5. Momento de subir de tier, melhorias de ataque/armadura ```python if not g.queue(hall) and g.can_do(hall, "hkee") in (0, 220): # a sede só melhora com a fila vazia (senão 185) g.upgrade(hall, "hkee") p = g.production(hall) # progresso da subida de tier if p and p.kind == "upgrade": print(f"Faltam {p.remaining:.0f} segundos para a sede") 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. Abrindo expansão: escolha a mina mais próxima pela distância a pé ```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) # minas em ilhas retornam None -> ficam por último ``` ## II. Reconhecimento e informação ### 7. Veja o que o adversário está fazendo ```python for b, p in g.all_production("enemy"): # o que as construções inimigas visíveis estão treinando / pesquisando / melhorando print(b.type, p.kind, p.queue, f"{p.progress:.0%}" if p.progress is not None else "travada") ``` Combine com eventos: `ev.kind == "production.done" and ev.owner != g.me()` — o que o adversário acabou de produzir. ### 8. Lembre do que você já viu (névoa de guerra) ```python for u, t, age in g.last_seen(max_age=60): # inimigos vistos nos últimos 60 segundos de jogo (última posição e vida) ... hero_seen = [r for r in g.last_seen() if r[0].is_hero] # onde o herói inimigo foi visto pela última vez ``` No modo justo, esta é a sua única fonte de informação sobre o adversário, igual para um jogador humano. ### 9. Onde o computador vai atacar (só para a IA do computador) ```python plan = g.enemy_ai_plan(some_enemy_soldier) # para onde o capitão do computador está indo ``` O computador define o ponto-alvo antes de sair de casa — leve seu exército para lá com antecedência. ## III. Creeping ### 10. Creepar à noite ```python if g.is_night(): # 18h ~ 6h: os creeps dormem (você ataca primeiro sem ser cercado), a visão de todos diminui ... wait = g.seconds_until(18) # segundos de jogo até anoitecer (um dia tem 480 segundos) ``` ### 11. Só ataque acampamentos que você consegue vencer ```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) # quanto tempo para limpar este acampamento (estimativa grosseira) theirs = g.time_to_kill(camp, weakest_of_mine) or 1e9 # quanto tempo para eles matarem nossa unidade mais fraca if ours < theirs and g.reachable(center, camp[0]): g.attack_move(army, camp[0].x, camp[0].y) ``` Exemplo completo: `_maybe_creep` em `micro_bot.py`. ## IV. Micro ### 12. Focar fogo: ataque o que morre mais rápido, não o mais próximo ```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) ``` Só dê ordem às unidades que ainda não estão atacando o alvo (`current_target`), para não interromper as que já estão. ### 13. Recuar unidades feridas ```python for u in army: if u.hp < u.hp_max * 0.35: g.move(u, *toward(home, u, 500)) # recua 500 na direção de casa; não puxe a mesma unidade de novo em 3 segundos ``` Como detectar fogo focado: nos eventos `damage`, a mesma unidade levando golpes de várias origens num intervalo curto = ela está cercada. ### 14. Mantenha os heróis vivos, não entregue XP ```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")) # poção de cura: ache o número do espaço com inventory(h) ``` ### 15. Vantagens de tipo: a unidade certa no alvo certo ```python s = g.stats(u) best = max(enemies, key=lambda e: s.dps_vs(g.stats(e))) # fuzileiros contra grifos (perfurante vs. armadura leve ×2), grifos contra soldados (mágico vs. armadura pesada ×2) ``` A tabela de vantagens vem dos dados do jogo: `combat.damage_multiplier("pierce", "small") == 2.0`. ### 16. Flanqueio e caminhos: contorne as torres ```python route = g.walk_path(army_center, target) # pontos de virada do menor caminho por terra g.path(army, route, attack=True) # atacar-mover passando por cada ponto em ordem ``` Para evitar torres, marque a área em volta de cada torre como intransitável na grade de pathing antes de calcular a rota: ```python grid = g.grid().copy() for t in towers: grid.block_area(t.x, t.y, 800) # alcance da torre 700 + margem route = grid.path((army_x, army_y), (target.x, target.y)) ``` ### 17. Envie os comandos de um tick em um lote ```python with g.batch(): g.attack(melee, target_a) g.attack(ranged, target_b) g.move(wounded, *home_xy) g.cast(hero, "thunderclap") ``` Dezenas de comandos esperam a thread do jogo uma única vez (medido: 8 movimentos caíram de 68 ms para 6.5 ms). ### 18. Cerco: artilharia ataca o chão ```python g.attack_ground(mortars, tower.x, tower.y) # morteiros / demolidores atiram numa área (atrás das árvores, unidades invisíveis) ``` ## V. Heróis ### 19. Ordem de habilidades ```python SKILLS = {"Hamg": ["AHwe", "AHbz", "AHwe", "AHbz", "AHwe", "AHmt"]} # Elemental da Água, Nevasca… no nível 6 a suprema Teleporte em Massa info = g.hero_info(h) if info and info["skill_points"]: g.learn(h, SKILLS[h.type][learned_count]) # se for rejeitado (nível baixo demais para a suprema), espere o próximo nível ``` ### 20. A magia saiu mesmo? ```python r = g.cast(h, "thunderbolt", target=enemy_hero) # no próximo tick: if g.cooldown(h, "AHtb"): # em recarga = saiu de verdade; aceito ≠ lançado ... ``` ### 21. Comprar poções, voltar para casa ```python g.buy(shop, "phea") # o herói está parado ao lado da loja g.use_item(hero, slot, x=home.x, y=home.y) # Pergaminho de Portal da Cidade (uso de item em ponto) ``` ## VI. Revisão pós-jogo - Registre as decisões de cada tick no log (`print` vai para a janela de execução) e use `g.say(unidade, "Recuar")` para vê-las dentro do jogo; - Os eventos `production.done` trazem "quantos segundos levou" — monte sua própria linha do tempo de construção (em que segundo saiu o primeiro herói, em que segundo você subiu de tier) e compare com a de jogadores de alto nível; - Deixe o agente revisar o próprio jogo: veja o relatório de partida em [Iteração autônoma do agente](https://war3ai.com/pt/docs/agent-loop/). --- # Bots de exemplo > Quatro exemplos, do mais simples ao mais completo. Todos rodam direto, e cada trecho de lógica corresponde a um recurso do SDK. Há também um cérebro de referência completo. Os exemplos ficam em `brains/examples/`; cada um herda do anterior e só acrescenta o que é novo. Leia na ordem: | Exemplo | O que ensina | Como rodar | |---|---|---| | `hello_bot.py` | Coleta (5 por mina; mina cheia, vai cortar madeira), treinar camponeses (só 1 na fila), erguer construções de comida, retomar fundações paradas; funciona com as quatro raças | `python tools/play.py --bot brains/examples/hello_bot.py` | | `rush_bot.py` | Quartel e altar (se não houver, constrói com `build_near`), herói primeiro (se morrer, revive), aprender habilidades quando houver pontos, juntar uma leva e atacar-mover | `… --bot brains/examples/rush_bot.py` | | `macro_bot.py` | Ordem de construção + voltar sozinho à mina ao terminar (Shift), repor comida assim que travar, fila do quartel com 1, melhorias de ataque/armadura, subir de tier e tropas avançadas, escolher o alvo pela **distância real por terra** e seguir o caminho | `… --bot brains/examples/macro_bot.py --speed 200` | | `micro_bot.py` | Assume o combate por cima da macro: focar no alvo que morre mais rápido, recuar unidades feridas, proteger o herói, à noite escolher acampamentos de creeps que dá para vencer, voltar para defender quando o inimigo chega à base; os comandos de um tick saem em um lote | `… --bot brains/examples/micro_bot.py --fair` | > **Nota** > > Os comentários de `hello_bot` e `rush_bot` registram armadilhas encontradas em partidas reais, como “sempre escolhia o primeiro trabalhador para construir, e as 3 fazendas ficaram todas como fundações pela metade” e “a coordenada fixa do quartel caía bem numa floresta; em 3 minutos, nenhum foi construído”. Ler os comentários rende mais do que ler o código. ## hello_bot: economia ```python # raça -> (trabalhador, edifícios principais, construção de comida) 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 # no máximo 5 camponeses por mina (mais que isso não aumenta a renda) LUMBER_CREW = 5 # quantos cortam madeira: 5 no ouro por mina + estes = meta de trabalhadores ``` Três tarefas: trabalhadores ociosos vão coletar ouro (o Bot conta quantos há em cada mina; mina cheia, eles vão cortar madeira); se faltar trabalhador, treina mais (só 1 na fila); quando a comida está perto do limite, escolhe um trabalhador que não esteja construindo e ergue uma construção de comida ao lado do edifício principal (Humanos e Orcs também mandam alguém retomar fundações paradas). ## rush_bot: tropas e ataque Sobre o `hello_bot`, acrescenta três coisas: construir quartel e altar se não houver; o altar treina o herói (**se ele morrer, reviva primeiro** — o herói é único) e aprende habilidades quando houver pontos; ao juntar 8 soldados, todos atacam-movem até o edifício principal inimigo e, se forem dizimados, voltam para casa e juntam de novo. Só dá ordens a soldados ociosos, para não interromper o combate tick após tick. ## macro_bot: fundamentos de 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"]), ... } ``` O que um jogador profissional faz em toda partida, cada item ligado a um recurso do SDK: tabela de ordem de construção + `gather(..., queue="after")` para voltar à mina ao terminar; `production().blocked` para perceber que a comida travou; `g.queue` para garantir só 1 na fila do quartel; `can_do` para perguntar ao motor se já dá para pesquisar o próximo nível de ataque/armadura; subir de tier e tropas avançadas (lição de partida real: ficou parado no tier 1 e, aos 23 minutos, foi arrasado por cavaleiros e grifos de tier 3); escolher alvos por `path_distance` e seguir os pontos de virada com `path()`. ## micro_bot: depois que a luta começa ```python def _fight(self, g, army, foes, home, now): ... visible = [e for e in foes if e.visible_to(me)] # alvos que não estão visíveis são rejeitados (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)) # o que morre mais rápido, não o mais próximo 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) ``` Em partida real: 5 minutos, 1497 ticks, 3023 comandos, 0 erros. ## Cérebro de referência: uma IA completa `brains/xwar3/` é uma IA completa, que expande, creepa e ataca, organizada em três camadas: | Camada | Local | Ritmo | O que faz | |---|---|---|---| | Camada de estratégia | `strategy/` | Segundos | Escolha e troca entre várias estratégias no estilo AMAI, tabelas de construção, tropas de counter, escolha de heróis; [conselheiro de estratégia com LLM](https://war3ai.com/pt/docs/llm-coach/) opcional | | Camada reflexa | `reflex/` (4 processos independentes) | Na casa dos 100 ms | Proteger unidades, lançar feitiços, focar fogo, pegar itens | | Modelo de vitória | `worldmodel/` | — | Dá para vencer esta luta? (subconjunto de inferência) | Os processos compartilham unidades por meio da **tabela de reivindicações**, que decide pela prioridade quem manda: humano 95 > proteção 90 > desviar de habilidades 85 > lançar feitiços 80 > pegar itens 70 > … > estratégia 50 > distribuir trabalhadores 45. O seu Bot aparece na tabela como `bot`, com prioridade padrão 50. > **Atenção** > > O cérebro de referência usa diretamente a camada baixa do SDK (`w3cmd` / `act`) e depende muito de informação do mapa inteiro. Ele serve como referência de “ideias”, mas não recomendamos que um LLM o copie diretamente. Ele precisa dos dados do AMAI: na primeira instalação, o `start.bat` baixa do repositório público do AMAI e gera esses dados (o AMAI tem licença própria; os arquivos gerados não entram no git; se não der certo, rode `start.bat setup` para tentar de novo). A forma mais simples de iniciar o cérebro de referência é pelo [console Farsight](https://war3ai.com/pt/docs/console/): na página “Instâncias e partidas”, marque o número da instância e clique em “Iniciar teste”. --- # Depuração e desempenho > Por que um tick está lento, por que um comando não teve efeito, por que o jogo não se mexe. Investigue pelo sintoma e confirme com os scripts de verificação em partidas reais que vêm no projeto. ## Veja o recibo O recibo de cada comando é a pista de primeira mão: ```python r = g.cast(hero, "blizzard", x=tx, y=ty) if not r: print(r.reason, r.verdict) # rejected(…) e o código de motivo print(r.exec_us, r.engine_us) # microssegundos que este comando levou na thread do jogo / quanto disso foi a própria função de ordem do motor ``` Normalmente, um comando leva de alguns microssegundos a algumas centenas de microssegundos na thread do jogo. Ao fim de um bloco de lote, `g.last_receipts` contém o recibo de cada comando do lote. ## Veja dentro do jogo ```python g.say(unit, "Recuar") # um balão de fala aparece sobre a unidade (não afeta o jogo) g.message("Indo creepar") # uma linha na área de mensagens, no canto inferior esquerdo (só visível nesta máquina) ``` O que você imprime com `print` aparece no terminal onde o Bot está rodando. Imprimir as decisões-chave de cada tick, junto com os balões de fala, é muito mais rápido do que ler o código. ## Um tick está lento Veja primeiro se é um destes casos: | Causa | Correção | |---|---| | Enviar comandos um a um, cada um esperando um frame | Envolva em `with g.batch():`; dezenas de comandos esperam uma vez só | | Chamar `g.visible()` / `g.can_do()` uma a uma (cada chamada passa pela via rápida e espera um frame) | Para visibilidade, use `u.visible_to()` do snapshot; para viabilidade, pergunte em lote com `g.can_do_many([...])` | | `sleep` ou espera dentro de `on_tick` | Anote o tempo de jogo e verifique de novo no tick seguinte | | Recalcular algo caro a cada tick (caminhos, varredura do mapa inteiro) | Guarde o resultado em cache e recalcule a cada poucos ticks. `g.grid()` já tem cache de 2 segundos, e os níveis de tecnologia de `g.stats()` são atualizados a cada 5 segundos | ## O jogo não se mexe / o Bot não consegue entrar na partida | Sintoma | Causa provável | |---|---| | Fica sempre “esperando a partida” | Número de instância errado; ou a janela do jogo está **minimizada** — minimizado, a simulação do jogo para (o relógio não anda) | | O jogo roda, mas as ordens do Bot não têm efeito | Ordens para unidades de outro jogador (recibo `not_owner`); ou o Bot se conectou como observer (`forbidden`) | | O comando fica `held` | A unidade está presa por uma camada de prioridade maior (a camada reflexa do cérebro de referência, uma ordem manual do console) e o comando não foi enviado | | Ainda dá para dar comandos com o jogo pausado | Normal: na pausa, o relógio do motor para, mas a distribuição de eventos continua e os comandos são executados normalmente | ## Conecte e veja o status ```bash python -m openwar3 status --inst 5 ``` Mostra o estado da conexão: pid do jogo, período de publicação do mundo e tempo de cada coleta, contadores da via rápida, se há uma partida em andamento, número de unidades, relógio do jogo. ## Scripts de verificação em partidas reais Abra uma instância de teste e verifique, item por item, se os recursos do SDK funcionam na sua máquina: ```bash python tools/sdk_live_check.py --inst 20 # tudo python tools/sdk_live_check.py --inst 20 --only prod # só uma seção ``` Seções: lote, tempo, produção, comandos em fila, atributos de combate, caminhos, modo justo. Cada seção dá comandos numa partida real, lê o efeito de volta e imprime quantos itens passaram. Os testes offline não precisam do jogo aberto: ```bash python tools/run_tests.py ``` ## “Parece bug”, mas não é - **O recibo de construção foi aceito, mas a fundação nunca aparece**: o motor aceita na hora até pontos dentro da floresta, e a falha só vem quando o trabalhador chega. Use `build_near`, que acompanha a construção e põe o ponto que falhou numa lista negra por um tempo. - **O recibo do feitiço foi aceito, mas nada foi lançado**: ele foi interrompido ou faltou mana. No tick seguinte, veja se `g.cooldown()` entrou em recarga. - **A ordem de ataque foi aceita, mas as tropas atacam outro alvo**: para atacar um alvo específico, use `g.attack(tropa, inimigo)` (semântica do clique direito). A ordem de ataque crua só troca a ordem, sem registrar o alvo, e a unidade vai atacar outra coisa por perto. - **A contagem de trabalhadores não bate**: trabalhadores dentro da mina de ouro não estão no snapshot. - **Não dá para treinar o herói que morreu**: o herói é único; use `g.revive(altar)`. Reviver exige comida, e só é possível cerca de 3 segundos de jogo depois da morte. --- # Companheiro de RPG > Dê ao jogador um companheiro de IA em mapas RPG e personalizados: ele segue você, ajuda a matar monstros, cura você quando sua vida está baixa e conversa com você. Quatro modos; herde uma classe, mude algumas propriedades e você tem o seu próprio companheiro. Não é só para partidas de confronto. Em mapas RPG e personalizados, você pode ter um **companheiro de IA**: ele anda com você, ajuda a matar monstros, cura você quando sua vida está baixa e, quando não há nada acontecendo, puxa conversa — as falas podem vir de um LLM local. **Como usar é decisão sua.** Isso se divide em três camadas de API, de baixo para cima, e qualquer uma delas pode ser usada diretamente: | Camada | O que é | Para quem | |---|---|---| | **Canal JASS** `g.jass` | As 1291 funções JASS que autores de mapas podem usar, chamadas direto pelo nome (criar unidades, definir aliados, dar itens, mudar nomes, exibir texto, reviver heróis…) | Quem quer criar a própria jogabilidade | | **APIs de conveniência** | `g.spawn`, `g.set_alliance`, `g.player_slots`, `g.show_text`, `g.map_data`: as tarefas mais comuns, já empacotadas | Quem quer escrever os próprios scripts auxiliares | | **Framework de companheiro** | `openwar3.companion.Companion` + `openwar3.talk.Talk`: herde, mude algumas propriedades e você tem um parceiro que segue, ajuda na luta, cura e conversa | Quem quer um companheiro | > **Atenção** > > Só para partidas **offline, em rede local ou criadas por você**. Criar unidades e definir aliados são mudanças no mundo que esta máquina faz por conta própria: numa partida solo (contra o computador), tudo bem; numa partida multijogador, os outros jogadores perdem a sincronia. Por isso, em partidas multijogador o canal JASS só libera funções de leitura, e o companheiro recua automaticamente para “só falar”. ## O jeito mais rápido: ativar com um clique no Farsight 1. **Escolha o mapa**: no Farsight, página “Instâncias e partidas” → “Próxima partida” → Mapa, escolha um mapa RPG (tudo o que estiver em `Scenario` e `Download`, dentro de `Maps` na pasta do jogo, aparece na lista, por exemplo `(4)WarChasers`). 2. **Escolha o esquema**: no menu “Esquema de IA” do card da instância, escolha **Exemplo de companheiro (buddy)** → “Selecionar”. 3. **Inicie o teste**: quando o jogo abrir, **jogue você mesmo na janela do jogo**. O companheiro — um paladino chamado “Luz” — aparece ao seu lado. Também dá para usar a linha de comando: ```bash python tools/play.py --bot brains/examples/buddy.py --inst 20 --rpg --map "\Maps\Scenario\(4)WarChasers.w3m" ``` `--rpg` (no manifesto do esquema, `"judge": false`) significa não decidir vitória e derrota pelas regras de confronto: em RPG, heróis mortos podem reviver, e não existe “perdeu todas as construções, perdeu a partida”. Muitos mapas RPG param em “Pressione qualquer tecla para continuar” depois de carregar; quando o SDK percebe que “está numa partida, mas o relógio do jogo continua em 0”, ele mesmo aperta espaço (`g.press_to_continue()`, que só envia uma mensagem de tecla para a janela do jogo, sem roubar o foco). ## Escreva o seu próprio companheiro ```python from openwar3.companion import Companion from openwar3.talk import Talk class MyBuddy(Companion): mode = "ally" # modo, veja a tabela abaixo unit = "Hpal" # o que criar: qualquer código de quatro caracteres, inclusive os definidos pelo mapa nickname = "Luz" heal = ("holybolt", "AHhb", 0.55) # (ordem de feitiço, habilidade a aprender, cura quando a vida do mestre ficar abaixo disto); None = não cura follow_distance = 350 talk = Talk(persona="Um paladino alegre e animado, que adora torcer pelo seu mestre") ``` ### Quatro modos | mode | Quem é o companheiro | Descrição | |---|---|---| | `ally` (padrão) | Ocupa um slot de jogador vazio como seu **aliado** | Tem cor e nome próprios (o placar e o painel de aliados mostram o `nickname`); você não consegue selecioná-lo, ele age sozinho. O framework configura aliança + visão compartilhada automaticamente | | `own` | Criado **em seu nome** | Você pode comandá-lo manualmente a qualquer momento; quando você não está cuidando dele, a IA o controla por você | | `adopt` | Assume uma unidade que **já existe** no mapa | Sobrescreva `adopt(g)` para retornar essa unidade (o mascote ou o seguidor que o mapa dá a você) | | `voice` | Não cria unidade, **só fala** | Faz companhia e dá avisos; não altera o mundo, funciona até em partidas multijogador | Sem slot vazio, `ally` recua automaticamente para `own`; em partidas multijogador, ou se não for possível criar a unidade, recua para `voice`. > **Nota** > > No modo `ally`, o companheiro se liga à “melhor unidade daquele slot no momento” (heróis primeiro), e não a uma unidade fixa. Nos testes, um mapa tratou o companheiro como um jogador de verdade: removeu o paladino e entregou um herói do mapa — o companheiro assumiu esse herói na hora e aprendeu as habilidades que o mapa definiu para ele. Quando o herói morre, a prioridade é revivê-lo ali mesmo; se o próprio mapa o reviver, ele continua usando esse herói. ### O que ele faz a cada tick Verifica em ordem e faz a primeira coisa que se aplicar: | Ordem | Comportamento | Condição | Ajuste | |---|---|---|---| | 1 | Recuar | A própria vida abaixo de 25% e inimigos por perto: recua para trás do mestre | `retreat_at` | | 2 | Curar | Vida do mestre abaixo do valor definido, habilidade fora de recarga, distância de até 900 | `heal` (None desliga) | | 3 | Ajudar na luta | Inimigos perto do mestre: **quem está atacando o mestre > quem o mestre está atacando > o mais próximo** | `assist_radius`, ou sobrescreva `pick_target` | | 4 | Seguir | Se estiver longe demais do mestre, vai atrás; se estiver muito longe, volta correndo sem ficar lutando | `follow_distance`, `leash` | | 5 | Conversar | Sem inimigos por perto, solta uma frase a cada 1 ~ 2.5 minutos | Tabela de falas | “Inimigo” segue as relações de aliança do próprio jogo (atualizadas a cada 20 segundos). Mapas RPG costumam ter vários aliados, então não dá para simplesmente tratar “todos os jogadores menos eu” como inimigos. Hooks que podem ser sobrescritos: `find_master` (quem é o mestre; por padrão, o herói de nível mais alto do jogador local), `adopt`, `pick_target`, `on_poke` (o mestre clicou com o botão direito no companheiro), além de `on_start` / `on_tick` / `on_event` / `on_end` do Bot. Curas, ajudas na luta, abates, seguimentos, recuos, falas e revividas são contados em `self.stats` e impressos no fim. ### Como chamá-lo - **Comandos de chat**: digite no chat `-follow` para ele seguir você, `-stay` para ficar de guarda onde está, `-heal` para curar na hora, `-hi` para cumprimentar. Para mudar a lista de comandos, altere `commands`; para mudar as reações, sobrescreva `on_command`. - **Clique com o botão direito no companheiro**: dispara `on_poke`. No exemplo, a reação é: se o mestre não estiver com a vida cheia, ele cura um pouco; senão, diz alguma coisa. - **Diálogo com retrato**: o cumprimento, a queda do mestre, a subida de nível do mestre e a volta do companheiro são ditos pelo diálogo com retrato do próprio jogo (o retrato na parte de baixo passa a ser o do companheiro e aparece uma legenda na tela); as outras falas saem em balões sobre a cabeça. - **Painel de status**: um painel no lado esquerdo da tela mostra a barra de vida do companheiro, o que ele está fazendo, o humor (feliz / empolgado / tenso / com medo / triste), os abates e o número de curas. Ele é desenhado com o [canvas](https://war3ai.com/pt/docs/canvas/) e é seguro em partidas multijogador. ### Falas e LLM local `Talk` escolhe falas conforme o evento e as mostra num balão sobre a cabeça; no modo `voice`, ou quando não dá para mostrar o balão, a fala aparece no canto inferior esquerdo da tela. Cada frase também vai para o log do esquema, para você conferir depois o que ele disse. | Evento | Quando | Evento | Quando | |---|---|---|---| | `hello` | Acabou de chegar | `master_low` | Mestre com pouca vida | | `poke` | O mestre clicou nele com o botão direito | `master_levelup` | Mestre subiu de nível | | `fight` | Começou a luta | `master_died` / `master_back` | Mestre caiu / reviveu | | `kill` | Matou um monstro (diz o nome dele) | `buddy_low` / `buddy_died` / `buddy_back` | O próprio companheiro com pouca vida / caiu / voltou | | `healed` | Curou o mestre | `idle` / `item` | Conversa à toa / pegou um item | As falas podem usar os placeholders `{master}`, `{me}`, `{map}`, `{enemy}`, `{level}` e `{item}`; para mudar as falas, altere `talk.lines`; a recarga fica em `talk.cooldown`. **Conectar um LLM local**: `Talk(llm=LocalLLM(url, model))`; qualquer API compatível com OpenAI serve (LM Studio, Ollama…). O modelo responde numa thread em segundo plano, e a fala só sai quando a resposta chega; se ele não estiver ligado, estourar o tempo ou der erro, o companheiro usa as falas fixas, sem travar o jogo. As requisições vão só para o endereço local que você informar, e o conteúdo é o que acontece no jogo (como o mestre se chama, que monstros ele matou). ## Nomes de unidades em mapas personalizados A maioria das unidades, itens e heróis de mapas RPG é criada pelo próprio mapa (códigos de quatro caracteres como `HC07` e `I00A`) e não está na tabela de nomes embutida. `g.map_data` lê direto o arquivo do mapa da partida atual: ```python md = g.map_data md.name_of("HC07") # 'Optimus Primo' — nomes alterados pelo mapa têm prioridade md.hero_names("HC07") # lista de títulos md.hero_skills("OC10") # habilidades que o mapa definiu para este herói md.tooltip("I00A") # texto de descrição ``` Mapas protegidos ou otimizados (muitos RPGs populares) não trazem os arquivos padrão de dados de objetos; nesse caso, os nomes são lidos dos dados de texto do mapa. Nos testes, todos os 38 mapas RPG / personalizados desta máquina foram lidos com sucesso, e 37 deles forneceram nomes de unidades. ## Compartilhe como esquema O companheiro é só uma subclasse de `openwar3.Bot`, então pode virar um [esquema de IA](https://war3ai.com/pt/docs/schemes/) e ser compartilhado. No manifesto, acrescente dois campos: ```json {"id": "my-buddy", "name": "Meu companheiro", "entry": "my_buddy.py", "fair": false, "judge": false} ``` `"fair": false`: para usar o canal JASS (criar unidades, definir aliados); `"judge": false`: não decidir vitória e derrota pelas regras de confronto. ## Registro de testes 2026-09-24, instância de teste, mapa WarChasers, velocidade 2×: - Canal JASS: todas as 18 verificações passaram — slots de jogador, conversão de ida e volta entre unidades e handles, valores de retorno reais, parâmetros de string, criar unidade num slot vazio, definir aliados, mudar nome, remover unidade; chamadas pela via do jogador e com o número errado de parâmetros foram corretamente rejeitadas. - Companheiro: passou sozinho pelo “Pressione qualquer tecla para continuar” → apareceu ao lado do mestre e cumprimentou → foi atrás dele até o círculo de poder da escolha de herói, recebeu um herói do mapa e o assumiu → seguiu (a 200 ~ 400 do mestre) → lutou contra monstros e, ao matar um, disse “Mandou bem!” → recuou com pouca vida → morreu, foi revivido pelo mapa e continuou seguindo. ## O que ainda falta 1. **Não dá para ler qualquer texto que o jogador digita no chat.** Os comandos de chat fixos já funcionam; para o companheiro conversar livremente com você, ainda é preciso obter o texto em si. 2. **O companheiro não entende a jogabilidade de um mapa específico** (missões, lojas, história). Ele faz o genérico: seguir, ajudar na luta, curar. Para entender um mapa, escreva isso na subclasse, conforme aquele mapa — `g.map_data` consulta nomes, `g.jass` chama qualquer função. Essa é justamente a parte que fica para você decidir. --- # Canvas > Desenhe caixas de texto, painéis, barras de progresso, imagens, círculos no chão e rotas com seta sobre a tela do jogo. O runtime desenha tudo a cada frame, sem alterar o estado do jogo, então é seguro em partidas multijogador; funciona por Python, HTTP ou escrevendo direto na memória compartilhada. Programas externos podem desenhar **caixas de texto, painéis, barras de progresso, imagens, círculos no chão e rotas no chão (com seta)** sobre a tela do jogo, e o runtime desenha tudo sozinho a cada frame. Serve para o seu próprio HUD, linhas de apoio, avisos, anotações didáticas e painéis de informação para lives. ## Canvas ou funções visuais do JASS: qual usar | | Canvas (esta página) | [Funções visuais do JASS](https://war3ai.com/pt/docs/jass/) | |---|---|---| | Quem desenha | O próprio runtime | O próprio jogo (texto flutuante, efeitos, painéis, diálogo com retrato…) | | Partidas multijogador | **Seguro**: só desenha na tela desta máquina, sem criar objetos nem alterar o estado do jogo | Só partidas solo | | Estilo | Livre: fontes com suporte a chinês, cantos arredondados, semitransparência, bordas, qualquer cor, imagens locais | Estilo nativo do jogo | | Acompanhar coisas | Segue unidades, coordenadas do mundo, posições da tela; círculos no chão acompanham o relevo | Depende da função | | Custo | Medido: 0.2 ~ 0.35 ms por frame (9 elementos) | Cerca de 13 ms por chamada | Dá para usar os dois juntos: efeitos com cara nativa pelo JASS; painéis, linhas de apoio e avisos personalizados pelo canvas. ## Python ```python c = g.canvas # no primeiro uso, o runtime instala o hook de desenho (cerca de 0.1 s) c.text("title", "Olá, este é o canvas", screen=(40, 110), color=(255, 220, 80), bg=(0, 0, 0, 170), border="#C49C40", size=22, bold=True) c.panel("status", "Companheiro · Luz", ["Humor: feliz", "Abates: 12"], screen=(16, 330)) c.bar("hp", 0.62, unit=hero, lift=260, width=100, height=12, color=(80, 220, 80), text="62%") # segue a unidade c.text("tag", "O chefe vai soltar o golpe especial!", 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) # área de perigo no chão c.circle("aura", hero, 450, color=(80, 200, 255, 220)) # círculo que segue a unidade 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 só limpa o que você desenhou c.expire("tag", 5) # some sozinho depois de 5 s with c.batch(): ... # muda vários itens de uma vez, escrevendo na memória compartilhada uma vez só c.stats() # drawnFrames subindo = está desenhando de verdade ``` Cada elemento é identificado por uma `key`: desenhar de novo com a mesma key atualiza o elemento. **Clicável**: acrescente `clickable=True` a caixas de texto e painéis (a cor ao passar o mouse é definida por `hover=`). Quando o jogador clica, chega um `ui.click` no fluxo de eventos, com `ev.key` igual a essa key, e o jogo não recebe esse clique. Botões, cartões de escolha, atalhos de teclado e cliques no chão prontos estão em [Interface e entrada](https://war3ai.com/pt/docs/ui-input/). **Posição** (uma por elemento): - `screen=(x, y)`: pixels da tela; números negativos contam a partir da direita / de baixo; `center=True` alinha pelo centro; - `frac=(0.5, 0.1)`: fração da tela; - `world=(x, y)`: coordenadas do mundo; - `unit=unidade`: segue a unidade. Textos e barras no mundo e em unidades ficam com o ponto médio da borda inferior sobre aquele ponto; `lift` os levanta. Por padrão, elementos no mundo e em unidades evitam o painel de comando embaixo e o relógio de dia e noite no topo (`over_ui=True` desenha por cima deles). **Cores** podem ser `(r, g, b)`, `(r, g, b, a)`, `"#RRGGBB"` ou `"#RRGGBBAA"`. | Método | O que desenha | Parâmetros comuns | |---|---|---| | `text(key, texto, ...)` | Caixa de texto; várias linhas com `\n` | `color`, `bg` fundo (sem ele, transparente), `border`, `size`, `bold`, `shadow`, `width` (quebra a linha nessa largura), `radius` cantos arredondados | | `panel(key, título, [linhas...], ...)` | Painel (fundo escuro semitransparente, borda dourada) | Os mesmos de `text` | | `bar(key, 0..1, ...)` | Barra de progresso: vida, recarga, conjuração | `width`, `height`, `color`, `bg`, `border`, `text` | | `image(key, caminho, ...)` | Imagem local (png / jpg / bmp / gif) | `width`, `height` (sem eles, tamanho original) | | `circle(key, unidade ou ponto, raio, ...)` | Círculo no chão, acompanhando o terreno | `color` cor da linha, `fill` preenchimento (com transparência), `width` espessura da linha | | `path(key, [pontos...], ...)` | Linha poligonal no chão | `color`, `width`, `arrow` seta no fim; os pontos podem ser coordenadas ou unidades | ## HTTP (qualquer linguagem) Backend do Farsight (só escuta na máquina local): ```http POST /api/instances/20/canvas {"set": [ {"key": "banner", "kind": "text", "text": "Canvas via 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 elementos desenhados agora + quantos frames já foram desenhados ``` `kind` é o nome do método em Python, e os nomes dos parâmetros também são os mesmos; para unidades, use o endereço `addr` do snapshot. ## Escrevendo direto na memória compartilhada Também dá para dispensar o Python e o Farsight: envie uma vez o comando semântico `canvas_enable` (opcode W3P 73), e o runtime cria o bloco de memória compartilhada `Local\War3Canvas_`: cabeçalho de 64 bytes + 256 entradas × 112 bytes + pool de 64 KB para textos / pontos. A escrita segue o seqlock (o número de sequência fica ímpar → as entradas e o pool são escritos → o número fica par); o runtime lê uma vez por frame, reaproveita o frame anterior se pegar uma escrita pela metade e devolve o número de frames desenhados, o número de elementos e o contador de falhas. A implementação de referência em Python é `sdk/python/w3canvas.py`, e as estruturas estão definidas no header do protocolo; veja [Protocolo W3P](https://war3ai.com/pt/docs/protocol/). ## Vários programas desenhando ao mesmo tempo Mods, Farsight, MCP e gateway podem desenhar na mesma partida ao mesmo tempo, mas só existe um canvas. A regra é: **cada programa só mexe nos próprios elementos**. - Antes de escrever, pega-se um lock nomeado, leem-se os elementos existentes, mantêm-se os dos outros, trocam-se os próprios e grava-se tudo de volta; - Cada elemento registra quem o desenhou (PID + número sequencial dentro do processo); quando o programa que o desenhou termina, ele é limpo na próxima vez que alguém escrever, e os botões dele deixam de interceptar cliques; - Os números dos elementos vêm de um contador compartilhado, então não colidem. O SDK em Python já faz assim, e `clear()` também só limpa o que é seu. Se você escrever direto na memória compartilhada, siga essas regras; do contrário, vai apagar o que os outros desenharam. Os detalhes do layout estão em [Protocolo W3P](https://war3ai.com/pt/docs/protocol/). ## Medições e cuidados - Medido em 2026-09-25 (1920×1080, velocidade 2×): 9 elementos, 0.27 ~ 0.34 ms por frame, cerca de 63 frames / s, 0 falhas; escrever 9 itens leva 6 ms; com o herói andando, os círculos, textos e barras de vida que seguem unidades acompanham sem atraso. A textura só é redesenhada quando o conteúdo muda; mudar só a posição não redesenha. - O canvas é desenhado depois da interface do jogo e antes do cursor do mouse: fica por cima das barras de vida, das unidades e da interface do próprio jogo, e o cursor fica por cima dele. Ele evita o painel de comando embaixo e o relógio de dia e noite no topo, mas **não evita os painéis do próprio mapa** (placar e contagem regressiva no canto superior direito) — não coloque os seus painéis no canto superior direito. - Fora de uma partida (menu principal, tela de resultados), elementos posicionados em coordenadas do mundo ou em unidades não são desenhados; os que estão numa posição da tela continuam aparecendo. - O círculo no chão projeta cada um dos 64 pontos da circunferência no terreno, então, quando o terreno tem altos e baixos, a forma acompanha o relevo — e isso está certo: ele é desenhado sobre o chão de verdade. - Na primeira ativação, é preciso instalar o hook e pré-aquecer as fontes, o que leva cerca de 1 segundo; nesse intervalo, os elementos de texto ainda não aparecem, mas círculos e linhas sim. - Se ocorrer uma falha durante o desenho, nada mais é desenhado nesta sessão (a mesma proteção dos balões sobre a cabeça), e `faults` em `stats()` passa a 1. - Textos, caminhos de imagem e pontos somam no máximo 64 KB, com até 256 elementos; o caminho da imagem precisa ser um caminho local que o processo do jogo consiga ler. O painel de status do [companheiro de IA](https://war3ai.com/pt/docs/companion/) é desenhado com o canvas: barra de vida, o que ele está fazendo, humor, abates e número de curas. --- # 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](https://war3ai.com/pt/docs/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 ```python 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](https://war3ai.com/pt/docs/mods/) 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+`. > **Atenção** > > 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](https://war3ai.com/pt/docs/protocol/)): | 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`: ```python 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.cursor` estão disponíveis com o mesmo nome no [gateway](https://war3ai.com/pt/docs/gateway/). Um cliente remoto não pode passar funções de callback: cliques e teclas de atalho chegam pelos eventos enviados (o evento `ui.click` traz a `key`). - **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 entrada `Local\War3Input_`, 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 bit `0x40` nas flags de um item do canvas significa “interativo”. O layout está em [Protocolo W3P](https://war3ai.com/pt/docs/protocol/). ## 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`, e `g.selection()` confere; lançar Escudo Divino → `spell.cast('AHds', 1, 35.0)`; - Texto do mapa → `message`; chat → `message`, com `.chat` separando 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.world` nã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. --- # Canal JASS > As 1291 funções JASS que autores de mapas podem usar agora podem ser chamadas pelo nome, de fora do jogo: criar unidades, alterar atributos, efeitos, painéis, caixas de diálogo, sons, câmera, névoa… Quatro formas de uso: console Farsight, linha de comando, HTTP e Python. As **1291 natives JASS** que autores de mapas podem usar nos scripts dos mapas agora podem ser chamadas pelo nome, de fora do jogo: criar unidades, alterar atributos, desenhar efeitos, abrir painéis e caixas de diálogo, tocar sons, mover a câmera, mudar a névoa… Use isso para personalizar ainda mais o jogo — auxiliares para RPG, [companheiros de IA](https://war3ai.com/pt/docs/companion/), minijogos próprios, ferramentas de depuração. | Forma de uso | Para quê | Onde | |---|---|---| | **Página “Console JASS” do Farsight** | Testar à mão, ajustando enquanto vê o resultado | Barra lateral esquerda, “Sistema → Console JASS”: escreva o script e clique em executar; à direita, consulte as funções por categoria e clique numa delas para inseri-la no script | | **Linha de comando** | Testar à mão, ou salvar como arquivo de script e rodar várias vezes | `python -m openwar3 jass --inst 20` (interativo), `-e "código"`, `my_script.j`, `--list palavra-chave` | | **HTTP** | Programas externos em qualquer linguagem | `POST /api/instances/{n}/jass` etc. (veja abaixo); o backend do Farsight só escuta na máquina local | | **Python** | Escrever esquemas, companheiros e ferramentas | `g.jass.QualquerFunção(...)`; os efeitos visuais e interações mais comuns estão empacotados em `openwar3.visual` | > **Atenção** > > Três limites, todos impostos pelo mecanismo: > > - Só em **partidas solo** (contra o computador, nesta máquina) dá para alterar o mundo. Se esta máquina cria objetos e altera unidades por conta própria, os outros jogadores de uma partida multijogador perdem a sincronia — por isso, em partidas multijogador, só funções de leitura são liberadas (`Get*`, `Is*`, `Count*`…). > - É só para as ferramentas desta própria máquina; chamadas feitas conectado como jogador (`Game(player=N)`) ou no modo justo são rejeitadas. > - Só para partidas offline, em rede local ou criadas por você. > > Para acrescentar coisas à tela numa partida multijogador, use o [canvas](https://war3ai.com/pt/docs/canvas/): ele é desenhado pelo próprio runtime e não altera o estado do jogo. ## Como escrever scripts O console, a linha de comando e o HTTP usam o mesmo tipo de script. Uma instrução por linha; **dá para colar JASS direto** (`call` / `set` / `local`, `true` / `false` / `null`, códigos de quatro caracteres como `'Hpal'`, comentários `//`), ou escrever no estilo Python: ```text set h = hero() // embutido: o herói principal do meu lado local texttag t = CreateTextTag() call SetTextTagText(t, "|cffffcc00+128 Crítico!|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("criei", u, "nível do herói", GetHeroLevel(h)) ``` - **As variáveis ficam guardadas**: na mesma instância e na mesma partida, as variáveis definidas com `set` num trecho continuam disponíveis no próximo; ao trocar de partida, elas são limpas automaticamente, e também dá para limpá-las à mão. - **Funções embutidas**: `hero()` o herói principal do seu lado, `me()` o jogador local, `unit('hfoo')` encontra uma unidade, `unit_at(x, y)`, `wait(segundos)`, `print(...)`. Unidades expõem `.x`, `.y`, `.hp`, `.hp_max`, `.mana`, `.type`, `.owner` e `.level`, com suporte a aritmética e comparações. - **Sem suporte** a `if`, `loop` e `function` — para escrever lógica, use o `g.jass` do Python (são chamadas de função comuns) ou escreva um [esquema](https://war3ai.com/pt/docs/schemes/). - Quando há erro, ele diz em que linha e por quê (função inexistente, número errado de parâmetros, variável não definida…); as instruções antes do erro já tiveram efeito. Parâmetros e valores de retorno: | Na assinatura | O que passar | Observações | |---|---|---| | Inteiro | Número; códigos de quatro caracteres como `'Hpal'` são convertidos automaticamente | | | Real | Número | O runtime converte para o formato que o motor espera | | Booleano | `true` / `false` | | | String | `"..."` | Aceita chinês (e outros caracteres fora do ASCII) e os códigos de cor do jogo; strings que o jogo guarda (texto flutuante, painéis, botões, comandos de chat) são copiadas na hora, com segurança | | Handle | Um handle guardado numa variável, ou uma unidade (algo como `hero()` vira handle automaticamente) | | | Função (code) | Só `null` | De fora, não dá para fornecer uma função JASS; algo como `TimerStart(t, 60, false, null)` funciona | | Retorno string | — | O motor retorna o índice na tabela de strings, e o texto não pode ser lido de volta. Para nomes de unidades, use `g.map_data.name_of` | ## Categorias As funções são agrupadas em categorias pelo nome; o lado direito do console e o `--list` seguem esse agrupamento: | Categoria | Quantidade | Exemplos | |---|---|---| | Efeitos visuais | 80 | Texto flutuante, raios entre unidades, efeitos especiais, imagens no chão, marcas no chão, cor / escala / animação de unidades | | Painéis de interface | 146 | Painéis de várias linhas, placares, janelas de contagem regressiva, caixas de diálogo, missões, texto na tela, pings no minimapa, diálogo com retrato, filtros de tela cheia | | Câmera | 44 | Campos da câmera, panorâmica, tremor de câmera | | Sons e música | 50 | Criar e tocar sons, tocar música | | Névoa e visão | 25 | Áreas visíveis, ligar e desligar a névoa | | Itens / heróis / unidades | 63 / 32 / 161 | Criar itens, definir o nível do herói, trocar de dono, adicionar habilidades | | Jogadores / alianças / recursos | 71 | Definir alianças, alterar ouro e madeira | | Gatilhos / eventos / temporizadores | 62 | Criar gatilhos, registrar eventos, temporizadores | | Terreno / clima / destrutíveis | 45 | Efeitos de clima, alterar o terreno, criar destrutíveis | | Fluxo da partida | 57 | Velocidade do jogo, pausa, hora do dia | | Outros | … | Grupos de unidades e regiões, armazenamento, scripts de IA do computador, conversão de tipos e matemática, respostas de evento… | Em 2026-09-24, **94** delas foram chamadas uma a uma em partidas reais, com o efeito conferido a olho; as demais passam pelo mesmo caminho, só não tiveram o efeito conferido uma a uma. > **Nota** > > Funções de “resposta de evento” (`GetTriggerUnit`, `GetClickedButton`…) só têm valor no instante em que um gatilho está executando; chamadas de fora recebem 0 ou vazio. Para saber “se aconteceu”, use a contagem de eventos descrita mais abaixo. ## HTTP Backend do Farsight (padrão `127.0.0.1:8866`, só escuta na máquina 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": {...}} -> erro: {"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 limpa as variáveis guardadas ``` Parâmetros de unidade são escritos como `{"unit": endereço}`, onde o endereço é o `addr` da unidade no snapshot. Medido: 60 ~ 90 ms por requisição. ## Python: g.jass e openwar3.visual ```python j = g.jass t = j.CreateTextTag() j.SetTextTagText(t, "Olá", 0.024) # mesmas regras de parâmetros do script; objetos de unidade e de item do snapshot podem ser passados direto j.signature("CreateImage") # consulta a assinatura ``` `openwar3.visual.Visual(g)` empacota os efeitos visuais comuns já testados, uma linha para cada (chame `v.tick()` a cada tick: remove o que expirou e move as linhas e os círculos que seguem unidades; `v.clear()` remove tudo): | Método | Efeito | |---|---| | `float_text(texto, unidade ou ponto, ...)` | Texto flutuante: números de dano, avisos sobre a cabeça; aceita chinês e cores | | `link(a, b, kind)` | Uma linha entre duas unidades, que acompanha as unidades: Magic Leash / Spirit Link / Drain Life / Healing Wave | | `effect(modelo, unidade ou ponto, ...)` | Modelo de efeito: sobre a cabeça, nos pés, ou tocado uma vez (explosão, coluna de luz) | | `ring(unidade ou ponto, raio, color)` | Círculo de alcance no chão: alcance de habilidade, área de perigo, ponto de encontro; pode seguir uma unidade | | `ping(ponto, color)` | Ping no minimapa | | `board(título, linhas...)` | Painel de várias linhas no canto superior direito (com ícones), editável célula a célula | | `countdown(título, segundos)` | Janela de contagem regressiva no canto superior direito; o próprio jogo conta os segundos | | `scene(nome, fala, portrait)` | Diálogo com retrato: o retrato na parte de baixo passa a ser a unidade que fala, e aparece a legenda “nome: fala” na tela | | `screen_tint(color, alpha)` | Filtro de tela cheia (padrão: bordas avermelhadas, aviso de vida baixa) | | `sound(caminho)` / `reveal(ponto, raio, segundos)` / `look(unidade, ...)` | Tocar um som / dissipar a névoa numa área / mudar a cor de uma unidade, aumentá-la, tocar uma animação, fazê-la piscar | ## Interação: saber o que o jogador fez sem escrever funções JASS Em JASS, reagir ao jogador exige escrever funções de gatilho, e de fora não dá para fornecer funções. A saída é: **criar um gatilho vazio, sem condições nem ações, só registrar o evento e contar quantas vezes ele executou.** Nos testes, gatilhos vazios contam normalmente. | Método | Uso | |---|---| | `chat_commands(["-follow", "-stay"])` → `.poll()` | Comandos que o jogador digita no chat (correspondência exata ou pelo início) | | `menu(título, [botões...])` → `.clicked()` | Menu de botões no centro da tela: qual deles foi clicado | | `hotkeys(("left", "right", "up", "down", "esc"))` → `.poll()` | Quantas vezes as setas e o Esc foram pressionados | | `on("TriggerRegister...Event", parâmetros...)` → `.poll()` | Quantas vezes qualquer evento JASS aconteceu: morte de unidade, entrada numa região, dano recebido, temporizador… | A limitação é que você só sabe “quantas vezes aconteceu”, não “quem foi, o que foi digitado”. Para distinguir quem foi, crie um contador para cada objeto. Os comandos de chat do [companheiro de IA](https://war3ai.com/pt/docs/companion/) foram ligados exatamente assim. ## Cuidados - **O que você cria, você mesmo precisa remover**: textos flutuantes, linhas, imagens, painéis, gatilhos… se não forem removidos, ficam lá para sempre (`Visual.clear()` remove o que ele mesmo criou). O jogo suporta no máximo cerca de 100 textos flutuantes ao mesmo tempo. - **Funções BJ não são natives**: `CreateTextTagUnitBJ` e similares são montadas com natives nos scripts dos mapas e não existem aqui — chame as natives seguindo a implementação delas. - **Algumas constantes precisam ser convertidas antes**: por exemplo, `ConvertPlayerColor(1)` e `ConvertFogState(4)` (os valores estão em common.j). - Cerca de 13 ms por chamada (incluindo a conversão de handles); na camada de protocolo, são os opcodes W3P 70 ~ 72; veja [Protocolo W3P](https://war3ai.com/pt/docs/protocol/). --- # Mods de jogabilidade > Um esquema não precisa ser uma IA que joga por você; também pode ser um conjunto de regras: você mesmo joga na janela do jogo, e o mod prepara o início, gera inimigos, dá recompensas, põe botões e cartões de escolha na tela e decide o resultado. Herde de openwar3.Mod: um arquivo é uma jogabilidade inteira. Os [esquemas de IA](https://war3ai.com/pt/docs/schemes/) são de dois tipos: `kind: bot` é uma IA que joga por você; `kind: mod` é **um conjunto de regras** — você mesmo joga na janela do jogo, e o mod cria os desafios: como a partida começa, quando gerar inimigos (por tempo ou por evento), que recompensas dar, que botões e cartões de escolha pôr na tela, quando a vitória acontece. Um mod usa só recursos que já existem: [Interface e entrada](https://war3ai.com/pt/docs/ui-input/) (botões e cartões clicáveis, teclas de atalho, cliques no chão), o [canvas](https://war3ai.com/pt/docs/canvas/) (painéis, barras de progresso, rotas), o [canal JASS](https://war3ai.com/pt/docs/jass/) (criar unidades, mudar atributos, dar itens) e o fluxo de eventos (mortes, subidas de nível, feitiços lançados, chat). ## Dois exemplos Dá para escolher no Farsight, em “Esquemas de IA” → “Embutidos”: | Mod | Como se joga | Recursos usados | |---|---|---| | **Roguelike de Heróis** `builtin/hero-roguelike` | Você só tem um paladino, e ondas de monstros vêm de todos os lados; a cada nível, escolha um de três reforços no meio da tela (o jogo pausa durante a escolha); sobreviva a 10 ondas para vencer; se o herói morrer, você perde | `g.ui.choice` (cartões clicáveis + pausa), eventos `hero.levelup` / `killed` / `spell.cast`, `-help` no chat, JASS para mudar atributos do herói e dar itens | | **Defesa Infinita** `builtin/endless-defense` | Os monstros saem do ponto de início oposto e correm pela linha vermelha no chão até a sua base; cada onda contida rende ouro; clique no botão na tela ou aperte F7 para chamar a próxima onda antes, com recompensa ×1.5; aperte F8 e clique com o botão esquerdo no chão para pôr uma torre de flechas grátis (botão direito cancela) | `g.ui.button`, `g.ui.hotkey`, `g.ui.mouse` (captura de cliques no chão), painel / barra de progresso / rota do canvas, JASS para gerar monstros e dar ouro | Cada exemplo tem cerca de 150 linhas; o código está em `brains/examples/mod_hero_roguelike.py` e `brains/examples/mod_endless_defense.py`. ```bash python tools/play.py --bot brains/examples/mod_hero_roguelike.py --inst 9 # inicia uma partida, o mod assume, e você joga na janela do jogo ``` ## Escreva um mod ```python from openwar3 import Mod class Survive(Mod): name = "survive" def on_start(self, g): super().on_start(g) # verificação de partida solo + neutraliza o computador adversário self.foe = self.wave_player(g) # um slot vazio vira o "jogador das ondas": sem aliança com ninguém, sem IA do computador self.every(30, self.wave) # uma onda a cada 30 s de jogo (não corre durante a pausa) 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", "A prefeitura caiu") ``` Em relação a `Bot`, `Mod` acrescenta: | Método / atributo | Descrição | |---|---| | `on_start / on_tick / on_event / on_end` | Iguais aos do Bot; ao sobrescrever `on_start` / `on_tick`, chame `super()` primeiro | | `every(segundos, fn, first=)` / `after(segundos, fn)` | Timers que correm pelo **tempo de jogo**; o callback é `fn(g)` | | `finish(result, reason)` | Encerra a partida (`'win'` / `'loss'` / `'unknown'`): o executor para no tick seguinte, um painel de resultado é desenhado no meio da tela, e os resultados do esquema são registrados com base nisso | | `wave_player(g)` | O primeiro slot de jogador vazio, para usar como jogador das ondas | | `spawn_ring(g, jogador, unidade, quantidade, centro, raio, attack_to=)` | Gera unidades num círculo, sem travar mesmo com dezenas por onda; retorna os handles JASS | | `alive_of(g, jogador)` / `attack_move_all(g, jogador, ponto)` | As unidades vivas de um jogador / manda todas em ataque-movimento até lá (chame a cada poucos segundos para os monstros irem atrás) | | `home(g)` / `hud(g, título, linhas)` | A posição da nossa base / o painel de informações no canto superior direito | | `neutralize_ai = True` | Neutraliza o computador adversário no início: as unidades dele são pausadas a cada 5 segundos, e o ouro e a madeira dele são zerados. Mapas de confronto sempre têm um computador, e quando o mod define as próprias regras, ele só atrapalha | | `single_player_only = True` | Se houver outros jogadores humanos, recusa-se a rodar (o JASS que altera o mundo tiraria os outros de sincronia) | | `linger_s = 6` | Depois de decidido o resultado, quantos segundos esperar na tela de resultado antes de encerrar | `finish()` também funciona em `Bot`: um Bot comum também pode declarar o fim da partida por conta própria. ## Transformar em esquema e compartilhar Escreva `"kind": "mod"` no `scheme.json` e defina uma subclasse de `Mod` no arquivo de entrada: ```json {"id": "survive", "name": "Aguente 10 ondas", "kind": "mod", "entry": "survive.py", "class": "Survive"} ``` Um mod **nunca usa o modo justo** (ele é o árbitro que cria os desafios, precisa ver o mapa inteiro e alterar o mundo) e **não tem o resultado decidido pelas regras de confronto** (quem informa o resultado é `finish`); `fair` / `judge` no manifesto não têm efeito. Exportar zip, importar, confiar e os resultados funcionam exatamente como nos esquemas de Bot; veja [Esquemas de IA](https://war3ai.com/pt/docs/schemes/). Um mod também é código: o mod de outra pessoa também exige confirmar a confiança antes da primeira execução. ## Medições 2026-09-25, numa instância de teste: - **Roguelike de Heróis**: a primeira onda aparece, e o painel no canto superior direito se atualiza; levar o herói ao nível 3 → cartões aparecem no meio da tela, e o relógio do jogo para; dois cliques em cartões → dois reforços aplicados (força 22 → 27), e o relógio volta a correr. - **Defesa Infinita**: painel, rota no chão e botão aparecem; F8 + clique no chão → surge uma torre de defesa ao lado da base; clicar no botão antes de limpar a onda atual → aparece o aviso “esta onda ainda não foi limpa”. ## Limites - **Só partidas solo**: criar unidades e mudar atributos passam pelo canal JASS e, em partidas multijogador, causam dessincronização. Isso decorre do modelo lockstep; jogabilidade multijogador depende de um canal de sincronização (veja o [roadmap](https://war3ai.com/pt/roadmap/)). - O mod vê o mapa inteiro — ele cria os desafios, não é um jogador. - O computador adversário nos mapas de confronto só é “neutralizado”, não removido (removê-lo acionaria a vitória pelas regras de confronto). --- # Console Farsight > Console web local e também o único ponto de entrada: definir a pasta do jogo, iniciar e parar os serviços, iniciar e parar instâncias do jogo, configurar a próxima partida, ver o que a IA está pensando, dar ordens manuais, dirigir a câmera e consultar o histórico de partidas. O Farsight é o console web que roda na sua máquina e **só escuta em 127.0.0.1**. Ele também é o único ponto de entrada de todo o sistema: iniciar partidas, trocar de IA, o gateway, os balões de fala e o LLM local, tudo é feito aqui, sem precisar procurar outros scripts. ```bash start.bat # verifica a instalação e depois abre o Farsight em http://127.0.0.1:8866 start.bat 5 6 # também inicia os testes nas instâncias 5 e 6 (jogo + cérebro de referência) start.bat restart # reinicia só o backend do Farsight (depois de alterar o código do servidor; jogos e serviços não são afetados) stop.bat # para tudo ``` A porta é configurada em `ports.console`, no `openwar3.json` (padrão 8866). ## Central de controle A página inicial do Farsight. - **Pasta do jogo**: localiza automaticamente ou você mesmo escolhe; verifica a versão do jogo e extrai os dados do seu jogo. - **Serviços locais**: [gateway](https://war3ai.com/pt/docs/gateway/), [balões de fala](https://war3ai.com/pt/docs/speech/), LLM local (LM Studio) e prévia local do site; cada cartão permite iniciar, parar, reiniciar e ver os logs. Também mostra se o servidor [MCP](https://war3ai.com/pt/docs/mcp/) foi conectado por algum cliente. - **Verificação do ambiente**: se o Python, os arquivos do runtime, os dados do jogo, os dados do AMAI e as demais partes estão instalados. - **Parar tudo** (canto superior direito): as instâncias do jogo, a IA, o gateway, os balões, o modelo local usado por este sistema e o backend do Farsight param, nessa ordem; é o mesmo que dar dois cliques em `stop.bat`. Os servidores MCP são gerenciados pelos clientes, como o Claude, e não são parados; o próprio programa LM Studio também não é fechado. ## Páginas | Grupo | Página | O que faz | |---|---|---| | Controle geral | Central de controle | Veja a seção anterior | | Partida | Visão geral | Resumo da partida na instância atual | | | Comando de batalha | Vista do mapa; permite dar ordens manuais (ordens manuais têm a maior prioridade na tabela de reivindicações: 95) | | | Dados de unidades | Ordem, alvo da tarefa, mana, nível e experiência do herói, recargas de habilidades e inventário de cada unidade | | | Decisões da IA / Decisões de combate | O que o cérebro de referência está pensando neste tick e os detalhes de cada decisão de combate | | | Conselheiro de estratégia | Status do [conselheiro com LLM](https://war3ai.com/pt/docs/llm-coach/): se o servidor do modelo está no ar, se cada instância está conectada, a última sugestão e a entrada que ele viu | | | Direção | Câmera automática, barras de vida sobre as unidades | | | Balões de fala | Fazer unidades falarem, conversar com o modelo local, roda de conversa dos camponeses, diálogo na câmera, gatilhos da partida, configurações do modelo. Veja [Balões de fala](https://war3ai.com/pt/docs/speech/) | | | Velocidade de comandos | APM e vazão de comandos | | | Eventos e entrada | O que aconteceu na partida: feitiços lançados, chat e mensagens na tela, cliques em botões, teclas de atalho, cliques no chão, seleção, jogadores que saem, com filtro por categoria; ao lado, a posição do mouse, o item sob o cursor e a seleção local. Veja [Interface e entrada](https://war3ai.com/pt/docs/ui-input/) | | Registros | Logs / Histórico de partidas | Fontes de log de cada instância; resultado, duração e pico de tropas de cada partida | | | Notas de problemas | No jogo, aperte Pause/Break para pausar e marcar o momento; depois, complete a descrição aqui | | Sistema | Instâncias e partidas | Iniciar e parar instâncias; configurar o mapa (mapas de confronto, ou também mapas RPG / personalizados), as raças, a dificuldade e a velocidade da **próxima partida**; escolher um esquema de IA para cada instância; “Iniciar teste” sobe o jogo + a IA com um clique | | | Esquemas de IA | Importar, exportar, copiar, confiar e excluir esquemas, trocar o esquema de uma instância (até a partida em andamento pode passar na hora para outra IA) e ver os resultados de cada esquema. Veja [Esquemas de IA](https://war3ai.com/pt/docs/schemes/) | | | Console JASS | Escreva um script JASS e clique em executar; à direita, consulte as 1291 funções por categoria e clique para inseri-las no script; as variáveis ficam guardadas durante toda a partida. Veja [Canal JASS](https://war3ai.com/pt/docs/jass/) | | | Conexão e extensão | Status do gateway e início com um clique; endereços de conexão gerados por papel (dev / jogador / observador), comandos e configuração para conectar o MCP; exemplos em JS e Python. Veja [Gateway](https://war3ai.com/pt/docs/gateway/) e [MCP](https://war3ai.com/pt/docs/mcp/) | | | Dados e disco | Quanto espaço em disco ocupa cada tipo de dado de execução (gravações, histórico de partidas, logs etc.), quanto foi adicionado no último dia e o que pode ser apagado (o Farsight nunca apaga nada automaticamente) | | | Configurações | Pasta do jogo, idioma da interface, aparência (escuro / claro, moderno / estilo Warcraft) etc. | | | Feedback e sugestões | Encontrou um problema ou tem uma sugestão? Envie direto para nós; as informações de diagnóstico só são anexadas se você marcar a opção, e dá para visualizá-las antes de enviar | Aperte Ctrl + K para abrir a paleta de comandos: ir para uma página, trocar de instância, encerrar a partida atual, iniciar um novo cérebro. Na parte de baixo da barra lateral, “Novidades” lista o que foi adicionado recentemente ao Farsight e à plataforma. Ao iniciar (e depois a cada 6 horas), o Farsight pergunta ao War3AI.com se há uma versão nova e, se houver, avisa você. Quando o Farsight é atualizado, aparece um aviso no topo da página: salve o que estiver digitando e clique em “Recarregar”. ## Várias instâncias O `runtime/farm.py` cuida de abrir várias instâncias (o Farsight o chama por você ao iniciar e parar instâncias): copia o lançador original `War3.exe` e o renomeia para `War3-.exe` (sem modificar nenhum arquivo do jogo), e cada instância recebe um número e uma pasta (`bin/inst/`). Ao fim de uma partida, a próxima começa automaticamente conforme o `next_game.json` (que é o que a página “Instâncias e partidas” do console edita). > **Dica** > > O seu Bot se conecta a uma instância específica com `--inst N`. A página “Instâncias e partidas” do console mostra quais números estão em uso — não use o mesmo número do cérebro de referência. ## Página de live `http://127.0.0.1:8866/live` é uma página de log com rolagem, feita para usar como “Fonte de navegador” no OBS, que mostra as decisões da IA e o andamento da partida. ## API O servidor do console é um conjunto de APIs REST + WebSocket locais (status das instâncias, configuração da próxima partida, detalhes de unidades, ordens manuais, logs, histórico de partidas, direção, esquemas de IA, chamadas JASS, canvas…); a página web é só um dos clientes, e programas em qualquer linguagem podem chamá-las diretamente. A lista de endpoints fica no cabeçalho do arquivo `console/server/app.py`; o uso dos grupos de esquemas, JASS e canvas está em [Esquemas de IA](https://war3ai.com/pt/docs/schemes/), [Canal JASS](https://war3ai.com/pt/docs/jass/) e [Canvas](https://war3ai.com/pt/docs/canvas/), respectivamente. --- # Esquemas de IA > Um esquema é uma IA completa. Troque com um clique no Farsight — até a partida em andamento pode ser assumida na hora por outra IA; exporte um zip para compartilhar, importe esquemas de outras pessoas para testar, e os resultados de cada esquema são contabilizados automaticamente. Um **esquema** = uma IA completa: uma pasta + um manifesto `scheme.json` + código. Cada instância do jogo escolhe um esquema; troque com um clique no Farsight, e **até a partida em andamento pode ser assumida na hora por outra IA**. Esquemas compartilhados por outras pessoas entram numa **área separada** quando importados, sem interferir nos seus; para alterar um deles, use “Copiar para os meus”. ```text schemes/ mine// meus esquemas: escritos por você ou copiados de outro esquema para alterar (altere à vontade; vale na próxima partida) installed// instalados: zips compartilhados por outras pessoas são extraídos aqui (é preciso confirmar a confiança antes da primeira execução) brains/xwar3/ embutido: o cérebro de referência (IA completa) brains/examples/ embutidos: quatro exemplos didáticos, hello / rush / macro / micro, o exemplo de companheiro buddy e dois mods de jogabilidade (Roguelike de Heróis, Defesa Infinita) ``` Um esquema não precisa ser uma IA que joga por você: um esquema `kind: mod` é um **conjunto de regras de jogo** — você mesmo joga, e ele cria os desafios; veja [Mods de jogabilidade](https://war3ai.com/pt/docs/mods/). ## Usando no Farsight Página “Esquemas de IA” (barra lateral esquerda, “Sistema → Esquemas de IA”): | Ação | O que faz | |---|---| | Importar esquema (zip) | Instala em `installed/`; se o mesmo id já estiver instalado, pergunta se deve substituir (depois de substituir, é preciso confirmar a confiança de novo) | | Usar na instância… | Escolha a instância + “Vale agora” (para a IA atual, e o novo esquema assume esta partida) ou “Vale no próximo Iniciar teste” | | Copiar para os meus | Copia para `mine/`, com autor “eu” e versão 0.1.0, e registra de qual versão de qual esquema a cópia foi feita | | Exportar zip | Empacota como `-.zip`; mandar esse arquivo para alguém é compartilhar | | Abrir pasta | Abre a pasta do esquema no Explorador de Arquivos, para alterar o código direto | | Confiar | Obrigatório antes da primeira execução de um esquema de outra pessoa (veja “Confiança e segurança” abaixo) | | Resultados recentes | Vitória ou derrota, duração e motivo do fim de cada partida deste esquema | | Excluir | Só dá para excluir “meus” e “instalados”; não é permitido enquanto alguma instância estiver usando o esquema | O card da instância também ganhou uma linha “Esquema de IA”: escolha o esquema no menu → “Trocar (vale agora)”. Quando a instância não está rodando, o botão se chama “Selecionar”, e o próximo “Iniciar teste” sobe a IA com esse esquema. ## O manifesto scheme.json ```json { "format": 1, "id": "fast-rush", "name": "Rush de três minutos", "version": "1.2.0", "author": "Fulano", "description": "Uma frase dizendo que estilo de jogo esta IA usa", "entry": "rush_bot.py", "class": "RushBot", "fair": true, "hz": 5, "races": ["human", "orc"], "license": "MIT" } ``` | Campo | Obrigatório | Descrição | |---|---|---| | `id` | ✔ | Letras minúsculas, números, `-` e `_`; 2 ~ 41 caracteres | | `entry` | ✔ | Um arquivo `.py` dentro da pasta do esquema (caminhos absolutos e `..` não são permitidos) | | `kind` | | Padrão `bot` (subclasse de `openwar3.Bot`, joga por você); `mod` = [mod de jogabilidade](https://war3ai.com/pt/docs/mods/) (subclasse de `openwar3.Mod`, nunca usa o modo justo e não tem o resultado decidido pelas regras de confronto) | | `class` | | Nome da subclasse de Bot (ou Mod) no arquivo de entrada; se omitido, usa a última subclasse de `openwar3.Bot` do arquivo de entrada | | `fair` | | Padrão `true`: só vê o que está no campo de visão, a mesma regra da Arena. `false` = mapa inteiro visível, e só assim é possível usar o [canal JASS](https://war3ai.com/pt/docs/jass/) (necessário para companheiros) | | `judge` | | Padrão `true`: decide vitória e derrota pelas regras de confronto. Esquemas de RPG / de companheiro usam `false` | | `hz` | | Quantas vezes por segundo `on_tick` é chamado; padrão 5 | | `format` | | Versão do formato do manifesto, atualmente 1; versões mais novas que o OpenWar3 desta máquina são rejeitadas com um aviso para atualizar | | Demais | | `name`, `version`, `author`, `description`, `races`, `license`, `homepage` e `forked_from` servem só para exibição | A pasta do esquema é adicionada ao caminho de busca de módulos do Python, então o arquivo de entrada pode fazer `import` de outros arquivos da mesma pasta. Pacotes de terceiros (numpy, torch…) não são instalados automaticamente — diga claramente em `description` do que o esquema precisa. **O menor esquema possível tem só dois arquivos**: ```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": "Minha primeira IA", "entry": "my_bot.py"} ``` Coloque em `schemes/mine/my-first/` e atualize o Farsight para vê-lo. Um ponto de partida ainda mais fácil: escolha um exemplo em “Embutidos” e clique em “Copiar para os meus”. ## Execução e resultados Os esquemas são executados pelo **executor de esquemas** (é ele que o “Iniciar teste / Trocar” do Farsight inicia): ```bash python tools/run_scheme.py --inst 20 --scheme builtin/micro --hours 6 ``` - Cada instância tem um processo supervisor permanente, que **inicia um subprocesso a cada partida** para rodar o esquema: se o código do esquema quebrar, o supervisor não cai junto; se você mudar o código de um dos “meus esquemas”, a próxima partida já usa a versão nova. - Ao fim de cada partida, é registrada uma linha de resultado: esquema, versão, autor, vitória ou derrota, motivo, duração da partida, número de erros. As taxas de vitória do Farsight são calculadas a partir daí. Como a vitória e a derrota são decididas: | Situação | Registrado como | |---|---| | Todas as construções do adversário destruídas | Vitória | | Todas as nossas construções destruídas (mesmo com tropas vivas — é assim que o confronto define a derrota) | Derrota | | Todas as nossas unidades mortas | Derrota | | Encerrada / parada manualmente no Farsight | Indefinido | | A partida já tinha mais de 60 segundos de jogo na hora da troca (assumida no meio) | Contada à parte, **fora da taxa de vitória** | | O relógio do jogo parado por muito tempo | Indefinido | Depois de definido o resultado, o executor fecha a tela de resultados, abre a próxima partida conforme a “Próxima partida”, e o esquema assume de novo — dá para deixar rodando a noite toda, acumulando resultados. Pausa não conta como fim: durante a pausa, o Bot continua rodando; só o relógio do jogo para. ## Confiança e segurança **Um esquema é código e roda com as mesmas permissões que você** (pode ler e gravar arquivos e acessar a rede). Por isso: - esquemas em `installed/` **não são confiáveis** por padrão: o Farsight e o executor se recusam a rodá-los até você clicar em “Confiar”; - substituir a instalação de um esquema com o mesmo id **reinicia a confiança** (versão nova = código novo); - na importação, verifica-se que o zip tem no máximo 50 MB e no máximo 2000 arquivos; caminhos absolutos e `..` não são permitidos (para impedir a gravação fora da pasta do esquema); manifesto inválido ou arquivo de entrada inexistente são rejeitados na hora. > **Atenção** > > Antes de confiar, use “Abrir pasta” e leia o código. Só aceite esquemas de pessoas em quem você confia. ## API (para scripts) | API | Descrição | |---|---| | `GET /api/schemes` | Lista de esquemas + resultados + o esquema escolhido e o esquema em execução em cada instância | | `GET /api/schemes/results?ref=` | As últimas 30 partidas de um esquema | | `POST /api/schemes/import` | Importa um zip | | `GET /api/schemes/export?ref=` | Baixa o zip | | `POST /api/schemes/fork` | Copiar para os meus | | `POST /api/schemes/trust` | Confiar | | `DELETE /api/schemes?ref=` | Excluir (rejeitado se alguma instância estiver usando o esquema) | | `POST /api/instances/{n}/scheme` | Troca o esquema de uma instância: assume esta partida na hora, ou vale no próximo Iniciar teste | No Python, use a biblioteca direto: `from openwar3 import schemes` (`list_schemes`, `install_zip`, `export_zip`, `fork`, `trust`, `stats`…). ## No futuro: site de esquemas O zip exportado já é a unidade de compartilhamento; o site só precisa acrescentar uma camada por fora: upload com um clique pelo Farsight; download pelo site, passando exatamente pelas mesmas verificações do “Importar esquema” e com a mesma confirmação de confiança; envio opcional de resultados, com o site agregando as taxas de vitória por versão. O botão “Compartilhar no site de esquemas” já tem lugar reservado no Farsight. Acompanhe o andamento no [roadmap](https://war3ai.com/pt/roadmap/). --- # Balões de fala e modelos locais > Faça qualquer unidade do jogo exibir um balão de fala sobre a cabeça, com qualquer identidade; conecte um LLM local e cada frase que entra vira uma resposta sobre a unidade. Os balões são uma camada visual: não afetam o resultado da partida e servem para lives, narração e depuração. - Qualquer unidade fala, com qualquer identidade; várias unidades podem falar ao mesmo tempo; - tamanho da fonte, cor, largura, rabicho, transparência e velocidade de digitação podem ser personalizados em cada balão; - conecta direto a um LLM local (LM Studio), com saída em streaming: o balão é atualizado enquanto o texto é gerado. ## Usando no Bot O jeito mais simples é o `say` que vem no SDK: ```python g.say(hero, "Sigam-me, ataquem!", seconds=4) ``` ## Inicialização e interface **O jeito mais fácil: página inicial do Farsight, “Central de controle”** — primeiro clique em “LLM local → Iniciar e carregar modelo” (o servidor local do LM Studio + carregar na VRAM o modelo configurado) e depois em “Balões de fala → Iniciar”. No cartão dá para ver os logs, parar e reiniciar. A interface fica na página “Balões de fala”, na barra lateral esquerda do Farsight: fazer uma unidade falar (escolher a unidade, escrever o texto, ajustar o estilo, conversar com o modelo), roda de conversa dos camponeses, diálogo na câmera, gatilhos da partida e configurações do modelo; tudo atua na instância escolhida na barra superior. Também dá para usar a linha de comando: ```bash python speech/speak_launch.py # inicia o servidor do modelo local + carrega e aquece o modelo + inicia a API de balões python speech/speak_launch.py --restart # reinicia a API depois de alterar o código python speech/speak_launch.py --stop # para a API e descarrega o modelo da VRAM ``` Cada etapa segue a regra “se já existe, pula”, então rodar de novo não tem efeito colateral. ## API HTTP Padrão: `http://127.0.0.1:8872/` (a porta fica em `ports.speech`, no `openwar3.json`); qualquer programa pode chamá-la. ### Fazer uma unidade falar `POST /api/say` ```json { "inst": 16, "bubbles": [ { "unit": "0x14A12614", "name": "Rei da Montanha", "text": "Sigam-me, ataquem!" }, { "unit": "0x14A12924", "name": "Arquimago", "text": "Deixa que eu lanço a Nevasca.", "style": { "font_px": 26, "text_color": "#FFE080", "bg_color": "#C0102040" } }, { "screen": [960, 110], "key": 1, "name": "Narrador", "text": "A primeira leva de orcs chega em 30 segundos.", "style": { "tail": false, "type_ms": 0 } }, { "world": [-4684, 2644], "key": 2, "text": "Ponto de encontro", "style": { "font_px": 16 } } ] } ``` | Campo | Descrição | |---|---| | `unit` / `world` / `screen` | Escolha um: segue a unidade (se ela tiver barra de vida, fica logo acima dela) / coordenada do mapa / pixel da tela (para narração) | | `name` | Quem fala, exibido na primeira linha; pode ser qualquer texto, não precisa ser esta unidade | | `text` | O texto, com quebra de linha automática | | `duration_ms` | Por quanto tempo fica visível; 0 = automático, 3 ~ 5 segundos | | `key` | Identificador de balões de mundo / tela; uma mensagem nova com a mesma key substitui a antiga | | `update` | Se o mesmo balão já existir, troca só o texto, sem reiniciar o tempo (para streaming) | | `style` | `font_px`, `max_width_px`, `text_color`, `bg_color`, `border_color`, `tail`, `side_px`, `opacity`, `type_ms`, `font`… | No máximo 32 balões ao mesmo tempo; o custo médio é de cerca de 0.1 ~ 0.2 ms por frame. ### Conversar com o modelo local `POST /api/chat` ```json { "inst": 16, "unit": "0x14A12614", "name": "Rei da Montanha", "persona": "Você interpreta Muradin, o Rei da Montanha de Warcraft, expansivo e bom de copo. Uma ou duas frases coloquiais, no máximo 40 palavras.", "message": "Tem um bando de ogros ali na frente. Vamos atacar?", "stream": true } ``` Retorna `{"reply": "...", "first_token_ms": 283, "total_ms": 342}`, e a resposta já aparece sobre aquela unidade ao mesmo tempo. Cada unidade lembra as últimas 6 rodadas de conversa. ### Outros | API | Descrição | |---|---| | `GET /api/instances` | Jogos em execução | | `GET /api/units?inst=16&mine=true&heroes=true` | Lista de unidades (com nome em chinês, coordenadas, vida) | | `POST /api/clear` | Remove um balão ou todos | | `GET /api/llm`, `POST /api/llm` | Ver / alterar a configuração do modelo (`base_url`, `model`, `max_tokens`, `temperature`) | | `POST /api/banter` | Roda de conversa dos camponeses: os trabalhadores da base se revezam reclamando conforme a persona de cada um, com abertura narrada (todos os dados da partida são reais) | | `POST /api/camtalk` | Diálogo na câmera: os heróis e seguidores em cena conversam conforme seus papéis | | `POST /api/events` | Gatilhos da partida: início de combate, fim de combate, herói morto, subida de tier, construção destruída… só fala quando algo acontece | ## Como escolher o modelo local Testado numa RTX 5090 (5 falas de jogo): | Modelo | VRAM | Velocidade | Uma resposta | Conclusão | |---|---|---|---|---| | **Qwen3.6-35B-A3B** (MoE, só 3B ativos por vez), Q4, raciocínio desligado | 20.6 GB | cerca de 142 token/s | **cerca de 0.3 s** (primeiro token em cerca de 0.27 s) | Recomendado: rápido, roleplay natural em chinês | | gpt-oss-20b (MXFP4), raciocínio low | 11.3 GB | cerca de 280 token/s | 0.3 ~ 0.8 s | Para quando a VRAM é curta; chinês um pouco sem graça | | Qwen3.6-27B (denso), Q4 | 17.2 GB | cerca de 39 token/s | Ainda pensando aos 5.5 s | Não serve para diálogo em tempo real | - **A velocidade depende dos “parâmetros ativos por vez”, não do total**: o MoE de 35B ativa só 3B e é 3 ~ 4 vezes mais rápido que o denso de 27B. - **É preciso desligar o “raciocínio” (thinking)**: senão, todos os tokens vão para o raciocínio e nenhuma palavra sai na resposta. - O balão digita cerca de 22 caracteres por segundo, então a velocidade de geração já não é o gargalo; o que realmente afeta a experiência é a **latência do primeiro token**. > **Falas que soam reais** > > Passe ao modelo sempre dados reais da partida (número de partidas, vitórias e derrotas, tropas, estoque) e diga explicitamente “use apenas estes fatos”. Nos testes, sem essa restrição, o modelo inventava batalhas que nunca aconteceram. --- # Gateway > Gateway WebSocket / JSON: as APIs públicas que o SDK em Python chama também podem ser chamadas de JS, C#, Go, Rust, páginas de navegador e programas em outra máquina. Três papéis, com cliente JS e página de demonstração no navegador incluídos; a latência é a da via rápida mais cerca de 1 ms. O gateway empacota a via rápida e o estado enviado como **WebSocket / JSON**. As APIs públicas do [catálogo da API](https://war3ai.com/pt/api/) que o SDK em Python chama podem ser chamadas de JS, C#, Go, Rust, páginas de navegador, programas em outra máquina e LLMs, com os mesmos nomes de método e parâmetros. A latência é a da via rápida mais cerca de 1 ms. **O jeito mais fácil: página inicial do Farsight, “Central de controle” → Gateway → Iniciar** (parar, reiniciar, ver os logs e abrir a página de demonstração também ficam nesse cartão). Pela linha de comando: ```bash python gateway/server.py # ws://127.0.0.1:8870/ws (a porta fica em ports.gateway no openwar3.json) python gateway/server.py --open # o mesmo, e quando a porta já está escutando abre a página de demonstração http://127.0.0.1:8870/demo python gateway/server.py --host 0.0.0.0 # para a rede local: exige token automaticamente (bin/gateway/token.txt) python gateway/server.py --allow-origin http://localhost:5173 # para a sua própria página web também conseguir conectar ``` ## Conexão e papéis Endereço de conexão: `ws://127.0.0.1:8870/ws?inst=9&role=dev` (também dá para usar `pid=` em vez de `inst=`; quando for preciso token, acrescente `&token=`). | Papel | O que pode chamar | Indicado para | |---|---|---| | `dev` | Tudo: observação, comandos, controle do jogo, sandbox (alterar o mundo via JASS), desenhar interface | Ferramentas locais, [mods de jogabilidade](https://war3ai.com/pt/docs/mods/), companheiros | | `player` (com `&player=N`) | Observação, comandar as unidades do jogador N, desenhar interface; **modo justo por padrão**, só enxerga o que está na visão do jogador N (`&fair=0` desliga) | Bots ou LLMs que jogam no lugar de um jogador | | `observer` | Só leitura (o runtime rejeita direto os comandos que ele enviar) | Assistir, narrar, coletar dados | O que `player` não recebe: controle do jogo, como encerrar a partida, mudar a velocidade ou pausar; `players` e `enemy_ai_plan`, que mostram as cartas dos outros; `canvas.image`, que faz o processo do jogo abrir arquivos locais; e JASS. Consultas que levam número de jogador, como `resources`, `tech` e `stats`, só podem consultar o próprio jogador. Uma conexão é uma sessão e ocupa uma via rápida (o runtime tem 16 no total). O gateway aceita no máximo 12 sessões ao mesmo tempo, para deixar algumas vias para Bots, mods e o Farsight. Ao desconectar, só é removido o que essa sessão desenhou e as teclas de atalho dela; o que outros programas desenharam fica intacto. ## Mensagens Ao conectar, a primeira mensagem recebida é `hello`: versão do protocolo, papel, PID do processo do jogo e a lista de métodos que esse papel pode chamar. Depois disso, cada requisição leva um `id`, e a resposta volta com o mesmo `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", "Comprar poção"], "kwargs": {"screen": [40, 300]}} → {"id": 4, "op": "subscribe", "state": {"hz": 4, "units": "mine"}, "events": true} ← {"type": "state", ...} {"type": "events", ...} daí em diante, envio contínuo → {"id": 5, "op": "overview"} resumo da partida numa página: recursos, contagem por tipo de unidade, heróis, inimigos visíveis, produção → {"id": 6, "op": "jass", "code": "call PingMinimapEx(0, 0, 3, 255, 0, 0, false)"} só para dev → {"id": 7, "op": "api"} catálogo de métodos (há também ping / unsubscribe) ``` - **Parâmetros de unidade** são escritos como `{"unit": endereço}`, em que o endereço é o `addr` do JSON da unidade; dá para incluir `"handle": [lo, hi]` para conferir que esse endereço não foi reaproveitado por outra unidade. - **Nomes de método** são os métodos públicos de Game, mais `ui.*` (button / choice / toast / hotkey / mouse / cursor…), `canvas.*` (text / panel / bar / image / circle / path / remove…) e `jass.` (só para dev). - Um cliente remoto não pode passar funções de callback: cliques e teclas de atalho chegam pelos eventos enviados, e o evento `ui.click` traz a `key`. Veja [Interface e entrada](https://war3ai.com/pt/docs/ui-input/). - Se uma chamada der erro, só aquela chamada responde com erro (`ok: false` mais `error`); a conexão continua. O mesmo vale quando o que chega não é JSON. - Os campos do JSON de eventos são os mesmos do [Protocolo W3P](https://war3ai.com/pt/docs/protocol/), com campos de conveniência adicionais (`spell`, `key`, `text`, `chat`, `button`, `player`, `mods`). HTTP também funciona, bom para chamadas avulsas e curl: `GET /api?role=player` lista o catálogo de métodos, e `POST /call` com `inst`, `role`, `method`, `args` e `kwargs` faz uma chamada. `/call` reaproveita a sessão: se o jogo for reaberto e o processo mudar, ele troca para uma nova automaticamente, e sessões paradas há 10 minutos são encerradas. ## Clientes **JS** (navegador ou Node 22+, zero dependências): `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", "Clique aqui", { screen: [40, 300] }); // o último objeto simples = argumentos nomeados ow.on("event:ui.click", (e) => console.log("clicou em", e.key)); await ow.subscribe({ state: { hz: 2, units: "mine" }, events: true }); ``` No Node 20 / 21, acrescente `--experimental-websocket`. O exemplo completo está em `gateway/clients/js/example.mjs`. **Página de demonstração no navegador** `http://127.0.0.1:8870/demo`: a situação da partida, a tabela das nossas unidades, um botão posto dentro do jogo e o fluxo de eventos, tudo numa página. **Outras linguagens**: qualquer biblioteca de WebSocket + o JSON acima bastam, sem tocar na memória compartilhada. **LLMs**: use direto o [servidor MCP](https://war3ai.com/pt/docs/mcp/), que transforma as tarefas comuns em ferramentas prontas. ## Medições 2026-09-25, conectado a uma partida real, verificação item por item 16/16 (9 do gateway + 7 do MCP): handshake (121 métodos no papel dev), `units('me')`, resumo da partida, aviso na tela, pôr um botão; depois de assinar, clicar nesse botão dentro do jogo → `ui.click` enviado ao cliente; JASS; passar uma unidade inválida só gera erro naquela chamada; HTTP `/call` (papel observer). O cliente JS (Node) e a página de demonstração no navegador também foram testados: o botão posto pela página foi clicado dentro do jogo, e o log de eventos da página recebeu `ui.click`. ## Segurança - Por padrão, só escuta na máquina local `127.0.0.1` e não exige token (igual ao Farsight). Quando `--host` não é um endereço local, o token passa a ser exigido automaticamente; `--auth` exige token também na máquina local. - **Outros sites abertos no navegador não conseguem conectar**: toda conexão iniciada por um navegador leva a origem (`Origin`), e o gateway só aceita a própria página de demonstração e os endereços passados em `--allow-origin`; programas como Python, Node e curl não enviam origem e conectam normalmente. Quando escuta só na máquina local, ele também confere o `Host`, bloqueando ataques que fazem um domínio externo resolver para a máquina local. - O papel é declarado pelo próprio cliente ao conectar: no modo local, ele é uma convenção, não uma fronteira de segurança. Na Arena, quem decide o papel de cada um é o processo árbitro; veja [Arena](https://war3ai.com/pt/arena/). --- # Protocolo W3P > O contrato completo entre o runtime e programas externos: oito blocos de memória compartilhada, leitura do estado do mundo, leitura de eventos, envio de comandos, recibos, papéis das vias, canvas e interface e entrada. Leia esta página se você for integrar com uma linguagem que não seja Python. O runtime e os programas externos trocam dados **somente por memória compartilhada**. Tudo o que existe está descrito abaixo. - A implementação de referência é a de Python: `sdk/python/w3world.py` (leitura) e `sdk/python/w3fast.py` (escrita); o tamanho e os offsets de cada struct estão definidos ali e são fixados por testes; - **O protocolo descreve apenas semântica e não depende da versão do jogo.** Quando a versão do jogo muda, o próprio runtime se adapta e o protocolo continua igual; campos novos só são acrescentados no fim de um bloco, então clientes antigos continuam funcionando. > **Nota** > > A maioria das pessoas não precisa desta página — basta usar o SDK Python. Ela só é necessária se você quiser integrar diretamente com C++ / C# / Rust / Go ou outra linguagem, ou quiser saber o que acontece por baixo do SDK. ## 1. Oito blocos de memória compartilhada `` é o ID do processo do jogo. | Nome | Direção | Conteúdo | Sincronização | |---|---|---|---| | `Local\War3World_` | runtime → você | Estado do mundo: cabeçalho + 16 jogadores + até 1024 unidades + 256 detalhes de unidade + 256 itens no chão + área de extensão + tabela de produção | seqlock | | `Local\War3Trees_` | runtime → você | Até 4096 destrutíveis (árvores etc.), atualizados a cada 2 segundos | seqlock | | `Local\War3Events_` | runtime → você | Anel de eventos, 8192 entradas | cada entrada traz seu próprio número de sequência | | `Local\War3Map_` | runtime → você | Mapa: células de terreno (128 por célula, até 256×256) + limites da área jogável + pontos de início; calculado em lotes nos primeiros segundos da partida | seqlock (não muda mais depois de calculado) | | `Local\War3Fast_` | nos dois sentidos | Vias de comando: 16 vias × 16 slots; cada slot guarda um comando + recibo; cada via tem um papel | um escritor e um leitor por slot | | `Local\War3Canvas_` | você → runtime | [Canvas](https://war3ai.com/pt/docs/canvas/): cabeçalho de 64 bytes + 256 elementos × 112 bytes + pool de 64 KB para texto / pontos; só é criado depois de enviar `canvas_enable` uma vez | seqlock (você escreve, o runtime lê a cada frame) | | `Local\War3Msgs_` | runtime → você | Anel de mensagens da tela: texto completo de dicas do jogo, chat e mensagens do sistema, 128 entradas × 256 bytes | cada entrada traz seu próprio número de sequência | | `Local\War3Input_` | nos dois sentidos | [Interface e entrada](https://war3ai.com/pt/docs/ui-input/): o runtime devolve a posição do mouse, o ponto do chão sob o cursor e o item sob o cursor; você escreve a tabela de teclas de atalho e as chaves do mouse; o runtime só passa a assumir a entrada depois de enviar `input_enable` uma vez | seqlock na tabela de teclas de atalho | **Vários clientes usando canvas e entrada ao mesmo tempo**: cada um desses dois blocos existe uma vez só, e se cada cliente escrever por conta própria um sobrescreve o outro. A convenção é a seguinte, e o seu próprio cliente também precisa segui-la: - **Canvas**: segure o mutex nomeado `Local\War3CanvasMutex_` e faça leitura - modificação - escrita, trocando só os seus elementos e mantendo os dos outros como estão (reorganizando os offsets do pool); remova os elementos sem dono e os de donos cujo processo já saiu. No elemento, `reserved[1]` = PID do processo dono e `reserved[2]` = número sequencial dentro do processo; os números dos elementos são alocados pelo contador no offset 60 do cabeçalho do bloco (a partir de `0x10000`). - **Entrada**: cada cliente registra as próprias teclas de atalho e chaves do mouse em `Local\War3InputClients_` (cabeçalho de 16 bytes + 16 clientes × 528 bytes); segurando `Local\War3InputMutex_`, atualiza a própria entrada e depois grava no bloco de entrada a combinação de todos os clientes vivos: as teclas de atalho são deduplicadas por "código de tecla + modificadores", e as chaves do mouse são unidas. Os eventos vão para todos os clientes, e cada um reconhece as próprias teclas de atalho por "código de tecla + modificadores". Enquanto houver outros clientes vivos na tabela de registro, não envie `input_enable 0`. - **Runtime**: itens clicáveis cujo processo dono já saiu deixam de interceptar cliques; a cada 2 segundos, o runtime confere a tabela de registro e, se todos os clientes registrados já saíram, zera a tabela de teclas de atalho e as chaves do mouse do bloco de entrada. ## 2. Lendo o estado do mundo (seqlock) ```text loop: s1 = block.seq (offset 8, int32) if s1 é ímpar: tente de novo (o runtime está escrevendo) copie cabeçalho + players + units[unitCount] + details[detailCount] + items[itemCount] if block.seq != s1: tente de novo ``` - **Cabeçalho**: contador de publicações (se não aumenta = a publicação parou), relógio de jogo do engine, um epoch que soma 1 a cada partida, número do jogador local, se há uma partida em andamento, velocidade do jogo, período de publicação, microssegundos gastos coletando esta cópia na thread do jogo, número de sequência de eventos, tempos por etapa. O cliente pode escrever `requestedPeriodMs` para pedir um período de publicação (16 ~ 1000 ms). - **Unidade** (112 bytes): par de handles (**identifique unidades pelo par de handles** — endereços são reutilizados), código de quatro caracteres do tipo, dono, flags, coordenadas, vida / mana (com os máximos), ordem atual + alvo da ordem, alvo da tarefa (o que ela está atacando de fato), nível / XP / pontos de habilidade do herói, índice de detalhe, `visibleTo` (bit p = o jogador p consegue vê-la neste momento). - **Detalhes** (288 bytes, heróis > unidades de jogadores > creeps, até 256): 12 habilidades (código / nível / flags / segundos restantes de recarga), 8 códigos de buff, 6 espaços de inventário. - **Extensões no fim do bloco** (só acrescentadas, os offsets anteriores nunca mudam, então clientes antigos continuam funcionando): área de extensão `EXT1` (hora do dia no jogo, velocidade do ciclo dia/noite, número de entradas da tabela de produção) e tabela de produção `prods[128]` (construções que estão treinando / pesquisando / construindo / melhorando, fila, duração total, tempo decorrido, se está travada). **Use-as só se o magic bater.** ## 3. Lendo eventos ```text head = ring.writeSeq (offset 8) for seq in (cursor, head]: e = ring.events[(seq - 1) % 8192] if e.seq > seq: uma se perdeu (sobrescrita porque você leu devagar demais) elif e.seq != seq: ainda não terminou de ser escrita, leia na próxima vez else: processe e ``` A struct de evento tem 64 bytes: `seq kind clockMs addr handleLo handleHi typeId owner a b x y value extra`. - Obtidos comparando duas publicações consecutivas (precisão = período de publicação): `unit.appeared / unit.died / unit.removed / unit.damaged / order.changed / hero.levelup / owner.changed / item.appeared / item.removed / game.started`; - Nível de engine (o runtime os registra na thread do jogo no momento em que acontecem, então há um para **cada golpe**): `damage` (origem, tipo de dano, tipo de ataque, posição, vida realmente perdida, dano antes da armadura), `killed` (quem matou); - Obtidos acompanhando a tabela de produção: `production.done` (código de quatro caracteres do que terminou, categoria, quantos segundos de jogo levou; também emitido para os adversários); - Verificados pelo runtime a cada publicação: `spell.cast` (a habilidade entrou em recarga: `a` código de quatro caracteres da habilidade, `b` nível, `value` recarga em segundos, `x/y` ponto de lançamento), `player.left` (`a` número do jogador, `b` novo estado do slot), `selection.changed` (a seleção do jogador local; a lista completa fica na área de extensão do bloco do mundo), `game.ended` (saída da partida); - Mensagens da tela: `message` (`a` = número de sequência da mensagem; o texto completo é consultado na memória compartilhada `Local\War3Msgs_`: 128 entradas × 256 bytes, com dicas do jogo, chat e mensagens do sistema; `b` = número da caixa de mensagens); - Interface e entrada (depois de ativar `input_enable`): `ui.click` (`a` id do item do canvas, `b` 1 botão esquerdo / 2 botão direito), `ui.hover`, `hotkey` (`a` id da tecla de atalho, `b` código de tecla virtual), `mouse.world` (`x/y` coordenadas no chão, `value` = 1 significa que foi engolido); as teclas modificadoras ficam em `extra`. ## 4. Enviando comandos 1. **Um objeto cliente ocupa uma via**: segure `Local\War3FastMutex_`, encontre uma via livre (ou cujo processo dono morreu) e escreva o papel, o número do jogador e o seu próprio pid. Se o mesmo processo precisar de dois papéis, abra duas vias; 2. Preencha os slots: flag de comando semântico, opcode, `args[11]`, prazo `deadlineMs`; 3. Depois de escrever todos os slots, marque-os como enviados e some 1 ao `submitSeq` da via; 4. Espere o evento `Local\War3FastDone__` (ou faça polling), leia os recibos e devolva os slots. O runtime executa os comandos em lotes dentro do despacho de eventos da thread do jogo: cada esvaziamento tem um orçamento de tempo de **4 ms** (timer real de alta resolução); o que passar do orçamento fica para o próximo despacho. **Slots que passaram do prazo nunca são executados** — então nunca acontece de "comandos antigos rodarem de novo depois de despausar". Índices de `args`: `0..2` unidade (endereço, handle lo, handle hi), `3` ID da ordem ou código de quatro caracteres, `4..6` alvo, `7/8` x / y (bits de float), `9` extra (número do jogador / número do slot / liga-desliga / flag de fila), `10` mode (0 sem alvo / 1 ponto / 2 alvo). ### Opcodes | Opcode | Nome | Descrição | |---|---|---| | 1 | `point` | Ordem de uma unidade para um ponto (mover / atacar-mover / patrulhar / atacar o chão / magia em ponto). extra bit0 = fila (insere depois da ordem atual) | | 2 | `target` | Ordem de uma unidade para um alvo (ataque com botão direito / coletar / reparar / magia em alvo / pegar item); o alvo precisa estar visível | | 3 | `immediate` | Comando sem alvo (parar / manter posição / treinar / pesquisar / melhorar / magia sem alvo) | | 4 | `build` | Trabalhador constrói uma construção (coordenadas alinhadas a 32) | | 5 | `learn` | Herói aprende uma habilidade | | 6 | `use_item` | Usa o espaço extra do inventário | | 7 | `revive` | Revive um herói no altar | | 8 | `rally` | Ponto de encontro (ponto / alvo) | | 9 | `buy` | Uma loja vende um item a um herói próximo | | 10 | `item_drop` | Soltar um item: entregar a um aliado, vender a uma loja (`code` = a unidade que recebe) ou largar no chão | | 20 ~ 25 | Consultas | `q_tech` contagem de tecnologia, `q_feasible` viabilidade, `q_visible` visibilidade, `q_mine_gold` ouro restante na mina, `q_captain` capitão do computador, `q_dead_heroes` lista de heróis mortos | | 30 | `pause` | Pausar / continuar | | 40 ~ 50 | Câmera | Ler estado da câmera, definir campo, olhar para um ponto, seguir, redefinir, girar, limites, suavização, mostrar/ocultar interface, tela limpa, névoa | | 60 ~ 63 | HUD | Texto do botão de missões, título e descrição do painel de missões, atualizar, ler se o painel está aberto | | 70 | `jass` | Chama uma native JASS pelo nome (1291 no total): o nome e os parâmetros de string vão na área adicional do slot, os demais parâmetros vão em `args` conforme a assinatura; o valor de retorno fica em `value[0]`. Só para vias de ferramentas locais; natives com parâmetros de função ou que suspendem a thread de script são sempre rejeitadas. Veja [Canal JASS](https://war3ai.com/pt/docs/jass/) | | 71 / 72 | `jass_handle_of` / `jass_unit_of` | Converte entre unidade do snapshot ↔ handle JASS (o par de handles do snapshot não é um handle JASS) | | 73 | `canvas_enable` | Cria a memória compartilhada do canvas e instala o hook de desenho; qualquer via pode enviar (o canvas só desenha na tela da máquina local). Na primeira vez é preciso instalar o hook, então use um timeout de 2 segundos ou mais | | 74 | `input_enable` | `extra` = 1 assume a entrada da janela do jogo (clique / passar o mouse em itens do canvas, teclas de atalho, cliques no chão), 0 = devolve. Bloco de entrada `Local\War3Input_`: cabeçalho de 128 bytes + 32 teclas de atalho × 16 bytes; você escreve a tabela de teclas de atalho e as chaves do mouse, e o runtime devolve a posição do mouse, o ponto do chão sob o cursor e o item sob o cursor. Qualquer via pode enviar (só afeta a entrada local). Veja [Interface e entrada](https://war3ai.com/pt/docs/ui-input/) | ## 5. Recibos Um recibo tem 52 bytes (+8 bytes de tempos): `status`, `engineReturn`, `verdict` (código de motivo da rejeição), `orderBefore / orderAfter` (a ordem da unidade lida de volta no mesmo frame), `value[8]` (resultados de consulta), `execUs` (quantos microssegundos este comando levou para executar na thread do jogo), `engineUs` (a parte gasta na própria função de ordem do engine). Para todos os códigos de status e códigos de motivo, veja [Recibos e códigos de motivo](https://war3ai.com/pt/docs/reason-codes/). ## 6. Papéis das vias | Papel | O que pode fazer | |---|---| | `dev` | Ferramentas locais: comandos semânticos (comandando as unidades do jogador local) + canal JASS | | `player` | Só comandos semânticos, e só para unidades do jogador dono da via (as de outros = `not_owner`) | | `observer` | Só consultas, câmera, leitura do estado do painel do HUD e ativar o canvas e a entrada local; todo o resto é `forbidden` | Duas IAs jogando uma contra a outra = duas vias `player` na mesma partida (player 0 / player 1). > **Atenção** > > No modo local, o papel é declarado pelo próprio cliente (uma convenção, não uma barreira de segurança). Na [Arena](https://war3ai.com/pt/arena/), o processo árbitro cria as vias e entrega apenas a via `player` a cada competidor. ## 7. Semântica verificada em partidas reais - Botão direito (smart) em um inimigo = atacar **este aqui** (tanto o alvo da ordem quanto o alvo da tarefa são essa unidade); uma ordem de ataque bruta enviada como comando de alvo só troca para a ordem de ataque sem registrar o alvo, e a unidade vai atacar outra coisa por perto; - O engine não aceita comandos de alvo em unidades que você não vê: depois que anoitece, acampamentos distantes caem na névoa de guerra e todo clique com o botão direito é rejeitado (1001); - Uma construção ser "aceita" só significa que o trabalhador recebeu a ordem: um ponto dentro de uma floresta também é aceito na hora, e o trabalhador só falha quando chega lá; pontos obviamente ocupados são rejeitados na hora; - Um herói só pode ser revivido cerca de 3 segundos de jogo depois de morrer; também é rejeitado se faltar comida (heróis ocupam comida); - Itens no inventário não contam como itens no chão; pegar um emite `item.removed`; - Com o jogo pausado, o relógio do engine para, mas ainda dá para enviar comandos; - Uma partida iniciada minimizada fica com a simulação parada (o relógio não anda). --- # Recibos e códigos de motivo > O recibo de cada comando traz um código de status e um código de motivo. Eles são a base para Bots e agentes se corrigirem sozinhos: transformam “por que não deu certo” em um número legível por máquina. ```python r = g.train(barracks, "hfoo") bool(r) # False r.status # 1 -> rejected r.verdict # 3 -> comida insuficiente r.reason # 'rejected(人口不够)' (= comida insuficiente) r.exec_us # quantos microssegundos este comando levou na thread do jogo ``` `if r:` equivale a `r.status == 0` (o motor aceitou). ## Código de status `status` | Código | Nome | Significado | Causas comuns | |---|---|---|---| | 0 | `accepted` | O motor aceitou | — (mas aceito ≠ concluído; veja abaixo) | | 1 | `rejected` | Rejeitado pelo motor | Veja `verdict` | | 2 | `bad_unit` | A unidade não existe ou o handle não confere | A unidade já morreu; objeto de unidade desatualizado | | 3 | `not_owner` | Não é sua unidade | Comandar unidades de outro jogador com a identidade `player` | | 4 | `fault` | Exceção durante a execução (o runtime contém o erro; o jogo não cai) | Relate com os passos para reproduzir | | 5 | `bad_args` | Parâmetro errado | Coordenada, índice de slot ou código de quatro caracteres errado | | 6 | `unsupported` | Não suportado | Esta versão do runtime não tem essa capacidade | | 7 | `bad_target` | Alvo inválido | O alvo já sumiu; tipo de alvo errado | | 8 | `forbidden` | O papel da via não permite | Dar ordens com a identidade `observer` | | 97 | `cancelled` | Exceção dentro do bloco de lote; o lote inteiro não foi enviado | Erro no código dentro do bloco `with g.batch():` | | 98 | `held` | A unidade está presa por uma camada de prioridade maior; o comando não foi enviado | A camada reflexa do cérebro de referência ou uma ordem manual do console está segurando a unidade | | 99 | `timeout` | Tempo esgotado | Com o jogo pausado / travando, o prazo passou (comandos vencidos não são executados depois) | ## Código de motivo `verdict` Quando um comando é rejeitado, o runtime usa a própria verificação de viabilidade do motor para explicar o motivo. Você também pode perguntar antes, sem dar a ordem: `g.can_do(unidade, código)` retorna o mesmo código. | Código | Significado | O que fazer | |---|---|---| | 0 / 220 | Pode | — | | 3 | Comida insuficiente | Erga construções de comida; use `g.production(b).blocked` para perceber antes | | 8 | Ouro insuficiente | Espere o dinheiro; antes de dar a ordem, use `g.can_afford(code)` | | 9 | Madeira insuficiente | Mande mais gente cortar madeira | | 32 | Fila de treino cheia (7 vagas) | Deixe só 1 na fila: coloque o próximo quando `g.queue(b)` esvaziar | | 183 | Falta tecnologia / construção pré-requisito | Construa o pré-requisito primeiro, suba de tier | | 185 | Construção ocupada | O altar está revivendo um herói; o edifício principal não pode ser melhorado enquanto a fila estiver ocupada | | 221 | Item inexistente / em construção / em melhoria / já existe | O herói já existe (se morreu, use `revive`); esta loja não vende isso | | 89 | A loja ainda não tem estoque | No início, o item só aparece após o tempo de estoque da tabela de itens; numa loja recém-construída, a contagem começa quando ela fica pronta | | 1001 | Alvo não visível | O alvo está na névoa de guerra ou na máscara preta; use `attack_move` na posição dele | ## Aceito ≠ concluído O recibo só diz que “o motor aceitou o comando”, lido de volta no mesmo frame. O que acontece depois está fora do alcance dele: | Comando | Pode falhar mesmo depois de aceito | Como confirmar | |---|---|---| | Construir | Um ponto numa floresta também é aceito na hora; a falha só vem quando o trabalhador chega | Use `build_near` (acompanha se a fundação aparece) ou espere `production.done` | | Lançar feitiço | Interrompido, sem mana | No tick seguinte, veja se `g.cooldown(u, habilidade)` entrou em recarga | | Treinar | Entra na fila, mas falta comida e nunca começa | `g.production(b).blocked` | | Mover / atacar | Alterado por outra lógica (ou por uma camada de prioridade maior) | `g.current_target(u)`, `g.order_of(u)` | ## APIs de consulta As APIs abaixo não dão ordens, só consultam o motor; o resultado também vai no `value` do recibo (o SDK retorna o valor direto): | API | Retorna | |---|---| | `g.can_do(u, code)` / `g.can_do_many([(u, code), ...])` | O código de motivo da tabela acima | | `g.tech(code, player=None)` / `g.tech_many([...])` | Nível de pesquisa / número de construções prontas (incluindo a cadeia de melhorias) | | `g.visible(x, y)` | Se este ponto está visível para o seu lado | | `g.gold_left(mine)` | Quanto ouro resta na mina | | `g.enemy_ai_plan(tropa_inimiga)` | Para onde o capitão do computador vai levar as tropas (só vale para a IA do computador) | --- # De onde vêm os dados > A origem e a precisão de cada tipo de dado. Quando algo “parecer errado”, comece por esta página. | Dado | Origem | Precisão | |---|---|---| | Unidades, recursos, ordens, habilidades, buffs, inventário | Bloco do mundo enviado pelo runtime a cada 50 ms | Período de publicação (ajustável até 16 ms) | | Eventos de dano e abate | Registrados pelo runtime na thread do jogo, a cada golpe | Imediata | | Outros eventos (aparecer, morrer, trocar de ordem, subir de nível…) | Comparação entre duas publicações consecutivas | Período de publicação | | Tabela de produção (treino / pesquisa / construção / melhoria) | Campos de tempo da habilidade de produção do motor + tempo decorrido acumulado pelo runtime | Cerca de ±0.2 segundo de jogo | | Atributos de combate, tabela de counters | Tabelas de dados do próprio jogo (extraídas do jogo na sua máquina) | Sem os modificadores de itens, auras e buffs | | Caminhos | Transitabilidade do terreno no motor (células de 128) + árvores + área ocupada por construções, A* no lado do SDK | Uma célula; passagens mais estreitas que uma célula contam como bloqueadas | | Horário do jogo | JASS `GetFloatGameState(GAME_STATE_TIME_OF_DAY)` | Período de publicação | | Visibilidade | Máscara de visibilidade por jogador que o runtime calcula para cada unidade | Período de publicação | | Contagem de tecnologia, viabilidade, ouro restante na mina | Consulta pela via rápida, direto ao motor | Imediata | ## Os dados do jogo não são distribuídos com o código Tabelas de unidades, habilidades, itens, heróis, buffs, tabela de dano por tipo etc. vêm dos arquivos de jogo da Blizzard e **não entram no repositório**. Depois que você define a pasta do jogo na “Central de controle” do Farsight, eles são extraídos automaticamente do seu próprio jogo; também dá para rodar manualmente: ```bash python data/tools/extract_game_data.py ``` O resultado fica em `data/game/` (fora do git): os arquivos originais `.slk` / `.txt` e os arquivos organizados `units.json`, `names.json`, `skills.json`, `items.json`, `heroes.json`, `buffs.json`. ## Alguns números concretos | Grandeza | Valor | |---|---| | Um dia | 480 segundos de jogo (240 segundos de dia e 240 de noite); uma hora = 20 segundos de jogo; a partida começa às 8 da manhã | | Dia | 6:00 ~ 18:00 | | Coeficiente de armadura | 0.06 (da tabela de dados do jogo) | | Capacidade do bloco do mundo | 16 jogadores, 1024 unidades, 256 detalhes de unidade, 256 itens no chão, 128 produções | | Árvores | Até 4096 destrutíveis, atualizados a cada 2 segundos | | Anel de eventos | 8192 entradas; se a leitura for lenta demais, há perda (o SDK detecta) | | Grade do mapa | 128 unidades de jogo por célula, até 256 × 256 | ## Exemplos calibrados com medições reais - Tempo de produção: Camponês 14.9, Fazenda 34.9, Iron Forged Swords 59.9 segundos de jogo, iguais aos enviados pelo runtime (erro menor que 0.2 segundo); - Atributos de combate conferidos com o painel do jogo: Paladino com 650 de vida, 255 de mana, 3.9 de armadura e ataque 24 ~ 34; Soldado com a primeira melhoria de ataque, 13 ~ 15; - O dano antes da armadura nos eventos de dano do motor (14 / 15 / 15) cai dentro do intervalo calculado por `stats()`; - Caminhos: em Echo Isles, 116 × 88 células; distância por terra até o edifício principal do outro lado, 10642 (em linha reta, 9856); montar a grade leva 18 ms, e um A* cerca de 1 ms. --- # Perguntas frequentes > Isto é um cheat? Quais versões são suportadas? O que a IA vê e o que ela pode fazer? Dá para usar sem saber programar? … ## Isto é um cheat? Não. É uma interface de desenvolvimento para pesquisa e entretenimento com IA, usada apenas com **um cliente que você possui legalmente**, na sua máquina, em rede local ou em partidas criadas por você, contra o computador ou contra outras IAs. Ela **não pode ser usada na Battle.net nem em qualquer servidor com anti-cheat**, e não oferece nenhum recurso voltado a partidas contra pessoas reais. Veja os [Limites de uso](https://war3ai.com/pt/docs/legal/). ## Quais versões do jogo são suportadas? No momento, só o **Warcraft III 1.27** (The Frozen Throne). As versões 1.24 ~ 1.28 compartilham a mesma estrutura de motor; o suporte a várias versões (tabela de símbolos por versão, busca por assinatura de bytes como fallback, autoteste na inicialização que gera a lista de capacidades) está na fase P4 do [roadmap](https://war3ai.com/pt/roadmap/). A 1.29 em diante e o Reforged usam outro motor e precisariam de uma adaptação própria; por enquanto, não há compromisso com isso. ## Ele modifica os arquivos do meu jogo? Não. O runtime é injetado com o jogo em execução e **não modifica o Game.dll no disco** nem qualquer arquivo do jogo. Para abrir várias instâncias, ele apenas copia e renomeia o lançador original `War3.exe`, sem alterá-lo. Os dados do jogo (tabelas de unidades etc.) são extraídos do seu próprio jogo e não são distribuídos com o código. ## O que a IA consegue ver? Basicamente tudo o que um jogador profissional gostaria de saber, atualizado a cada 50 ms: - ouro, madeira e comida de todos os jogadores; posição, vida e mana, ordem atual, **quem cada unidade está atacando**, nível e experiência de todas as unidades; - nível e recarga restante das habilidades de heróis e unidades, buffs ativos, inventário; - o que cada construção está treinando / pesquisando / construindo / melhorando, o progresso e se está travada por falta de comida; - itens no chão, árvores, a grade andável / construível do mapa, pontos de início, horário do jogo (dia e noite); - fluxo de eventos: unidades aparecendo e morrendo, **cada golpe de dano** (quem atacou, tipo de ataque, dano antes da armadura), abates, produção concluída, herói subindo de nível… - também dá para perguntar direto ao motor: se algo pode ser feito agora e, se não, por quê; o nível de uma tecnologia; se um ponto está visível; quanto ouro resta numa mina; para onde o computador adversário vai levar as tropas. Além disso, o SDK já calcula os atributos de combate (counters, armadura, melhorias de ataque/armadura), “quantos segundos para matar” e os caminhos por terra. Todas as APIs estão no [catálogo da API](https://war3ai.com/pt/api/). ## O que a IA consegue fazer? Praticamente tudo o que um jogador faz: mover, atacar-mover, atacar um alvo, parar, manter posição, patrulhar, atacar o chão, coletar, reparar, construir (com escolha automática de local), treinar / pesquisar / melhorar, cancelar, aprender habilidades, lançar feitiços (em unidade / em ponto / sem alvo), ponto de encontro, reviver heróis, pegar / usar / largar / dar / vender itens, comprar, Call to Arms; fila com Shift, marcha por pontos de caminho, um trabalhador construindo várias em sequência; e ainda velocidade do jogo, pausa e balões de fala. Todo comando tem recibo. Além das ações de jogador, dá para desenhar seus próprios painéis e marcações na tela do jogo ([Canvas](https://war3ai.com/pt/docs/canvas/)) e, em partidas solo, chamar as 1291 funções JASS disponíveis para autores de mapas ([Canal JASS](https://war3ai.com/pt/docs/jass/)). ## Dá para usar em mapas RPG / personalizados? Dá. Escolha um mapa RPG, selecione o esquema “Exemplo de companheiro” para a instância e, depois que a partida começar, jogue você mesmo: um parceiro de IA vai andar ao seu lado, ajudando a lutar, curando você e conversando com você; veja [Companheiro de RPG](https://war3ai.com/pt/docs/companion/). `g.map_data` lê os nomes das unidades personalizadas do mapa; o [Canal JASS](https://war3ai.com/pt/docs/jass/) cria unidades, define aliados, abre painéis… como jogar fica por sua conta. Operações que alteram o mundo só estão disponíveis em partidas solo (em partidas multijogador causam dessincronização); o canvas é seguro também em partidas multijogador. ## Dá para usar sem saber programar? Dá. Prepare o ambiente pelo [Início rápido](https://war3ai.com/pt/docs/quickstart/) e depois veja [Escreva um Bot com um LLM](https://war3ai.com/pt/docs/ai-bot/): você descreve a estratégia em linguagem comum e o LLM escreve o código; se algo der errado, conte a ele o erro ou o que você viu no jogo e peça a correção. ## Só funciona com Python? O SDK é em Python. Entre o runtime e os programas externos há apenas um protocolo de memória compartilhada ([W3P](https://war3ai.com/pt/docs/protocol/)), e qualquer linguagem que leia e escreva memória compartilhada do Windows pode se integrar. O jeito mais fácil é o [gateway](https://war3ai.com/pt/docs/gateway/) (WebSocket / JSON): JS, C#, Go, Rust, páginas de navegador e programas em outra máquina podem chamar as mesmas APIs; agentes de LLM podem se conectar direto pelo [MCP](https://war3ai.com/pt/docs/mcp/). ## Qual LLM é o melhor? Qualquer modelo popular que saiba escrever código serve. O que importa não é o modelo, e sim **dar a ele o material certo** (manual + `api.json` + um exemplo) e exigir que ele use só métodos que existem no catálogo da API. Decisões em tempo real durante a partida (conselheiro, falas) são sensíveis à latência, e modelos MoE locais se saem muito bem; veja [LLM como coach de estratégia](https://war3ai.com/pt/docs/llm-coach/) e [Balões de fala e modelos locais](https://war3ai.com/pt/docs/speech/). ## Isso deixa o jogo mais lento? Uma coleta do estado do mundo leva uma mediana de 0.5 ~ 0.9 ms na thread do jogo (100 ~ 120 unidades), uma vez a cada 50 ms. Cada comando leva alguns microssegundos na thread do jogo; cada esvaziamento da fila tem um orçamento de 4 ms, e o que não couber fica para o próximo, sem travar o jogo. Toda chamada ao jogo tem proteção contra exceções: se um Bot quebrar, só aquele lado para; o jogo não cai. ## Dá para abrir vários jogos ao mesmo tempo? Dá. O `runtime/farm.py` orquestra várias instâncias, cada uma com um número; você as inicia e para no [console Farsight](https://war3ai.com/pt/docs/console/). O seu Bot se conecta a uma instância específica com `--inst N`. ## Dá para pôr duas IAs para se enfrentar? Abrir dois canais `player` na mesma partida (`--player 0` / `--player 1`) já é IA contra IA. No modo local, o jogo limpo depende de um acordo entre as partes; partidas oficiais com árbitro, filtro de visão e verificação de propriedade ficam na [Arena](https://war3ai.com/pt/arena/) (fase P6). ## Funciona no Mac / Linux? No momento, só no Windows 10 / 11. ## Qual é a licença? A licença será publicada junto com a versão oficial. Componentes de terceiros mantêm suas próprias licenças (por exemplo, MinHook é BSD-2); o AMAI tem licença própria, e os dados derivados dele não são distribuídos com o projeto: eles são baixados do repositório público do AMAI e gerados durante a instalação. ## Onde relatar problemas? Um canal para relatar problemas será aberto após o lançamento oficial. Ao relatar, inclua o número da instância, a saída de `python -m openwar3 status` e os passos para reproduzir. Antes, veja se [Depuração e desempenho](https://war3ai.com/pt/docs/debugging/) resolve. --- # Limites de uso > O que é permitido, o que não é, as estatísticas de acesso deste site, e avisos sobre marcas e licenças de terceiros. Ao usar este projeto, você concorda em respeitar estes limites. ## Permitido - Usar em um cliente do Warcraft III 1.27 **que você possui legalmente**; - Pôr IAs contra o computador ou contra outras IAs em partidas locais, offline, em rede local ou criadas por você; - Pesquisar, ensinar, se divertir e transmitir partidas das suas próprias IAs; - Desenvolver sobre o SDK, o cérebro de referência, os exemplos e as ferramentas, respeitando as licenças deles. ## Não permitido - **Não pode ser usado na Battle.net nem em qualquer servidor ou plataforma com anti-cheat**, nem ao mesmo tempo que uma sessão ativa de anti-cheat; - Não pode ser usado para obter vantagem indevida em partidas contra pessoas reais; - Não é permitido distribuir arquivos de jogo da Blizzard nem dados extraídos deles (este projeto também não distribui: os dados do jogo são extraídos pelo próprio usuário, do jogo dele); - Respeite a licença de uso do runtime. ## Nossos compromissos técnicos - Não modificamos o `Game.dll` no disco nem qualquer arquivo do jogo; todas as alterações acontecem em tempo de execução; - Abrir várias instâncias é só copiar e renomear o lançador original `War3.exe`, sem alterá-lo; - O projeto não contém nenhum código nem arquivo de jogo da Blizzard. ## Sua responsabilidade As leis sobre engenharia reversa e modificação de jogos variam de região para região. **Cabe a você verificar se o uso deste projeto é legal onde você está, e você assume as consequências do uso.** Este projeto é fornecido “no estado em que se encontra”, sem garantias de qualquer tipo, expressas ou implícitas. ## Estatísticas de acesso deste site Este site (war3ai.com) usa o Microsoft Clarity para medir as visitas: quais páginas foram vistas, de onde os visitantes vieram, quanto tempo ficaram, onde clicaram e até onde rolaram, além de gravações anônimas de navegação e mapas de calor. Usamos isso apenas para melhorar a documentação e as páginas. - Não é preciso se cadastrar, e não coletamos informações de identidade como nome ou e-mail; por padrão, o texto digitado nos campos fica mascarado e não é registrado; - O Clarity guarda cookies no navegador para distinguir as várias visitas de um mesmo visitante; os dados são processados pela Microsoft — veja a [Declaração de Privacidade da Microsoft](https://privacy.microsoft.com/privacystatement); - Se não quiser entrar nas estatísticas: abra uma vez qualquer endereço deste site com `?stats=off` no final, e este navegador deixa de ser contabilizado daí em diante (`?stats=on` reativa); bloquear `clarity.ms` com o recurso de bloqueio de rastreamento do navegador também funciona, e o site continua funcionando normalmente. O Farsight, o SDK e o runtime que rodam na sua máquina não têm esse tipo de estatística. O Farsight só se conecta a war3ai.com em dois casos: ao iniciar e depois a cada 6 horas, para ler a lista de versões e ver se há uma versão nova; e quando você clica em enviar na página “Feedback e sugestões”, para mandar o feedback que você escreveu e um identificador desta máquina (um hash com sal do identificador do sistema, do qual não dá para recuperar o valor original, usado para evitar spam). As informações de diagnóstico só são anexadas se você marcar a opção, e você pode visualizá-las antes de enviar. Quando essas duas solicitações chegam a war3ai.com, o servidor registra o endereço IP, o país ou a região determinados pelo Cloudflare, e a versão do cliente (User-Agent), para evitar abusos e contar quantas instalações do Farsight estão em uso. Os registros de verificação de versão são apagados automaticamente após 90 dias; o feedback, junto com essas informações, fica guardado até que os mantenedores o processem e apaguem. Esses dados só podem ser vistos internamente pelos mantenedores do projeto, e não são fornecidos a terceiros. ## Marcas registradas Warcraft® é marca comercial ou marca registrada da Blizzard Entertainment, Inc. War3AI / OpenWar3 é um projeto comunitário independente, sem vínculo com a Blizzard Entertainment e sem aprovação ou patrocínio dela. Os outros nomes de produtos citados (Claude, GPT, Gemini, Qwen etc.) pertencem aos respectivos donos e são usados apenas para indicar compatibilidade. ## Componentes e dados de terceiros | Componente / dado | Licença | Tratamento | |---|---|---| | MinHook | BSD-2-Clause | Usado com o runtime, mantendo o aviso de licença | | AMAI | Licença própria | Não é distribuído com o projeto; durante a instalação, o `start.bat` baixa do repositório público do AMAI, e uma ferramenta gera os dados usados pelo cérebro de referência | | Dados do jogo (unidades, habilidades, itens etc.) | Blizzard | Não são distribuídos com o projeto; o usuário os extrai do próprio jogo | | Dados factuais extraídos de replays públicos de partidas (posições de construções, ordens de abertura) | — | Apenas dados factuais, usados pelo cérebro de referência | --- # Catálogo da API (api.json) Status: verified = caminho interno verificado em partidas reais; experimental = API nova, já funcionando e ainda em verificação item por item em partidas reais; inferred = inferido / não totalmente testado. Latência:Snapshot enviado(Lê a memória compartilhada, sem esperar a thread do jogo (~0.05 ms)); Via rápida(~1 frame: execução em lote na thread do jogo); Canal de controle(20~40 ms (caminho antigo para operações de interface)); Escrita direta(Sem fila na thread do jogo — escreve na memória compartilhada (canvas) ou envia uma mensagem à janela do jogo); Cálculo local(Cálculo puro ou leitura de arquivo, sem tocar no jogo) ## Observação Lê o estado sem alterar o jogo. Quase tudo lê direto o snapshot enviado, sem espera. - `snapshot(max_age: 'float' = 0.05)` [verified] [Snapshot enviado] Estado completo do mapa inteiro (WorldState): .units .players .items .clock .me; chamadas repetidas dentro de max_age segundos retornam a mesma cópia. ⚠ Trabalhadores dentro de uma mina de ouro não aparecem na tabela; por padrão o mapa inteiro é visível (no modelo lockstep, a máquina local tem tudo); só Game(fair=True) filtra pela visão. (Mecanismo: Bloco de mundo W3P Local\War3World_ (o runtime envia a cada 50 ms, seqlock)) - `last_seen(owner: 'str | int' = 'enemy', max_age: 'float | None' = None) -> 'list'` [verified] [Snapshot enviado] Unidades inimigas (ou 'creep' para creeps, ou um número de jogador) vistas pela última vez: [(a unidade como estava naquele momento, o relógio de jogo naquele momento, quantos segundos se passaram)], as mais recentes primeiro. Se você a vê morrer, ela sai da tabela. Tanto o modo justo quanto o normal registram pelo critério "o que nós vemos neste momento" — é o mapa que o jogador tem na cabeça: o exército que você reconheceu, onde viu o herói inimigo pela última vez, quando o adversário abriu a expansão. max_age limita aos últimos tantos segundos de jogo. (Mecanismo: visibleTo do snapshot enviado (a cada atualização do snapshot, registra as unidades inimigas/creeps visíveis)) - `map()` [verified] [Snapshot enviado] A tabela de terreno desta partida, MapInfo: .walkable(x,y) .buildable(x,y) .at(x,y) .bounds (área jogável) .starts (pontos de início) .cells (bit0 não caminhável, bit1 não construível). Leva alguns segundos para ficar pronta depois do início da partida; antes disso retorna None. As árvores não estão incluídas (use trees()). (Mecanismo: Bloco de mapa W3P Local\War3Map_ (o runtime calcula em lotes depois do início da partida; IsTerrainPathable para caminhar/construir)) - `me() -> 'int | None'` [verified] [Snapshot enviado] Qual é o meu número de jogador (0~11). (Mecanismo: Cabeçalho do bloco de mundo) - `resources(player: 'int | None' = None) -> 'dict | None'` [verified] [Snapshot enviado] {'gold','lumber','food_used','food_cap','gold_gathered','lumber_gathered'}; player é o nosso por padrão, e dá para ler de qualquer jogador. Retorna None se não der para ler — não trate como 0. (Mecanismo: players[16] do bloco de mundo) - `players() -> 'list'` [verified] [Snapshot enviado] Todos os 16 slots de jogador: Player(id, gold, lumber, food_used, food_cap, gold_gathered, lumber_gathered, race, known). (Mecanismo: players[16] do bloco de mundo) - `units(owner: 'str | int' = 'all', types=None, alive: 'bool' = True) -> 'list'` [verified] [Snapshot enviado] Filtra unidades por dono/tipo. owner: 'me' / 'enemy' / 'creep' / 'all' / número do jogador. types: conjunto de códigos de quatro caracteres. (Mecanismo: units[] do bloco de mundo) - `unit(handle) -> 'object | None'` [verified] [Snapshot enviado] Encontra uma unidade pelo par de handles (lo, hi) (alvos de ordem, alvos de tarefa e eventos trazem pares de handles). (Mecanismo: by_handle do bloco de mundo) - `is_building(u) -> 'bool'` [verified] [Snapshot enviado] Se é uma construção (incluindo torres). Decide pela velocidade de movimento 0 na tabela de unidades; a área ocupada da sede dos Mortos-vivos é 0, então não use a área ocupada para decidir. (Mecanismo: Snapshot + units.json (spd==0 = construção)) - `my_workers() -> 'list'` [verified] [Snapshot enviado] Nossos trabalhadores (Camponês/Peão/Acólito/Wisp). (Mecanismo: Snapshot enviado) - `idle_workers() -> 'list'` [verified] [Snapshot enviado] Trabalhadores sem nada para fazer: sem ordem e sem tarefa (os que você acabou de designar neste tick não contam). ⚠ Dar nova ordem de coleta a um trabalhador que tem tarefa interrompe o ciclo de coleta (a renda vai a zero). (Mecanismo: Snapshot enviado (slot de ordem + slot de tarefa)) - `my_heroes() -> 'list'` [verified] [Snapshot enviado] Nossos heróis vivos (os mortos ficam na lista de revivência do altar; veja revive). (Mecanismo: Snapshot enviado) - `my_army() -> 'list'` [verified] [Snapshot enviado] Nossas unidades de combate: nem trabalhadores, nem construções. (Mecanismo: Snapshot enviado + units.json) - `my_buildings(types=None) -> 'list'` [verified] [Snapshot enviado] Nossas construções (incluindo torres e fundações em obra); types pode restringir a alguns tipos, como {'hbar'}. (Mecanismo: Snapshot enviado) - `is_constructing(worker) -> 'bool'` [verified] [Snapshot enviado] Se este trabalhador está construindo (ou indo construir / ajudando a reparar; inclui os designados neste tick). Pule-o ao escolher um construtor, senão a fundação anterior para. (Mecanismo: Snapshot enviado (ordem = código de quatro caracteres da construção, ou ordem de construir/reparar)) - `under_construction(building) -> 'bool'` [verified] [Snapshot enviado] Esta construção ainda não ficou pronta (vida não está cheia). ⚠ Construções danificadas também não estão com vida cheia — basta para decisões na abertura, mas depois que a luta começa, combine com o tempo. (Mecanismo: Snapshot enviado (a vida da fundação sobe de muito baixa até cheia)) - `gold_mines() -> 'list'` [verified] [Snapshot enviado] As minas de ouro do mapa. ⚠ A Mina de Ouro Enredada dos Elfos Noturnos e a mina neutra têm cada uma sua própria unidade na mesma coordenada; mande a coleta para a sua. (Mecanismo: Snapshot enviado (ngol/egol/ugol)) - `enemies(fighters_only: 'bool' = False) -> 'list'` [verified] [Snapshot enviado] Unidades dos jogadores inimigos (sem creeps). fighters_only: remove trabalhadores e construções. (Mecanismo: Snapshot enviado) - `creeps() -> 'list'` [verified] [Snapshot enviado] Creeps (neutros hostis). ⚠ À noite a visão diminui; quando um acampamento distante cai na névoa de guerra, comandos de alvo contra ele são rejeitados (código de motivo 1001). (Mecanismo: Snapshot enviado (owner 12 = neutro hostil)) - `life_mana(u) -> 'dict | None'` [verified] [Snapshot enviado] {'hp','hp_max','mana','mana_max'} (floats, valores brutos do engine). Para u, basta passar a unidade obtida do snapshot (ela é trocada pela cópia mais recente). (Mecanismo: hp/hpMax/mana/manaMax da unidade no bloco de mundo) - `hero_info(hero) -> 'dict | None'` [verified] [Snapshot enviado] {'level','xp','skill_points'}. (Mecanismo: level/xp/skillPoints da unidade no bloco de mundo) - `abilities(u) -> 'list'` [verified] [Snapshot enviado] [{code, level, cooldown, flags}]; os buffs ficam em buffs(u). Só unidades "com detalhes" têm isso (heróis > unidades de jogadores > creeps, até 256). (Mecanismo: Detalhes do bloco de mundo: habilidades (código/nível/flags/recarga restante)) - `buffs(u) -> 'list'` [verified] [Snapshot enviado] Códigos de buff na unidade (por exemplo, 'BHds' Escudo Divino, 'Bslo' Lentidão). O efeito de cada código está em data/game/buffs.json. (Mecanismo: Detalhes do bloco de mundo: objetos de habilidade que começam com B) - `cooldown(u, ability: 'str') -> 'float | None'` [verified] [Snapshot enviado] Quantos segundos (de jogo) faltam de recarga desta habilidade; 0 = pode lançar; retorna None se não tiver a habilidade (ou se a unidade não tiver detalhes). (Mecanismo: Detalhes do bloco de mundo: recarga restante da habilidade (timer da habilidade)) - `inventory(hero) -> 'list | None'` [verified] [Snapshot enviado] Códigos de quatro caracteres dos itens nos 6 espaços (espaço vazio é None); retorna None se não houver inventário. (Mecanismo: Detalhes do bloco de mundo: 6 espaços de inventário) - `current_order(u) -> 'dict | None'` [verified] [Snapshot enviado] {'order','target','x','y'}: a ordem que a unidade está executando (order é 0x000D00xx ou o código de quatro caracteres de uma construção; 0 = ociosa). target é um par de handles; use g.unit(target) para obter a unidade. (Mecanismo: order / alvo da ordem / ponto-alvo da ordem da unidade no bloco de mundo) - `current_target(u)` [verified] [Snapshot enviado] A unidade que ela está **de fato atacando/perseguindo** (None se não houver). ⚠ Depois de uma ordem de ataque, o slot de ordem esvazia rápido e o ataque fica preso à tarefa — para saber "quem ela está atacando", use isto, não current_order. (Mecanismo: Alvo da tarefa da unidade no bloco de mundo) - `clock() -> 'float | None'` [verified] [Snapshot enviado] Relógio de jogo do engine (segundos de jogo; 0 durante o carregamento). Com a velocidade do jogo aumentada, ele anda mais rápido que o relógio real. (Mecanismo: clockMs do cabeçalho do bloco de mundo (relógio de jogo do engine)) - `production(building)` [verified] [Snapshot enviado] O que esta construção está fazendo: Production(kind, queue, duration, elapsed, blocked, progress, remaining…); retorna None se não estiver fazendo nada. kind 'queue' (treino/pesquisa/herói; queue tem até 7 espaços, [0] é o que está em andamento) / 'construction' (em obra) / 'upgrade' (melhoria da sede/torre); blocked = há algo na fila mas não começou (quase sempre falta comida — hora de fazer uma Fazenda); progress 0..1. Também funciona para construções do adversário (no modo justo, só para as construções visíveis). (Mecanismo: Tabela de produção do bloco de mundo (objetos de habilidade Aque/ABnP/AUnP + tempo decorrido acompanhado pelo runtime; erro medido < 0.2 segundo de jogo)) - `queue(building) -> 'list'` [verified] [Snapshot enviado] Códigos de quatro caracteres na fila de treino/pesquisa ([0] em andamento); ociosa ou não é construção de produção = []. (Mecanismo: Tabela de produção do bloco de mundo) - `all_production(owner: 'str | int' = 'me') -> 'list'` [verified] [Snapshot enviado] Toda a produção em andamento [(construção, Production)]. owner igual a units(): 'me' / 'enemy' / número do jogador / 'all'. Uso profissional: ver que unidades o adversário está treinando, que tecnologias está pesquisando e quando sobe de tier (quando você reconhece as construções dele). (Mecanismo: Tabela de produção do bloco de mundo) - `path_distance(a, b) -> 'float | None'` [verified] [Snapshot enviado] Distância percorrida por uma unidade terrestre de a até b (a e b podem ser unidades ou (x,y)); None se não houver caminho. Em mapas com ilhas, use isto para saber "dá para chegar por terra a este acampamento/expansão"; é mais confiável que a distância em linha reta (contorna florestas, penhascos e construções). Precisão de uma célula de 128; frestas mais estreitas que uma célula contam como bloqueadas. (Mecanismo: Bloco de mapa (IsTerrainPathable do engine) + bloco de árvores + área ocupada pelas construções, A* no lado do SDK (128 por célula)) - `reachable(a, b) -> 'bool | None'` [verified] [Snapshot enviado] Se dá para chegar por terra (bloco de mapa ainda não calculado = None). (Mecanismo: Igual ao anterior) - `walk_path(a, b) -> 'list | None'` [verified] [Snapshot enviado] Pontos de virada do caminho [(x,y)...] (o último ponto é b); combine com path(units, lista de pontos) para a tropa seguir esse caminho (contornar torres, pegar trilhas secundárias). (Mecanismo: Igual ao anterior) - `upkeep(player: 'int | None' = None) -> 'dict | None'` [inferred] [Snapshot enviado] Faixa de manutenção: {'level': 'none'/'low'/'high', 'income': 1.0/0.7/0.4, 'next_at': comida da próxima faixa (sem próxima = None)}. Conhecimento de profissional: ao subir para o tier 3 ou fazer melhorias de ataque/armadura, pare em 50 de comida; só suba para 80 antes da batalha decisiva. (Mecanismo: Regra fixa da 1.27: 0~50 de comida sem cobrança, 51~80 renda ×0.7, 81~100 ×0.4) - `xp_to_next(hero) -> 'int | None'` [verified] [Snapshot enviado] Quanta experiência falta para o herói subir de nível (nível 10 = 0). (Mecanismo: level/xp do bloco de mundo + fórmula NeedHeroXP de MiscGame) - `creep_camps(link: 'float' = 600.0) -> 'list'` [verified] [Snapshot enviado] Agrupa os creeps (visíveis) do mapa em acampamentos: [{'x','y','units','level','hp','max_level'}], do mais perto para o mais longe da nossa base principal. level = nível total do acampamento (a medida usual de dificuldade de creeping), hp = vida total. Combine com time_to_kill / path_distance para escolher o acampamento. (Mecanismo: Snapshot enviado (creeps a até 600 uns dos outros formam um grupo) + nível de units.json) - `buff_info(code: 'str') -> 'dict | None'` [verified] [Cálculo local] O que é um código de buff: {'ability','effect','dur','hero_dur','targets'} (ex.: 'Bslo' -> Lentidão). Quando um código tem várias linhas, retorna a primeira. (Mecanismo: data/game/buffs.json (BuffID de AbilityData.slk -> habilidade/efeito/duração)) - `stats(u, player: 'int | None' = None)` [verified] [Snapshot enviado] Atributos de combate da unidade, combat.UnitStats: vida/mana máximas, armadura (incluindo melhorias de ataque/armadura e agilidade do herói), tipo de armadura, velocidade de movimento, visão de dia/à noite, armas (o que pode atacar, alcance, intervalo de ataque, faixa de dano, tipo de ataque, dano em área). u pode ser uma unidade (usa automaticamente a tecnologia do dono e o nível do herói) ou um código de quatro caracteres (player é o nosso por padrão). Depois combine com .dps_vs(outro) / .hits_to_kill(outro) / combat.time_to_kill(grupo, outro). ⚠ Não inclui itens, auras nem buffs. (Mecanismo: Tabelas de dados (UnitBalance/UnitWeapons/UpgradeData/MiscGame) + níveis de tecnologia em tempo real + nível do herói) - `time_to_kill(attackers, target) -> 'float | None'` [verified] [Snapshot enviado] Quantos segundos de jogo este grupo leva para matar target atacando junto (usa a vida atual de target; considera vantagens de tipo, armadura e melhorias de ataque/armadura; não considera posicionamento, dano em área nem cura). Uso profissional: ao focar fogo, ataque primeiro quem "morre mais rápido" (menor time_to_kill), não o mais próximo. Se não conseguem atingir = None. (Mecanismo: stats() + vida em tempo real) - `time_of_day() -> 'float | None'` [verified] [Snapshot enviado] Hora do dia no jogo (horas, 0~24). A partida começa às 8 da manhã; um dia inteiro = 480 segundos de jogo (240 s de dia e 240 s de noite, escalados pela velocidade do ciclo dia/noite). Retorna None se não der para ler (runtime antigo / fora de partida). (Mecanismo: Área de extensão do bloco de mundo: GetFloatGameState(GAME_STATE_TIME_OF_DAY)) - `is_night() -> 'bool | None'` [verified] [Snapshot enviado] Se agora é noite (18:00~6:00). Jogada profissional: à noite os creeps dormem (você ataca primeiro sem ser cercado) e a visão de todas as unidades diminui (boa hora para ataques-surpresa); as Sentinelas e as unidades dos Elfos Noturnos ficam invisíveis perto das árvores à noite. Retorna None se não der para ler. (Mecanismo: Área de extensão do bloco de mundo (dia das 6h às 18h)) - `seconds_until(hour: 'float') -> 'float | None'` [verified] [Snapshot enviado] Quantos segundos de jogo faltam até a hora hour do jogo (por exemplo, seconds_until(18) = quanto falta para anoitecer, útil para planejar creeping noturno). (Mecanismo: Área de extensão do bloco de mundo + dia de 480 segundos (medido: 20 segundos de jogo por hora)) - `items_on_ground() -> 'list'` [verified] [Snapshot enviado] Itens no chão [Item(addr, handle_lo, handle_hi, type, x, y, life)]. Pegar/usar um item emite o evento item.removed. (Mecanismo: items[] do bloco de mundo (só os que estão no chão: handle do portador todo FF)) - `trees(x: 'float | None' = None, y: 'float | None' = None, limit: 'int' = 60) -> 'list'` [verified] [Snapshot enviado] Árvores vivas (as de DestructableData cujo targType inclui tree); se você passar (x,y), vêm ordenadas da mais perto para a mais longe, no máximo limit árvores. Cada uma é Tree(addr, handle_lo, handle_hi, type, x, y, life) e pode ser passada direto para gather para cortar madeira. (Mecanismo: Bloco de árvores Local\War3Trees_ (atualizado a cada 2 segundos)) - `events() -> 'list'` [verified] [Snapshot enviado] O que aconteceu desde a última chamada: unit.appeared / unit.died / unit.removed / unit.damaged / order.changed / hero.levelup / owner.changed / item.appeared / item.removed / game.started (estes vêm da comparação entre publicações, precisão = período de publicação de 50 ms), além dos eventos de nível de engine damage / killed (o runtime os registra na thread do jogo no momento em que acontecem, então há um para **cada golpe**): damage: handle = quem apanhou, .source_addr = quem bateu (use snapshot().unit_by_addr para obter a unidade), .value = vida realmente perdida, .raw_damage = dano antes da armadura, .attack_type (normal/pierce/siege/magic/chaos/hero/spell), .damage_type killed: este golpe a matou, .source_addr = quem matou e ainda production.done, obtido pelo runtime acompanhando a tabela de produção (precisão = período de publicação): unidade = a construção, .done_code = código de quatro caracteres do que terminou, .done_kind = 'training' (unidade/herói/revivência) / 'research' / 'construction' (construção pronta) / 'upgrade' (subir de tier/melhorar torre), .value = quantos segundos de jogo levou Completados em 09-25: spell.cast: unidade = quem lançou, .spell código de quatro caracteres da habilidade, b nível, value recarga em segundos, x,y ponto de lançamento (detectado quando a habilidade entra em recarga, precisão = período de publicação) player.left: .player número do jogador que saiu / foi removido por derrota; game.ended: saída da partida selection.changed: a seleção do jogador local mudou (use g.selection() para obter as unidades) message: uma linha numa caixa de mensagens da tela (dicas do jogo, chat, sistema): .text texto completo, .frame número da caixa de mensagens, .chat = {'channel', 'sender', 'text'} (quando é chat; o que o jogador digita no chat é lido daqui) ui.click / ui.hover / hotkey / mouse.world: interface e entrada (g.ui), .key é a key do canvas / a tecla de atalho como foi registrada Cada um é Event(seq, kind, clock, addr, handle, type, owner, a, b, x, y, value, extra). No modo justo (fair=True) só vêm: eventos das suas próprias unidades, eventos de unidades visíveis agora (ou que estavam visíveis até 1 segundo atrás), dano contra nós / causado por nós e os eventos locais de interface / mensagens / partida. (Mecanismo: Anel de eventos Local\War3Events_ (comparação entre publicações + eventos de dano capturados pelo runtime)) - `selection() -> 'list'` [verified] [Snapshot enviado] Unidades que o jogador local tem selecionadas agora (a unidade principal vem primeiro; no máximo 12). Quando a seleção muda, é emitido o evento selection.changed. (Mecanismo: selAddrs na área de extensão do bloco de mundo W3P (o runtime inclui a seleção do jogador local em cada publicação)) - `messages() -> 'list'` [verified] [Snapshot enviado] Mensagens novas nas caixas de mensagens da tela desde a última chamada: [{'text', 'frame', 'repeat', 'seq', 'game_ms'}]. Dicas do jogo (“Você precisa de mais fazendas”, “Não é possível construir aí”), chat e mensagens do sistema estão todos aqui; frame indica qual caixa de mensagens. São as mesmas mensagens dos eventos message do fluxo de eventos (cada um com seu próprio cursor). (Mecanismo: Memória compartilhada Local\War3Msgs_ (mensagens na tela capturadas pelo runtime)) - `tech(code: 'str', player: 'int | None' = None) -> 'int | None'` [verified] [Via rápida] Nível de pesquisa / número de construções concluídas (a cadeia de melhorias conta: um Castelo também conta como htow). player é o nosso por padrão; dá para consultar qualquer jogador. (Mecanismo: Consulta W3P q_tech (contagem de tecnologia do jogador no engine)) - `can_do(u, code: 'str') -> 'int | None'` [verified] [Via rápida] O veredito de viabilidade do engine: 0/220 pode dar a ordem; 3 comida 8 falta ouro 9 falta madeira 32 fila cheia 183 falta pré-requisito 185 altar revivendo 221 item inexistente/em construção. ⚠ Para trabalhador construindo, sempre retorna 221 — não serve para avaliar o local (use build_near). (Mecanismo: Consulta W3P q_feasible (verificação de viabilidade do engine)) - `can_do_many(pairs) -> 'list'` [verified] [Via rápida] Faz muitos can_do de uma vez: pairs = [(unidade, código de quatro caracteres), ...]; retorna a lista de códigos de veredito na mesma ordem (os que não puderam ser consultados são None). Ao planejar o que construir/treinar em um tick, pergunte tudo de uma vez primeiro: é N vezes mais rápido que um can_do por vez (cérebro de referência, 09-23: planejamento de construção 76 -> 25 ms). (Mecanismo: Consulta W3P q_feasible × N, enviada em um lote) - `tech_many(codes, player: 'int | None' = None) -> 'dict'` [verified] [Via rápida] Consulta de uma vez várias contagens de tecnologia/construção: {código de quatro caracteres: quantidade ou None}. (Mecanismo: Consulta W3P q_tech × N, enviada em um lote) - `visible(x: 'float', y: 'float') -> 'bool | None'` [verified] [Via rápida] Se nós vemos este ponto agora (fora da névoa de guerra/máscara preta). Um bot em modo justo deve usar só inimigos visíveis. (Mecanismo: Consulta W3P q_visible (visível / névoa de guerra / máscara preta)) - `gold_left(mine) -> 'int | None'` [inferred] [Via rápida] Quanto ouro ainda resta na mina. (Mecanismo: Consulta W3P q_mine_gold (ouro restante na mina, segundo o engine)) - `enemy_ai_plan(enemy_unit) -> 'dict | None'` [verified] [Via rápida] O capitão da IA do computador: para onde ele está levando as tropas (você sabe onde ele vai atacar sua base antes de ele sair). Só funciona contra adversários controlados pelo computador; retorna None se a unidade não estiver seguindo um capitão. (Mecanismo: Consulta W3P q_captain (o capitão do computador que a unidade inimiga está seguindo)) - `order_of(u) -> 'int | None'` [verified] [Snapshot enviado] A ordem atual da unidade, **incluindo a que você acabou de dar neste tick** (enquanto o snapshot não alcança, usa a nova ordem do recibo). ⚠ Partida real de 09-23: hello_bot tinha acabado de mandar um camponês construir uma Fazenda, e no mesmo tick rush_bot viu no snapshot que ele estava "ocioso" e o mandou construir um Quartel; a Fazenda foi abandonada no meio várias vezes. Para escolher unidades "ociosas/que não estão construindo", use isto em vez de u.order. (Mecanismo: Ordem do snapshot + comandos deste processo que acabaram de ser aceitos (recibos)) - `can_afford(code: 'str') -> 'bool'` [verified] [Snapshot enviado] Se o ouro/madeira atuais bastam para comprar code (unidades, construções; pelos preços de units.json). O que não estiver na tabela de preços conta como acessível. ⚠ Os códigos de quatro caracteres de subir de tier têm preço acumulado na tabela, então aqui o resultado é conservador; no fim, vale o recibo do engine. (Mecanismo: Nossos recursos no snapshot enviado + preços de units.json) - `map_data()` [verified] [Cálculo local] Os dados do mapa em jogo (openwar3.mapdata.MapData): name_of('HC07') para nomes de unidades/itens/habilidades personalizados, hero_names, tooltip. A maioria das unidades de mapas RPG é criada pelo próprio mapa e não está na tabela de nomes embutida; se o jogo não foi aberto pelo lançador (o arquivo do mapa não é encontrado), retorna None. (Mecanismo: Arquivo do mapa (o caminho --map do lançador): w3u/w3t/w3a + wts; em mapas protegidos, lê os TXT de dentro do mapa) ## Comandos Faz as unidades agirem. Executado em cerca de um frame, cada um com recibo. - `batch() -> 'Batch'` [verified] [Via rápida] Junta os comandos de um tick em um lote: with g.batch() as b: g.attack(archers, target) # retorna Pending, que só vira recibo quando o bloco termina g.move(wounded, *home) g.cast(hero, "thunderclap") print(b.sent, b.wait_ms, [r.reason for r in b.receipts]) Cada comando enviado sozinho espera a thread do jogo processar uma vez (cerca de 10 ms); um lote espera uma única vez — com isso o cérebro de referência (09-23) caiu de 48 -> 26 ms por rodada. * A arbitragem continua comando a comando (unidades reservadas por outro recebem na hora um recibo held e não entram no lote); * Comandos dentro do bloco retornam Pending: ler .ok antes do fim do bloco lança erro (o recibo ainda não existe); depois do fim, use como um Receipt; * Exceção dentro do bloco = o lote inteiro é descartado (status 97 cancelled), e as unidades reservadas são liberadas; * Consultas (can_do / tech / visible …), build_near e buy não entram no lote e continuam sendo feitas na hora — o resultado delas é usado na hora; para perguntar muitas coisas de uma vez, use can_do_many / tech_many; * with g.batch() aninhados se juntam ao lote mais externo; acima de 16 comandos, o runtime divide automaticamente em partes (uma espera por parte). (Mecanismo: Os comandos do bloco são acumulados em um lote e enviados de uma vez quando o bloco termina (executados no mesmo frame, esperando a thread do jogo uma única vez)) - `move(units, x: 'float', y: 'float', queue: 'str | None' = None)` [verified] [Via rápida] Vai até (x,y) sem atacar no caminho (use para recuar). Aceita uma unidade ou uma lista (a ordem sai para todas no mesmo frame). queue='after': vai depois de terminar o que está fazendo (inserido após a ordem atual). values[0] do recibo = quantas ordens a unidade tem na fila depois do comando (incluindo a atual). (Mecanismo: W3P point: move (bits de extra = modo de fila)) - `attack_move(units, x: 'float', y: 'float', queue: 'str | None' = None)` [verified] [Via rápida] Atacar-mover (A no chão): ataca os inimigos que encontrar no caminho. queue igual a move. (Mecanismo: W3P point: attack em um ponto) - `attack(units, target, force: 'bool' = False, queue: 'str | None' = None)` [verified] [Via rápida] Ataca target. Por padrão usa o botão direito (em um inimigo = atacar este aqui; medido em 09-23: o alvo da ordem e o alvo da tarefa são ele). ⚠ O alvo precisa estar no campo de visão; os que você não vê são rejeitados (código de motivo 1001). force=True usa a ordem de ataque 0x0F (necessária para atacar aliados/bichos neutros) — medido: ela só troca para a ordem de ataque sem memorizar o alvo, e a unidade vai atacar outros inimigos por perto; não use para atacar um alvo específico. (Mecanismo: W3P target: comando de alvo (botão direito, smart)) - `stop(units)` [verified] [Via rápida] Para tudo o que está fazendo (ID de ordem 0x000D0004) e limpa também as ordens na fila. (Mecanismo: W3P immediate: stop) - `hold(units, queue: 'str | None' = None)` [verified] [Via rápida] Manter posição (não persegue; só ataca o que estiver no alcance). (Mecanismo: W3P immediate: holdposition) - `patrol(units, x: 'float', y: 'float', queue: 'str | None' = None)` [inferred] [Via rápida] Patrulha entre a posição atual e (x,y). (Mecanismo: W3P point: patrol) - `attack_ground(units, x: 'float', y: 'float', queue: 'str | None' = None)` [verified] [Via rápida] Atacar o chão: a artilharia dispara numa área (contra unidades invisíveis, inimigos atrás das árvores, para bloquear uma passagem). Só unidades que podem atacar o chão aceitam. (Mecanismo: W3P point: attackground (unidades de cerco / morteiros / demolidores)) - `cancel(building)` [verified] [Via rápida] Cancela: o último espaço da fila de treino/pesquisa (devolve o dinheiro), a construção em obra (devolve 75%), a sede em melhoria. (Mecanismo: W3P immediate: cancel) - `path(units, points, attack: 'bool' = False)` [verified] [Via rápida] Passa por uma sequência de pontos em ordem (Shift com vários pontos: waypoints, contornar torres, rotas de reconhecimento). attack=True faz cada trecho ser um atacar-mover. Um único envio; um recibo por ponto (na ordem de points). (Mecanismo: Um lote: o primeiro trecho é executado na hora, o resto é inserido em ordem inversa com queue='after' (o engine só sabe inserir depois da ordem atual)) - `gather(workers, target, queue: 'str | None' = None)` [verified] [Via rápida] Minerar ouro/cortar madeira (target é uma mina de ouro ou uma árvore de trees()). ⚠ Só designe trabalhadores ociosos (idle_workers): dar nova ordem a quem tem tarefa interrompe o ciclo de coleta. Uso profissional: voltar a minerar depois de construir = build(...) seguido de gather(worker, mine, queue='after'). (Mecanismo: W3P target: harvest (mina de ouro ou árvore)) - `repair(workers, building, queue: 'str | None' = None)` [verified] [Via rápida] Reparar / ajudar a construir (as obras dos Humanos e dos Orcs param se ninguém estiver construindo). (Mecanismo: W3P target: repair) - `build(worker, code: 'str', x: 'float', y: 'float', queue: 'str | None' = None)` [verified] [Via rápida] Manda o trabalhador construir code em (x,y) (coordenadas alinhadas a 32). Recibo aceito = a ordem do trabalhador já é esta construção (ou a ordem de começar a obra); com queue='after' = entrou na fila de ordens do trabalhador (values[0] do recibo = tamanho da fila). ⚠ Aceito ≠ construído: o engine também aceita na hora um ponto dentro de uma floresta, e o trabalhador só falha quando chega lá (medido em 09-23); se o dinheiro for gasto em outra coisa, a fundação também não sai. Se não souber onde cabe, use build_near (ele acompanha o resultado e bloqueia os pontos que falharam). Para construir várias em sequência, use build_queue. (Mecanismo: W3P build: ordem de construção; confirma lendo a ordem do trabalhador no mesmo frame) - `build_queue(worker, plan)` [verified] [Via rápida] Um trabalhador constrói várias em sequência (Shift): plan = [(código de quatro caracteres, x, y), ...]. Um único envio; recibos na ordem de plan. ⚠ O dinheiro só é descontado quando a obra começa (não ao enfileirar) — se você enfileirar 3 mas só tiver dinheiro para 1, as outras duas falham quando o trabalhador chegar. (Mecanismo: Um lote: a primeira na hora, o resto em ordem inversa com queue='after') - `build_near(worker, code: 'str', x: 'float', y: 'float', min_r: 'float' = 450, max_r: 'float' = 1500, max_tries: 'int' = 24)` [verified] [Via rápida] Procura em volta de (x,y), do mais perto para o mais longe, um ponto onde caiba code e constrói. **Não bloqueia**; pode chamar a cada tick: * já existe uma tentativa em andamento para este tipo de construção (o trabalhador está a caminho) -> retorna esse ponto, sem repetir a ordem; * a última deu certo (a fundação apareceu) -> desta vez procura um ponto novo, se precisar; * a última falhou (o trabalhador chegou e viu que não cabia, o engine cancelou a ordem, não há fundação) -> esse ponto fica bloqueado por 45 segundos e passa para o próximo; * falta dinheiro -> retorna None direto (sem tentar, sem bloquear); se esgotar as tentativas, retorna None. ⚠ Por que acompanhar: na partida real de 09-23, o engine **aceitou na hora** um ponto dentro de uma floresta, e o trabalhador só falhou ao chegar lá (o recibo do mesmo frame não tem como saber); além disso, a verificação de local do engine sempre retorna 221 quando é um trabalhador construindo, então não dá para "consultar" antes de construir. Só pontos obviamente ocupados (bem no meio da sede) são rejeitados na hora. (Mecanismo: build ponto a ponto + acompanhamento (fundação apareceu = sucesso; trabalhador abandonou a ordem sem fundação = ponto bloqueado)) - `train(building, code: 'str')` [verified] [Via rápida] Treina unidade / pesquisa tecnologia / melhora a sede (subir de tier = dar à própria sede o código de quatro caracteres da sede alvo, como 'hkee'). Quando rejeitado, o reason do recibo diz por quê (falta comida, falta ouro, falta madeira, fila cheia, falta pré-requisito…). (Mecanismo: W3P immediate: código de quatro caracteres; quando rejeitado, traz o código de motivo da viabilidade) - `learn(hero, ability: 'str')` [verified] [Via rápida] O herói aprende uma habilidade (código de quatro caracteres, como 'AHbz' Nevasca). (Mecanismo: W3P learn: só conta como aprendida quando os pontos de habilidade diminuem) - `cast(u, spell, target=None, x: 'float | None' = None, y: 'float | None' = None)` [verified] [Via rápida] Lança uma magia. spell é uma string de ordem ('thunderbolt' Storm Bolt, 'blizzard', 'holybolt' Luz Sagrada…, veja data/order-ids.txt) ou um ID de ordem. Com target = em uma unidade; com x,y = no chão; sem nenhum dos dois = sem alvo (Trovoada, Escudo Divino, invocar Elemental da Água). Recibo aceito só significa que o engine aceitou; para saber se a magia saiu, veja se cooldown() entrou em recarga ou se apareceu algo em buffs(). (Mecanismo: W3P target / point / immediate (escolhido pelos parâmetros)) - `rally(building, x: 'float | None' = None, y: 'float | None' = None, target=None)` [verified] [Via rápida] Define o ponto de encontro (em um ponto, ou em uma unidade/mina de ouro). (Mecanismo: W3P rally) - `revive(altar, hero=None)` [verified] [Via rápida] Revive no altar um herói morto (sem hero, revive o primeiro da lista). Motivos comuns de rejeição (aparecem no reason do recibo): falta comida (heróis também ocupam comida), falta dinheiro, morreu há pouco (só dá para reviver cerca de 3 segundos de jogo depois da morte), revivência já em andamento (ao aceitar, o engine limpa aquele espaço na hora). (Mecanismo: W3P revive: lista de heróis mortos -> o altar lança a revivência no herói morto) - `pick_up(hero, item)` [verified] [Via rápida] O herói vai pegar um item do chão (item vem de items_on_ground). Depois de pegar, o item aparece no inventário e o chão emite o evento item.removed. (Mecanismo: W3P target: botão direito no item) - `use_item(hero, slot: 'int', target=None, x: 'float | None' = None, y: 'float | None' = None)` [verified] [Via rápida] Usa o item do espaço slot (0~5) do inventário; pode levar uma unidade-alvo ou um ponto-alvo. ⚠ Ao usar um item em um ponto (por exemplo, a Torre de Marfim), o engine retorna 0 mesmo quando dá certo, então o recibo sempre conta como aceito — confira se aquele espaço do inventário esvaziou. (Mecanismo: W3P use_item (pelo número do espaço)) - `drop_item(hero, slot: 'int', x: 'float', y: 'float')` [verified] [Via rápida] Larga o item do espaço slot do inventário em (x,y) (o herói vai até lá e o deixa). (Mecanismo: W3P item_drop (copiado de JASS UnitDropItemPoint: dropitem 0xD0021 em um ponto + alvo imediato do item)) - `give_item(hero, slot: 'int', to)` [verified] [Via rápida] Dá o item do espaço slot do inventário a to (outro herói / unidade; ele vai até lá e entrega). Dar a uma loja = vender (veja sell_item). (Mecanismo: W3P item_drop (copiado de JASS UnitDropItemTarget: dropitem em uma unidade)) - `sell_item(hero, slot: 'int', shop)` [verified] [Via rápida] Vende o item do espaço slot do inventário para a loja (o herói precisa ir até perto da loja; só aceita itens vendáveis, pagando metade do preço). (Mecanismo: Igual a give_item, com a loja como alvo (medido: o Staff of Sanctuary foi vendido por 125 de ouro)) - `move_item(hero, slot: 'int', to_slot: 'int')` [verified] [Via rápida] Troca de espaço dentro do inventário (move o espaço slot para o espaço to_slot; se os dois estiverem ocupados, eles trocam). Útil para organizar as teclas de atalho. (Mecanismo: W3P target: ordem 0xD0022+número do espaço, alvo = item (copiado de JASS UnitDropItemSlot)) - `buy(shop, item_code: 'str')` [inferred] [Via rápida] Compra um item na loja (para o herói parado ao lado da loja). Se faltar pré-requisito de tecnologia, o engine retorna 0 e não desconta o dinheiro. (Mecanismo: W3P buy: a loja vende ao herói que está ao lado) - `call_to_arms(hall, on: 'bool' = True)` [verified] [Via rápida] Chamado às Armas dos Humanos: os camponeses viram Milícia (a sede de tier 1, Town Hall, não tem essa habilidade; só funciona em Keep/Castle). (Mecanismo: W3P immediate: townbellon/off) ## Controle do jogo Velocidade, pausa, período de publicação, balões de fala, canvas, interface e entrada, mensagens. - `ui()` [verified] [Escrita direta] Interface e entrada (openwar3.ui.UI): botões e cartões de escolha clicáveis, teclas de atalho, clique no chão para escolher uma posição, para onde o mouse aponta. O clique dado sobre um botão não chega ao jogo; só entrada local + desenho local, seguro também em partidas multijogador. (Mecanismo: W3P 74 input_enable + memória compartilhada Local\War3Input_ (o runtime recebe a entrada da janela)) - `set_speed(percent: 'int') -> 'bool'` [verified] [Canal de controle] Velocidade do jogo (100 = velocidade normal). (Mecanismo: Ação 47 (25~800%)) - `pause(on: 'bool' = True)` [verified] [Via rápida] Pausa / retoma o jogo. Com o jogo pausado, o relógio do engine para, mas a via rápida continua aceitando ordens (o despacho de eventos continua rodando). (Mecanismo: W3P pause) - `set_publish_period(ms: 'int') -> 'None'` [verified] [Snapshot enviado] Período de publicação do estado do mundo (16~1000 ms, padrão 50). Uma coleta leva cerca de 0.5 ms, então até 33 ms funciona; há um único valor compartilhado pela máquina inteira, e vale a última escrita. (Mecanismo: requestedPeriodMs do bloco de mundo) - `say(u, text: 'str', seconds: 'float' = 4.0) -> 'bool'` [verified] [Canal de controle] Mostra um balão de fala sobre a unidade (para transmissões/depuração; não afeta o jogo). Retorna False se o balão não apareceu; o motivo fica em g.last_say_error. (Mecanismo: Ação 56) - `message(text: 'str') -> 'bool'` [inferred] [Canal de controle] Escreve uma linha na área de mensagens no canto inferior esquerdo do jogo (só visível na máquina local). Só funciona depois que o próprio jogo tiver mostrado alguma mensagem (a DLL captura a caixa de mensagens nesse momento). (Mecanismo: Ação 45) - `end_game() -> 'bool'` [verified] [Canal de controle] Encerra este processo do jogo (farm.py --keep abre a próxima partida automaticamente conforme next_game.json). (Mecanismo: Ação 22) - `canvas()` [verified] [Escrita direta] Canvas: desenha na tela do jogo caixas de texto, painéis, barras de progresso, imagens, círculos e rotas no chão (openwar3.canvas.Canvas). Quem desenha é o próprio runtime, sem criar handles de jogo nem alterar o estado do jogo — seguro também em partidas multijogador; estilo livre (texto em chinês, cantos arredondados, transparência). (Mecanismo: W3P 73 canvas_enable + memória compartilhada Local\War3Canvas_ (o runtime desenha a cada frame logo antes de o jogo desenhar o cursor do mouse; o cursor fica por cima)) - `press_to_continue() -> 'bool'` [verified] [Escrita direta] Aperta espaço uma vez na tela de carregamento "Pressione qualquer tecla para continuar". Muitos mapas RPG / de história exigem uma tecla depois de carregar para começar (medido em 09-24 no WarChasers: sem apertar, o jogo fica parado na tela de carregamento, com relógio de jogo 0 e a via rápida sem esvaziar). O openwar3.run aperta sozinho enquanto espera a partida começar; em geral não é preciso chamar à mão. (Mecanismo: PostMessage WM_KEYDOWN/UP de espaço para a janela do jogo (sem roubar o foco)) ## Sandbox Canal JASS: criar unidades, definir aliados, renomear, exibir texto… para auxiliares de RPG e companheiros; só altera o mundo em partidas solo e em ferramentas locais. - `jass()` [verified] [Via rápida] Chama qualquer native JASS pelo nome: g.jass.CreateUnit(g.jass.Player(1), "Hpal", x, y, 270.0). Parâmetros I/R/B/S/H são convertidos automaticamente (objetos de unidade/item podem ser passados direto); em partidas multijogador, só as funções de leitura podem ser chamadas. Detalhes em openwar3/jass.py e docs/COMPANION_ZH.md. (Mecanismo: W3P 70 jass (o runtime procura a native pelo nome na tabela de natives, 1291 no total)) - `player_slots() -> 'list[dict]'` [verified] [Via rápida] Os 16 slots de jogador: controller (user = humano / computer / neutral…), state (empty / playing / left), human, me, ally (se é nosso aliado). Serve para achar um slot vazio para o companheiro em mapas RPG e para saber se a partida é solo. (Mecanismo: JASS GetPlayerController / GetPlayerSlotState / IsPlayerAlly) - `spawn(code: 'str', x: 'float', y: 'float', player: 'int | None' = None, facing: 'float' = 270.0)` [verified] [Via rápida] Cria uma unidade em (x,y) (player é o jogador local por padrão) e retorna a unidade do snapshot (espera a próxima publicação do mundo, ~50 ms); se não conseguir criar, retorna None. A unidade retornada tem um atributo extra, jass_handle. ⚠ Só funciona em partidas solo (em partidas multijogador, causa dessincronização). (Mecanismo: JASS CreateUnit + W3P 72 handle -> unidade) - `set_alliance(a: 'int', b: 'int', allied: 'bool' = True, vision: 'bool' = True, control: 'bool' = False, xp: 'bool' = False, both: 'bool' = True) -> 'None'` [verified] [Via rápida] Define a aliança do jogador a com b: allied = não se atacam + pedem ajuda um ao outro; vision = visão compartilhada; control = controle de unidades compartilhado (b pode comandar as unidades de a); xp = experiência compartilhada. both=True define os dois sentidos de uma vez (control só de a -> b). (Mecanismo: JASS SetPlayerAlliance) - `set_player_name(player: 'int', name: 'str') -> 'None'` [verified] [Via rápida] Muda o nome do jogador (o que aparece no placar, no chat e no painel de aliados). Serve para dar nome ao companheiro. (Mecanismo: JASS SetPlayerName) - `show_text(text: 'str', seconds: 'float' = 6.0, player: 'int | None' = None) -> 'None'` [verified] [Via rápida] Mostra uma linha de texto no canto inferior esquerdo da tela (o tipo de texto que os gatilhos do mapa usam), por padrão só para o jogador local. Aceita códigos de cor |cffRRGGBB. (Mecanismo: JASS DisplayTimedTextToPlayer) ## Conexão e utilitários Estado da conexão e utilitários de cálculo puro. - `status() -> 'dict'` [verified] [Cálculo local] Estado da conexão: pid, publicação do mundo (período, tempo de coleta), contadores da via rápida. (Mecanismo: Bloco de mundo + via rápida + tabela de reivindicação) - `nearest(candidates, to)` [verified] [Cálculo local] O mais próximo de to (uma unidade ou (x,y)); retorna None se não houver candidatos. (Mecanismo: Cálculo puro)