Docs Extensiones de juego

Compañero para RPG

Dale al jugador un compañero IA en mapas de RPG o personalizados: te sigue, te ayuda contra los monstruos, te cura cuando vas mal de vida y charla contigo. Cuatro modos; hereda una clase, cambia unas cuantas propiedades y ya tienes tu propio compañero.

No todo son enfrentamientos. En mapas de RPG y personalizados puedes llevar tu propio compañero IA: te sigue, te ayuda contra los monstruos, te cura cuando te queda poca vida y, cuando no pasa nada, charla un poco contigo; sus frases pueden salir incluso de un LLM local.

Cómo usarlo lo decides tú. Está dividido en tres capas de API, de abajo arriba, y cualquiera de ellas se puede usar directamente:

CapaQué esIdeal si
Canal JASS g.jassLas 1291 funciones JASS que tienen los autores de mapas, llamadas directamente por su nombre (crear unidades, fijar aliados, dar objetos, cambiar nombres, mostrar texto, revivir héroes…)Quieres crear tu propia mecánica de juego
API de convenienciag.spawn, g.set_alliance, g.player_slots, g.show_text, g.map_data: las tareas más habituales, ya empaquetadasEscribes tus propios scripts de ayuda
Framework de compañeroopenwar3.companion.Companion + openwar3.talk.Talk: hereda, cambia unas cuantas propiedades y tienes un compañero que te sigue, lucha a tu lado, te cura y charlaQuieres un compañero

Solo para partidas sin conexión o en LAN creadas por ti. Crear unidades o fijar aliados cambia el mundo de forma unilateral desde tu equipo: en partidas de un jugador (contra el ordenador) no hay problema; en multijugador desincronizaría a los demás jugadores, así que en esas partidas el canal JASS solo deja pasar funciones de solo lectura y el compañero pasa automáticamente a “solo hablar”.

Lo más rápido: activarlo con un clic en Farsight

  1. Elige el mapa: página “Instancias y partida” de Farsight → “Siguiente partida” → Mapa, y elige un mapa de RPG (aparecen los de Scenario y Download dentro de Maps en el directorio del juego, p. ej. (4)WarChasers).
  2. Elige el esquema: en el desplegable “Esquema de IA” de la tarjeta de la instancia, selecciona Ejemplo de compañero (buddy) → “Seleccionar”.
  3. Iniciar prueba: cuando el juego arranque, juegas tú en la ventana del juego. El compañero, un paladín llamado “Luz”, aparecerá a tu lado.

También puedes hacerlo desde la línea de comandos:

python tools/play.py --bot brains/examples/buddy.py --inst 20 --rpg --map "<directorio-del-juego>\Maps\Scenario\(4)WarChasers.w3m"

--rpg (en el manifiesto del esquema, "judge": false) indica que el resultado no se decide con las reglas de las partidas competitivas: en un RPG los héroes muertos pueden revivir y tampoco existe “pierdes si te quedas sin edificios”. Muchos mapas de RPG se quedan en “Pulsa cualquier tecla para continuar” al terminar de cargar; cuando el SDK detecta que “está en partida, pero el reloj de juego sigue en 0”, pulsa espacio por su cuenta (g.press_to_continue(), que solo envía un mensaje de tecla a la ventana del juego y no le roba el foco).

Escribe tu propio compañero

from openwar3.companion import Companion
from openwar3.talk import Talk

class MyBuddy(Companion):
    mode = "ally"                        # modo, ver la tabla de abajo
    unit = "Hpal"                        # qué crear: cualquier código de cuatro caracteres, también los propios del mapa
    nickname = "Luz"
    heal = ("holybolt", "AHhb", 0.55)    # (orden del hechizo, habilidad que aprender, cura si la vida del amo baja de este valor); None = no cura
    follow_distance = 350
    talk = Talk(persona="Un joven paladín alegre al que le encanta animar a su amo")

Cuatro modos

