Accueil › Infos modding › Classic, un exemple développé
Chapitre 09

Classic, un exemple développé

Le joueur ordinateur intégré écrit en Lua : ses fichiers, la table des tâches, une tâche, les notes de bizarrerie, ce qui reste dans le moteur, et comment les mêmes ordres sont prouvés.

Ce chapitre résume docs/brains/oabrain-standard.md, docs/brains/authoring.md et docs/playing-with-brains.md. Ces documents sont le texte normatif.

Ce qu'est Classic

Classic est brains/classic. C'est la documentation du joueur ordinateur intégré : les mêmes ordres, écrits pour que vous puissiez les lire. Le joueur ordinateur intégré reste dans le moteur et reste le choix par défaut. Choisir Classic fait siéger ce paquet. Laisser le siège sur le joueur ordinateur intégré fait tourner le joueur ordinateur du moteur.

Le manifeste dit que works-with est une étoile, et que fullAwareness vaut true. Sa description dit qu'il joue exactement comme le joueur ordinateur intégré. La vue complète est le drapeau du manifeste. Elle est permise dans une partie en réseau, et le siège la signale. L'identifiant, le nom et le chemin d'installation ne font pas d'un paquet Classic. Les fichiers doivent être le dossier brains/classic de cette construction, les mêmes chemins et les mêmes octets.

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

Les fichiers

brain.lua construit une tâche pour chaque escouade de 0 à 9. Au premier tick, onTick rafraîchit les listes de construction et ensemence les grilles. Chaque tick exécute ensuite le tri des escouades sur son compte à rebours et exécute chaque tâche dont le tour est venu.

CheminRôle
oabrain.yamlLe manifeste.
brain.luaLe siège, la table des tâches et onTick.
lua/build.luaNote un type et en choisit un dans la liste d'un constructeur.
lua/knowledge.luaPoids, limites, grilles de construction et gisements de métal. Lit l'enregistrement du moteur dans me.knowledge.
lua/place.luaCherche un site, en élargissant jusqu'à ce que la recherche soit aussi large que la carte.
lua/profile.luaLit le texte du profil et applique les poids.
lua/report.luaÉcrit le texte que report renvoie.
lua/rules.luaLit les règles ai.* selon lesquelles Classic joue.
lua/squads.luaRange les unités en escouades et donne un ordre à une escouade.
lua/types.luaLit les champs de type que le joueur ordinateur utilise.
lua/tasks/structures, construction, strike, rally, air_raid et siege.
README.mdUne note. Le jeu ne la charge pas.
LICENSELa licence.

La table des tâches

Les escouades 1 et 5 construisent des structures. L'escouade 5 ne le fait que quand les règles le disent, et reste inactive sinon. Les escouades 2 et 6 sont des groupes de frappe. Les escouades 3 et 7 se rallient sur eux. L'escouade 4 est la construction. L'escouade 8 est l'escadre d'aéronefs. L'escouade 9 est siege. Le commentaire sur l'escouade 9 est une bizarrerie : le tri ne remplit jamais cette escouade, donc la tâche siege ne fait que garder son horaire, sauf si une unité y était déjà.

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

Au premier tick, onTick rafraîchit les listes de construction et ensemence les grilles. Le tri lui-même est le compte à rebours. Il commence à 30. Chaque tick en retire un, et en bas il revient à 30 et appelle le tri des escouades.

-- …
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 dit ce que fait le tri : les unités sélectionnables dans la portée du siège prennent des ordres permanents, et une unité encore dans l'escouade 0 rejoint l'escouade dans laquelle son type se range. Une unité déjà dans une escouade garde cette escouade.

Une tâche

lua/tasks/rally.lua est l'intégralité de la tâche rally. Quand l'escouade rally et le groupe strike ont tous deux des membres, l'escouade rally se déplace au centre du groupe 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

Les notes de bizarrerie

Une ligne marquée @quirk enregistre un comportement du joueur ordinateur intégré. La note est dans le script pour qu'un lecteur la voie à côté du code. La note de l'escouade 9 est dans la table des tâches plus haut. Deux autres se trouvent dans profile.lua. Le mot any ne compte que comme premier mot après plan, et ce même mot est testé de nouveau sur chaque mot suivant de la ligne. Le ai_weight d'un type téléchargeable est appliqué une fois par joueur ordinateur, donc le poids se compose selon ce nombre.

-- 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 construction retient des constructions, et patrouille quand même. Une petite escadre d'aéronefs sans base, sous ai.patrol-null-enemy-skip, tire une patrouille au bord de la carte, les trois mêmes tirages qu'une grande escadre prend, et patrouille là au lieu de mettre en file une patrouille sur l'ennemi le plus proche.

Ce qui reste dans le moteur

Le joueur ordinateur intégré reste le choix par défaut. Un siège n'est Classic que quand ses fichiers correspondent au dossier de cette construction. Ce test accorde deux comportements, et un troisième tirage sur le flux de mise en place. brain.shared_random tire du flux partagé de la partie, celui dont tirent les scripts d'unités et la simulation, pour que les intervalles d'aéronefs et de siege de Classic restent au pas du joueur ordinateur intégré. unit:stance avec direct mis à true écrit les bits de posture de mouvement et de tir et ne donne pas d'ordre. Tout autre siège est refusé. brain.setup_random tire d'une copie de ce flux prise à la mise en place intégrée du siège, et n'avance que la copie.

Tout autre cerveau n'a aucune des deux fonctions aléatoires, et une écriture directe de stance est refusée. brain.section est installé pour chaque cerveau. Dans une partie, la marque ne fait rien. La vue complète n'est pas dans cette liste. C'est le drapeau du manifeste. Steady Commander n'en appelle aucune.

Comment il est prouvé

En shadow, Classic est mis en siège à côté du joueur ordinateur intégré. Les ordres du joueur ordinateur intégré sont ceux que la partie joue. Les ordres de Classic sont enregistrés et comparés. Aucun tick ne peut différer. Les tests du moteur pour cette comparaison sont sim-brains-shadow et sim-brains-shadow-match.

Mis à la place, Classic est mis en siège là où le joueur ordinateur intégré jouerait. Les tests du moteur pour cela sont 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 et native-net-loopback-each-ai-classic.

Un soak joue 1 000 escarmouches ensemencées et sans interface, de 20 000 ticks chacune, sur le jeu de base et les mods publiés. La même graine joue la même partie. Le soak est la longue vérification qu'un siège qui fait tourner Classic reste sur les ordres du joueur ordinateur intégré pendant toute une partie.

Comment le lire

Lisez Classic comme le comportement du joueur ordinateur intégré, mis par écrit. Le joueur ordinateur du moteur reste le joueur qu'un siège utilise quand personne n'a choisi de cerveau. Installer Classic, ou l'empaqueter, ne change rien tant qu'un siège n'est pas réglé dessus.