Docs Referencia

Protocolo W3P

Todo el contrato entre el runtime y los programas externos: ocho bloques de memoria compartida, lectura del estado del mundo, lectura de eventos, envío de comandos, recibos, roles de carril, lienzo e interfaz y entrada. Si te integras desde un lenguaje distinto de Python, lee esta página.

El runtime y los programas externos intercambian datos solo a través de memoria compartida. Lo que sigue es todo lo que hay.

  • La implementación de referencia es la de Python: sdk/python/w3world.py (lectura) y sdk/python/w3fast.py (escritura); ahí están definidos el tamaño y los offsets de cada estructura, fijados por tests;
  • El protocolo solo describe semántica y no depende de la versión del juego. Cuando cambia la versión del juego, el runtime se adapta solo y el protocolo no cambia; los campos nuevos solo se añaden al final de un bloque, así que los clientes antiguos siguen funcionando.

La mayoría no necesita leer esta página: basta con usar el SDK de Python. Solo la necesitas si quieres integrarte directamente desde C++ / C# / Rust / Go u otro lenguaje, o si quieres saber qué ocurre por debajo del SDK.

1. Ocho bloques de memoria compartida

<pid> es el ID del proceso del juego.

NombreDirecciónContenidoSincronización
Local\War3World_<pid>runtime → túEstado del mundo: cabecera + 16 jugadores + hasta 1024 unidades + 256 detalles de unidad + 256 objetos en el suelo + zona de extensión + tabla de producciónseqlock
Local\War3Trees_<pid>runtime → túHasta 4096 destructibles (árboles, etc.), se refresca cada 2 segundosseqlock
Local\War3Events_<pid>runtime → túAnillo de eventos, 8192 entradascada entrada lleva su propio número de secuencia
Local\War3Map_<pid>runtime → túMapa: celdas de terreno (128 por celda, hasta 256×256) + límites del área jugable + puntos de inicio; se calcula por lotes durante los primeros segundos de la partidaseqlock (no cambia una vez calculado)
Local\War3Fast_<pid>bidireccionalCarriles de comandos: 16 carriles × 16 slots; cada slot contiene un comando + su recibo; cada carril tiene un rolun escritor y un lector por slot
Local\War3Canvas_<pid>tú → runtimeLienzo: cabecera de 64 bytes + 256 elementos × 112 bytes + 64 KB de pool de texto / puntos; solo se crea después de enviar una vez canvas_enableseqlock (tú escribes, el runtime lee en cada fotograma)
Local\War3Msgs_<pid>runtime → túAnillo de mensajes en pantalla: texto completo de avisos del juego, chat y mensajes del sistema, 128 entradas × 256 bytescada entrada lleva su propio número de secuencia
Local\War3Input_<pid>bidireccionalInterfaz y entrada: el runtime escribe la posición del ratón, el punto del suelo al que apunta y el elemento bajo el cursor; tú escribes la tabla de atajos de teclado y los interruptores del ratón; el runtime solo empieza a capturar la entrada después de enviar una vez input_enableseqlock para la tabla de atajos

Varios clientes usando el lienzo y la entrada a la vez: de estos dos bloques solo hay una copia, y si cada uno escribe por su cuenta se sobrescriben entre sí. Esta es la convención, y tu propio cliente también tiene que seguirla:

  • Lienzo: con el mutex con nombre Local\War3CanvasMutex_<pid> tomado, haz lectura - modificación - escritura, cambia solo tus propios elementos y deja intactos los de los demás (recolocando los offsets del pool); elimina los que no tienen dueño y los de dueños cuyo proceso ya terminó. En cada elemento, reserved[1] = ID del proceso dueño y reserved[2] = número de secuencia dentro del proceso; los números de elemento se asignan desde el contador en el offset 60 de la cabecera del bloque (empezando en 0x10000).
  • Entrada: cada cliente registra sus atajos de teclado y sus interruptores del ratón en Local\War3InputClients_<pid> (cabecera de 16 bytes + 16 clientes × 528 bytes); con Local\War3InputMutex_<pid> tomado, actualiza su propia entrada y luego escribe en el bloque de entrada la combinación de todos los clientes vivos: los atajos se deduplican por «código de tecla + modificadores» y los interruptores del ratón se unen. Los eventos se envían a todos los clientes, y cada uno reconoce sus atajos por «código de tecla + modificadores». Mientras queden otros clientes vivos en la tabla de registro, no envíes input_enable 0.
  • Runtime: los elementos clicables cuyo proceso dueño ya terminó dejan de interceptar clics; cada 2 segundos revisa la tabla de registro y, si todos los clientes registrados han terminado, pone a cero la tabla de atajos y los interruptores del ratón del bloque de entrada.

2. Leer el estado del mundo (seqlock)

