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) esdk/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.
| Nome | Direção | Conteúdo | Sincronizaçã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ção | seqlock |
Local\War3Trees_<pid> | runtime → você | Até 4096 destrutíveis (árvores etc.), atualizados a cada 2 segundos | seqlock |
Local\War3Events_<pid> | runtime → você | Anel de eventos, 8192 entradas | cada 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 partida | seqlock (não muda mais depois de calculado) |
Local\War3Fast_<pid> | 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_<pid> | você → runtime | 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_<pid> | 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_<pid> | nos dois sentidos | Interface 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 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_<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 ereserved[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 de0x10000). - 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); segurandoLocal\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 envieinput_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
requestedPeriodMspara 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çãoprods[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:acódigo de quatro caracteres da habilidade,bnível,valuerecarga em segundos,x/yponto de lançamento),player.left(anúmero do jogador,bnovo 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 compartilhadaLocal\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(aid do item do canvas,b1 botão esquerdo / 2 botão direito),ui.hover,hotkey(aid da tecla de atalho,bcódigo de tecla virtual),mouse.world(x/ycoordenadas no chão,value= 1 significa que foi engolido); as teclas modificadoras ficam emextra.
4. Enviando comandos
- 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; - Preencha os slots: flag de comando semântico, opcode,
args[11], prazodeadlineMs; - Depois de escrever todos os slots, marque-os como enviados e some 1 ao
submitSeqda via; - 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
| 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 |
| 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_<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
| 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).
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).