Информация Гайд Всё о Lua скриптинге для MoonLoader

Содержание
Введение
Установка MoonLoader
Среда разработки

Изучение Lua и документация по MoonLoader
Программирование

Рекомендации
Дополнительно
Продвинутое программирование
Заключение




Это руководство призвано исполнить две цели: обучить разработке скриптов с нуля и восполнить все пробелы в знаниях о Lua скриптинге под MoonLoader. В нём освещены все аспекты разработки - от самого простого до продвинутых возможностей и приемов, то есть оно подходит как и для начинающих скриптеров, так и для программистов с опытом. Предварительное прочтение старого руководства не требуется, здесь есть вся необходимая и более актуальная информация.
А если вы всё ещё не знакомы с MoonLoader, то сейчас самое время ознакомиться.
Ну что, приступим?


Установка MoonLoader
Перед продолжением необходимо установить сам MoonLoader, желательно через программу установки и с дополнительными инструментами:
1. Скачайте установщик MoonLoader последней версии и запустите, следуйте шагам программы установки
2. На странице выбора компонентов отметьте нужные скрипты (рекомендуется выбрать все)
AutoReboot - позволяет автоматизировать перезагрузку ваших скриптов при их редактировании не выходя из игры. Особенностью данного скрипта является то, что он не загружает новые добавленные скрипты из папки moonloader самостоятельно.
Reload All - позволяет перезагружать абсолютно все скрипты в папке moonloader, включая добавленные после запуска игры. Для перезагрузки нажмите сочетание клавиш Ctrl + R.
SF Integration - упрощает отслеживание состояния скриптов путём вывода сообщений отладки, ошибок и другой информации в консоль SAMPFUNCS. По сути дублирует сообщения из moonloader.log в консоль.​
3. Если вы намерены делать скрипты для SA:MP, выберите SAMP.Lua и отдельно установите SAMPFUNCS
4. Это не обязательно, но не помешает установить и MoonAdditions - эту библиотеку используют некоторые скрипты и она неплохо расширяет стандартные возможности скриптинга
5. Выберите установку расширения для Notepad++, если вы будете использовать программу Notepad++ для редактирования Lua скриптов


Среда разработки
Для лёгкой и удобной работы с кодом Lua скриптов вам понадобится настроить для себя среду разработки. Для работы с Lua достаточно любого текстового редактора, но какой-нибудь блокнот Windows для этого подходит совсем плохо, поэтому лучше использовать специализированные программы. На текущий момент полная поддержка MoonLoader есть в Atom и Notepad++, помимо этого есть пользовательские дополнения для Visual Studio Code и Sublime Text.


Atom
Atom - полнофункциональный и свободно расширяемый бесплатный редактор кода, поддерживающий большое количество языков программирования, включая Lua. Для начала работы с Lua нужно установить сам Atom.
Скачать Atom
После установки Atom обязательно установить пакет для удобства редактирования скриптов.
Нажимаем Ctrl + , и переходим во вкладку Install, в поле поиска вводим "moonloader" и устанавливаем первый в списке пакет.
Атом 1.jpg
После установки этого пакета вам будет предложено установить дополнительные пакеты, соглашаемся и забываем про это.
Атом 2.png

Проект по-умолчанию.
В меню File выбираем пункт Open Folder... и указываем путь до папки moonloader, после этого она откроется как проект.

Кодировка по-умолчанию.
Для установки кодировки по-умолчанию при создании нового скрипта переходим в настройки всё тем же сочетанием Ctrl + , и выбираем пункт Core. В поле File Encoding выбираем Cyrillic (Windows-1251).


Notepad++
Notepad++ - еще один бесплатный и удобный редактор. Он является функциональной заменой стандартного блокнота и отлично подходит для написания Lua скриптов.
Скачать Notepad++

Проект по-умолчанию.
Как и в Atom, здесь есть возможность показа меню проекта, а точнее "Папка как Проект". В меню "Файл" выбираем пункт "Открыть Папку как Проект" и указываем путь к папке "moonloader".

Кодировка по-умолчанию.
Над лентой выбираем пункт Опции и переходим в Настройки. В меню слева выбираем пункт Новый документ и в разделе кодировки ставим флажок на список, в котором выбираем кодировку Windows-1251


Изучение Lua и документация по MoonLoader
MoonLoader основан на языке программирования Lua, знание хотя бы основ которого обязательно для дальнейшей работы. Поскольку Lua очень популярен в среде разработки игр и других сферах, по нему полно учебных материалов. Ниже приведено несколько ресурсов, позволяющих изучить Lua от корки до корки. Не стоит пренебрегать этими уроками даже более опытным разработчикам, зачастую можно найти для себя много нового. Имейте в виду, что в MoonLoader используется LuaJIT - Lua 5.1 с некоторыми фичами Lua 5.2 и своими дополнениями, поэтому некоторые мелочи из этих материалов будет неприменимы в MoonLoader.

