Inicio › Info de modding › Classic, un ejemplo desarrollado
Capítulo 09

Classic, un ejemplo desarrollado

El jugador de ordenador integrado escrito en Lua: sus archivos, la tabla de tareas, una tarea, las notas de peculiaridades, lo que se queda en el motor y cómo se demuestran las mismas órdenes.

Este capítulo resume docs/brains/oabrain-standard.md, docs/brains/authoring.md y docs/playing-with-brains.md. Esos documentos son el texto normativo.

Qué es Classic

Classic es brains/classic. Es la documentación del jugador de ordenador integrado: las mismas órdenes, escritas para que puedas leerlas. El jugador de ordenador integrado sigue en el motor y sigue por defecto. Elegir Classic sienta este paquete. Dejar el asiento en el jugador de ordenador integrado ejecuta el jugador de ordenador del propio motor.

El manifiesto dice que works-with es un asterisco, y que fullAwareness es true. Su descripción dice que juega exactamente como el jugador de ordenador integrado. La visión total es la bandera del manifiesto. Se permite en una partida en red, y el asiento la señala. El identificador, el nombre y la ruta de instalación no hacen que un paquete sea Classic. Los archivos tienen que ser la carpeta brains/classic de esta build, las mismas rutas y los mismos bytes.

Manifiesto · oabrain.yaml
# SPDX-FileCopyrightText: The Open Annihilation Authors; see COPYRIGHT
# SPDX-License-Identifier: GPL-3.0-only

oabrain: 1
id: classic
name: Classic
description: "The engine's own computer player, written in Lua: it plays exactly as the built-in computer player does."
version: "1.0"
author:
  name: The Open Annihilation Authors
tags: [classic, teaching]
works-with: ["*"]
requires:
  engine: ">= 0.8.6"
levels:
  - id: easy
    name: Easy
    difficulty: easy
  - id: medium
    name: Medium
    difficulty: medium
  - id: hard
    name: Hard
    difficulty: hard
fullAwareness: true
entry: brain.lua

Los archivos

brain.lua arma una tarea por cada escuadra de 0 a 9. En el primer tick onTick refresca las listas de construcción y siembra las rejillas. Cada tick ejecuta después la ordenación de escuadras en su cuenta atrás, y ejecuta cada tarea a la que le ha llegado el turno.

RutaQué hace
oabrain.yamlEl manifiesto.
brain.luaEl asiento, la tabla de tareas y onTick.
lua/build.luaPuntúa un tipo y elige uno de la lista de un constructor.
lua/knowledge.luaPesos, límites, las rejillas de construcción y los puntos de metal. Lee el registro del motor en me.knowledge.
lua/place.luaBusca un sitio y ensancha la búsqueda hasta que es tan ancha como el mapa.
lua/profile.luaLee el texto del perfil y aplica los pesos.
lua/report.luaEscribe el texto que devuelve report.
lua/rules.luaLee las reglas ai.* con las que juega Classic.
lua/squads.luaOrdena las unidades en escuadras y da una orden a una escuadra.
lua/types.luaLee los campos de tipo que usa el jugador de ordenador.
lua/tasks/structures, construction, strike, rally, air_raid y siege.
README.mdUna nota. El juego no la carga.
LICENSELa licencia.

La tabla de tareas

Las escuadras 1 y 5 construyen estructuras. La escuadra 5 lo hace solo cuando lo dicen las reglas, y si no queda inactiva. Las escuadras 2 y 6 son grupos de ataque. Las escuadras 3 y 7 se concentran sobre ellos. La escuadra 4 es construcción. La escuadra 8 es el ala de aviones. La escuadra 9 es asedio. El comentario de la escuadra 9 es una peculiaridad: la ordenación nunca llena esa escuadra, así que la tarea de asedio solo conserva su horario, salvo que ya hubiera una unidad en ella.

