Config Core
Прочитайте файл конфига — INI, YAML или JSON, какой написал админ, — в объект той же формы, что его значения по умолчанию, типизированный в редакторе, измените его и запишите файл обратно, с комментариями на своих местах.
Возможности
- Типизированный объект.
configs.load("settings", { chat: { prefix: "[Server]" } })даётsettings.chat.prefixтого же типа, что значение по умолчанию; опечатка — ошибка редактора, а не молча пропавшая настройка. - Три формата, один API.
settings.yaml,settings.jsonилиsettings.ini: один и тот же код читает любой из них. - Ошибки — с местом. Значение не того вида, имя не из своего юниона, незнакомый ключ — каждое называется в консоли сервера с файлом и строкой (в YAML и JSON — и со столбцом), с «did you mean», и остаётся значение по умолчанию.
- Записывает то, что прочитал.
configs.save(settings)кладёт значения на их места; комментарии на отдельных строках переживают сохранение, отсутствующий файл пишется в YAML. - И файлы любой формы. Дерево значений с путями —
shop.items[0].name— для файла, ключи которого заранее неизвестны. - INI с блоками и строками. Секции, блоки и строки значений читаются в те же объекты — а 28 нативов
cfg_*отдают их Pawn-плагинам. - Один экземпляр на сервер. Все плагины, на TypeScript и на Pawn, видят одни и те же загруженные файлы и одну базовую папку.
Установка
npx amxts module add config-core
pnpm amxts module add config-core
yarn amxts module add config-core
bunx amxts module add config-core
Команда ставит пакет и добавляет его в modules в amxts.config.ts проекта. Настройки модуля пишутся рядом, в configs:
export default defineConfig({
modules: ["@amxts/config-core"],
configs: {
baseDir: "myserver", // имена файлов читаются из configs/myserver/
},
});
| Опция | По умолчанию | Что делает |
|---|---|---|
baseDir | "" | Папка внутри configs/, из которой читаются файлы; "" — сама configs/. |
Модуль, который читает свои файлы через Config Core, например Menu Core, приносит его с собой — добавлять для него ничего не нужно.
Использование
Плагин пользуется модулем как configs, без строки импорта: сборка добавляет импорт в те плагины, которые им пользуются, и собирает модуль, только когда им пользуется хоть один.
const settings = configs.load("settings", { // configs/myserver/settings.yaml, .json или .ini
chat: { prefix: "[Server]" },
round: { time: 2.5 },
maps: ["de_dust2"],
});
console.log(`${settings.chat.prefix} ${settings.maps.length} maps`); // из файла или по умолчанию
settings.round.time = 3;
configs.save(settings); // в формате файла, с комментариями
Значения по умолчанию задают форму объекта. Что есть в файле, заменяет значение по умолчанию, значение за значением; чего в файле нет, остаётся по умолчанию. Интерфейс даёт полям описания, необязательные поля и юнионы имён:
type Mode = "normal" | "dm" | "knife";
interface Settings {
/** Текст перед каждым сообщением в чате. */
chat: { prefix: string };
round: { time: number; mode: Mode };
maps: string[];
/** Показывается при входе; если не задан, ничего не показывается. */
motd?: string;
}
const settings = configs.load<Settings>("settings", {
chat: { prefix: "[Server]" },
round: { time: 2.5, mode: "normal" },
maps: [],
});
Ошибка в файле оставляет значение по умолчанию, а консоль говорит, где она:
[ConfigCore] configs/myserver/settings.yaml:6:9: "mode" is "dmm", not one of "normal", "dm", "knife" - did you mean "dm"? - the default stays
[ConfigCore] configs/myserver/settings.yaml:2:3: unknown key "prefx" in "chat" - did you mean "prefix"?
| В объекте | В файле |
|---|---|
string, number, boolean | значение; число можно текстом, логическое — true/false, yes/no, on/off в любом регистре или число (0 — ложь) |
юнион имён, "normal" | "dm" | одно из них |
string[], number[], boolean[], список имён | список — элемент другого вида пропускается |
string[][] (текста, чисел, логических значений или имён) | список списков: строки значений; в INI — блок строк |
| объект любой вложенности | объект |
| список объектов | список объектов — поле, которого нет у элемента, пустое, и об этом сказано |
Map<string, number> (текста, чисел, логических значений или имён) | объект |
field?: T | можно не писать: undefined |
configs.load()отдаёт копию значений по умолчанию со значениями из файла; второй вызов читает файл заново.configs.save(settings)пишет каждое поле в файл, из которого объект прочитан.- У объекта, который
load()прочитал без типа, нет имени типа, чтобы написать его в параметре функции: чтобы передавать настройки дальше, объявите интерфейс и пишитеconfigs.load<Settings>(...). - Сборка читает форму из исходника: значения по умолчанию — объектным литералом (в списке — элемент), или указан тип —
configs.load<Settings>(...). Чего так не прочитать — пустой список без типа, список списков списков, — останавливает сборку с местом и тем, как исправить.
Какой файл читается
load("settings") и read("settings") читают первый из settings.ini, settings.yaml, settings.yml, settings.json и settings.jsonc, который есть. Если есть два, читается первый, а консоль сервера об этом говорит: оставьте один. Имя с расширением — load("settings.json", ...) — читает этот файл. Файла нет — читаются значения по умолчанию, а сохраняется он как settings.yaml.
Файлы неизвестной формы
Файл, ключи которого заранее неизвестны, — список карт со своими настройками, файл, который пишет другой плагин, — читается деревом значений:
const maps = configs.read("maps");
for (const map of maps.values()) {
console.log(`${map.key}: ${map.getNumber("rounds", 30)} rounds`);
}
maps.setNumber("de_dust2.rounds", 20);
maps.save();
Значение — это ConfigNode: объект, массив, текст, число, логическое значение или null (kind), с файлом, строкой и столбцом, где оно прочитано. Путь ведёт внутрь — chat.prefix, items[0].name, — а геттер принимает запасное значение на случай, если значения нет или оно другого вида.
API
| Функция | Что делает |
|---|---|
load(name, defaults) | Читает файл конфига в объект той же формы, что defaults. |
save(settings) | Записывает объект, прочитанный load(), обратно в его файл. |
read(name) | Читает файл конфига деревом значений; верхнее значение файла. |
resolve(name) | Файл, который читают load(name) и read(name), например "settings.yaml". |
parse(text, format) | Читает текст — "yaml", "json" или "ini" — деревом. |
ConfigNode | Что делает |
|---|---|
kind · key · file · format · line · column | Что это за значение и где оно прочитано. |
get(path) · has(path) | Значение, к которому ведёт путь, или null; есть ли оно. |
keys(path?) · values(path?) | Ключи объекта; элементы массива или значения объекта. |
getString(path?, fallback?) · getNumber · getBoolean · getStrings | Значение как текст, число, логическое значение, список текста. |
set(path, text) · setNumber · setBoolean · setStrings | Задаёт значение, создавая объекты на пути; false, если путь идёт через текст или число. |
remove(path) · save() | Удаляет значение; записывает весь файл обратно в его формате. |
Форматы файлов
Одни и те же настройки в каждом из них:
# settings.yaml
chat:
prefix: "[Server]"
rules:
- Be nice
- No cheats
round:
time: 2.5
hud:
enabled: true
// settings.json или settings.jsonc
{
"chat": {
"prefix": "[Server]",
"rules": ["Be nice", "No cheats"],
},
"round": { "time": 2.5 },
"hud": { "enabled": true },
}
; settings.ini
[chat]
prefix = [Server]
rules = "Be nice" "No cheats"
[round]
time = 2.5
[hud]
enabled = 1
- YAML: отображения и списки — с отступами или в
{ }и[ ]; простой текст, текст в'одинарных'и"двойных"кавычках со своими экранированиями; блоки|и>для текста в несколько строк; числа,true/false,nullи~, как их читает YAML 1.2, —yesэто текст; комментарии#;---перед документом и...после. - Чего в YAML нет: якорей и ссылок (
&,*), тегов (!), сложных ключей (?), директив (%YAML), нескольких документов в одном файле и простого значения, которое продолжается на следующих строках. Тогда файл читается пустым, а консоль называет строку и столбец:configs/settings.yaml:4:9: anchors and aliases (& and *) are not supported - write the value out. - JSON: как пишется JSON, плюс комментарии
//и/* */из JSONC и запятая после последнего поля или элемента — и в файлах.jsonтоже. - INI: каждая
[секция]— объект верхнего уровня:[chat]сprefix = [Server]— этоsettings.chat.prefix; строка из нескольких значений — список; блокkey = { ... }— объект или список строк. Правила, которые добавляет INI, — в INI-файлах. - Сохранение пишет файл в его формате: комментарии на отдельных строках и пустые строки остаются; комментарий после значения на той же строке и комментарии внутри
{ }и[ ]в YAML — нет. JSON сохраняет свои отступы.
INI-файлы
INI-конфиг читается в такой же объект: каждая [секция] — объект, блок ключей key = { ... } — объект в нём, строка из нескольких значений — список, а блок строк в кавычках — список списков:
; configs/myserver/settings.ini
[MAIN]
CHAT_PREFIX = [MYPLUGIN]
MAPS = de_dust2 de_inferno de_nuke
HUD = {
HIDE_TIME = 255 50 50
}
CVARS = {
"mp_timelimit" "30"
"mp_freezetime" "3"
}
const settings = configs.load("settings", {
MAIN: {
CHAT_PREFIX: "[Server]",
MAPS: ["de_dust2"],
HUD: { HIDE_TIME: [255, 255, 255] },
CVARS: [["mp_timelimit", "20"]],
},
});
for (const row of settings.MAIN.CVARS) {
const cvar = new Cvar(row[0]);
cvar.value = row[1];
}
- Блок строк остаётся блоком, когда объект сохраняется, — и строки из одного значения тоже; комментарии над его строками остаются.
- Путь в дереве такого файла (
configs.read()) пишется так же:MAIN.HUD.HIDE_TIME[0],MAIN.CVARS[1][0]. Элемент списка — это[0]:MAIN.MAPS.0ищет ключ0.
- Ключи ищутся без учёта регистра, как их ищут Pawn-нативы, имена секций — нет:
[main]— неMAIN. Из двух секций с одним именем берётся последняя. - Значение с пробелами берётся в кавычки:
TITLE = "Main menu". Без кавычек это список слов: текстовое поле остаётся со значением по умолчанию, а консоль говорит почему, и нативыcfg_*Pawn и Menu Core читают первое слово. - Нет места значению на верхнем уровне объекта, которое не объект, и списку объектов: в INI-файле такое поле остаётся по умолчанию, и консоль один раз об этом говорит — такой конфиг пишите в YAML или JSON.
Pawn-плагины
Pawn-плагин тоже читает и пишет конфиги через Config Core — 28 нативами, имена которых начинаются с cfg_: cfg_load_file, cfg_get_value, cfg_set_int и остальные. Они читают INI-файлы. Их include лежит в пакете в include/:
#include <universal_config>
Pawn-плагин, уже собранный с этим include, работает как есть — пересобирать ничего не нужно.
Config Core работает на сервере как один из плагинов проекта. Если им не пользуется ни один плагин проекта на TypeScript, оставьте его в сборке для Pawn-плагинов: pawn: ["@amxts/config-core"] в amxts.config.ts. Все нативы с сигнатурами — в PAWN.ru.md.