modeQuién es el compañeroNotas
ally (por defecto)Ocupa una ranura de jugador libre y es tu aliadoTiene su propio color y nombre (el marcador y el panel de aliados muestran nickname); no puedes darle órdenes, lucha por su cuenta. El framework configura automáticamente la alianza + visión compartida
ownSe crea a tu nombrePuedes darle órdenes a mano cuando quieras; cuando no lo controlas, la IA lo maneja por ti
adoptToma el control de una unidad que ya existe en el mapaSobrescribe adopt(g) para que devuelva esa unidad (la mascota o el acompañante que te da el mapa)
voiceNo crea ninguna unidad, solo hablaTe hace compañía y te avisa de cosas; no cambia el mundo, así que también funciona en multijugador

Si no hay ranuras libres, ally pasa automáticamente a own; en multijugador, o si no se puede crear la unidad, pasa automáticamente a voice.

En el modo ally, el compañero se queda con “la mejor unidad que tenga esa ranura en ese momento” (los héroes primero), en lugar de aferrarse a una unidad concreta. En las pruebas, un mapa trató al compañero como a un jugador humano: borró el paladín y le dio un héroe del mapa. El compañero tomó directamente el control de ese héroe y también aprendió las habilidades que el mapa le había asignado. Si el héroe muere, primero intenta revivirlo en el sitio; si lo revive el propio mapa, sigue usándolo.

Qué hace en cada tick

Comprueba en este orden y hace lo primero que se cumpla:

OrdenAcciónCondiciónAjustable
1RetirarseSu propia vida baja del 25 % y hay enemigos cerca: se retira detrás del amoretreat_at
2CurarLa vida del amo baja del umbral, la habilidad está lista y la distancia es menor de 900heal (None la desactiva)
3ApoyarHay enemigos alrededor del amo: el que ataca al amo > al que ataca el amo > el más cercanoassist_radius, o sobrescribe pick_target
4SeguirSi se aleja demasiado del amo, lo alcanza; si se aleja mucho, vuelve corriendo sin entretenerse en combatesfollow_distance, leash
5CharlarSin enemigos cerca, suelta una frase cada 1 ~ 2.5 minutosTabla de frases

Quién es “enemigo” se decide según las relaciones de alianza del juego (se refrescan cada 20 segundos). Los mapas de RPG suelen tener varios bandos aliados, así que no basta con tratar como enemigo a “todo jugador que no sea yo”.

Hooks que puedes sobrescribir: find_master (quién es el amo; por defecto, el héroe de mayor nivel del jugador local), adopt, pick_target, on_poke (el amo hace clic derecho sobre el compañero), además de on_start / on_tick / on_event / on_end del Bot. El número de curas, apoyos, bajas, seguimientos, retiradas, frases y resurrecciones se guarda en self.stats y se imprime al terminar.

Cómo llamarlo

  • Comandos de chat: escribe en el chat -follow (sígueme), -stay (quédate aquí), -heal (cúrame ya) o -hi (saluda). Para cambiar la lista de comandos, cambia commands; para cambiar cómo reacciona, sobrescribe on_command.
  • Clic derecho sobre el compañero: dispara on_poke. En el ejemplo, si el amo no tiene la vida al máximo, le da una cura; si no, dice algo.
  • Diálogo con retrato: el saludo, cuando cae el amo, cuando sube de nivel y cuando vuelve el compañero se dicen con el diálogo con retrato del propio juego (el retrato de abajo cambia al del compañero y aparecen subtítulos en pantalla); el resto sale en burbujas sobre su cabeza.
  • Panel de estado: un panel en el lado izquierdo de la pantalla con la barra de vida del compañero, qué está haciendo, su ánimo (feliz / emocionado / nervioso / asustado / triste) y cuántas bajas y curas lleva. Se dibuja con el lienzo, así que también es seguro en multijugador.

Hablar, y el LLM local

Talk elige frases según los eventos y las muestra en una burbuja sobre la cabeza; en el modo voice, o cuando no puede mostrar la burbuja, aparecen en la esquina inferior izquierda de la pantalla. Cada frase se guarda también en el log del esquema, para poder consultar después qué dijo.