-- …
    for index = 0, 9 do
        seat.tasks[index] = blank(index)
    end
    local wave = loaded_rules.attack_wave
    seat.tasks[1].kind = "structures"
    seat.tasks[1].every = 30
    seat.tasks[2].kind = "strike"
    seat.tasks[2].min_size = 3
    seat.tasks[2].launch = wave
    seat.tasks[2].merge_radius = 20000
    seat.tasks[2].source = 3
    seat.tasks[2].every = 300
    seat.tasks[3].kind = "rally"
    seat.tasks[3].source = 2
    seat.tasks[3].every = 150
    seat.tasks[4].kind = "construction"
    seat.tasks[4].every = 90
    if loaded_rules.squad5_factory_tick then
        seat.tasks[5].kind = "structures"
        seat.tasks[5].every = 30
    else
        seat.tasks[5].kind = "idle"
    end
    seat.tasks[6].kind = "strike"
    seat.tasks[6].min_size = 3
    seat.tasks[6].launch = wave
    seat.tasks[6].merge_radius = 50000
    seat.tasks[6].source = 7
    seat.tasks[6].every = 300
    seat.tasks[7].kind = "rally"
    seat.tasks[7].source = 6
    seat.tasks[7].every = 150
    seat.tasks[8].kind = "air_raid"
    seat.tasks[8].every = function()
        return 30 + brain.shared_random(900)
    end
    seat.tasks[8].run = function()
        air_raid.run(seat, seat.tasks[8])
    end
    -- @quirk The sort never fills squad 9, so the siege task only keeps its
    -- timetable unless a unit was already in that squad.
    local centre = map_centre(game)
    seat.tasks[9].kind = "siege"
    seat.tasks[9].every = function()
        return 30 + brain.shared_random(150)
    end
    seat.tasks[9].siege_target = point_of(centre)
    seat.tasks[9].siege_probe = point_of(centre)
    seat.tasks[9].siege_step = point_of(centre)
    seat.tasks[9].siege_weight = 0
-- …

En el primer tick onTick refresca las listas de construcción y siembra las rejillas. La ordenación es la cuenta atrás. Empieza en 30. Cada tick resta uno, y al llegar abajo vuelve a 30 y llama a la ordenación de escuadras.

-- …
function hooks.onTick(tick)
    local seat = hooks.seat
    if seat.builds_ready ~= 1 then
        types.refresh_builds(seat.types, seat.game)
        seat.builds_ready = 1
    end
    if seat.grids_ready ~= 1 then
        knowledge.seed_grids(seat)
    end
    seat.sort_countdown = seat.sort_countdown - 1
    if seat.sort_countdown < 1 then
        seat.sort_countdown = 30
        brain.section("sort")
        squads.sort(seat)
    end
    for index = 0, 9 do
        local task = seat.tasks[index]
        if task.kind ~= "none" and task.next <= tick then
            if task.kind ~= "idle" then
                brain.section("task" .. index)
                local interval = task.every
                if type(interval) == "function" then
                    interval = interval()
                end
                task.next = (tick + interval) & 0xFFFFFFFF
                task.run()
            end
        end
    end
end
-- …

squads.lua dice qué hace la ordenación: las unidades seleccionables al alcance del asiento toman órdenes permanentes, y una unidad que sigue en la escuadra 0 entra en la escuadra en la que la clasifica su tipo. Una unidad que ya está en una escuadra conserva esa escuadra.

Una tarea

lua/tasks/rally.lua es la tarea rally entera. Cuando la escuadra de rally y el grupo de ataque tienen miembros los dos, la escuadra de rally se mueve al centro del grupo de ataque.

-- SPDX-FileCopyrightText: The Open Annihilation Authors; see COPYRIGHT
-- SPDX-License-Identifier: GPL-3.0-only

-- A waiting squad. When it and the strike group both have members, it moves
-- to the strike group's centre. The timetable is already set.

local squads = require("squads")

local rally = {}