Материалы по Lua на русском:
Для начинающих в программировании есть книга "Программирование на языке Lua (третье издание)", её можно купить или скачать в интернете
Неплохой вводный курс в Lua
Lua за 60 минут для знакомых с программированием
Полностью переведённое официальное руководство по Lua 5.3
Справочник по Lua
Обзор Lua
Статья на википедии

Полезные ресурсы на английском:
Официальное руководство Lua 5.1 | На русском
Сайт LuaJIT
Большая вики по Lua

MoonLoader:
Вики MoonLoader
История изменений
Гайды на нашем форуме


Программирование
После установки среды разработки, изучения основ Lua и ознакомления с документацией MoonLoader, можно приступать от теории к практике. Давайте сначала рассмотрим самое основное, на всякий случай.


Lua скрипты и логи
Для начала нужно создать сам скрипт - он из себя представляет обычный текстовый файл с расширением .lua. Поместить его необходимо в папку moonloader, находящуюся в папке игры. Из корня этой папки MoonLoader загружает все скрипты с расширениями .lua и .luac (скомпилированные).
В этой же папке создаётся файл moonloader.log, в который ведётся журналирование всех важных событий, происходящих со скриптами: загрузка, завершение, сообщения скриптов и самое главное - ошибки. Да, ошибки, происходящие в скриптах, будут выводиться в этот файл, и в первую очередь нужно обращаться в него, если ваш скрипт не работает и вы не знаете почему. Для удобства вы можете установить скрипт SF Integration, тогда логи будут выводиться ещё и в консоль SAMPFUNCS.
Итак, откройте созданный вами скрипт и продолжайте читать.


Глобальная область
Глобальная область - это основное тело скрипта, т.е. всё, что находится вне функций. В основном глобальная область используется для указания директив, подключения модулей, объявления каких-либо глобальных переменных и функций. Она выступает первым этапом загрузки скрипта, код из неё выполняется один раз после загрузки скрипта (а скрипты загружаются почти сразу же после запуска игры) и не может быть приостановлен. Основная работа со скриптом производится в потоке main.
Пример: Загружаем библиотеку VKEYS, записываем моё имя в переменную myName, объявляем функцию main.
Lua:
local vkeys = require 'vkeys'
local myName = "Alexander"

function main()
    -- В данном примере никаких действий с main нет, поэтому тут его можно не объявлять. Скрипт выполнит глобальную область и завершит работу.
end

main - основной поток любого скрипта для MoonLoader. В отличие от глобальной области, вызывается уже после загрузки игры, поэтому все манипуляции с игрой производятся в теле потока. main вызывается один раз после чего скрипт завершает свою работу и выгружается, если в нём нет активных потоков и зарегистрированных событий. Для того, чтобы скрипт продолжал работать, необходимо удерживать поток активным. Для решения этой задачи есть 2 способа - бесконечная задержка и бесконечный цикл. Оба способа несовместимы друг с другом и применяются при разных условиях работы скрипта.
Пример #1: main выводит в moonloader.log текст и завершает работу скрипта.
Lua:
function main()
    print("Hello World!") -- Выводим текст в лог
    print("main dead :(")
end
Пример #2: main выводит в moonloader.log текст, но не завершает работу скрипта
Lua:
function main()
    print("Hello World!")
    wait(-1) -- wait позволяет установить потоку задержку в миллисекундах (ms), -1 в данном случае означает бесконечное ожидание.
    -- В данном примере поток main не будет завершен и скрипт продолжит работу
end
Пример #3: main раз в секунду выводит в лог текст "I'm alive".
Lua:
function main()
    print("Hello World!") -- Выполняется один раз при запуске
    while true do -- Объявляем бесконечный цикл
        wait(1000) -- Ждём секунду, 1000 ms = 1 s
        print("I'm alive!") -- Выводим текст в лог
    end -- Конец тела цикла
    -- В этом примере поток main не будет завершен из-за бесконечного цикла
end


Задержки и потоки
Задержка в скрипте позволяет приостанавливать исполнение кода на указанное количество миллисекунд или на один кадр. В ML для осуществления задержек используется функция wait(int time), её аргумент time принимает следующие значения:
-1 - устанавливает непрерывное ожидание, использование допустимо только в main
0 - задержка на один кадр
остальные значения указывают задержку в миллисекундах
Пример: Запущенный скрипт будет в вечном ожидании
Lua:
function main()
    wait(-1)
