Docs Referência

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.

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

<pid> é o ID do processo do jogo.

NomeDireçãoConteúdoSincronização
Local\War3World_<pid>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çãoseqlock
Local\War3Trees_<pid>runtime → vocêAté 4096 destrutíveis (árvores etc.), atualizados a cada 2 segundosseqlock
Local\War3Events_<pid>runtime → vocêAnel de eventos, 8192 entradascada entrada traz seu próprio número de sequência
Local\War3Map_<pid>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 partidaseqlock (não muda mais depois de calculado)
Local\War3Fast_<pid>nos dois sentidosVias de comando: 16 vias × 16 slots; cada slot guarda um comando + recibo; cada via tem um papelum escritor e um leitor por slot
Local\War3Canvas_<pid>você → runtimeCanvas: cabeçalho de 64 bytes + 256 elementos × 112 bytes + pool de 64 KB para texto / pontos; só é criado depois de enviar canvas_enable uma vezseqlock (você escreve, o runtime lê a cada frame)
Local\War3Msgs_<pid>runtime → vocêAnel de mensagens da tela: texto completo de dicas do jogo, chat e mensagens do sistema, 128 entradas × 256 bytescada entrada traz seu próprio número de sequência
Local\War3Input_<pid>nos dois sentidosInterface e entrada: 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 vezseqlock 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_<pid> 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_<pid> (cabeçalho de 16 bytes + 16 clientes × 528 bytes); segurando Local\War3InputMutex_<pid>, 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)

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

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_<pid>: 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_<pid>, 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_<pid>_<lane> (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

OpcodeNomeDescrição
1pointOrdem 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)
2targetOrdem de uma unidade para um alvo (ataque com botão direito / coletar / reparar / magia em alvo / pegar item); o alvo precisa estar visível
3immediateComando sem alvo (parar / manter posição / treinar / pesquisar / melhorar / magia sem alvo)
4buildTrabalhador constrói uma construção (coordenadas alinhadas a 32)
5learnHerói aprende uma habilidade
6use_itemUsa o espaço extra do inventário
7reviveRevive um herói no altar
8rallyPonto de encontro (ponto / alvo)
9buyUma loja vende um item a um herói próximo
10item_dropSoltar um item: entregar a um aliado, vender a uma loja (code = a unidade que recebe) ou largar no chão
20 ~ 25Consultasq_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
30pausePausar / continuar
40 ~ 50CâmeraLer estado da câmera, definir campo, olhar para um ponto, seguir, redefinir, girar, limites, suavização, mostrar/ocultar interface, tela limpa, névoa
60 ~ 63HUDTexto do botão de missões, título e descrição do painel de missões, atualizar, ler se o painel está aberto
70jassChama 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
71 / 72jass_handle_of / jass_unit_ofConverte entre unidade do snapshot ↔ handle JASS (o par de handles do snapshot não é um handle JASS)
73canvas_enableCria 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
74input_enableextra = 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_<pid>: 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

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.

6. Papéis das vias

PapelO que pode fazer
devFerramentas locais: comandos semânticos (comandando as unidades do jogador local) + canal JASS
playerSó comandos semânticos, e só para unidades do jogador dono da via (as de outros = not_owner)
observerSó 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).

No modo local, o papel é declarado pelo próprio cliente (uma convenção, não uma barreira de segurança). Na 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).