Para agentes
Alderun tiene una interfaz hecha para programas. Un agente de IA, un bot o un complemento puede leer lo que ve un jugador y, si lo declara antes de la partida, jugar un puesto por sí mismo. Recibe exactamente lo mismo que un jugador: los enemigos ocultos en la niebla siguen ocultos, y cada orden que da recorre el mismo camino que un clic.
Conectarse
En el juego de navegador la interfaz es window.alderun, en la propia página del juego. Cada
petición es un objeto con un op, y cada respuesta es una promesa de {ok: true, result}
o {ok: false, error}. Si pones un id en una petición, la respuesta lo devuelve.
// espera a que el juego instale la interfaz
if (!window.alderun)
await new Promise(r => addEventListener('alderun-api', r, {once: true}));
const hello = await alderun.request({op: 'hello'});
// {ok: true, result: {api: 1, mode: null, inMatch: false, gameOpen: false, ...}}
En la versión para Windows, inicia el juego con Alderun.exe --api=8990. Entonces acepta un
WebSocket en ws://127.0.0.1:8990, solo desde este ordenador. Envía cada petición como un mensaje
de texto JSON; cada respuesta vuelve como un mensaje. Sin esa opción, no escucha nada. Una página web no puede conectarse: se rechaza la conexión que dice venir
de un sitio web.
Query y Command
La interfaz tiene dos mitades.
| Mitad | Qué puede hacer | Para quién |
|---|---|---|
| Query | Leer la partida, el mapa, tus unidades, todo lo que ves y la sala. | Complementos: superposiciones, entrenadores, grabadores. Los bots también la usan. |
| Command | Dar órdenes a tus unidades, aprender habilidades, usar y comerciar objetos, elegir héroe, crear partidas y unirse a ellas. | Bots. |
Antes de la partida, declara qué mitad vas a usar: {op: 'declare', mode: 'query'} o
{op: 'declare', mode: 'command'}. Command incluye Query. La declaración queda fijada en cuanto
empieza la partida y se libera cuando termina, así que un programa que quiera jugar debe declararse antes.
Una petición de Command sin declaración se rechaza.
Un puesto que declara Command es un bot, y todos los jugadores lo ven: su nombre aparece como Nombre (bot) en la sala, en el marcador y en el panel de la unidad. Dos reglas deciden dónde puede jugar un bot.
- Una partida personalizada acepta bots solo si su anfitrión activa API de bots en
Avanzado... en la pantalla Crear partida. Está desactivado por defecto, y un bot que intenta unirse
recibe el rechazo
nobots. - La Partida rápida nunca acepta bots.
Cuando empieza una partida que permite bots, se avisa en pantalla a cada jugador, con los nombres de los bots que hay en ella.
Query no necesita permiso de nadie. Un complemento que solo lee funciona en cualquier partida.
Empezar desde un enlace
Un agente puede abrir el juego de navegador directamente en una partida añadiendo parámetros a su dirección. Cada uno hace lo mismo que el botón de menú correspondiente.
| Parámetros | Qué pasa |
|---|---|
?api=command?api=query | Declara el modo al cargar el juego. |
?go=single&mission=arena&hero=mage | Empieza una partida para un jugador en ese mapa con el preajuste Normal. Sin hero, el héroe se elige ya en el juego. |
?go=create&mission=crossing&bots=1&private=1 | Crea una partida personalizada. bots=1 acepta bots; private=1 la hace privada, con entrada por código. |
?go=join&code=4821?go=join&room=L123456 | Se une a una partida por su código o por su identificador de sala. |
?go=quick&mission=duel | Entra en la Partida rápida. Un bot es rechazado. |
Claves de mapa: crossing (El Cruce), gulch (Estandartes del Barranco),
valley (Valle Helado), arena, highpass (El Paso Alto),
frostmarch (Valle de la Marcha Helada), sunfall (Laberinto del Ocaso),
alderun, duel, ash_road. Claves de héroe: paladin, mage, lich,
barbar (Bárbaro), rogue, warrior, vampire,
hunter, druid, valkyrie, shieldmaiden,
warlock.
Jugar en línea requiere una cuenta, igual que para un jugador. Si el juego muestra la pantalla de la cuenta,
inicia sesión o pulsa Jugar como invitado, y el juego sigue hasta donde apuntaba el enlace. Un bot que
crea su propia partida abre
https://www.alderun.net/play/?api=command&go=create&mission=arena&bots=1.
En la sala
{op: 'lobby'} funciona en cualquier momento y te dice dónde estás.
{ flow: 'LobbyRoom', inRoom: true, lobbyId: 'L123456', error: null,
state: { map: 'arena', isPublic: false, allowBots: true, code: '4821', isHost: true,
slots: [ { team: 0, idx: 0, kind: 'player', name: 'Ann', hero: 'mage',
ready: false, isYou: true, api: 'command' }, ... ] } }
Solo el anfitrión ve el code de entrada; pásaselo al siguiente agente. error guarda
el último rechazo, como nobots o bad code. Un bot que declaró Command puede
manejar la sala:
| Petición | Qué hace |
|---|---|
createGame {mission, private, bots} | Crea una partida, como ?go=create. |
joinGame {code} o {room} | Se une a una partida. |
quickMatch {maps: ['duel']} | Entra en la Partida rápida. |
leaveGame | Sale de la sala o de la cola. |
claimSlot {team, idx, hero} | Ocupa un puesto. |
lobbyPickHero {hero} | Cambia de héroe. |
ready {on: true} | Te marca como listo. |
lobbyChat {text} | Escribe en el chat de la sala. |
setSlot {team, idx, kind, difficulty} anfitrión | Abre un puesto (open), lo cierra (none) o se lo da a la IA (ai, dificultad de 0 Fácil a 3 Extremo). |
kick {team, idx} anfitrión | Expulsa al jugador de ese puesto. |
startGame anfitrión | Empieza la partida. |
Una respuesta {applied: 'sent'} significa que la petición llegó al servidor. Vuelve a leer
lobby para ver el resultado.
Conocer un mapa
{op: 'guide', map: 'crossing'} devuelve las instrucciones del mapa: el objetivo, dónde están los
edificios que deciden la partida, los jefes y los campamentos neutrales con sus coordenadas de casilla, y las
reglas propias del mapa: cómo se invoca a un jefe, qué hace una bandera capturada, cuándo se abre la puerta.
Funciona en el menú y durante la partida, en cualquier modo; sin map devuelve el mapa que se está
jugando. Todos los mapas tienen instrucciones; las claves de mapa están en
Empezar desde un enlace. La consola del juego muestra el mismo texto:
guide o guide highpass. El texto está en inglés.
{ map: 'duel', name: 'Duel', width: 44, height: 44,
coordinates: 'Coordinates are map tiles (x,y), the same indices the tile, units and map ops use; ...',
time: 'Times are match time (m:ss since the match began), ...',
lines: ['# Duel - champions in a pit', 'GOAL: be the last team with a living champion. ...', ...] }
No incluye las habilidades de los héroes ni las estadísticas de los jefes: se leen con spells,
unit y hero.
En una partida
Las peticiones sobre la partida solo responden durante una partida, y no en sus primeros 3 segundos; hasta
entonces devuelven the match is starting. hello indica gameOpen: true
cuando ya funcionan.
| Query | Devuelve |
|---|---|
state | Todo a la vez: el tiempo de juego, día o noche, el nombre, tamaño y reglas del mapa, tu oro, cada bando y su relación contigo, tus héroes completos, tus demás unidades, las unidades que ves, los héroes que esperan revivir y, en un mapa de captura de bandera, las banderas (ctf, el mismo bloque que devuelve flags). |
map | El mapa como filas de texto, . transitable y # bloqueado, más el nivel de altura de cada casilla. |
tile {x, y} | Una casilla: terreno, altura, si es transitable y qué hay en ella si la ves. |
units {filter} | Unidades, filtradas por all, mine, others, heroes o structures. |
unit {id}, hero {id} | Una unidad: todas las estadísticas si es tuya, lo general si es de otro. |
spells {unit} | Las habilidades de tu unidad: si cada una está lista, el enfriamiento restante, el coste y el alcance. |
items {unit} | Los objetos de la mochila y los equipados, sus cargas y si cada uno se puede usar ahora. |
skills {unit} | El árbol de habilidades, el nivel de cada una y si se puede aprender ahora. |
shops {hero} | Los mercaderes que ves y, para ese héroe, el precio de cada objeto y si se puede comprar. |
events {since} | Lo que pasó después del número que indicas: muertes, bajas, subidas de nivel, reapariciones, oleadas, anocheceres y amaneceres, una bandera tomada, soltada, devuelta o capturada, y el final de la partida. |
flags | En un mapa de captura de bandera (Estandartes del Barranco): las capturas de cada bando y su relación contigo, y el estado de cada bandera (home, carried, dropped), dónde está ahora mismo, su base, quién la lleva, cuándo vuelve sola una bandera soltada, y la debilidad de los portadores mientras ambas están fuera. La posición de una bandera se muestra a todos en un mapa así, por lo que la niebla no filtra esta respuesta: la casilla de un portador enemigo aparece aunque el propio portador no esté entre las unidades que ves (carrier.visible lo indica). |
result | Cómo terminó la partida: tu veredicto y el marcador final. Consulta Después de la partida. |
Las posiciones son coordenadas de casilla. Los tiempos van en ticks de juego, 60 por segundo de juego. Las unidades que no ves quedan fuera, igual que los edificios enemigos que nadie de tu bando está vigilando.
| Command | Qué hace |
|---|---|
move {units, x, y} | Ir allí. |
attackMove {units, x, y} | Ir allí luchando contra todo lo que encuentre. |
attack {units, target} | Atacar a una unidad que ves. |
stand {units} | Detenerse. |
cast {unit, spell, target} | Usar una habilidad sobre una unidad. Omite target para una habilidad sobre ti mismo. |
castAt {unit, spell, x, y} | Usar una habilidad dirigida al suelo sobre un punto. Una habilidad dirigida a una unidad va por cast. |
learn {unit, skill} | Gastar un punto de habilidad. |
useItem {unit, item, target}, equip, unequip, drop | Usar, equipar, quitar o tirar un objeto. |
buy, sell {unit, target, item} | Comerciar con el mercader target: uno con el que tu bando puede comerciar, con el héroe a 4 casillas o menos de él, como en la ventana de la tienda. |
resetSkills {unit} | Reiniciar el árbol de habilidades a cambio de oro, en tu base. |
pickHero {key} | Elegir héroe mientras el selector está abierto. |
buyback | Recomprar a tu héroe caído, en los mapas que lo permiten. |
dropFlag | En un mapa de captura de bandera, tu héroe deja la bandera que lleva donde está, para pasársela a un compañero; no podrá volver a levantarla durante 3 segundos. |
chat {text} | Escribir a tu equipo en una partida en línea. |
ping {x, y, kind} | Marcar un punto: kind 0 para atención, 1 para peligro. |
Solo tus propias unidades aceptan órdenes. Las habilidades, los talentos y los objetos se nombran por el
name que devuelve Query. Una respuesta applied significa que la orden ya tuvo efecto.
En una partida en línea la respuesta es queued, y la orden llega una fracción de segundo después,
como las órdenes de cualquier jugador.
Después de la partida
Cuando la partida termina, state y hello indican over: true, y en
events llega un registro matchEnded con el veredicto. Entonces
{op: 'result'} devuelve la ventana de resultados como datos:
{ result: 'Victory', won: true, mission: 'duel', clock: '4:12', seconds: 252, tick: 15120,
columns: ['Kills', 'Losses', 'Gold', 'Score'],
me: 0,
rows: [ { name: 'Ann (bot)', relation: 'self', ai: false, api: 'command', isPlayer: true,
values: [3, 1, 940, 1210] },
{ name: 'Red player 1', relation: 'enemy', ai: true, api: null, isPlayer: false,
values: [1, 3, 610, 540] }, ... ],
summary: [] }
result es Victory, Defeat, Tie o Surrender, y
won es el veredicto de tu bando: true, false o null en un
empate. rows tiene una fila por bando en el orden de la tabla, la mejor puntuación primero;
values sigue el orden de columns y me es el índice de tu fila. Solo
aparecen las columnas en las que alguien sumó algo, y siempre Score; los nombres son claves en
inglés como Kills, Losses, Assists, Gold (el oro ganado en
la partida), Captures y Score. summary contiene las líneas finales del
mapa, por ejemplo qué fortaleza cayó.
El resultado sigue disponible después de volver al menú, hasta que empieza la siguiente partida, así que un bot
puede volver primero al menú y leerlo allí. Si todavía no ha terminado ninguna partida, la respuesta es
no match has ended yet; durante una partida, the match has not ended yet. Funciona en
los dos modos.
Límites
La interfaz comparte el ordenador del jugador con el juego, así que trabaja dentro de unos límites fijos. Están pensados para que un bot razonable nunca los alcance.
| Límite | Qué pasa |
|---|---|
| 10 órdenes por segundo | Las órdenes se limitan a 10 por segundo, con una ráfaga de 10. Por encima, la respuesta es {ok: false, error: 'rate limited', retryAfterMs}: espera ese tiempo y vuelve a enviarla. Las peticiones de la sala cuentan para el mismo límite, igual que cambiar tu declaración mientras estás en una sala; salir de una partida no. Una orden rechazada no gasta el límite. |
| Órdenes repetidas | Un move, attackMove, attack o stand idéntico a tu orden anterior, enviado en menos de un segundo, no se vuelve a enviar. La respuesta es applied: 'unchanged' y no cuenta para el límite. Las demás órdenes se envían siempre: un segundo learn de la misma habilidad es su siguiente rango. |
| Unidades por orden | Una orden nombra como máximo 64 unidades. Una unidad nombrada dos veces cuenta una vez. |
| Tamaño de la petición | Una petición de más de 65.536 caracteres se rechaza sin leerla: {ok: false, error: 'request too large'}. |
| Trabajo por fotograma | En cada fotograma el juego responde peticiones hasta una cantidad fija de trabajo. state y shops son las más costosas, y las lecturas pequeñas como spells casi no cuestan nada. Una petición que no cabe espera al siguiente fotograma. |
| El mapa | El primer map de una partida ocupa un fotograma entero; después se guarda y las lecturas siguientes casi no cuestan nada. |
| Peticiones en espera | Pueden esperar hasta 256 peticiones. Por encima, la respuesta es {ok: false, error: 'busy', retryAfterMs}: espera y vuelve a enviarla. |
| Partidas en línea | El servidor también limita cada puesto, jugador o bot, a 15 órdenes por segundo con una ráfaga de 30, y descarta el resto. Te avisa cuando lo hace: hello y state cuentan las órdenes descartadas en ordersDropped, y un evento ordersDropped llega en menos de un segundo. |
La respuesta siempre llega en un fotograma posterior, así que un bucle sin pausas solo ralentiza tu propio programa, nunca el juego.
Un bot pequeño
Abre https://www.alderun.net/play/?api=command&go=single&mission=arena&hero=warrior
y ejecuta esto en la página, desde una extensión del navegador o desde la herramienta que controla el
navegador. El héroe aprende una habilidad siempre que puede y ataca al enemigo visible más cercano. Cuando la partida termina, muestra el resultado.
const call = req => alderun.request(req);
const sleep = ms => new Promise(r => setTimeout(r, ms));
const dist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
if (!window.alderun)
await new Promise(r => addEventListener('alderun-api', r, {once: true}));
while (!(await call({op: 'hello'})).result.gameOpen) await sleep(500);
for (;;) {
const s = (await call({op: 'state'})).result;
if (!s || s.over) break;
const hero = s.heroes[0];
if (!hero) { await sleep(1000); continue; } // aún no ha aparecido o espera para revivir
const skill = hero.skills.find(k => k.canLearn);
if (skill) await call({op: 'learn', unit: hero.id, skill: skill.name});
const enemySides = s.fractions.filter(f => f.relation === 'enemy').map(f => f.id);
const enemies = s.visible.filter(u => enemySides.includes(u.fraction))
.sort((a, b) => dist(a, hero) - dist(b, hero));
if (enemies.length && (!hero.order || hero.order.target !== enemies[0].id))
await call({op: 'attack', units: [hero.id], target: enemies[0].id});
await sleep(500);
}
const r = (await call({op: 'result'})).result;
console.log(r.result, r.clock, r.columns, r.rows[r.me].values);
La interfaz no ve a través de la niebla, no da órdenes a las unidades de otros y no juega un puesto que no se haya declarado bot.