function rally.run(seat, task)
    local me = seat.me
    if squads.count(me, task.squad) < 1 or squads.count(me, task.source) < 1 then
        return
    end
    local center = squads.centroid(me, task.source)
    if center == nil then
        return
    end
    squads.order(me, task.squad, "move", false, nil, squads.fixed_point(center))
end

return rally

Las notas de peculiaridades

Una línea marcada con @quirk registra un comportamiento del jugador de ordenador integrado. La nota está en el script para que, al leerlo, se vea junto al código. La nota de la escuadra 9 está en la tabla de tareas de arriba. Otras dos están en profile.lua. La palabra any solo cuenta como la primera palabra después de plan, y esa misma palabra se vuelve a probar en cada palabra posterior de la línea. El ai_weight de un tipo descargable se aplica una vez por jugador de ordenador, así que el peso se acumula por esa cuenta.

-- The builders' tick. Each builder places a weighted pick near the base,
-- and an idle one patrols home. A capturing builder builds only while the
-- player is under the withhold count and the hold tick has passed.
-- @quirk ai.builder-withhold-threshold moves only the build limit: an idle
-- capturing builder still circles the base from five build-capable units.

Construcción retiene las construcciones, y aun así patrulla. Un ala de aviones pequeña y sin base, bajo ai.patrol-null-enemy-skip, sortea una patrulla por el borde del mapa, las mismas tres tiradas que hace un ala grande, y patrulla allí en vez de encolar una patrulla sobre el enemigo más cercano.

Qué se queda en el motor

El jugador de ordenador integrado sigue por defecto. Un asiento es Classic solo cuando sus archivos coinciden con la carpeta de esta build. Esa prueba concede dos comportamientos, y una tercera tirada del flujo de preparación. brain.shared_random extrae del flujo compartido de la partida, el mismo del que extraen los scripts de unidad y la simulación, así que los intervalos de aviones y de asedio de Classic siguen el paso del jugador de ordenador integrado. unit:stance con direct en true escribe los bits permanentes de movimiento y de fuego y no da orden. Cualquier otro asiento se rechaza. brain.setup_random extrae de una copia de ese flujo tomada en la preparación integrada del asiento, y solo avanza la copia.

Ningún otro cerebro tiene ninguna de las dos funciones aleatorias, y una escritura directa de postura se rechaza. brain.section se instala en cada cerebro. En una partida la marca no hace nada. La visión total no está en esta lista. Es la bandera del manifiesto. Steady Commander no llama a ninguna de ellas.

Cómo se demuestra

En sombra, Classic se sienta junto al jugador de ordenador integrado. Las órdenes del jugador de ordenador integrado son las que juega la partida. Las de Classic se registran y se comparan. Ningún tick puede diferir. Las pruebas del motor de esa comparación son sim-brains-shadow y sim-brains-shadow-match.

Puesto en su lugar, Classic se sienta donde jugaría el jugador de ordenador integrado. Las pruebas del motor de eso son sim-brains-classic, sim-brains-classic-data, sim-brains-classic-match, ai-computer-player, match-computer-build, native-trace, native-stage, native-director-render, native-classic-trace, native-classic-stage, native-classic-director-render, native-net-loopback-computer, native-net-loopback-computer-watcher, native-net-loopback-each-ai, native-net-loopback-computer-classic, native-net-loopback-computer-watcher-classic y native-net-loopback-each-ai-classic.

Una prueba de resistencia juega 1.000 escaramuzas sin interfaz y con semilla, de 20.000 ticks cada una, sobre el juego base y los mods publicados. La misma semilla juega la misma partida. La prueba de resistencia es la comprobación larga de que un asiento con Classic se mantiene en las órdenes del jugador de ordenador integrado durante toda una partida.

Cómo leerlo

Lee Classic como el comportamiento del jugador de ordenador integrado, puesto por escrito. El jugador de ordenador del propio motor sigue siendo el jugador que usa un asiento cuando nadie eligió un cerebro. Instalar Classic, o empaquetarlo, no cambia nada hasta que un asiento se pone en él.