end
Использование задержек недопустимо в callback-функциях. Для создания задержки в такой функции используйте создание потока:
Lua:
function callback()
    lua_thread.create(function ()
        doFade(1000, true)
        wait(3000) -- на 3 секунды гасим экран
        doFade(1000, false)
    end
end

Потоки - это сопрограммы, которые могут быть созданы в процессе работы скрипта для параллельного выполнения задач. Потоки в MoonLoader служат для выноса определенных действий за рамки основного потока, а так же для создания задержек выполняемого кода там, где это невозможно реализовать стандартными методами.
Для запуска потока используются функции lua_thread.create и lua_thread.create_suspended.
Пример: создаётся два потока для параллельного вывода текста в лог
Lua:
 -- Создадим функцию, которая с задержкой будет выводить текст
function printText()
    print("printText: One")
    wait(1000)
    print("printText: Two")
    wait(1000)
    print("printText: Bye!")
end

function secondPrintText()
    print("secondPrintText: One")
    wait(500)
    print("secondPrintText: Two")
    wait(1500)
    print("secondPrintText: Bye!")
end

-- Создадим оба потока одновременно
lua_thread.create(printText)
lua_thread.create(secondPrintText)
После запуска этого кода будет видно, что сообщения вывелись не в последовательности вызова функций, а в порядке завершения задержек.
Как и с main, поток будет завершен и уничтожен, если его не удерживать активным.
Практическое применение потоков довольно широко, но в небольших скриптах они чаще всего не нужны, о потоках нужно знать, но не применяйте их без необходимости. Реальными случаями использования потоков может быть разделение логики скрипта и отображения графики или использование задержек в консольных и чат командах.
Подробнее о потоках можно почитать на вики: lua - luathread | BlastHack — DEV_WIKI(https://blast.hk/wiki/lua:luathread)


Директивы
Скрипты для MoonLoader могут содержать о себе некоторую информацию и иметь определённые свойства исполнения - и то, и другое задаётся с помощью директив. Директивы - это обычные функции, предназначенные для указания информации о скрипте и изменения его поведения. Их принято обозначать в самом начале скрипта.
Все параметры, задающиеся директивами, можно получить из любого скрипта, обратившись к соответствующим полям класса LuaScript.
Пример:
Lua:
script_name("A complete guide to MoonLoader") -- устанавливает имя скрипта, отображаемое, например, в логе
script_authors("DonHomka", "FYP") -- задает несколько авторов скрипта. для указания одного автора
используйте script_author
script_version("0.1.0-beta") -- указывает версию скрипта
script_description[[
- Пример использования директив
- Вывод информации в лог
- Использование многострочного описания
]]

-- теперь выведем эту информацию. script.this - это объект текущего скрипта
print(script.this.name .. ' v' .. script.this.version) -- название и версия скрипта
print('Авторы: ' .. table.concat(script.this.authors, ', ')) -- список авторов содержится в таблице, даже если указан один автор
print(script.this.description) -- описание
В этом примере показаны не все директивы, за полным списком обращайтесь к соответствующей странице на вики.


События и колбэки
Событиями, а точнее их обработчиками, в MoonLoader называются функции, вызывающиеся в скриптах автоматически при каком-либо действии в игре, требующим обработки. Обработчики событий могут иметь входящие и возвращаемые параметры: входящие передают какую-то информацию скрипту, а возвращаемые позволяют повлиять на дальнейшую обработку после совершения события.
Зарегистрировать обработчик события можно двумя способами: просто добавить в скрипт функцию с соответствующим именем, либо воспользоваться функцией addEventHandler. Учтите, что первым способом обработчик может быть зарегистрирован только один раз.
Скрипты с зарегистрированными событиями не завершаются самостоятельно.
Пример: onScriptTerminate вызывается при завершении какого-либо скрипта
Lua:
function main()
    print("hello world")
end

function onScriptTerminate(script, quitGame)
    -- script - указатель класса LuaScipts. Имеет все выше описанные свойства скрипта, т.е. имя, авторов и тп.
    -- quitGame - логическое значение возвращает true если скрипт был завершен в результате завершения игры.
    if script == thisScript() then -- зададим условие что именно текущий скрипт завершает работу
        print("bye world")
    end
end
Внутри обработчиков событий нельзя использовать задержки, поскольку вызов события требует немедленного возврата из функции-обработчика. Для обхода этого ограничения можно использовать потоки.
Со списком всех событий и их назначениями можно ознакомиться на вики: moonloader - events | BlastHack — DEV_WIKI(https://blast.hk/wiki/moonloader:events)

Колбэки (функции обратного вызова) выступают реакцией на действие и по концепции очень похожи на события, но у них есть два отличия: первое - колбэк всегда регистрируется явно, чаще путём вызова функции с передачей функции-колбэка в качестве аргумента, второе - он всегда связан с какой-либо сущностью (командой, идентификатором и т.п.), т.е. будет вызван только если возникшее событие касается связанной сущности (например, колбэк команды будет вызван при вводе только одной команды, а не каждой, как это было бы с событием). Примером функции с колбэком является downloadUrlToFile, принимающая последним аргументом callback-функцию.
В колбэках, как и в событиях, нельзя использовать задержки.
Пример:
Lua:
local fpath = os.getenv('TEMP') .. '\\moonloader-version.json'
downloadUrlToFile('https://blast.hk/moonloader/data/version-info.json', fpath, downloadCallback)

function downloadCallback(id, status, p1, p2) -- будет вызываться при каждом обновлении статуса загрузки файла
    print(id, status, p1, p2)
end


Использование библиотек
Библиотеки, либо модули всячески дополняют стандартный набор возможностей новыми и позволяют использовать в скриптах готовые инструменты для разработчиков. Модули делятся на стандартные и сторонние. Стандартные включены в дистрибутив MoonLoader и не требуют отдельной установки.
Все модули располагаются в директории moonloader/lib/ и устанавливаются туда же. Подключение библиотек осуществляется при помощи функции require, которая в качестве аргумента принимает название файла. Чаще всего подключение модулей производится в начале скрипта, но может быть выполнено в любом месте. Каждый модуль загружается отдельно для каждого скрипта.
Пример: Подключим модуль "vkeys", позволяющий работать с виртуальными клавишами.
Lua:
local vkeys = require 'vkeys'
-- теперь переменная vkeys имеет все свойства, полученные из модуля. Таким образом можно получить иды и названия клавиш
print(vkeys.VK_RSHIFT, vkeys.id_to_name(vkeys.VK_RSHIFT)) -- выведем в лог ид и название правой клавиши Shift.
Работа с модулями не ограничивается стандартным набором, часто приходится иметь дело со сторонними модулями - такие модули не поставляются вместе с MoonLoader и требуют ручную установку. Примерами таких модулей являются Dear ImGui и SAMP.Lua.
Вы можете создать собственный модуль и использовать его в своих скриптах. Благодаря этому вам будет легче оказывать им поддержку, скрипты станут чище и компактнее, а повторяющегося кода будет намного меньше.
Помимо этой возможности в MoonLoader присутствует система импорта, позволяющая использовать работающий скрипт как модуль с общим доступом - об этом и о создании модулей будет сказано позже.
Настоятельная рекомендация: никогда не публикуйте свои работы вместе со стандартными библиотеками или с изменениями в сторонних библиотеках - это может привести к проблемам у пользователей.


Рекомендации
Несмотря на обилие информации в теме, знать всё невозможно, поэтому чаще заглядывайте на Wiki, а также не забывайте о теме Вопросы на Lua скриптингу, где вам смогут помочь при возникновении сложностей.
Больше информации именно по Lua лучше искать в поисковиках, язык довольно простой и если эта тема вам никак не помогла - стоит поискать более углубленные уроки.


Базовые указания по повышению качества кода
Чтобы ваш код был чист и понятен, необходимо придерживаться некоторых правил, вот основные из них:
  • Добавляйте информацию о скрипте при помощи директив
  • Соблюдайте единый стиль кода
  • Соблюдайте табуляцию (отступы)
  • Отделяйте блоки кода логически: пустые строки между функциями и блоками переменных, пробелы между блоками кода, осуществляющими логически завершённое действие и т.д.
  • Называйте переменные и функции внятными именами
  • Комментируйте неочевидные участки кода
Следование этим простым правилам уже значительно повлияет на качество вашего кода, но если вам этого не хватает, вот отличный гайд по стилю кода (на английском): luarocks/lua-style-guide(https://github.com/luarocks/lua-style-guide)


Современные решения
С момента релиза ML прошло уже довольно много времени и, конечно, многое поменялось, так, например, вместо предопределенных переменных playerPed и playerHandle стоит использовать PLAYER_PED и PLAYER_HANDLE соответственно.
Помимо стандартного рендеринга, для создания сложных меню можно использовать фреймворк Dear ImGui. Для удобной обработки сетевого трафика SA:MP есть библиотека SAMP.Lua. Библиотека SA Memory для прямого взаимодействия со структурами игры. И MoonAdditions, добавляющая множество интересных функций.
vkeys - стандартный модуль, хранящий все ID и имена виртуальных клавиш. Так уж вышло, что этот модуль изначально не входил в состав MoonLoader и вместо него все константы загружались из модуля moonloader глобально, но со временем выяснилось, что это было плохим решением и поэтому коды клавиш были перенесены в отдельный модуль с немного другой реализацией. Но константы в старом модуле пришлось оставить для совместимости со старыми скриптами и теперь их использование оттуда считается устаревшим. Библиотека vkeys тут приведена в качестве примера, кроме неё были и другие нововведения, приведшие к устареванию старых решений.
Поэтому, если вы занимаетесь активной разработкой, всегда обращайте внимание на список изменений в обновлениях и пользуйтесь новейшими инструментами.


Упрощение процесса установки скриптов
"Да закинь вот эти файлы в папку CORE в папке SOURCE та что в папке с либами где под папкой IT хранится SCORE" - Бррр, чтобы подобное не случалось и ваш собеседник не впадал в ступор, старайтесь упростить установку до максимума - чтобы можно было просто скопировать все составляющие мода в одну папку. То есть соберите один архив так, чтобы неопытный пользователь мог свободно его установить или приложите инструкцию, если процесс установки сложнее стандартного. Чем установка проще, тем лучше и для вас, и для пользователя.


Дополнительно

Компиляция скриптов
Во многих других языках программирования выполнение компиляции необходимо для запуска приложения на целевой машине, но в Lua компиляция не требуется - скрипты загружаются из исходного кода без дополнительных манипуляций. Однако, компиляция Lua скриптов возможна и чаще всего применяется для сокрытия исходного кода от любопытных глаз. Чаще всего это применяют для продаваемых скриптов, где защита этого самого скрипта - дело первостепенной важности. Не стоит злоупотреблять этой возможностью и прятать каждый свой скрипт под замок.
Для компиляции Lua скриптов под MoonLoader v.026 и выше скачайте интерпретатор LuaJIT v2.1.0-beta3, распакуйте архив в любое место и перетаскивайте lua-файл на compile.bat, рядом создастся luac-файл - это и есть скомпилированный скрипт. Для компиляции скриптов под более старые версии MoonLoader, вам понадобится LuaJIT 2.0.4.


Продвинутое программирование
С основными принципами разработки вы теперь знакомы и при этих знаниях сможете выполнить большинство задач, однако некоторые задачи требуют применения специальных техник. Давайте рассмотрим некоторые из них.


Создание модулей
Модули делятся на два типа: Lua и DLL. Lua-модули пишутся, как вы уже могли догадаться, на языке Lua и в результате представляют из себя привычные Lua-скрипты, только с некоторыми особенностями.
Давайте рассмотрим пример простого модуля, назовём его example:
Lua:
-- в модуле все переменные должны быть локальными, в том числе и функции
-- глобальные переменные назначаются только в особых случаях
local mod = {} -- создаём пока что пустую таблицу
mod._VERSION = '1.0.0' --  добавляем в таблицу версию модуля (это не обязательно)

local function typeAssert(value, t) -- локальная функция, не будет доступна извне
    assert(type(value) == t)
end

function mod.showTextFormatted(msg, duration, ...) -- а эта функция добавляется в таблицу модуля
    typeAssert(msg, 'string'); typeAssert(duration, 'number')
    printStringNow(string.format(msg, ...), duration)
end

return mod -- возвращаем таблицу
Теперь используем модуль из скрипта:
Lua:
local example = require 'example' -- загружаем его

function main()
    example.showTextFormatted('My module\'s version is %.', example._VERSION)
end
Что же происходит при вызове require? Сначала выполяется весь код модуля до строки return mod, после чего возвращённое передаётся в скрипт в результате вызова, теперь в переменной example у нас таблица с функцией и версией модуля. Последующие вызовы require для загрузки этого же модуля больше не будут приводить к выполнению кода, вместо этого будет использовано значение, полученное при первом вызове.
Как вы уже заметили, строение модуля несколько отличается от обычного скрипта, у модулей есть ещё две отличительных особенности. Первая: событие нельзя регистрировать обычным способом - оно не будет работать, для регистрации события в модуле необходимо использовать функцию addEventHandler. Вторая: поток main тоже не будет работать, в модуле поток нужно создавать самостоятельно (lua_thread.create).
В этом примере было продемонстрировано создание привычного модуля, и в большинстве случаев большего и не нужно, но имейте в виду, что этим возможности не ограничиваются и внутри модуля можно выполнять любые операции.
Вот другой пример, показывающий как можно реализовать события с помощью внутреннего потока:
Эта простая библиотека реализует функции для создания и удаления чекпоинтов, а также события входа в область чекпоинта и выхода из неё.
Lua:
local checkpoints = {}
local mod = {}
local task = nil

-- в игре есть баг: функция locateCharAnyMeans3d и подобные отображают чекпоинт в два раза меньше его зоны
-- поэтому используем свою функцию с обходом бага
local function fixedLocateCharAnyMeans3d(ped, x, y, z, radiusX, radiusY, radiusZ, sphere)
    if sphere then
        locateCharAnyMeans3d(ped, x, y, z, radiusX * 2, radiusY * 2, radiusZ, true)
    end
    return locateCharAnyMeans3d(ped, x, y, z, radiusX, radiusY, radiusZ, false)
end

-- поток с бесконечным циклом
-- в нём проверяется нахождение игрока в зоне чекпоинта и производится вызов событий
local function checkCheckpointsTask()
    while true do
        wait(0)
        if doesCharExist(PLAYER_PED) then
            for i, cp in ipairs(checkpoints) do
                local result = fixedLocateCharAnyMeans3d(PLAYER_PED, cp.x, cp.y, cp.z, cp.size / 2, cp.size / 2, cp.size, cp.sphere)
                if result and not cp.triggered then
                    cp.triggered = true
                    if mod.onPlayerEnterCheckpoint then
                        mod.onPlayerEnterCheckpoint(cp)
                    end
                elseif not result and cp.triggered then
                    cp.triggered = false
                    if mod.onPlayerExitCheckpoint then
                        mod.onPlayerExitCheckpoint(cp)
                    end
                end
            end
        end
    end
end

-- функция для создания чекпоинта
-- создаёт новый чекпоинт, добавляя его в таблицу и возвращая его идентификатор. запускает поток при первом вызове
function mod.createCheckpoint(x, y, z, diameter, sphere)
    sphere = sphere or true
    local cp = {x = x, y = y, z = z, size = diameter, sphere = sphere, triggered = false}
    table.insert(checkpoints, cp)
    if not task then
        task = lua_thread.create(checkCheckpointsTask)
    end
    return cp
end

-- функция для удаления чекпоинта
function mod.removeCheckpoint(cp)
    for idx, it in ipairs(checkpoints) do
        if it == cp then
            table.remove(checkpoints, idx)
            return
        end
    end
end

return mod
Использование:
Lua:
local checkpoints = require 'checkpoints'

local cpHealth = checkpoints.createCheckpoint(1234, 567, 8, 3, true)
local cpKill = checkpoints.createCheckpoint(1234 + 5, 567, 8, 3, true)

-- событие будет вызвано библиотекой при входе игрока в зону чекпоинта
function checkpoints.onPlayerEnterCheckpoint(cp)
    print(('Игрок вошёл в зону чекпоина по координатам %0.1f, %0.1f, %0.1f'):format(cp.x, cp.y, cp.z))
    if cp == cpHealth then
        setCharHealth(PLAYER_PED, 100)
        printStringNow('Health restored!', 1000)
    elseif cp == cpKill then
        printStringNow('Don\'t leave the red circle!', 1000)
    end
end

-- а это - при выходе
function checkpoints.onPlayerExitCheckpoint(cp)
    print(('Игрок вышел из зоны чекпоина по координатам %0.1f, %0.1f, %0.1f'):format(cp.x, cp.y, cp.z))
    if cp == cpKill then
        printStringNow('~r~YOU LOSE!', 1000)
        local x, y, z = getCharCoordinates(PLAYER_PED)
        addExplosionNoSound(x, y, z, 7)
        setCharHealth(PLAYER_PED, 0)
    end
end
Принцип работы событий в данном примере прост: при первом создании чекпоинта стартует поток, постоянно проверяющий нахождение игрока в заданной зоне. В этом потоке выполняется вызов функций, выступающих событиями, из локальной таблицы mod, но поскольку это таблица модуля, возвращаемая при require, и благодаря тому, что в Lua таблицы передаются по ссылке, мы можем перезаписывать в ней данные через скрипт и эти изменения распространятся и на внутренню работу библиотеки. В итоге мы имеем простой способ создания событий внутри библиотек.
DLL
Процесс разработки DLL-модуля в корне отличается от Lua. Такие модули пишутся на других языках программирования, в частности на C и C++, и их разработка требует дополнительных знаний. Здесь этому внимание уделено не будет, поскольку это очень обширная тема, достойная отдельной статьи. Вы можете почитать этот гайд, если уже программируете на C++.


Импорт и экспорт
Lua модули загружаются для каждого скрипта отдельно и поэтому не позволяют разделить общие данные между ними, но иногда без такой возможности просто не обойтись - в этом случае нас выручит система экспорта. Принцип её работы таков: скрипт, выступающий в качестве модуля, открывает другим скриптам доступ к определённым данным посредством назначения глобальной переменной EXPORTS (по умолчанию эта переменная является пустой таблицей, но может быть значением любого типа), после чего другие скрипты могут получить доступ к этим данным, используя функцию import.
Для примера применения этой системы сделаем скрипт, показывающий текстовое уведомление внизу экрана. Назовём его "status_notification.lua":
Lua:
script_name('status notification')

local fflags = require('moonloader').font_flag
local font = renderCreateFont('Arial', 13, fflags.SHADOW + fflags.BOLD)
local notification = nil

function split(str, delim, plain)
    local lines, pos, plain = {}, 1, not (plain == false) --[[ delimiter is plain text by default ]]
    repeat
        local npos, epos = string.find(str, delim, pos, plain)
        table.insert(lines, string.sub(str, pos, npos and npos - 1))
        pos = epos and epos + 1
    until not pos
    return lines
end

function EXPORTS.notify(msg, color) -- функция с общим доступом
    if not msg then return end
    local displayDuration = math.max(#msg * 0.065, 1.5) -- продолжительность в секундах
    notification = {
        color = bit.band(color or 0xEEEEEE, 0xFFFFFF),
        lines = split(msg, '\n'),
        duration = displayDuration,
        tick = localClock()
    }
end

function main()
    while true do
        wait(0)
        if isPlayerPlaying(PLAYER_HANDLE) and not isGamePaused() then
            if notification then
                if localClock() - notification.tick <= notification.duration then
                    local sw, sh = getScreenResolution()
                    local fontH, y = renderGetFontDrawHeight(font), sh - 50
                    local alpha = 255 * math.min(1, notification.duration - (localClock() - notification.tick)) -- затухание на последней секунде
                    local color = bit.bor(notification.color, bit.lshift(alpha, 24))
                    for k = #notification.lines, 1, -1 do
                        local text = notification.lines[k]
                        if #text > 0 then
                            local dlen = renderGetFontDrawTextLength(font, text)
                            renderFontDrawText(font, text, sw / 2 - dlen / 2, y, color)
                        end
                        y = y - fontH
                    end
                else
                    notification = nil
                end
            end
        end
    end
end
Использование в скрипте:
Lua:
local notification = import 'status_notification'

function main()
    while true do
        wait(0)
        if wasKeyPressed(0x31) then
            notification.notify(script.this.name .. ' says hello!')
        end
    end
end
Хотите спросить зачем всё это для столь простой задачи? А дело в том, что если вывод оповещения будет реализован в библиотеке или просто в виде функции, и если несколько скриптов будут одновременно отображать оповещения, текст оповещений наложится друг на друга и окажется неразборчив. Поэтому и нужно использовать общую систему оповещений. Давайте теперь рассмотрим как это работает.
Как видно, экспортирующий скрипт имеет полное сходство с видом обычного скрипта - так потому, что это и есть обыкновенный скрипт. Он может быть помещён в папку moonloader, тогда будет загружен автоматически, либо можно расположить его в другой директории - в этом случае загрузка будет произведена при вызове import. Процесс импортирования скрипта состоит в полном копировании содержимого переменной EXPORTS в импортирующий скрипт, но копирование производится только при первом вызове, последующие вызовы вернут сохранённое значение, поэтому обновление данных таким способом невозможно. Для обновления данных и в большинстве других случаев нужно использовать функции. У импортированных функций есть отличительная особенность: вместо копирования для них создаётся интерфейс, позволяющий вызывать функцию так, как будто она была вызвана в экспортирующем скрипте и при этом возвращать результаты в вызывающий скрипт. То есть вызов notification.notify в данном примере приведёт к вызову EXPORTS.notify в скрипте status_notification.lua и таким образом все изменения, производимые в этой функции, будут исполнены от имени экспортирующего скрипта. В результате статус оповещения обновится, текст перезапишется и оповещение будет выведено на экран корректно.
Система экспорта/импорта позволяет не только вызывать функции из другого скрипта, но и передавать функции в качестве аргумента, таким образом можно реализовать колбэки и события.


Заключение
Полученных знаний должно быть достаточно, чтобы самостоятельно начать разрабатывать Lua скрипты. Здесь были рассмотрены все аспекты разработки под MoonLoader, за исключением принципов моддинга самой игры и применения FFI. Дело в том, что это слишком объёмные темы, которые должны быть рассмотрены по отдельности и более раскрыто. В интернете достаточно много информации о моддинге GTA San Andreas, но она не касается непосредственно MoonLoader, сильно разрозненна и в основном на английском, по этой причине ссылки на неё не приводятся. Про LuaJIT FFI информации на русском вообще нет, но есть официальная руководство на английском, освещающее FFI практически целиком, однако оно предполагает понимание устройства памяти процесса и ознакомленности с языком программирования C. Эти темы остаются не раскрытыми, но когда появятся статьи об этом для MoonLoader, ссылки на них обязательно будут добавлены в этот гайд.
Периодически и с выходом новых версий MoonLoader в гайд будут вводиться изменения и дополнения, только своевременные обновления не всегда будут возможны, так что помните про историю изменений, чтобы оставаться в курсе всех изменений в MoonLoader.
Не забывайте возвращаться к этому гайду и использовать вики при затруднениях, а также о том, что всегда можно обратиться на форум.

Актуальная версия MoonLoader на момент последнего обновления статьи: .026
 
Последнее редактирование:

AnWu

https://t.me/anwublog
Автор темы
Всефорумный модератор
4,771
5,376

darkjer

Активный
134
68
Sublime Text? Скиньте мне разширение на него
 

{$FANTOMCODER .cs}

Известный
212
53
о ней было сказано в статье, но не было ссылки. добавил.

Lua 5.1 (в LuaJIT, используемом в мунлоадере) был выпущен в 2006 году, Lua 5.2 (часть фич которого есть в LuaJIT) - в 2011. о какой устаревшей дезе может быть речь, раз книга 2015 года?
а мунлоадер досих пор функционирует на 5.1 и если это так то не собирается ли он переходить на более новые версии языка
 

FYP

Известный
Администратор
1,763
5,911
а мунлоадер досих пор функционирует на 5.1 и если это так то не собирается ли он переходить на более новые версии языка
мунлоадер использует luajit, который полностью реализует lua 5.1 и включает некоторые фичи lua 5.2. заменять luajit на что-то другое не планируется.
 
  • Клоун
Реакции: Fott

kizn

\ 0 _ 0 /
Всефорумный модератор
2,408
2,090
мунлоадер использует luajit, который полностью реализует lua 5.1 и включает некоторые фичи lua 5.2. заменять luajit на что-то другое не планируется.
надеюсь заменять актуальную версию мунлоадер с 026 на 027 планируется
 
  • Ха-ха
Реакции: kin4stat

Don_Yagon

Участник
43
2
Шел 2022 год а я сталкиваюсь с багом в коде
Lua:
while true do
    wait(0)
 if main_window_state.v == false then
    imgui.Process = false
 end
при запуске скрипта получаю ошибку

'end' expected (to close 'while' at line 32) near '<eof>

мб кто шарит за эту дичь?
 

kizn

\ 0 _ 0 /
Всефорумный модератор
2,408
2,090
Шел 2022 год а я сталкиваюсь с багом в коде
Lua:
while true do
    wait(0)
 if main_window_state.v == false then
    imgui.Process = false
 end
при запуске скрипта получаю ошибку

'end' expected (to close 'while' at line 32) near '<eof>

мб кто шарит за эту дичь?
Шел 2022 год, а я сталкиваюсь с отсутствием чтения ошибки

'end' ожидался (чтобы закрыть 'while' на строчке 32) рядом с '<конец строки>'

то есть у тебя пропущен один end, чтобы закрыть цикл while. поставь его
 

Don_Yagon

Участник
43
2
Шел 2022 год, а я сталкиваюсь с отсутствием чтения ошибки

'end' ожидался (чтобы закрыть 'while' на строчке 32) рядом с '<конец строки>'

то есть у тебя пропущен один end, чтобы закрыть цикл while. поставь его
а бля точняк, я слепой прост я потерял ещё 1 'end' , нашел ошибку сяб
 

PPPaketik

Новичок
3
2
Такой вопросик, как в крипте прописать время в которое он будет включатся
т.е. Когда наступает например 25 минута 36 секунда любого часа, скрипт начинает выполнять прописанное ему действие
 
  • Нравится
Реакции: PGGEMES99

Похожие темы

  1. Ответы
    497
    Просмотры
    257K
  2. Ответы
    83K
    Просмотры
    7M
  3. Ответы
    22
    Просмотры
    2M
  4. У
    Ответы
    65
    Просмотры
    42K