EventoCuándoEventoCuándo
helloAl llegarmaster_lowAl amo le queda poca vida
pokeEl amo le hace clic derechomaster_levelupEl amo sube de nivel
fightEmpieza un combatemaster_died / master_backEl amo cae / revive
killMata a un monstruo (dice el nombre del monstruo)buddy_low / buddy_died / buddy_backAl compañero le queda poca vida / cae / vuelve
healedHa curado al amoidle / itemCharla / recoge un objeto

En las frases puedes usar los marcadores {master}, {me}, {map}, {enemy}, {level} e {item}; para cambiar las frases, edita talk.lines; el tiempo de espera entre frases está en talk.cooldown.

Conectar un LLM local: Talk(llm=LocalLLM(url, model)); sirve cualquier API compatible con OpenAI (LM Studio, Ollama…). El modelo responde en un hilo en segundo plano y la frase solo se dice cuando llega la respuesta; si el modelo no está en marcha, tarda demasiado o falla, se dice una frase fija, así que nunca bloquea el juego. Las peticiones solo van a la dirección local que indiques, y lo que se envía es lo que pasa en la partida (cómo se llama el amo, qué monstruo ha matado).

Nombres de unidades en mapas personalizados

Las unidades, objetos y héroes de los mapas de RPG suelen ser creaciones del propio mapa (con códigos de cuatro caracteres como HC07 o I00A) y no aparecen en la tabla de nombres integrada. g.map_data lee directamente el archivo del mapa de la partida actual:

md = g.map_data
md.name_of("HC07")        # 'Optimus Primo': manda el nombre modificado por el mapa
md.hero_names("HC07")     # lista de nombres propios del héroe
md.hero_skills("OC10")    # habilidades que el mapa asigna a este héroe
md.tooltip("I00A")        # texto de descripción

Los mapas protegidos u optimizados (muchos RPG populares) no incluyen los archivos estándar de datos de objetos del editor, así que los nombres se leen de los datos de texto del mapa. En las pruebas, los 38 mapas de RPG / personalizados de este equipo se analizaron sin errores, y en 37 se obtuvieron los nombres de las unidades.

Compartirlo como esquema

Un compañero no es más que una subclase de openwar3.Bot, así que puedes convertirlo en un esquema de IA y compartirlo. En el manifiesto se añaden dos campos:

{"id": "my-buddy", "name": "Mi compañero", "entry": "my_buddy.py", "fair": false, "judge": false}

"fair": false: necesita el canal JASS (crear unidades, fijar aliados); "judge": false: el resultado no se decide con las reglas de las partidas competitivas.

Registro de pruebas

24 de septiembre de 2026, instancia de prueba, mapa WarChasers, velocidad 2×:

  • Canal JASS: las 18 comprobaciones pasaron: ranuras de jugador, conversión de ida y vuelta entre unidades y handles, valores de retorno de tipo real, parámetros de cadena, crear unidades en una ranura libre, fijar aliados, cambiar nombres, borrar unidades; las llamadas desde un carril de jugador y las que tenían un número de parámetros incorrecto se rechazaron correctamente.
  • Compañero: pulsó por su cuenta “Pulsa cualquier tecla para continuar” → apareció junto al amo y lo saludó → lo siguió hasta el círculo de poder para elegir héroe; el mapa le dio un héroe y tomó su control → lo siguió (a 200 ~ 400 del amo) → luchó contra monstruos y, al matar uno, dijo “¡Genial!” → se retiró con poca vida → murió, el mapa lo revivió y siguió acompañándolo.

Lo que falta

  1. No puede leer cualquier texto que el jugador escriba en el chat. Los comandos de chat fijos ya funcionan; para que el compañero charle contigo libremente de verdad, hace falta obtener el propio texto.
  2. El compañero no entiende la mecánica de cada mapa concreto (misiones, tiendas, historia). Hace lo genérico: seguir, apoyar y curar; para que entienda un mapa concreto, escríbelo en tu subclase para ese mapa: g.map_data te da los nombres y g.jass puede llamar a cualquier función. Esa es justo la parte que queda en tus manos.