Настройки скрипта в MoonLoader: inicfg и CP1251
Сохранение настроек в INI через inicfg и работа с русским текстом: где CP1251, где UTF-8 и как между ними переключаться.
Почти любому скрипту нужно что-то запоминать между запусками: звание игрока, задержку между строками, включённые функции. В MoonLoader для этого есть библиотека inicfg, которая читает и пишет INI-файлы. Вторая постоянная тема в скриптах для SA:MP – кодировка: чат работает в CP1251, ImGui в UTF-8, и русский текст ломается на стыке. Обе темы встречаются в одном скрипте, поэтому разберём их вместе.
Police Tools хранила настройки в INI, а Police Tools Lite сочетает окно на mimgui с кодировкой CP1251: ровно тот случай, который разбирает вторая половина статьи.
inicfg: загрузка и значения по умолчанию
inicfg входит в стандартный набор библиотек MoonLoader и подключается через require. Настройки описываются таблицей секций, внутри каждой – пары ключ-значение:
local inicfg = require("inicfg")
local CONFIG_FILE = "my_script.ini"
local cfg = inicfg.load({
main = {
rank = "Сержант",
delay = 1000,
greet = true,
},
}, CONFIG_FILE)
Первый аргумент inicfg.load – значения по умолчанию, второй – имя файла. Если файла ещё нет, функция вернёт таблицу по умолчанию. Если файл есть, но в нём не хватает каких-то ключей, недостающие возьмутся из той же таблицы. Это удобно при обновлении скрипта: новая настройка появляется у всех пользователей сразу, старый конфиг удалять не нужно.
Числа и true/false при загрузке превращаются обратно в числа и логические значения, поэтому cfg.main.delay можно сразу передать в wait.
Где лежит файл и как сохранять
Если в имени нет полного пути, inicfg ищет и сохраняет файл в moonloader/config/. Файл из примера окажется в moonloader/config/my_script.ini. Структура папок после установки описана в статье про установку MoonLoader.
Сохраняет изменения inicfg.save(cfg, CONFIG_FILE). Удобно вызвать его сразу после загрузки: при первом запуске на диске появится файл со значениями по умолчанию, и пользователь сможет поправить его в блокноте. Дальше сохраняйте в тот момент, когда настройка изменилась, например в обработчике команды. Сохранение на каждом кадре в главном цикле бессмысленно нагружает диск.
Имя файла лучше давать по имени скрипта. В config/ складывают настройки все установленные скрипты, и общее имя вроде settings.ini рано или поздно совпадёт с чужим.
Почему CP1251
Клиент SA:MP появился задолго до повсеместного UTF-8 и работает с текстом в однобайтовой кодировке Windows. Русские серверы используют CP1251: в ней сервер отправляет чат и диалоги, в ней же ждёт текст от игрока. Строка в UTF-8, отправленная через sampSendChat или sampAddChatMessage, превращается в набор случайных символов.
Отсюда первое правило: Lua-файл скрипта с русским текстом сохраняется в CP1251 (в Notepad++ это «Кириллица (Windows-1251)»). Тогда все строковые литералы в коде уже в нужной кодировке, а чат и INI-файл получают их без преобразований.
encoding и u8 для ImGui
Проблема возвращается, когда в скрипте появляется окно на ImGui. Dear ImGui работает только с UTF-8, и строка в CP1251 в окне превратится в знаки вопроса. Перевести её помогает библиотека encoding:
local encoding = require("encoding")
encoding.default = "CP1251"
local u8 = encoding.UTF8
encoding.default задаёт кодировку, в которой лежат строки скрипта. После этого u8("текст") переводит строку из CP1251 в UTF-8 для ImGui, а u8:decode(text) делает обратное: превращает UTF-8 из поля ввода ImGui в CP1251 для чата или INI. Внутри окна это выглядит так: imgui.Text(u8("Звание: " .. cfg.main.rank)).
Удобное правило: внутри скрипта и в конфиге всё хранится в CP1251, в UTF-8 текст переводится только на входе в ImGui и обратно на выходе из него.
Пример целиком
Скрипт хранит звание игрока и задержку, по команде /rank меняет звание и сохраняет его в INI, при входе в игру выводит приветствие в чат. Команда /rankinfo открывает маленькое окно на mimgui с тем же званием, уже через u8:
script_name("config-demo")
script_author("Pakulichev")
local inicfg = require("inicfg")
local imgui = require("mimgui")
local encoding = require("encoding")
encoding.default = "CP1251"
local u8 = encoding.UTF8
local CONFIG_FILE = "config-demo.ini"
local show = imgui.new.bool(false)
local cfg = inicfg.load({
main = {
rank = "Сержант",
delay = 1000,
greet = true,
},
}, CONFIG_FILE)
inicfg.save(cfg, CONFIG_FILE)
local function set_rank(args)
if args == "" then
sampAddChatMessage("Использование: /rank [звание]", 0xAAAAAA)
return
end
cfg.main.rank = args
if inicfg.save(cfg, CONFIG_FILE) then
sampAddChatMessage("Звание сохранено: " .. args, 0x33AA33)
else
sampAddChatMessage("Не удалось сохранить " .. CONFIG_FILE, 0xFF5555)
end
end
imgui.OnFrame(function() return show[0] end, function()
imgui.Begin("config-demo", show)
imgui.Text(u8("Звание: " .. cfg.main.rank))
imgui.End()
end)
function main()
while not isSampAvailable() do wait(100) end
sampRegisterChatCommand("rank", set_rank)
sampRegisterChatCommand("rankinfo", function()
show[0] = not show[0]
end)
if cfg.main.greet then
wait(cfg.main.delay)
sampAddChatMessage("Ваше звание: " .. cfg.main.rank, 0xF5A524)
end
wait(-1)
end
Текст, введённый в чат после /rank, приходит в CP1251 и сохраняется в INI без преобразований, поэтому файл читается в блокноте в той же кодировке. В окно то же значение попадает через u8. Если позже добавить в окно поле ввода звания, значение из поля нужно будет пропустить через u8:decode перед записью в cfg.main.rank.
Частые вопросы
Что будет, если удалить INI-файл скрипта?
При следующем запуске inicfg.load вернёт значения по умолчанию из таблицы в коде, а inicfg.save создаст файл заново. Пользовательские настройки при этом пропадут.
Можно ли хранить в inicfg вложенные таблицы и списки?
Нет, INI плоский: секции и пары ключ-значение. Для списков вроде биндов удобнее отдельный файл в своём формате или JSON. Police Tools Lite, например, держит бинды в config/PToolsLite.list отдельно от настроек.
Почему текст в ImGui отображается знаками вопроса?
ImGui ждёт UTF-8, а строки в скрипте, сохранённом в CP1251, лежат в CP1251. Перед передачей в ImGui их нужно пропустить через u8. Вторая возможная причина – в шрифте нет кириллических глифов.