# Настройки скрипта в MoonLoader: inicfg и CP1251

> Как хранить настройки Lua-скрипта для SA:MP: inicfg.load и inicfg.save, значения по умолчанию, папка moonloader/config, кодировка CP1251 и библиотека encoding для чата и ImGui.

- URL: https://pakulichev.com/wiki/moonloader-config
- Author: Pakulichev (https://pakulichev.com/)
- Language: ru
- Updated: 2026-10-16
- Whole site for LLMs: https://pakulichev.com/llms.txt (index), https://pakulichev.com/llms-full.txt (full text)

# Настройки скрипта в MoonLoader: inicfg и CP1251

Почти любому скрипту нужно что-то запоминать между запусками: звание игрока, задержку между строками, включённые функции. В MoonLoader для этого есть библиотека `inicfg`, которая читает и пишет INI-файлы. Вторая постоянная тема в скриптах для SA:MP – кодировка: чат работает в CP1251, ImGui в UTF-8, и русский текст ломается на стыке. Обе темы встречаются в одном скрипте, поэтому разберём их вместе.

[Police Tools](https://pakulichev.com/projects/police-tools) хранила настройки в INI, а [Police Tools Lite](https://pakulichev.com/projects/police-tools-lite) сочетает окно на mimgui с кодировкой CP1251: ровно тот случай, который разбирает вторая половина статьи.

## inicfg: загрузка и значения по умолчанию

`inicfg` входит в стандартный набор библиотек MoonLoader и подключается через `require`. Настройки описываются таблицей секций, внутри каждой – пары ключ-значение:

```lua
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](https://pakulichev.com/wiki/moonloader-install).

Сохраняет изменения `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](https://pakulichev.com/wiki/imgui-for-gta-sa). Dear ImGui работает только с UTF-8, и строка в CP1251 в окне превратится в знаки вопроса. Перевести её помогает библиотека `encoding`:

```lua
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`:

```lua
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`.

## FAQ

### Что будет, если удалить INI-файл скрипта?

При следующем запуске inicfg.load вернёт значения по умолчанию из таблицы в коде, а inicfg.save создаст файл заново. Пользовательские настройки при этом пропадут.

### Можно ли хранить в inicfg вложенные таблицы и списки?

Нет, INI плоский: секции и пары ключ-значение. Для списков вроде биндов удобнее отдельный файл в своём формате или JSON. Police Tools Lite, например, держит бинды в config/PToolsLite.list отдельно от настроек.

### Почему текст в ImGui отображается знаками вопроса?

ImGui ждёт UTF-8, а строки в скрипте, сохранённом в CP1251, лежат в CP1251. Перед передачей в ImGui их нужно пропустить через u8. Вторая возможная причина – в шрифте нет кириллических глифов.