loop:
    s1 = block.seq                (offset 8, int32)
    if s1 es impar: reintentar    (el runtime está escribiendo)
    copiar cabecera + players + units[unitCount] + details[detailCount] + items[itemCount]
    if block.seq != s1: reintentar
  • Cabecera: contador de publicaciones (si no aumenta, la publicación se ha cortado), reloj de juego del motor, un epoch que suma 1 en cada partida, número del jugador local, si hay una partida en curso, velocidad de juego, periodo de publicación, microsegundos que costó recopilar esta copia en el hilo del juego, número de secuencia de eventos y tiempos por etapa. El cliente puede escribir requestedPeriodMs para pedir un periodo de publicación (16 ~ 1000 ms).
  • Unidad (112 bytes): par de handles (identifica las unidades por el par de handles: las direcciones se reutilizan), código de cuatro caracteres del tipo, dueño, flags, coordenadas, vida / maná (con sus máximos), orden actual + objetivo de la orden, objetivo de tarea (a quién ataca realmente), nivel / experiencia / puntos de habilidad del héroe, índice de detalle, visibleTo (bit p = el jugador p la ve en este momento).
  • Detalle (288 bytes; héroes > unidades de jugador > creeps; hasta 256): 12 habilidades (código / nivel / flags / segundos de enfriamiento restantes), 8 códigos de buff, 6 casillas de inventario.
  • Extensiones al final del bloque (solo se añaden; los offsets anteriores nunca se mueven, así que los clientes antiguos siguen funcionando): zona de extensión EXT1 (hora del día dentro del juego, velocidad del ciclo día/noche, número de entradas de la tabla de producción) y tabla de producción prods[128] (edificios que están entrenando / investigando / construyendo / mejorando, cola, duración total, tiempo transcurrido, si está atascada). Úsalas solo si el magic coincide.

3. Leer eventos

head = ring.writeSeq               (offset 8)
for seq in (cursor, head]:
    e = ring.events[(seq - 1) % 8192]
    if e.seq > seq:  se perdió una (sobrescrita por leer demasiado despacio)
    elif e.seq != seq: aún no está escrita del todo, léela la próxima vez
    else: procesar e

La estructura de evento ocupa 64 bytes: seq kind clockMs addr handleLo handleHi typeId owner a b x y value extra.

  • Obtenidos comparando dos publicaciones consecutivas (precisión = periodo de publicación): unit.appeared / unit.died / unit.removed / unit.damaged / order.changed / hero.levelup / owner.changed / item.appeared / item.removed / game.started;
  • A nivel de motor (el runtime los registra en el hilo del juego en el momento en que ocurren, así que hay uno por cada golpe): damage (origen, tipo de daño, tipo de ataque, posición, vida perdida real, daño antes de armadura), killed (el asesino);
  • Obtenido siguiendo la tabla de producción: production.done (código de cuatro caracteres de lo terminado, categoría, cuántos segundos de juego tardó; también se emite para los rivales);
  • Comprobados por el runtime en cada publicación: spell.cast (la habilidad entra en enfriamiento: a código de cuatro caracteres de la habilidad, b nivel, value segundos de enfriamiento, x/y punto de lanzamiento), player.left (a número de jugador, b nuevo estado de la ranura), selection.changed (selección del jugador local; la tabla completa está en la zona de extensión del bloque del mundo), game.ended (se sale de la partida);
  • Mensajes en pantalla: message (a = número de secuencia del mensaje; el texto completo se busca en la memoria compartida Local\War3Msgs_<pid>: 128 entradas × 256 bytes, con avisos del juego, chat y mensajes del sistema; b = número del marco de mensajes);
  • Interfaz y entrada (después de input_enable): ui.click (a id del elemento del lienzo, b 1 botón izquierdo / 2 botón derecho), ui.hover, hotkey (a id del atajo, b código de tecla virtual), mouse.world (x/y coordenadas del suelo, value = 1 significa que el clic se tragó), con las teclas modificadoras siempre en extra.

4. Enviar comandos

  1. Cada objeto cliente ocupa un carril: con Local\War3FastMutex_<pid> tomado, busca un carril libre (o cuyo proceso dueño haya muerto) y escribe el rol, el número de jugador y tu propio pid. Si un mismo proceso necesita dos roles, abre dos carriles;
  2. Rellena los slots: flag de comando semántico, código de operación, args[11] y plazo límite deadlineMs;
  3. Cuando todos los slots estén escritos, márcalos como enviados y suma 1 al submitSeq del carril;
  4. Espera al evento Local\War3FastDone_<pid>_<lane> (o haz polling), lee los recibos y devuelve los slots.

El runtime ejecuta los comandos por lotes dentro del despacho de eventos del hilo del juego: cada vaciado tiene un presupuesto de 4 ms (temporizador real de alta precisión); lo que no cabe se deja para el siguiente despacho. Los slots que han superado su plazo nunca se ejecutan, así que no ocurre que «al reanudar tras una pausa se vuelvan a ejecutar comandos viejos».

Índices de args: 0..2 unidad (dirección, handle lo, handle hi), 3 ID de orden o código de cuatro caracteres, 4..6 objetivo, 7/8 x / y (bits de float), 9 extra (número de jugador / número de casilla / interruptor / modo de cola), 10 mode (0 sin objetivo / 1 a un punto / 2 a un objetivo).

Códigos de operación

