Головна › Моддинг › Classic: розібраний приклад
Розділ 09

Classic: розібраний приклад

Вбудований компʼютерний гравець на Lua: файли, таблиця завдань, одне завдання, нотатки про особливості, що лишається в рушії і як доводять, що накази ті самі.

Цей розділ стисло викладає docs/brains/oabrain-standard.md, docs/brains/authoring.md і docs/playing-with-brains.md. Нормативний текст — саме ці документи.

Що таке Classic

Classic — це brains/classic. Це документація вбудованого компʼютерного гравця: ті самі накази, записані так, щоб їх можна було прочитати. Вбудований гравець лишається в рушії й лишається типовим. Вибір Classic садить на місце цей пакет. Якщо лишити на місці вбудованого гравця, грає власний компʼютерний гравець рушія.

Маніфест задає works-with зірочкою, а fullAwareness — true. Його опис каже, що він грає точно так, як вбудований компʼютерний гравець. Повна видимість — це прапорець маніфесту. Його дозволено в мережевій грі, і місце його позначає. id, назва й шлях установлення не роблять пакет Classic. Файли мають бути текою brains/classic цієї збірки: ті самі шляхи й ті самі байти.

Маніфест · 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

Файли

brain.lua будує по одному завданню для кожного загону від 0 до 9. На першому тіку onTick оновлює списки будівництва й ініціалізує сітки. Далі кожен тік запускає сортування загонів за його зворотним відліком і виконує кожне завдання, чия черга настала.

ШляхЩо робить
oabrain.yamlМаніфест.
brain.luaМісце, таблиця завдань і onTick.
lua/build.luaОцінює тип і обирає один зі списку будівельника.
lua/knowledge.luaВаги, ліміти, сітки будівництва й родовища металу. Читає запис рушія з me.knowledge.
lua/place.luaШукає ділянку, розширюючи пошук, доки він не охопить усю мапу.
lua/profile.luaЧитає текст профілю й застосовує ваги.
lua/report.luaПише текст, який повертає report.
lua/rules.luaЧитає правила ai.*, за якими грає Classic.
lua/squads.luaРозподіляє юніти по загонах і віддає загону наказ.
lua/types.luaЧитає поля типу, які використовує компʼютерний гравець.
lua/tasks/structures, construction, strike, rally, air_raid і siege.
README.mdНотатка. Гра її не завантажує.
LICENSEЛіцензія.

Таблиця завдань

Загони 1 і 5 будують споруди. Загін 5 робить це лише тоді, коли так кажуть правила, інакше бездіє. Загони 2 і 6 — ударні групи. Загони 3 і 7 збираються до них. Загін 4 — будівництво. Загін 8 — авіакрило. Загін 9 — облога. Коментар до загону 9 — це особливість: сортування ніколи не заповнює цей загін, тож завдання облоги лише дотримується свого розкладу, якщо в загоні вже не було юніта.

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

На першому тіку onTick оновлює списки будівництва й ініціалізує сітки. Саме сортування — це зворотний відлік. Він починається з 30. Кожен тік віднімає одиницю, а дійшовши до кінця, відлік повертається до 30 і викликає сортування загонів.

-- …
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 визначає, що робить сортування: юніти, які можна виділити, у межах місця отримують постійні накази, а юніт, що ще в загоні 0, приєднується до загону, до якого його відносить тип. Юніт, що вже в загоні, лишається в ньому.

Одне завдання

lua/tasks/rally.lua — це все завдання збору. Коли і в загоні збору, і в ударній групі є учасники, загін збору рухається до центру ударної групи.

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

Нотатки про особливості

Рядок, позначений @quirk, фіксує поведінку, яка є у вбудованого компʼютерного гравця. Нотатка стоїть у скрипті, щоб читач бачив її поруч із кодом. Нотатку про загін 9 наведено в таблиці завдань вище. Ще дві лежать у profile.lua. Слово any зараховується лише як перше слово після plan, і те саме слово знову перевіряється на кожному наступному слові рядка. ai_weight завантажуваного типу застосовується один раз на кожного компʼютерного гравця, тож вага накопичується відповідно до їхньої кількості.

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

Завдання construction притримує будівництво, але все одно патрулює. Невелике авіакрило без бази за ai.patrol-null-enemy-skip бере патруль біля краю мапи — ті самі три вибірки, що й велике крило, — і патрулює там, замість ставити в чергу патруль до найближчого ворога.

Що лишається в рушії

Вбудований компʼютерний гравець лишається типовим. Місце є Classic лише тоді, коли його файли збігаються з текою цієї збірки. Ця перевірка дає дві поведінки й третю вибірку з потоку налаштування. brain.shared_random бере значення зі спільного потоку матчу — того, з якого беруть значення скрипти юнітів і симуляція, — тож інтервали авіації й облоги Classic лишаються синхронними з вбудованим гравцем. unit:stance з direct, установленим у true, записує біти постійних наказів руху й вогню і не віддає наказу. Для будь-якого іншого місця це відхиляється. brain.setup_random бере значення з копії цього потоку, знятої під час вбудованого налаштування місця, і просуває лише копію.

Жоден інший мозок не має жодної з цих випадкових функцій, а прямий запис режиму відхиляється. brain.section встановлюється для кожного мозку. У грі ця позначка нічого не робить. Повної видимості в цьому переліку немає. Це прапорець маніфесту. Steady Commander не викликає жодної з них.

Як це доводять

У тіньовому режимі Classic садять поруч із вбудованим гравцем. Матч виконує накази вбудованого гравця. Накази Classic записують і порівнюють. Жоден тік не може відрізнятися. Тести рушія для цього порівняння — sim-brains-shadow і sim-brains-shadow-match.

У режимі заміни Classic садять туди, де грав би вбудований гравець. Тести рушія для цього — 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 і native-net-loopback-each-ai-classic.

Тривалий тест (soak) грає 1000 сутичок без інтерфейсу з заданими seed, по 20 000 тіків кожна, на базовій грі й опублікованих модах. Той самий seed грає той самий матч. Soak — це довга перевірка того, що місце з Classic упродовж усієї гри не відходить від наказів вбудованого гравця.

Як це читати

Читай Classic як записану поведінку вбудованого компʼютерного гравця. Власний компʼютерний гравець рушія — це й далі той гравець, якого використовує місце, коли ніхто не обрав мозок. Установлення чи пакування Classic нічого не змінює, доки на місце не призначено цей мозок.