Содержание
Введение
Установка MoonLoader
Среда разработки
Изучение Lua и документация по MoonLoader
Программирование
Продвинутое программирование
Заключение
Это руководство призвано исполнить две цели: обучить разработке скриптов с нуля и восполнить все пробелы в знаниях о Lua скриптинге под MoonLoader. В нём освещены все аспекты разработки - от самого простого до продвинутых возможностей и приемов, то есть оно подходит как и для начинающих скриптеров, так и для программистов с опытом. Предварительное прочтение старого руководства не требуется, здесь есть вся необходимая и более актуальная информация.
А если вы всё ещё не знакомы с MoonLoader, то сейчас самое время ознакомиться.
Ну что, приступим?
1. Скачайте установщик MoonLoader последней версии и запустите, следуйте шагам программы установки
2. На странице выбора компонентов отметьте нужные скрипты (рекомендуется выбрать все)
4. Это не обязательно, но не помешает установить и MoonAdditions - эту библиотеку используют некоторые скрипты и она неплохо расширяет стандартные возможности скриптинга
5. Выберите установку расширения для Notepad++, если вы будете использовать программу Notepad++ для редактирования Lua скриптов
Atom
Atom - полнофункциональный и свободно расширяемый бесплатный редактор кода, поддерживающий большое количество языков программирования, включая Lua. Для начала работы с Lua нужно установить сам Atom.
Скачать Atom
После установки Atom обязательно установить пакет для удобства редактирования скриптов.
Нажимаем Ctrl + , и переходим во вкладку Install, в поле поиска вводим "moonloader" и устанавливаем первый в списке пакет.
После установки этого пакета вам будет предложено установить дополнительные пакеты, соглашаемся и забываем про это.
Проект по-умолчанию.
В меню File выбираем пункт Open Folder... и указываем путь до папки moonloader, после этого она откроется как проект.
Кодировка по-умолчанию.
Для установки кодировки по-умолчанию при создании нового скрипта переходим в настройки всё тем же сочетанием Ctrl + , и выбираем пункт Core. В поле File Encoding выбираем Cyrillic (Windows-1251).
Notepad++
Notepad++ - еще один бесплатный и удобный редактор. Он является функциональной заменой стандартного блокнота и отлично подходит для написания Lua скриптов.
Скачать Notepad++
Проект по-умолчанию.
Как и в Atom, здесь есть возможность показа меню проекта, а точнее "Папка как Проект". В меню "Файл" выбираем пункт "Открыть Папку как Проект" и указываем путь к папке "moonloader".
Кодировка по-умолчанию.
Над лентой выбираем пункт Опции и переходим в Настройки. В меню слева выбираем пункт Новый документ и в разделе кодировки ставим флажок на список, в котором выбираем кодировку Windows-1251
Материалы по Lua на русском:
Для начинающих в программировании есть книга "Программирование на языке Lua (третье издание)", её можно купить или скачать в интернете
Неплохой вводный курс в Lua
Lua за 60 минут для знакомых с программированием
Полностью переведённое официальное руководство по Lua 5.3
Справочник по Lua
Обзор Lua
Статья на википедии
Полезные ресурсы на английском:
Официальное руководство Lua 5.1 | На русском
Сайт LuaJIT
Большая вики по Lua
MoonLoader:
Вики MoonLoader
История изменений
Гайды на нашем форуме
Lua скрипты и логи
Для начала нужно создать сам скрипт - он из себя представляет обычный текстовый файл с расширением .lua. Поместить его необходимо в папку moonloader, находящуюся в папке игры. Из корня этой папки MoonLoader загружает все скрипты с расширениями .lua и .luac (скомпилированные).
В этой же папке создаётся файл moonloader.log, в который ведётся журналирование всех важных событий, происходящих со скриптами: загрузка, завершение, сообщения скриптов и самое главное - ошибки. Да, ошибки, происходящие в скриптах, будут выводиться в этот файл, и в первую очередь нужно обращаться в него, если ваш скрипт не работает и вы не знаете почему. Для удобства вы можете установить скрипт SF Integration, тогда логи будут выводиться ещё и в консоль SAMPFUNCS.
Итак, откройте созданный вами скрипт и продолжайте читать.
Глобальная область
Глобальная область - это основное тело скрипта, т.е. всё, что находится вне функций. В основном глобальная область используется для указания директив, подключения модулей, объявления каких-либо глобальных переменных и функций. Она выступает первым этапом загрузки скрипта, код из неё выполняется один раз после загрузки скрипта (а скрипты загружаются почти сразу же после запуска игры) и не может быть приостановлен. Основная работа со скриптом производится в потоке main.
Пример: Загружаем библиотеку VKEYS, записываем моё имя в переменную myName, объявляем функцию main.
main - основной поток любого скрипта для MoonLoader. В отличие от глобальной области, вызывается уже после загрузки игры, поэтому все манипуляции с игрой производятся в теле потока. main вызывается один раз после чего скрипт завершает свою работу и выгружается, если в нём нет активных потоков и зарегистрированных событий. Для того, чтобы скрипт продолжал работать, необходимо удерживать поток активным. Для решения этой задачи есть 2 способа - бесконечная задержка и бесконечный цикл. Оба способа несовместимы друг с другом и применяются при разных условиях работы скрипта.
Пример #1: main выводит в moonloader.log текст и завершает работу скрипта.
Пример #2: main выводит в moonloader.log текст, но не завершает работу скрипта
Пример #3: main раз в секунду выводит в лог текст "I'm alive".
Задержки и потоки
Задержка в скрипте позволяет приостанавливать исполнение кода на указанное количество миллисекунд или на один кадр. В ML для осуществления задержек используется функция wait(int time), её аргумент time принимает следующие значения:
-1 - устанавливает непрерывное ожидание, использование допустимо только в main
0 - задержка на один кадр
остальные значения указывают задержку в миллисекундах
Пример: Запущенный скрипт будет в вечном ожидании
Использование задержек недопустимо в callback-функциях. Для создания задержки в такой функции используйте создание потока:
Потоки - это сопрограммы, которые могут быть созданы в процессе работы скрипта для параллельного выполнения задач. Потоки в MoonLoader служат для выноса определенных действий за рамки основного потока, а так же для создания задержек выполняемого кода там, где это невозможно реализовать стандартными методами.
Для запуска потока используются функции lua_thread.create и lua_thread.create_suspended.
Пример: создаётся два потока для параллельного вывода текста в лог
После запуска этого кода будет видно, что сообщения вывелись не в последовательности вызова функций, а в порядке завершения задержек.
Как и с main, поток будет завершен и уничтожен, если его не удерживать активным.
Практическое применение потоков довольно широко, но в небольших скриптах они чаще всего не нужны, о потоках нужно знать, но не применяйте их без необходимости. Реальными случаями использования потоков может быть разделение логики скрипта и отображения графики или использование задержек в консольных и чат командах.
Подробнее о потоках можно почитать на вики: lua - luathread | BlastHack — DEV_WIKI(https://blast.hk/wiki/lua:luathread)
Директивы
Скрипты для MoonLoader могут содержать о себе некоторую информацию и иметь определённые свойства исполнения - и то, и другое задаётся с помощью директив. Директивы - это обычные функции, предназначенные для указания информации о скрипте и изменения его поведения. Их принято обозначать в самом начале скрипта.
Все параметры, задающиеся директивами, можно получить из любого скрипта, обратившись к соответствующим полям класса LuaScript.
Пример:
В этом примере показаны не все директивы, за полным списком обращайтесь к соответствующей странице на вики.
События и колбэки
Событиями, а точнее их обработчиками, в MoonLoader называются функции, вызывающиеся в скриптах автоматически при каком-либо действии в игре, требующим обработки. Обработчики событий могут иметь входящие и возвращаемые параметры: входящие передают какую-то информацию скрипту, а возвращаемые позволяют повлиять на дальнейшую обработку после совершения события.
Зарегистрировать обработчик события можно двумя способами: просто добавить в скрипт функцию с соответствующим именем, либо воспользоваться функцией addEventHandler. Учтите, что первым способом обработчик может быть зарегистрирован только один раз.
Скрипты с зарегистрированными событиями не завершаются самостоятельно.
Пример: onScriptTerminate вызывается при завершении какого-либо скрипта
Внутри обработчиков событий нельзя использовать задержки, поскольку вызов события требует немедленного возврата из функции-обработчика. Для обхода этого ограничения можно использовать потоки.
Со списком всех событий и их назначениями можно ознакомиться на вики: moonloader - events | BlastHack — DEV_WIKI(https://blast.hk/wiki/moonloader:events)
Колбэки (функции обратного вызова) выступают реакцией на действие и по концепции очень похожи на события, но у них есть два отличия: первое - колбэк всегда регистрируется явно, чаще путём вызова функции с передачей функции-колбэка в качестве аргумента, второе - он всегда связан с какой-либо сущностью (командой, идентификатором и т.п.), т.е. будет вызван только если возникшее событие касается связанной сущности (например, колбэк команды будет вызван при вводе только одной команды, а не каждой, как это было бы с событием). Примером функции с колбэком является downloadUrlToFile, принимающая последним аргументом callback-функцию.
В колбэках, как и в событиях, нельзя использовать задержки.
Пример:
Использование библиотек
Библиотеки, либо модули всячески дополняют стандартный набор возможностей новыми и позволяют использовать в скриптах готовые инструменты для разработчиков. Модули делятся на стандартные и сторонние. Стандартные включены в дистрибутив MoonLoader и не требуют отдельной установки.
Все модули располагаются в директории moonloader/lib/ и устанавливаются туда же. Подключение библиотек осуществляется при помощи функции require, которая в качестве аргумента принимает название файла. Чаще всего подключение модулей производится в начале скрипта, но может быть выполнено в любом месте. Каждый модуль загружается отдельно для каждого скрипта.
Пример: Подключим модуль "vkeys", позволяющий работать с виртуальными клавишами.
Работа с модулями не ограничивается стандартным набором, часто приходится иметь дело со сторонними модулями - такие модули не поставляются вместе с MoonLoader и требуют ручную установку. Примерами таких модулей являются Dear ImGui и SAMP.Lua.
Вы можете создать собственный модуль и использовать его в своих скриптах. Благодаря этому вам будет легче оказывать им поддержку, скрипты станут чище и компактнее, а повторяющегося кода будет намного меньше.
Помимо этой возможности в MoonLoader присутствует система импорта, позволяющая использовать работающий скрипт как модуль с общим доступом - об этом и о создании модулей будет сказано позже.
Настоятельная рекомендация: никогда не публикуйте свои работы вместе со стандартными библиотеками или с изменениями в сторонних библиотеках - это может привести к проблемам у пользователей.
Больше информации именно по Lua лучше искать в поисковиках, язык довольно простой и если эта тема вам никак не помогла - стоит поискать более углубленные уроки.
Базовые указания по повышению качества кода
Чтобы ваш код был чист и понятен, необходимо придерживаться некоторых правил, вот основные из них:
Современные решения
С момента релиза 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:
Теперь используем модуль из скрипта:
Что же происходит при вызове require? Сначала выполяется весь код модуля до строки return mod, после чего возвращённое передаётся в скрипт в результате вызова, теперь в переменной example у нас таблица с функцией и версией модуля. Последующие вызовы require для загрузки этого же модуля больше не будут приводить к выполнению кода, вместо этого будет использовано значение, полученное при первом вызове.
Как вы уже заметили, строение модуля несколько отличается от обычного скрипта, у модулей есть ещё две отличительных особенности. Первая: событие нельзя регистрировать обычным способом - оно не будет работать, для регистрации события в модуле необходимо использовать функцию addEventHandler. Вторая: поток main тоже не будет работать, в модуле поток нужно создавать самостоятельно (lua_thread.create).
В этом примере было продемонстрировано создание привычного модуля, и в большинстве случаев большего и не нужно, но имейте в виду, что этим возможности не ограничиваются и внутри модуля можно выполнять любые операции.
Вот другой пример, показывающий как можно реализовать события с помощью внутреннего потока:
DLL
Процесс разработки DLL-модуля в корне отличается от Lua. Такие модули пишутся на других языках программирования, в частности на C и C++, и их разработка требует дополнительных знаний. Здесь этому внимание уделено не будет, поскольку это очень обширная тема, достойная отдельной статьи. Вы можете почитать этот гайд, если уже программируете на C++.
Импорт и экспорт
Lua модули загружаются для каждого скрипта отдельно и поэтому не позволяют разделить общие данные между ними, но иногда без такой возможности просто не обойтись - в этом случае нас выручит система экспорта. Принцип её работы таков: скрипт, выступающий в качестве модуля, открывает другим скриптам доступ к определённым данным посредством назначения глобальной переменной EXPORTS (по умолчанию эта переменная является пустой таблицей, но может быть значением любого типа), после чего другие скрипты могут получить доступ к этим данным, используя функцию import.
Для примера применения этой системы сделаем скрипт, показывающий текстовое уведомление внизу экрана. Назовём его "status_notification.lua":
Использование в скрипте:
Хотите спросить зачем всё это для столь простой задачи? А дело в том, что если вывод оповещения будет реализован в библиотеке или просто в виде функции, и если несколько скриптов будут одновременно отображать оповещения, текст оповещений наложится друг на друга и окажется неразборчив. Поэтому и нужно использовать общую систему оповещений. Давайте теперь рассмотрим как это работает.
Как видно, экспортирующий скрипт имеет полное сходство с видом обычного скрипта - так потому, что это и есть обыкновенный скрипт. Он может быть помещён в папку moonloader, тогда будет загружен автоматически, либо можно расположить его в другой директории - в этом случае загрузка будет произведена при вызове import. Процесс импортирования скрипта состоит в полном копировании содержимого переменной EXPORTS в импортирующий скрипт, но копирование производится только при первом вызове, последующие вызовы вернут сохранённое значение, поэтому обновление данных таким способом невозможно. Для обновления данных и в большинстве других случаев нужно использовать функции. У импортированных функций есть отличительная особенность: вместо копирования для них создаётся интерфейс, позволяющий вызывать функцию так, как будто она была вызвана в экспортирующем скрипте и при этом возвращать результаты в вызывающий скрипт. То есть вызов notification.notify в данном примере приведёт к вызову EXPORTS.notify в скрипте status_notification.lua и таким образом все изменения, производимые в этой функции, будут исполнены от имени экспортирующего скрипта. В результате статус оповещения обновится, текст перезапишется и оповещение будет выведено на экран корректно.
Система экспорта/импорта позволяет не только вызывать функции из другого скрипта, но и передавать функции в качестве аргумента, таким образом можно реализовать колбэки и события.
Периодически и с выходом новых версий MoonLoader в гайд будут вводиться изменения и дополнения, только своевременные обновления не всегда будут возможны, так что помните про историю изменений, чтобы оставаться в курсе всех изменений в MoonLoader.
Не забывайте возвращаться к этому гайду и использовать вики при затруднениях, а также о том, что всегда можно обратиться на форум.
Актуальная версия MoonLoader на момент последнего обновления статьи: .026
Введение
Установка MoonLoader
Среда разработки
Изучение Lua и документация по MoonLoader
Программирование
Lua скрипты и логи
Глобальная область
Задержки и потоки
Директивы
События и колбэки
Использование библиотек
РекомендацииГлобальная область
Задержки и потоки
Директивы
События и колбэки
Использование библиотек
Базовые указания по повышению качества кода
Современные решения
Упрощение процесса установки скриптов
ДополнительноСовременные решения
Упрощение процесса установки скриптов
Продвинутое программирование
Заключение
Это руководство призвано исполнить две цели: обучить разработке скриптов с нуля и восполнить все пробелы в знаниях о 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 и отдельно установите SAMPFUNCSReload All - позволяет перезагружать абсолютно все скрипты в папке moonloader, включая добавленные после запуска игры. Для перезагрузки нажмите сочетание клавиш Ctrl + R.
SF Integration - упрощает отслеживание состояния скриптов путём вывода сообщений отладки, ошибок и другой информации в консоль SAMPFUNCS. По сути дублирует сообщения из moonloader.log в консоль.
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" и устанавливаем первый в списке пакет.
Проект по-умолчанию.
В меню 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
Lua:
function main()
print("Hello World!")
wait(-1) -- wait позволяет установить потоку задержку в миллисекундах (ms), -1 в данном случае означает бесконечное ожидание.
-- В данном примере поток main не будет завершен и скрипт продолжит работу
end
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
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 присутствует система импорта, позволяющая использовать работающий скрипт как модуль с общим доступом - об этом и о создании модулей будет сказано позже.
Настоятельная рекомендация: никогда не публикуйте свои работы вместе со стандартными библиотеками или с изменениями в сторонних библиотеках - это может привести к проблемам у пользователей.
Рекомендации
Несмотря на обилие информации в теме, знать всё невозможно, поэтому чаще заглядывайте на Wiki, а также не забывайте о теме Вопросы на Lua скриптингу, где вам смогут помочь при возникновении сложностей.Больше информации именно по Lua лучше искать в поисковиках, язык довольно простой и если эта тема вам никак не помогла - стоит поискать более углубленные уроки.
Базовые указания по повышению качества кода
Чтобы ваш код был чист и понятен, необходимо придерживаться некоторых правил, вот основные из них:
- Добавляйте информацию о скрипте при помощи директив
- Соблюдайте единый стиль кода
- Соблюдайте табуляцию (отступы)
- Отделяйте блоки кода логически: пустые строки между функциями и блоками переменных, пробелы между блоками кода, осуществляющими логически завершённое действие и т.д.
- Называйте переменные и функции внятными именами
- Комментируйте неочевидные участки кода
Современные решения
С момента релиза 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
Как вы уже заметили, строение модуля несколько отличается от обычного скрипта, у модулей есть ещё две отличительных особенности. Первая: событие нельзя регистрировать обычным способом - оно не будет работать, для регистрации события в модуле необходимо использовать функцию addEventHandler. Вторая: поток main тоже не будет работать, в модуле поток нужно создавать самостоятельно (lua_thread.create).
В этом примере было продемонстрировано создание привычного модуля, и в большинстве случаев большего и не нужно, но имейте в виду, что этим возможности не ограничиваются и внутри модуля можно выполнять любые операции.
Вот другой пример, показывающий как можно реализовать события с помощью внутреннего потока:
Эта простая библиотека реализует функции для создания и удаления чекпоинтов, а также события входа в область чекпоинта и выхода из неё.
Использование:
Принцип работы событий в данном примере прост: при первом создании чекпоинта стартует поток, постоянно проверяющий нахождение игрока в заданной зоне. В этом потоке выполняется вызов функций, выступающих событиями, из локальной таблицы mod, но поскольку это таблица модуля, возвращаемая при require, и благодаря тому, что в Lua таблицы передаются по ссылке, мы можем перезаписывать в ней данные через скрипт и эти изменения распространятся и на внутренню работу библиотеки. В итоге мы имеем простой способ создания событий внутри библиотек.
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
Процесс разработки 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
Последнее редактирование: