Биндер для SA:MP на Lua: команды и отыгровки
Разбор небольшого биндера на Lua: от одной чат-команды до тегов и задержек по образцу Police Tools Lite.
Биндер отправляет в чат заранее записанные строки по команде или клавише. На RP-серверах так оформляют однотипные отыгровки: надеть наручники, проверить документы, доложить в рацию. Биндер был основой моей Police Tools, а в Police Tools Lite от большого скрипта остался только он. Ниже соберём упрощённую версию того же механизма: команда, несколько строк с паузами, теги $ID$, $NAME$, $ARGS$, $SLEEP:мс$.
Для работы нужны MoonLoader и SAMPFUNCS (как установить). Код написан по моим правилам оформления Lua.
Чат-команда
sampRegisterChatCommand связывает команду с функцией. Функция получает одну строку – всё, что игрок написал после команды:
script_name("mini-binder")
script_author("Pakulichev")
function main()
while not isSampAvailable() do wait(100) end
sampRegisterChatCommand("hi", function(args)
sampSendChat("Здравствуйте, " .. args)
end)
wait(-1)
end
/hi Иван отправит в чат «Здравствуйте, Иван». Если вызвать /hi без аргументов, args будет пустой строкой, не nil. Регистрировать команды нужно после isSampAvailable(): до загрузки SA:MP функции чата недоступны.
Русский текст в коде сработает, если файл сохранён в CP1251. Почему так и что делать с UTF-8, разобрано в статье про inicfg и кодировку.
Несколько строк с задержкой
Отыгровка из трёх строк, отправленных одновременно, выглядит как флуд и может попасть под антифлуд сервера. Между строками нужна пауза, а wait внутри колбэка команды не работает. Паузы делают в отдельном потоке через lua_thread.create:
local function play_lines(lines)
lua_thread.create(function()
for i, line in ipairs(lines) do
if i > 1 then wait(1000) end
sampSendChat(line)
end
end)
end
Секунда между строками – значение по умолчанию и в Police Tools Lite. Для серверов со строгим антифлудом её стоит увеличить.
Теги: ID, имя и аргументы
Тег – метка в тексте бинда, которую скрипт заменяет перед отправкой. ID своего персонажа даёт sampGetPlayerIdByCharHandle(PLAYER_PED), ник по ID – sampGetPlayerNickname. На RP-серверах ник имеет вид Имя_Фамилия, поэтому имя и фамилию можно вырезать шаблоном:
local function apply_tags(line, args)
local _, id = sampGetPlayerIdByCharHandle(PLAYER_PED)
local nick = sampGetPlayerNickname(id)
local name = nick:match("^([^_]+)") or nick
local surname = nick:match("_(.+)$") or ""
line = line:gsub("%$ID%$", function() return tostring(id) end)
line = line:gsub("%$NAME%$", function() return name end)
line = line:gsub("%$SURNAME%$", function() return surname end)
line = line:gsub("%$ARGS%$", function() return args end)
return line
end
$ в шаблонах Lua означает конец строки, поэтому в тегах он экранируется как %$. Замену передаю функцией: если игрок введёт в аргументах %, строковая замена его исказит.
Бинд целиком
Теперь соберём всё вместе и добавим $SLEEP:мс$: тег в начале строки задаёт паузу перед ней вместо стандартной секунды. Флаг busy не даёт запустить второй бинд, пока не закончился первый, иначе строки двух отыгровок перемешаются.
script_name("mini-binder")
script_author("Pakulichev")
local vkeys = require("vkeys")
local binds = {
cuff = {
"/me снял наручники с пояса",
"$SLEEP:1500$/me надел наручники на $ARGS$",
"/do Наручники застёгнуты.",
},
doc = {
"Здравствуйте, я $NAME$ $SURNAME$, предъявите документы.",
},
}
local busy = false
local function apply_tags(line, args)
local _, id = sampGetPlayerIdByCharHandle(PLAYER_PED)
local nick = sampGetPlayerNickname(id)
local name = nick:match("^([^_]+)") or nick
local surname = nick:match("_(.+)$") or ""
line = line:gsub("%$ID%$", function() return tostring(id) end)
line = line:gsub("%$NAME%$", function() return name end)
line = line:gsub("%$SURNAME%$", function() return surname end)
line = line:gsub("%$ARGS%$", function() return args end)
return line
end
local function play_bind(lines, args)
if busy then return end
busy = true
lua_thread.create(function()
for i, raw in ipairs(lines) do
local delay = tonumber(raw:match("^%$SLEEP:(%d+)%$"))
local line = raw:gsub("^%$SLEEP:%d+%$", "")
wait(delay or (i > 1 and 1000 or 0))
sampSendChat(apply_tags(line, args))
end
busy = false
end)
end
function main()
while not isSampAvailable() do wait(100) end
for cmd, lines in pairs(binds) do
sampRegisterChatCommand(cmd, function(args)
play_bind(lines, args)
end)
end
while true do
wait(0)
local typing = sampIsChatInputActive() or sampIsDialogActive()
if isKeyJustPressed(vkeys.VK_F2) and not typing then
play_bind(binds.doc, "")
end
end
end
Бинд на клавишу проверяется в бесконечном цикле main. isKeyJustPressed срабатывает один раз на нажатие, коды клавиш берутся из библиотеки vkeys. Проверки sampIsChatInputActive() и sampIsDialogActive() нужны, чтобы F2 не запускал отыгровку, пока игрок печатает в чат или заполняет диалог.
Куда развивать
Если команда /cuff вызвана без аргументов, $ARGS$ превратится в пустое место. Простая защита – проверить args == "" в play_bind и вывести подсказку через sampAddChatMessage, не отправляя ничего в чат. В Police Tools Lite для строк «только для себя» есть отдельный тег $NOSEND$; его можно сделать так же: найти тег, убрать его из строки и вызвать sampAddChatMessage вместо sampSendChat.
Следующий шаг – вынести таблицу binds из кода в файл в moonloader/config/, чтобы бинды можно было менять без правки скрипта, и сделать окно для редактирования на ImGui. Так устроена Police Tools Lite: бинды в config/PToolsLite.list, редактор в окне /plite.
Биндер экономит время на рутинных отыгровках, но решения за игрока он принимать не должен. Перед использованием проверьте правила сервера: ограничения на частоту сообщений и автоматизацию там почти всегда есть.
Частые вопросы
Почему отыгровку нельзя отправить в цикле без lua_thread?
Колбэк команды выполняется вне корутины скрипта, и wait в нём вызвать нельзя. lua_thread.create запускает отдельный поток MoonLoader, где паузы между строками работают.
Зачем в gsub передавать функцию, а не строку?
Во второй строке-замене gsub символ % служебный. Если аргументы команды содержат %, строковая замена исказит текст или выдаст ошибку. Функция возвращает текст как есть.
Разрешены ли биндеры на серверах SA:MP?
На многих RP-серверах биндеры отыгровок разрешены, но правила у каждого проекта свои. Перед использованием проверьте их, особенно ограничения на флуд и частоту сообщений.