Home › Info modding › Classic, un esempio svolto
Capitolo 09

Classic, un esempio svolto

Il giocatore computer integrato scritto in Lua: i suoi file, la tabella dei compiti, un compito, le note sulle particolarità, che cosa resta nel motore e come si dimostrano gli stessi ordini.

Questo capitolo riassume docs/brains/oabrain-standard.md, docs/brains/authoring.md e docs/playing-with-brains.md. Quei documenti sono il testo normativo.

Che cos'è Classic

Classic è brains/classic. È la documentazione del giocatore computer integrato: gli stessi ordini, scritti perché tu possa leggerli. Il giocatore computer integrato resta nel motore e resta il predefinito. Scegliere Classic mette al posto questo pacchetto. Lasciare il posto sul giocatore computer integrato esegue il giocatore computer proprio del motore.

Il manifesto dice che works-with è un asterisco, e che fullAwareness è true. La sua descrizione dice che gioca esattamente come il giocatore computer integrato. La vista completa è il flag del manifesto. È ammessa in una partita in rete, e il posto la segnala. L'identificativo, il nome e il percorso di installazione non rendono Classic un pacchetto. I file devono essere la cartella brains/classic di questa build, gli stessi percorsi e gli stessi byte.

Manifesto · 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

I file

brain.lua costruisce un compito per ogni squadra da 0 a 9. Al primo tick onTick aggiorna le liste di costruzione e semina le griglie. Ogni tick esegue poi l'ordinamento delle squadre sul suo conto alla rovescia, ed esegue ogni compito a cui è arrivato il turno.

PercorsoCosa fa
oabrain.yamlIl manifesto.
brain.luaIl posto, la tabella dei compiti e onTick.
lua/build.luaDà un punteggio a un tipo e ne sceglie uno dalla lista di un costruttore.
lua/knowledge.luaPesi, limiti, le griglie di costruzione e i punti di metallo. Legge la scheda del motore da me.knowledge.
lua/place.luaCerca un sito, e allarga la ricerca finché è larga quanto la mappa.
lua/profile.luaLegge il testo del profilo e applica i pesi.
lua/report.luaScrive il testo che restituisce report.
lua/rules.luaLegge le regole ai.* con cui gioca Classic.
lua/squads.luaSmista le unità in squadre e dà un ordine a una squadra.
lua/types.luaLegge i campi del tipo che usa il giocatore computer.
lua/tasks/structures, construction, strike, rally, air_raid e siege.
README.mdUna nota. Il gioco non la carica.
LICENSELa licenza.

La tabella dei compiti

Le squadre 1 e 5 costruiscono strutture. La squadra 5 lo fa solo quando lo dicono le regole, e altrimenti resta inattiva. Le squadre 2 e 6 sono gruppi d'assalto. Le squadre 3 e 7 si radunano su di essi. La squadra 4 è costruzione. La squadra 8 è l'ala aerei. La squadra 9 è assedio. Il commento sulla squadra 9 è una particolarità: l'ordinamento non riempie mai quella squadra, quindi il compito di assedio tiene solo il suo orario, salvo che ci fosse già un'unità.

-- …
    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
-- …

Al primo tick onTick aggiorna le liste di costruzione e semina le griglie. L'ordinamento stesso è il conto alla rovescia. Parte da 30. Ogni tick sottrae uno, e in fondo torna a 30 e chiama l'ordinamento delle squadre.

-- …
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 che cosa fa l'ordinamento: le unità selezionabili nel raggio del posto prendono gli ordini permanenti, e un'unità ancora nella squadra 0 entra nella squadra in cui la smista il suo tipo. Un'unità che è già in una squadra tiene quella squadra.

Un compito

lua/tasks/rally.lua è l'intero compito rally. Quando la squadra rally e il gruppo strike hanno entrambi delle unità, la squadra rally si sposta al centro del gruppo strike.

-- 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

Le note sulle particolarità

Una riga marcata @quirk registra un comportamento del giocatore computer integrato. La nota sta nello script così chi legge la vede accanto al codice. La nota sulla squadra 9 è nella tabella dei compiti sopra. Altre due stanno in profile.lua. La parola any conta solo come prima parola dopo plan, e quella stessa parola viene riprovata su ogni parola successiva della riga. L'ai_weight di un tipo scaricabile si applica una volta per giocatore computer, quindi il peso si compone per quel conteggio.

-- 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.

La costruzione trattiene le costruzioni, e pattuglia lo stesso. Un'ala aerei piccola e senza base, sotto ai.patrol-null-enemy-skip, estrae un pattugliamento sul bordo della mappa, le stesse tre estrazioni che fa un'ala grande, e pattuglia lì invece di accodare un pattugliamento sul nemico più vicino.

Che cosa resta nel motore

Il giocatore computer integrato resta il predefinito. Un posto è Classic solo quando i suoi file coincidono con la cartella di questa build. Quel test concede due comportamenti, e una terza estrazione dal flusso di preparazione. brain.shared_random estrae il flusso condiviso della partita, quello da cui estraggono gli script delle unità e la simulazione, così gli intervalli degli aerei e dell'assedio di Classic restano al passo con il giocatore computer integrato. unit:stance con direct impostato a true scrive i bit permanenti di movimento e di fuoco e non dà un ordine. Qualsiasi altro posto è rifiutato. brain.setup_random estrae da una copia di quel flusso presa alla preparazione integrata del posto, e fa avanzare solo la copia.

Nessun altro cervello ha nessuna delle due funzioni casuali, e una scrittura diretta di stance è rifiutata. brain.section è installato per ogni cervello. In una partita il segno non fa nulla. La vista completa non è in questo elenco. È il flag del manifesto. Steady Commander non ne chiama nessuno.

Come si dimostra

In ombra, Classic viene messo al posto accanto al giocatore computer integrato. Gli ordini del giocatore computer integrato sono quelli che gioca la partita. Quelli di Classic vengono registrati e confrontati. Nessun tick può differire. I test del motore per quel confronto sono sim-brains-shadow e sim-brains-shadow-match.

Messo al suo posto, Classic viene messo dove giocherebbe il giocatore computer integrato. I test del motore per questo sono 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 e native-net-loopback-each-ai-classic.

Un soak gioca 1.000 schermaglie headless con seme, di 20.000 tick ciascuna, sul gioco base e sulle mod pubblicate. Lo stesso seme gioca la stessa partita. Il soak è il controllo lungo che un posto che esegue Classic resti sugli ordini del giocatore computer integrato per un'intera partita.

Come leggerlo

Leggi Classic come il comportamento del giocatore computer integrato, messo per iscritto. Il giocatore computer proprio del motore è ancora il giocatore che un posto usa quando nessuno ha scelto un cervello. Installare Classic, o impacchettarlo, non cambia nulla finché un posto non è impostato su di esso.