CódigoNombreDescripción
1pointOrden de una unidad a un punto (mover / atacar-mover / patrullar / atacar el suelo / hechizo a un punto). extra bit0 = en cola (se inserta tras la orden actual)
2targetOrden de una unidad a un objetivo (atacar con clic derecho / recolectar / reparar / hechizo a una unidad / recoger objeto); el objetivo tiene que ser visible
3immediateComando sin objetivo (detener / mantener posición / entrenar / investigar / mejorar / hechizo sin objetivo)
4buildUn trabajador construye un edificio (coordenadas alineadas a 32)
5learnEl héroe aprende una habilidad
6use_itemUsar la casilla extra del inventario
7reviveRevivir un héroe en el altar
8rallyPunto de reunión (a un punto / a un objetivo)
9buyUna tienda vende un objeto al héroe que está al lado
10item_dropSoltar un objeto: dárselo a un aliado, vendérselo a una tienda (code = la unidad que lo recibe) o dejarlo en el suelo
20 ~ 25Consultasq_tech recuento de tecnología, q_feasible viabilidad, q_visible visibilidad, q_mine_gold oro restante en una mina, q_captain capitán del ordenador, q_dead_heroes lista de héroes muertos
30pausePausar / reanudar
40 ~ 50CámaraLeer el estado de la cámara, fijar un campo, mirar a un punto, seguir, restablecer, rotar, límites, suavizado, mostrar / ocultar la interfaz, imagen limpia, niebla
60 ~ 63HUDTexto del botón de misiones, título y descripción del panel de misiones, refrescar, leer si el panel está abierto
70jassLlama a una native de JASS por su nombre (1291 en total): el nombre y los parámetros de tipo string van en la zona adicional del slot, el resto de parámetros se colocan en args según la firma; el valor de retorno queda en value[0]. Solo para carriles de herramientas locales; se rechaza cualquier llamada con parámetros de tipo función o que pueda suspender el hilo de scripts. Ver Canal JASS
71 / 72jass_handle_of / jass_unit_ofConvierte entre unidad de la instantánea ↔ handle de JASS (el par de handles de la instantánea no es un handle de JASS)
73canvas_enableCrea la memoria compartida del lienzo e instala el hook de dibujo; cualquier carril puede enviarlo (el lienzo solo se dibuja en la pantalla local). La primera vez hay que instalar el hook: da un timeout de 2 segundos o más
74input_enableextra = 1 captura la entrada de la ventana del juego (clic / hover sobre elementos del lienzo, atajos de teclado, clics en el suelo); 0 = la devuelve. Bloque de entrada Local\War3Input_<pid>: cabecera de 128 bytes + 32 atajos × 16 bytes; tú escribes la tabla de atajos y los interruptores del ratón, y el runtime escribe la posición del ratón, el punto del suelo al que apunta y el elemento bajo el cursor. Cualquier carril puede enviarlo (solo afecta a la entrada local). Ver Interfaz y entrada

5. Recibos

Un recibo ocupa 52 bytes (+8 bytes de tiempos): status, engineReturn, verdict (código de motivo del rechazo), orderBefore / orderAfter (la orden de la unidad leída en el mismo frame), value[8] (resultados de consultas), execUs (microsegundos que tardó este comando en ejecutarse en el hilo del juego), engineUs (la parte que corresponde a la propia función de órdenes del motor).

Todos los códigos de estado y de motivo están en Recibos y códigos de motivo.

6. Roles de carril

RolQué puede hacer
devHerramientas locales: comandos semánticos (mandar las unidades del jugador local) + canal JASS
playerSolo comandos semánticos, y solo para las unidades del jugador al que pertenece el carril (las de otro = not_owner)
observerSolo consultas, cámara, lectura del estado de los paneles del HUD y activar el lienzo y la entrada local; todo lo demás es forbidden

Dos IA enfrentadas = dos carriles player en la misma partida (player 0 / player 1).

En modo local, el rol lo declara el propio cliente (es una convención, no una frontera de seguridad). En la Arena, el proceso árbitro crea los carriles y solo entrega el carril player a cada participante.

7. Semántica verificada en partidas reales

  • Clic derecho (smart) sobre un enemigo = atacar a ese en concreto (el objetivo de la orden y el objetivo de tarea son esa unidad); la orden de ataque en bruto enviada como comando a un objetivo solo cambia a la orden de ataque sin recordar el objetivo, así que la unidad se va a atacar a otro cercano;
  • El motor no permite comandos a un objetivo sobre unidades que no ves: al anochecer, los campamentos lejanos quedan en la niebla de guerra y todos los clics derechos se rechazan (1001);
  • Que una construcción sea «aceptada» solo significa que el trabajador recibió la orden: un punto dentro de un bosque también se acepta en el acto, y el trabajador solo falla al llegar; los puntos claramente ocupados se rechazan en el acto;
  • Un héroe solo puede revivirse unos 3 segundos de juego después de morir; también se rechaza si no hay comida suficiente (los héroes ocupan comida);
  • Los objetos del inventario no cuentan como objetos en el suelo; al recoger uno se emite item.removed;
  • Durante la pausa el reloj del motor se detiene, pero se pueden seguir enviando comandos;
  • Una partida iniciada minimizada tiene la simulación detenida (el reloj no avanza).