Как написать свой lua скрипт для самп

moonloader.png

Данная тема посвящена нюансам и особенностям разработки Lua скриптов для MoonLoader.


Список вопросов:
Начало разработки.
Взаимодействие с игроками.
Работа с переменными.
Работа с командами.

c184o.png

Начало разработки.
Каждый Lua скрипт при запуске создает поток «main» который и является основным местом работы со скриптом.
Сразу обговорим — скрипт «жив» пока «жив» поток main(), поэтому если мы хотим продолжать работать со скриптом после запуска — будем держать поток активным:

function main() -- Объявляем поток main()
    wait(-1) -- Устанавливаем бесконечное ожидание
end -- закрываем функцию

Запускаем скрипт — работает!
Что ж, поставим себе цель — при запуске выводить сообщение «Привет мир» в чат.
Что нам для этого нужно?
Заходим на WIKI (Раздел «Функции» moonloader`a) и ищем функции чата — Поиск -> «Chat». Видим функцию sampAddChatMessage(message, color) — то что надо.
Вставляем:

function main()
    sampAddChatMessage("Привет мир!", 0xFFFFFFFF) -- Выводим сообщение в чат
    wait(-1) -- Устанавливаем бесконечное ожидание
end

Запускаем скрипт — ошибка?

(error) test.lua: opcode '0AF8' call caused an unhandled exception

Бежим на WIKI (Опять раздел «Функции» moonloader`a) и в поиск вводим наш опкод «0AF8».
Находим нашу функцию sampAddChatMessage()
Что это значит? Дело в том что нельзя вызывать функции для работы с SA:MP пока сам SA:MP не готов. Что делать? Добавим перед выводом сообщения такой код:

if not isSampfuncsLoaded() or not isSampLoaded() then -- Если SF или SA:MP не загружены
    return -- Завершаем работу скрипта
end
while not isSampAvailable() do -- Ждём пока функция isSampAvailable() вернет true
    wait(0) -- Устанавливаем минимальное ожидание, что бы наша игра не зависла
    -- значение 0 говорит что мы ждём следующий кадр (Frame)
end

Теперь наш скрипт будет ждать пока самп полностью не загрузится и продолжит выполнение кода.
Что такое wait() и с чем его едят?
Wait позволяет ставить

поток

в режим ожидания на указанное количество мили-секунд (ms)
Wait нельзя в CallBack-функциях, это приведет к ошибке, а в последствии и смерти, скрипта.
Почему мы ставим 0? Потому-что это минимальная задержка.

Подведем краткий итог кода:

function main()
    if not isSampfuncsLoaded() or not isSampLoaded() then
        return
    end
    while not isSampAvailable() do
        wait(0)
    end
    sampAddChatMessage("Привет мир!", 0xFFFFFFFF)
    wait(-1)
end

Теперь при входе в игру / перезапуске скрипта в чат будет выводиться сообщение «Привет мир».
К списку вопросов

c184o.png

Взаимодействие с игроками
Перейдем к примерам посложнее — попробуем получить ник, ид и счет игрока в которого мы прицелились.
Цель поставили, какие нам нужны средства?
Опять бежим к WIKI, всё в тот же раздел функций и ищем функции связанные с целями, Поиск -> Target.
Смотрим в списке есть getCharPlayerIsTargeting(ped).
Она возвращает 2 значения — result, ped.
Первое значение — возвращает результат проверки на целится ли указанный Ped в кого-либо.
Второе — если указанный Ped целится — возвращает саму цель.
Как нам это использовать? Всё просто, уберем бесконечное ожидание и будем проверять значение result.

function main()
    if not isSampfuncsLoaded() or not isSampLoaded() then
        return
    end
    while not isSampAvailable() do
        wait(0)
    end
    sampAddChatMessage("Привет мир!", 0xFFFFFFFF)
    while true do -- Создаем бесконечный цикл, вместо бесконечного ожидания
        wait(0) -- Опять таки чтобы наша игра не зависла, ждем след кадр
        local result, ped = getCharPlayerIsTargeting(PLAYER_HANDLE) -- Каждый кадр получаем данные функции, PLAYER_HANDLE - возвращает ваш Handle
        if result then -- Если result вернет true
            print("True!") -- Выведем сообщение в лог / консоль
        end
    end
end

Что мы можем с этим делать? Теперь если мы целимся в кого-либо, мы увидим сообщения в moonloader.log об этом. Мы получили Ped игрока в которого мы целимся. Давайте используем эту информацию.

function main()
    if not isSampfuncsLoaded() or not isSampLoaded() then
        return
    end
    while not isSampAvailable() do
        wait(0)
    end
    sampAddChatMessage("Привет мир!", 0xFFFFFFFF)
    while true do
        wait(0)
        local result, ped = getCharPlayerIsTargeting(playerPed)
        if result then
            local result2, id = sampGetPlayerIdByCharHandle(ped) -- Попробуем получить ID игрока по его Ped
            if result2 then -- Если получилось
                local nickname = sampGetPlayerNickname(id) -- Запишем его ник
                local score = sampGetPlayerScore(id) -- и счет
                sampAddChatMessage(string.format("%s[%d] имеет счет %d", nickname, id, score), -1) -- И выведем это в чат
            end
        end
    end
end

К списку вопросов

c184o.png

Работа с переменными
Что у нас получилось? Мы будем получать кучу сообщений в чат о игрока по пока целимся в кого-либо, но как убрать этот флуд?
Давайте создадим переменную перед main(), например targeting, и присвоим ей значение false.
И задействуем её в коде

local targeting = false -- Создаем булевую переменную
function main()
    if not isSampfuncsLoaded() or not isSampLoaded() then
        return
    end
    while not isSampAvailable() do
        wait(0)
    end
    sampAddChatMessage("Привет мир!", 0xFFFFFFFF)
    while true do
        wait(0)
        local result, ped = getCharPlayerIsTargeting(playerPed)
        if result and not targeting then -- Если мы целимся, но ранее ни в кого не целились
            local result2, id = sampGetPlayerIdByCharHandle(ped)
            if result2 then
                local nickname = sampGetPlayerNickname(id)
                local score = sampGetPlayerScore(id)
                sampAddChatMessage(string.format("%s[%d] имеет счет %d", nickname, id, score), -1)
                targeting = true -- запишем что мы целимся в кого либо и наше условие выше станет ложным
            end
        elseif not result and targeting then -- Если условие обратное - скажем скрипту что мы не целимся
            targeting = false
        end
    end
end

Отлично, теперь если мы целимся в игрока — получаем одно сообщение, меняем цель или заново целимся в того же игрока — снова получим о нём информацию.
К списку вопросов

c184o.png

Работа с командами.
Уже лучше, давайте теперь работаем с командами.
Уберем лишний код и оставим следующее:

function main()
    if not isSampfuncsLoaded() or not isSampLoaded() then
        return
    end
    while not isSampAvailable() do
        wait(0)
    end
    while true do
        wait(0)
    end
end

Как добавить команду? Давайте опять порыщем на WIKI, Поиск -> Command
Нашли sampRegisterChatCommand(command, callback)? Отлично, можно уже приступать.
Зарегистрируем нашу команду, например «toster»:

function main()
    if not isSampfuncsLoaded() or not isSampLoaded() then
        return
    end
    while not isSampAvailable() do
        wait(0)
    end

    sampRegisterChatCommand("toster", tosterCallBack) -- Регистрируем ДО бесконечного цикла.
    -- Первый параметр - команда, без слэша.
    -- Второй - функция-callback которая будет вызваться если будет введена команда.

    wait(-1) -- Уберем бесконечный цикл, он тут вовсе не нужен, но нам же нужно чтобы скрипт работал
end

function tosterCallBack(params)
    sampAddChatMessage("Дзынь!", -1)
end

Теперь при вводе команды «/toster» мы увидим в чате сообщение. Успех? Но как например вывести свой текст в чат? Легко!

sampAddChatMessage(params, -1)

Теперь мы выводим весь текст после команды «/toster» в чат. Например: /toster Привет жалкий мирок!

Помните я говорил что нельзя использовать wait() внутри callback-функций, это всё еще так, но есть тут одна хитрость!

function tosterCallBack()
    sampAddChatMessage("Дзынь!", -1)
    lua_thread.create(function() -- Создаем новый поток
        wait(5000) -- Ждём 5 секунд
        sampAddChatMessage("Тоже дзынь, но на 5 секунд позже!", -1) -- Выводим текст в чат
    end) -- Тут наш поток умирает :(
end

К списку вопросов

c184o.png

На сегодня всё, теперь вы чертовы волшебники, поняв как всё работает, можно приступать к изучению примеров Lua скриптов!

Тема будет дополняться по мере изучения популярных вопросов по Lua скриптингу.

Я понимаю что вы невъебенные мастера луа скриптинга, но это тема не для вас

Доброго времени суток, господа.
В этой теме я попробую ввести вас в lua скриптинг, в кратце расскажу про основы.
И так, начинаем.

Любой луа скрипт начинается с function main() — это главная функция, без неё не один луа скрипт работать не будет. С начала выполняются действия с функции main, а потом уже выполняются другие функции.
Если вы пишете скрипт для сампа, то в начале main’a пишем такие строки:
if not isSampfuncsLoaded() or not isSampLoaded() then return end
while not isSampAvailable() do wait(100) end

Первая строка это проверка на то, что сампфункс и самп загрузился, во второй строке если перевести на русский написано, пока самп не запустился делать wait(100), после этих проверок может быть ваш код.
Далее у нас будет

Код

while true do
wait(0)
— здесь может быть ваш код
end

Это бесконечный цикл.
Цикл это повторение.
Т.е это бесконечное повторение каких-то действий.
Вместо этого цикла можно использовать wait(-1) в конце main’a, но об этом посже.
Также забыл сказать что все функции, циклы и потоки закрываются end’ом. Пример вы можете увидеть в бесконечном цикле сверху.
Теперь давайте рассмотрим одну из самых простых функций луа — sampAddChatMessage(text, color)
Для начала напишем начальный код:

Код

function main()
if not isSampfuncsLoaded() or not isSampLoaded() then return end
while not isSampAvailable() do wait(100) end
while true do
wait(0)
— теперь пишем sampAddChatMessage — с помощью этой функции вы можете вывести текст в чат, который будет виден только вам.
sampAddChatMessage(«{FFFFFF}test», -1) — {FFFFFF} — это белый цвет, -1 — это ставим если перед текстом поставили какой-то цвет, в даном случае белый.
end
end — закрываем

Теперь при заходе в самп, вам будет флудить в чат текстом который вы написали в sampAddChatMessage.

На этом пока-что всё, 1 урок закончен.

Надеюсь кому-то он был полезен, в следующем уроке рассмотрим переменные и условия.
Всем пока.

Введение

Lua — это скриптовый язык программирования такой. Быстрый, легкий и удобный.
Изначально Lua создавался как язык программирования баз данных. Фактически, все программирование на Lua сводится к различным манипуляциям с таблицами. Таблицы — это краеугольный камень философии Lua. Таблица — это набор данных, где каждому уникальному ключу соответствует значение. Простой синтаксис и легкость встраивания Lua в приложение обеспечили Lua широкое распростанение в среде игроделов (откуда родом я сам). Во многих игровых компаниях при приеме на работу знание Lua уже MUST.
Теперь небольшой вводный курс в Lua.

Типы данных

nil — ничего, означающее отсутствие какого либо значения
boolean — булевская переменная, может принимать значения true или false
number — число
string — строка
function — функция (да-да, это таже тип данных!)
userdata — специальный тип данных, позволяющий хранить в Lua данные из С (фактически это указатель void*)
thread — сопрограмма Lua (позволяет организовать превдо-многопоточность)
table — таблица — ассоциативный массив (набор пар ключ-значение), причем в качестве и ключа и значения может выступать любой тип данных (угу, и функция тоже может быть ключом!)

Lua — язык с динамической типизацией, то есть тип переменной устанавливается не заранее, как, например, в С, а в момент присвоения переменной значения.
Примеры:

var = true — var — переменная типа boolean
var = 1 — теперь var число
var = «string» — теперь var строка
var = function(a, b) return a + b end — а теперь var функция, которая принимает два параметра и возвращает их сумму
var = coroutine.create(var) — а теперь var сопрограмма
var = {} — а тепеь var таблица
var = nil — а теперь… теперь нет var!

Два символа — означают начало однострочного комментария.

Область видимости переменных

Переменные могут быть локальными или глобальными. Локальные переменные объявляются с ключевым словом local и определены внутри блока. При выходе за пределы блока значение локальной переменной становится nil. Блоком является либо файл, либо последовательность выражений между ключевыми словами do и end.
Пример:

local x = 10 — локальная переменная х

Переменная становитя глобальной после первого присвоения ей значения.
Пример:

y = 20 — глобальня переменная y

Функции

Итак, мы выяснили, что функция является типом данных. То есть, функции можно возвращать из функций.
Пример:

function outer()
    function inner()
        return 1
    end
     return inner
end
local i  = outer() — значение переменной i равно функции inner
local j = i() — вызываем функцию i, значение переменной j равно 1

Функции могут принимать несколько значений. Если при вызове функции последние значения опущены, им присваивается nil. Параметры функции являются локальными переменными внутри функции.
Пример:

function f(x, y, z) — определение функции
end

f() — вызов функции. x, y, z равны nil
f(1) — вызов функции. x = 1, y, z равны nil
f(1, 2) — вызов функции. x = 1, y = 2, z равно nil
f(1, 2, 3) — вызов функции. x = 1, y = 2, z = 3
f(1, 2, 3, 4) — вызов функции. x = 1, y = 2, z = 3, 4 лишний параметр

Функции могут возвращать несколько значений.
Пример:

function f()
    return 1, 2, 3 — функция возвращает три числа
end

local x, y, z = f() — вызов функции. x = 1, y = 2, z = 3

Переменная _ используется в Lua для замены ненужной переменной.
Например, из функции f() нам нужно получить только третий параметр:

local _, _, z = f() — z = 3

«Замороженные» переменные (upvalues)

Функция может обращаться к локальным переменным, которые определены вне ее. Если при вызове функции локальная переменная уже вышла из области видимости и была разрушена, то функция пользуется тем значением внешней локальной переменной (external local variable), которое было на момент определения функции.
Пример:

local x = 10 — установили значение x
function f()
    return x + 1 — на момент создания функции значение переменной x равно 10
end
x = 20 — изменили x
local y = f() — значение у равно 21, локальная переменная х продолжает существовать.

Изменим время жизни локальной переменной x:

do — начало блока
    local x = 10 — установили значение x
    function f()
        return x + 1 — на момент создания функции значение переменной x равно 10
    end
end — конец блока, переменной x больше нет
local y = f() — значение у равно 11, хотя переменная х больше не существует

Функции, кстати, тоже могут быть локальными:

local f = function(x, y, z) — да, функцию можно объявить и так, это ведь всего лишь один из типов данных.
    return z, y, x — хе-хе, неплохая функция swap получилась, верно?
end

  Некоторые особенности при вызове функций

Например, есть две функции:

function f1()
    return 1, 2
end

function f2(x, y)
    return x + y
end

Поскольку первая функция возвращает два значения, а вторая принимает два значения, то функции могут быть вызваны так:

loсal x = f2( f1() ) — значение x равно 3

Задачка посложнее:

function f1()
    return 1, 2, 3
end

function f2(x, y, z)
    return x + y + z
end

local x = f2(f1()) — x = 1 + 2 + 3

Теперь так:

local x = f2(4, f1()) — x = 4 + 1 + 2

Понятно, что f1 дополняет последние параметры f2 нужным числом своих параметров. Однако это работает, только если вызов f1 стоит последним в списке параметров. Что будет если вызвать функции так:

local x = f2(f1(), 4, 5) — x = 1 + 4 + 5

В этом случае из f1 будет взят только первый параметр. А если так:

local x = f2(f1(), 4) — x = 1 + 4 + nil — ошибка!

Да, третьим параметром будет nil, что при выполнении математической операции сгенерирует ошибку.

Переменное число параметров

В Lua, как и в С, в функцию можно передавать переменное число параметров. Синтаксис также похож:

function f(x, y, …)
end

Все параметры, скрытые за … Lua упаковывает в локальную таблицу arg. У таблицы arg есть поле n, содержащее число переданных аргументов. Для примера, напечатаем все переданные в функцию аргументы:

function f(…)
    for i = 1, arg.n do
        print(arg[i])
    end
end

                      f(1, 2, 3, «Вася») — выведет соответственно 1, 2, 3, «Вася»

Таблицы

Таблица — это набор пар ключ-значение. Ключом в таблице может быть любое значение, кроме nil. Значением  поля в таблице так же может быть любое значение, кроме nil. Cоздание таблицы:

local t = {}

Заполним ее различными типами данных:

t[1] = 10
t[«Вася»] = «осел»
t[3] = true

local function f()
    return 1
end

t[f] = «функция»
t[«функция»] = f
t.me = t — тоже самое, что t[«me»] = t — ссылка на самого себя
print(t[«функция»]()) — > 1

Таблица может быть проинициализирована при создании:

local t = {1, 2, 3, x = 5, [«Вася»] = «осел»}

Массивы

Если при инициализации таблицы ключи не были указаны, то Lua сама присваивает значениям ключи, начиная с 1.
Внимание! Индексация массивов в Lua начинается с 1, не с 0!

local t = {3, 4, 5}
print(t[1], t[2], t[3]) — > 3, 4, 5

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

local t = {1, 2, 3, 4, 5}
table.insert(t, 6) — добавляет элемент в конец массива. Теперь  t = {1, 2, 3, 4, 5, 6}
table.insert(t, 0, 1) — вставляет элемент по индексу, сдвигая оставшиеся элементы массива. Теперь  t = {0, 1, 2, 3, 4, 5, 6}
table.remove(t, 3) — удаляет из таблицы элемент по индексу 3 и сдвигает оставшиеся элементы. Теперь t = {0, 1, 3, 4, 5, 6}

Получение размера массива выполняется оператором #:

local count = #t

Оператор # возвращает целое число n, такое, что t[n] не nil, и t[n + 1] равно nil. Другими словами оператор #, возвращает  максимальный индекс непрерывной последовательности ключей от начала массива. Соответственно, для таблицы:

local t = {1, [100] = 2}
print(#t) — > 1, поскольку t[1] не nil, а t[1 + 1] равно nil.

Для массива, в котором значения хранятся одно за другим, оператор # вернет количество элементов в массиве.

Обход элементов таблицы

В общем случае узнать, сколько элементов хранится в таблице Lua, кроме как обойдя все элементы таблицы, нельзя. Исключение — это массивы и оператор # для определения длины массива. Массив обходится обычным циклом for:

local t = {1, 2, 3, «Вася»}
for i = 1, #t, 1 do
    print(t[i])
end

— > 1
— > 2
— > 3
— > Вася

Обычные таблицы обходятся циклом for с итератором:

local t = {1, 2, x = 4, y = 5, [«Вася»] = «осел»}
for key, value in pairs(t) do
    print(key, value)
end

— > 1    1
— > 2    2
— > y    5
— > x    4
— > Вася    осел

Как видно из печати, порядок элементов в таблице отличается от порядка, в котором значения помещали в таблицу, поскольку внутри хэш-таблицы ключи сортируются по некоему признаку.

Методы

Рассмотрим следующую конструкцию (попытка сымитировать ООП):

local t = {x = 1}

function t:fun()
    print(self.x)
end

                     t:fun()

— > 1

 Что мы здесь сделали?
1. Создали таблицу t;
2. В таблице t завели поле x и назначили ему значение 1;
3. В таблице t завели функцию fun;
4. Внутри функции fun обратились к таблице через специальную переменную self (на самом деле self это первый скрытый параметр у функции fun. Вызывая функцию как t:fun() , мы а самом деле сделали то же самое, что и t.fun(t))
5. Вызвали у таблицы (уже почти объекта!) t метод fun;

На что все это похоже? Это похоже на ООП. Есть, объект, есть метод объекта, есть вызов метода объекта. Чего нет? Нет возможности создавать объекты определенного типа (например типа t). То есть и объект, и метод существуют в единственном экземпляре. Для того, чтобы создать объект типа tt, который ведет себя так же, как объект типа t, нам придется повторить всю процедуру с самого начала:

local tt = {x = 2}

function tt:fun()
    print(self.x)
end

                     function checkMetod(x)
    x:fun()
end

checkMetod(t)
checkMetod(tt)

—> 1
—> 2

Есть ли выход из сложившейся ситуации? Да, есть. Но о нем мы поговорим после того, как рассмотрим метатаблицы.

Метатаблицы.

Метатаблицы позволяют переопределить поведение встроенных типов. Для таблиц это можно сделать прямо в Lua, а для других типов придется это делать через С.
Метатаблицы схожи с конструкцией operator() в C++. Рассмотрим, например, как можно складывать две таблицы (в самом языке такая операция не предусмотрена).

— первый операнд
local data1 = {x = 1}
— второй операнд
local data2 = {x = 2}
— создаем таблицу
local meta = {}
— в таблице определяем специальное поле __add (оператор +)
function meta.__add(op1, op2)
  return op1.x + op2.x
end
— устанавливаем метатаблицу первому операнду
setmetatable(data1, meta)
— проверяем
print(data1 + data2) —> 3

Итак, что мы сделали? Создали два объекта, каждый из которых содержит поле х. Затем создали обычную таблицу meta. В ней определили функцию со специальным именем __add. Затем установили метатаблицу первому операнду.
С тем же успехом мы могли бы установить эту метатаблицу и второму операнду. Когда Lua обнаруживает операцию над значением, то сначала ищется подходящий обработчик в метатаблице первого операнда, и если там нет, то ищется обработчик в метатаблице второго операнда. Если обработчика и там нет, то генерируется ошибка.
Вот список операций, обработчики которых могут быть переопределены в метатаблице:

Обработчик Оператор Lua Примечание
__add(op1, op2) + сложение
__sub(op1, op2) вычитание
__mul(op1, op2) * умножение
__div(op1, op2) / деление
__mod(op1, op2) % деление по модулю
__pow(op1, op2) ^ возведение в степень
__unm(op) унарный минус
__concat(op1, op2) .. конкатенация (склейка)
__len(op) # унарный оператор взятия длины
__eq(op1, op2) == оператор «равно»
__lt(op1, op2) < оператор «меньше»
__le(op1, op2) <= оператор «меньше либо равно»
__index(op, key) [] оператор обращения по ключу
вызовется если, например,  вызвать local x = data1[1]
__newindex(op, key, value) [] оператор присвоения нового значения по ключу
если сделать эту функцию пустой, то таблица станет «readonly», то есть в нее нельзя будет добавить новое поле со значением
 вызовется если, например,  вызвать data1[1] = 1
__call(op, …) () оператор вызова функции
вызовется если, например,  вызвать data1(1, 2, 3)
в обработчик __call первым параметром придет операнд (в нашем случае data1), а следующими параметрами будут те параметры, что стояли в скобках (в нашем случае 1, 2, 3).

Как видно, все очень просто.

ООП

С помощью метатаблиц можно реализовать некое подобие объектно-ориентированного программирования.
Попробуем это сделать. Сначала создадим базовый класс (обычная таблица):

Base = {}

В ней создадим поле field:

Base.field = «text»

и две функции для работы с этим полем:

function Base:setField(value)
    self.field = value
end

function Base:getField()
    return self.field
end

Обратите внимание на двоеточие перед именем метода — первым параметром в метод будет передаваться self, то есть сам объект класса (см. сюда), по аналогии с С++ это this, только в С++ this передается в метод неявно.
Итак, базовый класс создан. Это шаблон, по которому должны создаваться объекты данного типа.
Создадим объект типа Base:

local base = {}

Как видно, пока это обычная таблица. Установим у этой таблицы новую метатаблицу.

setmetatable(base, {__index = Base})

 Вуаля! Теперь самая обычная таблица стала объектом типа Base. Что это значит? Согласно правилам, описанным в предыдущем пункте Метатаблицы, оператор __index в метатаблице вызывается всякий раз, когда осуществляется поиск в таблице по ключу. А что есть вызовы «методов» и обращение к «членам класса», как не поиск поля в таблице?
Сначала ищутся поля в самой таблице. Если поле не найдено,  в метатаблице ищется метод __index. Если __index является функцией, вызывается эта функция. Если __index является таблицей, то поиск поля осуществляется в этой таблице. Эти вызовы делаются рекурсивно до тех пор, пока метатаблица либо __index не станет равным nil. С учетом вышесказанного становится понятным, почему следующий вызов дает такой результат:

print(base:getField()) —> text

Сначала был произведен поиск поля  getField в таблице base. Ничего не найдено. Далее в таблице base ищется метатаблица. Найдено. В ней ищется поле __index. Найдено. Поле __index является таблицей. В таблице __index (а это на самом деле ссылка на таблицу Base) ищется поле  getField. Найдено. Это функция. Поскольку мы вызывали base:getField(), то в функцию getField первым параметром передается таблица base (см. сюда). Далее внутри функции getField повторяется аналогичный поиск для поля field. В итоге печатается первоначально заданное значение этого поля «text».

Изменим значение этого поля:

base:setField(‘aaa’)
print(base:getField()) —> aaa

Теперь мы хотим наследоваться от класса Base. Наследование выполняется аналогично созданию объекта класса Base:

Child = {}
setmetatable(Child, {__index = Base})

Переопределим в Child метод getField(), фактически, делаем этот метод виртуальным:

function Child:getField()
    return ‘zzz’
end

Создадим объект типа Child:

local child = {}
setmetatable(child, {__index = Child})
print(child:getField()) —> zzz

А как же быть, если мы хотим вызвать для Child метод родительского класса? А очень просто:

child:setField(‘ooo’)
print(Base.getField(child)) —> ooo

Модули

Модули — это один из способов грамотно организовать большой проект в Lua. Это что-то типа namespace в С++.
Для начала рассмотрим наивную попытку организовать модули самостоятельно. Сделать это нетрудно. Достаточно создать файл, где будут созданы глобальные таблицы с именами модулей, и далее в программе обращаться к этим глобальным таблицам.
Например:

файл modules.lua:

Module1 = {}
Module2 = {}

файл module1.lua:

Module1.var1 = 1
function Module1.fun1(a, b)
    return a + b + Module1.var1
end

аналогично файл module2.lua:

Module2.var1 = 2
function Module2.fun1(a, b)
    return a + b — Module2.var1
end

файл запуска приложения main.lua:

dofile(‘modules.lua’)
dofile(‘module1.lua’)
dofile(‘module2.lua’)

print(Module1.fun1(1, 2)) —> 4
print(Module2.fun1(1, 2)) —> 1

В общем, все очевидно. Неудобств несколько — необходимость внутри файла модуля (например, в module1.lua) при обращении к переменным модуля все время ссылаться на глобальную таблицу с именем модуля (Module1.var1), вероятность засорения глобального пространства имен именами временных переменных (написав, например, вместо local x = 1 просто x = 1), ну и еще есть какие-нибудь недостатки, включая главный — рукотворность этой
схемы.

Теперь рассмотрим, что нам предлагает Lua. Модули создаются глобальной функцией module(name). Эта функция проверяет, существует ли глобальная таблица с именем name. Если нет, то такая таблица создается. Затем эта таблица помещается в глобальную таблицу package.loaded[name] = table. Функция module(name) также устанавливает в созданной таблице поля t._NAME = name, t._M = t и t._PACKAGE = полное имя модуля минус последний компонент (если имя модуля составное). Имя модуля может состоять из нескольких частей, разделенных точками. В этом случае функция module создает (или использует, если уже существуют) таблицы для каждого компонента. Например вызов  module(a.b.c) создаст глобальную таблицу a, в ней таблицу b, а в таблице b создаст таблицу с. Кроме того, функция module делает еще одну важную вещь — устанавливает созданную таблицу как новый environment (окружение). Это значит, что все последующие обращения к глобальным переменным (до конца блока, обычно до конца текущего файла) будут направляться в новую таблицу.

Загрузка модулей

Чтобы модулем можно было пользоваться, его надо загрузить. Для этого нужно вызвать глобальную функцию require(name). Эта функция начинает с просмотра таблицы package.loaded, чтобы определить, модуль name  уже загружен или еще нет. Если модуль name загружен, то require возвращает значение package.loaded[name]. Если же нет, то require ищет загрузчик модуля.
<Далее идет почти буквальный перевод мануала. Переводил тщательно, чтобы самому все понять :)>
Загрузчики модулей хранятся втаблице package.loaders. Каждое поле этой таблицы является функцией поиска. Когда функция require ищет модуль, то она вызывает эти функции по очереди. Функция поиска может вернуть либо другую функцию (загрузчик модуля), либо строку, которая разъясняет, почему модуль не был найден (либо nil, если ей нечего сказать). Lua инициализирует таблицу package.loaders четырьмя функциями.

Первая функция ищет загрузчик в таблице package.preload.

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

package.path = «./?.lua;./?.lc;/usr/local/?/init.lua»

то для загрузки модуля foo Lua будет пытаться открыть следующие файлы (порядок поиска совпадает с порядком задания шаблонов):

./foo.lua, ./foo.lc, and /usr/local/foo/init.lua.

Третья функция ищет загрузчик С-библиотек, используя пути, прописанные в package.cpath. Например, если

package.cpath = «./?.so;./?.dll;/usr/local/?/init.so»

то загрузчик будет пытаться открыть следующие файлы (порядок сохранен):

 ./foo.so, ./foo.dll, and /usr/local/foo/init.so.

Первая найденная библиотека динамически линкуется с программой. Затем загрузчик пытается найти С функцию внутри библиотеки, чтобы использовать ее для загрузки. Имя этой С-функции должно быть «luaopen_» + копия имени модуля, причем в имени модуля все точки должны быть заменены на подчеркивания. Кроме того, если в имени модуля есть дефис, то весь префикс, включая знак дефис, должен быть удален. Например, если имя модуля a.v1-b.c, то имя функции должно быть luaopen_b_c.

Четвертая функция пытается использовать загрузчик «все в одном». Эта функция использует шаблоны для загрузки С-библиотек, чтобы найти библиотеку для корневого модуля. Например, для модуля a.b.c эта функция будет искать С-библиотеку модуля a. Если такая библиотека найдена, то внутри нее ищется функция для загрузки субмодулей. В нашем примере такая функция должна иметь имя luaopen_a_b_c. Такая организация позволяет упаковывать несколько субмодулей внутри одной библиотеки, используя для загрузки каждого субмодуля отдельную функцию.
Уф, насилу перевел!
Теперь быстрее к самому вкусному — ООП на модулях!

ООП на модулях

В С++ это довольно привычно, располагать каждый класс в отдельном файле (обычно в двух, ИмяКласса.h и ИмяКласса.cpp). В Lua, поверьте, это тоже удобно :).
Проект удобно организовать следующим образом:
.
..
<luamodules> — папка
<cmodules> — папка
start.lua

Пути к Lua-библиотекам и С-библиотекам (загрузчики) можно прописать двумя различными способами.
Первый способ:
В первых двух строчках файла start.lua написать:

package.path = ‘./luamodules/?.lua’ — пути к Lua библиотекам
package.cpath = ‘./cmodules/?.dll’ — пути к С библиотекам

Второй путь:
Использовать системные переменные.
Создать файл start.bat в котором написать следующее:

set LUA_PATH=.luamodules?.lua;
set LUA_CPATH=.cmodules?.dll
lua.exe start.lua

а в самом файле start.lua уже ничего не писать.

Итак, повторим наш учебный проект, но теперь с использованием модулей.
Первый модуль назовем Factory. Он будет создавать объекты классов и устанавливать метатаблицы.
Создадим файл Factory.lua (мы еще помним, что при поиске модуля Factory в шаблон package.path будет подставляться имя модуля?). Сохраним его в папке ./luamodules. В этом файле напишем следующее:

local base = _G

module(‘Factory’)

function setBaseClass(class, baseClass)
  base.assert(baseClass.mtab) 
  base.setmetatable(class, baseClass.mtab)
end

function create(class, …)
  local w = {}
  setBaseClass(w, class)
  w:construct(base.unpack(arg))

   return w
end

В начале файла мы видим магическую строку

local base = _G

Зачем она? Функция module, если вспомнить вышесказанное о ней, во время своего выполнения переключает локальный контекст. Это означает, что после ее вызова, если ничего не предпринять, доступ к глобальным переменным Lua будет закрыт. Поэтому мы сохраняем специальную глобальную переменную Lua _G (_G._G = _G) в локальной переменной base. Теперь доступ к глобальным переменным осуществляется через base.имя_переменной (в нашем случае это base.assert, base.setmetatable и base.unpack).

Так, прочный фундамент мы заложили, приступим к надстройке. Создадим файл ./luamodules/Base.lua

local base = _G

module(‘Base’)
mtab = { __index = _M }

local Factory = base.require(‘Factory’)

function new()
  return Factory.create(_M)
end

function construct(self)
  base.print(‘Base created!’)
  self.field = ‘text’
end

function setField(self, field) — метод получения значения поля field
  self.field = field
end

function getField(self) — метод установки значения поля field
  return self.field
end

Проясним некоторые места:

local Factory = base.require(‘Factory’)

Для работы нам будет нужен модуль Factory.

Внимание!
Для избежания ошибок с циклическими зависимостями модулей, все функции require нужно вызывать после выполнения функции module!

mtab = { __index = _M }

Вспоминаем про функцию module — _M — это ссылка на саму таблицу Base.

mtab = { __index = _M }

Таблица mtab будет установлена как метатаблица у нового объекта класса Base при вызове метода create() модуля Factory. Это означает, что при поиске полей внутри объекта класса Base, если поле будет не найдено, то поиск будет осуществляться в таблице, на которую ссылается поле метатаблицы __index (см. метатаблицы).

function new()
  return Factory.create(_M)
end

Для удобства. Конечно, в прикладном коде можно вызвать и

local base = Factory.create(Base)

но мне кажется что вызов

local base = Base.new()

короче и очевидней.

function construct(self)
  base.print(‘Base created!’)
end

Эта функция будет вызвана из метода create() модуля Factory. Фактически, это конструктор класса.
Все методы класса имеют первым параметром ссылку на объект класса self.
Напишем тестовый скрипт ./start.lua:

package.path = ‘./luamodules/?.lua’ — пути к Lua библиотекам
package.cpath = ‘./cmodules/?.dll’ — пути к С библиотекам

require(‘Base’)

local base = Base.new()
print(base:getField())
base:setField(1)
print(base:getField())

Запускаем его на выполнение. 

lua.exe start.lua

Должны получить:

Base created!
text
1

Отлично! Двигаемся дальше. Создадим файл ./luamodules/Child.lua

local base = _G

module(‘Child’)
mtab = { __index = _M }

local Factory = base.require(‘Factory’)
local Base = base.require(‘Base’)

Factory.setBaseClass(_M, Base) — устанавливаем Base как базовый класс

function new(param1, param2) — передаем параметры в конструктор
  return Factory.create(_M, param1, param2)
end

function construct(self, param1, param2) — конструктор с параметрами
  Base.construct(self) — вызов конструктора базового класса
  base.print(‘Child created!’, param1, param2)
end

function getField(self) — переопределяем метод
  return ‘zzz’
end

Модернизируем немного ./start.lua, добавив в него строки:

require(‘Child’)

и

local child = Child.new(1, 2)
print(child:getField())
child:setField(1)
print(child:getField())

Запускаем его на выполнение. 

lua.exe start.lua

Должны получить:

Base created!
text
1
Base created!
Child created!  1       2
zzz
zzz

Завершающие штрихи. Как создасть статический член класса? Очень просто. Внутри модуля объявить его либо как

member = 1

либо (предпочтительнее, поскольку более очевидно чего вы хотите)

_M.member = 1

Соответственно, статический метод класса будет без первого параметра self:

function staticFun(param1, param2)
end

Здравствуйте. В этом уроке мы познакомимся с таким программированием, как Lua. Данный учебник будет написан на простом и понятном языке, чтобы не усложнять жизнь начинающим скриптерам. В введении в курс Lua я буду сравнивать аналогичные функции с SA-MP Pawn.

Введение в скриптинг MTA Lua

Содержание:

1) Client’n’Server Sides (2 стороны сервера – C-Сторона и S-Сторона) – Введение
2) Иерархическое строение сервера (meta.xml)
3) Администрирование (ACL)
4) Аргументы игрока (source, thePlayer, getLocalPlayer())
5) Вывод (Окончание учебника)
6) Дополнительный материал

Введение

Наша статья прямо связана с сетевой игрой для GTA San Andreas – Multi Theft Auto. Начну с того, что в MTA Lua вам предоставлены 2 стороны – Server (связана с функциями и действиями в основном с игроками и игровым миром) и Client (связана с функциями и действиями, не связанными с игроком). На S-Стороне пишут в основном системы, которые непосредственно связаны с игроком, например система команд. На C-Стороне пишут диалоговые окна (GUI), изображения на экран (dxDraw). Про dxDraw могу сказать то, что он немного похож на TextDraw из SA-MP, но имеет функцию загрузки любого выбранного вами изображения (на примере рабочего стола в моей системе Lunix OS).

Иерархическое строение сервера (meta.xml)

Сервер МТА имеет иерархическое строение, а именно один каталог принадлежит другому, а тот в свою очередь принадлежит третьему. Главным каталогом является папка или архив с названием ресурса (не имеет значения, как её назвать) в папке /MTA San Andreas Ver/server/mods/deathmatch/resources/

в котором в свою очередь обязательно должен находится файл meta.xml, где хранятся данные о ресурсе (тип, название ресурса, автор, версия, дочерние файлы (изображения, модели, текстуры, lua файлы ресурса, и другое)).
В МТА присутствует функция отделения карты/ресурса от главного запущенного (им является ресурс с пометкой «Gamemode» в файле meta.xml), следовательно, можно выгружать и загружать ресурс или карту прямо на сервер, не перезагружая при этом сервер.

Так-же тут присутствует возможность использовать функции из других ресурсов, например есть 2 ресурса – «admin» (система администрирования) и «TvoyRes». Вашим ресурсом является «TvoyRes». Вы можете прямо использовать функции из ресурса «admin», ничего не подгружая (кроме директории ресурса в meta.xml)

Администрирование (ACL)

Поговорим об администрировании сервера MTA.

В МТА администратор ресурса зовётся resourceRoot (некоторая связь с администрированием систем Linux). Главными resourceRoot’ами являются командная строка запуска сервера, и игровая консоль (вызывающаяся на символ «~» при английской раскладке, или клавишей F8). Игровая консоль от командной строки сервера отличаются тем, что в консоли можно вводить команды ресурсов (в командной строке нельзя), и входить в режим resourceRoot в консоли не нужно.

Давайте сравним типы администрирования Rcon (SA-MP) и resourceRoot, или просто Root (MTA):

— В SA-MP Rcon может войти любой игрок, зная определённый единственный пароль для входа.
— В МТА Root могут войти только зарегистрированные на сервере пользователи, имеющие привилегии, указанные в специальном файле.

Это показывает то, что в МТА по стандартным установкам сервера последних версий уже имеется система аккаунтов, нежели в SA-MP, которую, если нужно, можно написать.

В МТА так же имеется система уровней администрирования:

— Everyone (Default) – зарегистрированные аккаунты, они сюда после регистрации не попадают, их нужно записывать самому.
— Moderator – модератор, с ограниченным набором команд.
— SuperModerator – модератор, имеющий больше привилегий.
— Admin – администратор, который имеет доступные привилегии модератора и супер-модератора с добавлением некоторых полномочий.
— RPC – связан с доступом в интернет, имеет только функцию callRemote, которая связана с загрузкой деталей из интернета.
— MapEditor – имеющий доступ к редактированию карты (если загружен скрипт редактирования (с сервера, не имеющий отношения к Map Editor в меню игры))
— raceACLGroup – многие знают, что МТА с начала существования начинала с серверов, связанных с гонками (нежели SA-MP был полон DM серверов), эта группа связана с администрированием Race ресурсов (думаю данная группа не важна).
— Console – resourceRoot всего сервера, имеющий все полномочия вместе взятых групп.

По сравнению с SA-MP, в МТА в сервер встроены команды для администраторов.

Использование функций ACL (администрирования) в скриптинге выглядит не так просто, потому что для того, чтобы найти администратора на сервере через скрипт, нужно ввести поиск полномочий пользователя. Например, функция поиска объекта, имеющего доступ к чему-либо (только S-Сторона):

hasObjectPermissionTo

(object, what_is_to, bool)

И функция поиска объекта в ACL группах (поиск по группам администрирования) (только S-Сторона):

isObjectInACLGroup

(object, acl_group)

Рассмотрим эти функции на примере:

if

hasObjectPermissionTo

(source,

“function.addBan”

,

true

)

then

ВАШ КОД

end

Эта функция проверит, доступна ли у игрока source доступ к функции addBan (доступен администраторам и выше)

if

isObjectInACLGroup

(

“user.”

..

getPlayerName

(source),

aclGetGroup

(

“Admin”

))

then

ВАШ КОД

end

Данный код найдёт пользователя в группе Admins. Так как в файле администраторов игрок прописывается через user.name, то и поиск его тоже надо сделать через user. Функция getPlayerName покажет имя игрока, и, следовательно, исходный запрос будет выглядеть как user.name. Функция aclGetGroup ищет по группе администрирования, указанной в аргументе. Важно вводить группу такой, какая она есть (Admin, SuperModerator, Console, MapEditor), соблюдая регистр.

Это конечно выглядит и понимается сложней, чем то же самое в SA-MP:

if

IsPlayerAdmin

(playerid){ КОД }

return

1;

Аргументы игрока (source, thePlayer, getLocalPlayer())

Раз дело пошло о аргументе Source, расскажу о нём.

В МТА есть несколько аргументов на поиск игрока. Самым основным является source – он доступен для обеих сторон (S- и C- сторон), и определяет любого «раздражителя» (например, если объект выполняет действие с source, то в выполнении source’ом является именно этот объект, но не какой-либо левый игрок). Так же присутствует аргумент thePlayer, определяющий игрока, работает в S-Стороне. Ещё присутствует функция getLocalPlayer(), определяющая игрока в пределах C-Стороны.

Вывод (Окончание учебника)

На этом я заканчиваю данный урок. В заключении я добавлю, что МТА Lua является упрощённым языком, и пишется проще, чем SA-MP Pawn, но данный язык усложнён своими функциями, но именно они упрощают вид языка.

Дополнительный материал

Для тех кто думает переходить с Pawn в Lua (я всеми руками за) я могу предложить переменную, которая оставит вам привычный playerid.

Для S-Стороны:

playerid = source

Для C-Стороны:

playerid =

getLocalPlayer

()

Данный код можно писать в начале ресурса

Как редактор ресурсов, советую вам использовать MTA Script Editor.

Обновлено: 03.03.2023

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

Перед продолжением необходимо установить сам MoonLoader, желательно через программу установки и с дополнительными инструментами:
1. Скачайте установщик MoonLoader последней версии и запустите, следуйте шагам программы установки
2. На странице выбора компонентов отметьте нужные скрипты (рекомендуется выбрать все)

3. Если вы намерены делать скрипты для SA:MP, выберите SAMP.Lua и отдельно установите SAMPFUNCS
4. Это не обязательно, но не помешает установить и MoonAdditions — эту библиотеку используют некоторые скрипты и она неплохо расширяет стандартные возможности скриптинга
5. Выберите установку расширения для Notepad++, если вы будете использовать программу Notepad++ для редактирования Lua скриптов

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

После установки этого пакета вам будет предложено установить дополнительные пакеты, соглашаемся и забываем про это.

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

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

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

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

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

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

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

Задержки и потоки
Задержка в скрипте позволяет приостанавливать исполнение кода на указанное количество миллисекунд или на один кадр. В ML для осуществления задержек используется функция wait(int time), её аргумент time принимает следующие значения:
-1 — устанавливает непрерывное ожидание, использование допустимо только в main
0 — задержка на один кадр
остальные значения указывают задержку в миллисекундах
Пример: Запущенный скрипт будет в вечном ожидании
Использование задержек недопустимо в callback-функциях. Для создания задержки в такой функции используйте создание потока:

Потоки — это сопрограммы, которые могут быть созданы в процессе работы скрипта для параллельного выполнения задач. Потоки в MoonLoader служат для выноса определенных действий за рамки основного потока, а так же для создания задержек выполняемого кода там, где это невозможно реализовать стандартными методами.
Для запуска потока используются функции lua_thread.create и lua_thread.create_suspended.
Пример: создаётся два потока для параллельного вывода текста в лог

Директивы
Скрипты для MoonLoader могут содержать о себе некоторую информацию и иметь определённые свойства исполнения — и то, и другое задаётся с помощью директив. Директивы — это обычные функции, предназначенные для указания информации о скрипте и изменения его поведения. Их принято обозначать в самом начале скрипта.
Все параметры, задающиеся директивами, можно получить из любого скрипта, обратившись к соответствующим полям класса LuaScript.
Пример:

В этом примере показаны не все директивы, за полным списком обращайтесь к соответствующей странице на вики.

Колбэки (функции обратного вызова) выступают реакцией на действие и по концепции очень похожи на события, но у них есть два отличия: первое — колбэк всегда регистрируется явно, чаще путём вызова функции с передачей функции-колбэка в качестве аргумента, второе — он всегда связан с какой-либо сущностью (командой, идентификатором и т.п.), т.е. будет вызван только если возникшее событие касается связанной сущности (например, колбэк команды будет вызван при вводе только одной команды, а не каждой, как это было бы с событием). Примером функции с колбэком является downloadUrlToFile, принимающая последним аргументом callback-функцию.
В колбэках, как и в событиях, нельзя использовать задержки.
Пример:

Использование библиотек
Библиотеки, либо модули всячески дополняют стандартный набор возможностей новыми и позволяют использовать в скриптах готовые инструменты для разработчиков. Модули делятся на стандартные и сторонние. Стандартные включены в дистрибутив MoonLoader и не требуют отдельной установки.
Все модули располагаются в директории moonloader/lib/ и устанавливаются туда же. Подключение библиотек осуществляется при помощи функции require, которая в качестве аргумента принимает название файла. Чаще всего подключение модулей производится в начале скрипта, но может быть выполнено в любом месте. Каждый модуль загружается отдельно для каждого скрипта.
Пример: Подключим модуль «vkeys», позволяющий работать с виртуальными клавишами.

Работа с модулями не ограничивается стандартным набором, часто приходится иметь дело со сторонними модулями — такие модули не поставляются вместе с MoonLoader и требуют ручную установку. Примерами таких модулей являются Dear ImGui и SAMP.Lua.
Вы можете создать собственный модуль и использовать его в своих скриптах. Благодаря этому вам будет легче оказывать им поддержку, скрипты станут чище и компактнее, а повторяющегося кода будет намного меньше.
Помимо этой возможности в 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:

TheChampGuess | Уроки Lua SAMP

Благодарность

Ваня Веспанов

Назар Чиж

Коля Руснак

Коля Руснак

Кеша Кешин

TheChampGuess | Уроки Lua SAMP

TheChampGuess | Уроки Lua SAMP запись закреплена

Hellllloooo

Дарья Щур

Дарья Щур

Александр Адаев

Александр Адаев

Николай Фролов

TheChampGuess | Уроки Lua SAMP

TheChampGuess | Уроки Lua SAMP запись закреплена

Сам скрипт будет прикреплен к посту, а также в документе. Кроме того, вы можете ознакомиться с базовой версией функции, с которой и была написана оптимизированная версия текущего скрипта. За предварительную оптимизацию спасибо главному администратору данного сообщества — Vlad Tsurkan

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

Діма Колодич

Діма Колодич

Как писать луа скрипты самп

Обучение Lua — Руководство для начинающих по написанию скриптов

Изучайте основы как работает Lua

Это первое руководство в этой серии.

1,617 уникальных посетителей
37 добавили в избранное

Это руководство предназначено для тех, у кого ограниченный опыт работы с LUA. Мы рассмотрим основы того, как оформлять код, строительные блоки для Вас, чтобы создавать более сложный код и предоставим некоторые примеры. Руководство написано так, чтобы сразу применять его на практике. Поэтому Вам следует открыть Tabletop Simulator и Ваш редактор LUA, чтобы следовать дальше.

Это первое руководство в этой серии. Второй – это Изучение Lua Подробнее. Третий представляет собой набор полезных функций под названием Learning Lua Functions.

Когда Вы сохраняете свои скрипты в Tabletop, он будет использовать Ваше последнее сохранение, а затем загрузит в него скрипты. Поэтому для любого скрипта, который Вы намереваетесь написать, Вам нужно будет сделать следующее:

  • Подготовьте стол так, как Вы этого хотите.
  • Сохраните стол.
  • Загрузите стол.

Не забудьте сохранить/загрузить, а затем открыть скрипт в Atom или перейти в Host>Scripting в Tabletop Simulator, чтобы начать.

Global.lua — это скрипт, который является частью файла сохранения. Именно здесь мы будем работать над большей частью этого руководства. При новом сохранении он всегда начинается с некоторого текста, сохраненного в редакторе. Просто удалите его, мы его не будем использовать.

Также можно писать скрипты и прикреплять их к объектам вместо Global. Таким образом, если Вы сохраните объект, то сохраниться и его LUA скрипт. Вы можете выполнять большинство функций с использованием скрипты Global или объекта, но мы будем работать в Global.

Часто используемая функция, встроенная в Tabletop Simulator — onload(). Эта функция запускается каждый раз при загрузке скрипта (например, если нажата кнопка Отменить(Undo) / Повторить(Redo), а также во время загрузки сохранений).
Замечу, что все переменные вне функций также инициализируются всякий раз при загрузке скрипта.
Итак, давайте начнем с его использования, чтобы запустить функцию, которую мы создадим. Функции должны начинаться с строчной буквы и не содержать пробелов. Мы будем использовать exampleFunction.

Теперь наш скрипт, когда он загружается, попытается запустить функцию с именем exampleFunction. Но мы еще не писали! Итак, теперь мы создадим нашу собственную функцию сразу после завершения функции onload.

Extra Credit: Когда Вы создаете свою собственную функцию, Вы также можете передавать переменные вместе с ней для их использования функцией. Другой способ написать наше начальное упражнение:

function onload() exampleFunction(‘Hello, World.’) end function exampleFunction(passedString) print(passedString) end
Мы создали переменную для представления строки (passString), а затем напечатали то, что содержалось в этой переменной.

Чтобы повлиять и на объект, сначала мы должны идентифицировать его в LUA. Существует несколько способов сделать это, например, идентифицировать элементы, которые подбираются или отпускаются игроками, находить объекты в скриптовых зонах и многое другое. Мы будем идентифицировать эти объекты по их GUID.

GUID – это уникальный идентификатор, который имеет каждый сгенерированный элемент в игре. Даже два одинаковых элемента будут иметь разные GUID. Чтобы найти GUID объекта, щелкните по нему правой кнопкой мыши и перейдите к Scripting. Если Вы нажмете на GUID, он скопирует его в буфер обмена. GUID всегда является строкой, поэтому не забывайте, что строки всегда в кавычках. Давайте создадим несколько переменных с идентификаторами GUID наших объектов. ОБРАТИТЕ ВНИМАНИЕ: Ваши идентификаторы GUID будут отличаться от моих.

object1_GUID = ‘195868’ object2_GUID = ‘333365’ checker_GUID = ‘7dc60d’

Затем, создадим переменные для представления наших объектов. Используем функцию onLoad(), чтобы создание происходило при загрузке скрипта. Все эти имена переменных, которых мы делали, должны начинаться со строчной буквы и не содержать пробелов, но, кроме этого, Вы можете свободно сами составлять имена переменных. Используйте такие имена, чтобы было понятно, что объект из себя представляет. Я буду использовать object1, object2 и checker для представления моих Объектов. Функция, которую мы будем использовать для идентификации, будет getObjectFromGUID(строка). Мы помещаем GUID в место для строки.

function onload() object1 = getObjectFromGUID(object1_GUID) object2 = getObjectFromGUID(object2_GUID) checker = getObjectFromGUID(checker_GUID) end

Теперь нам нужно каким-то образом манипулировать этими объектами. Мы дадим им имена. В onload() после определения наших объектов мы будем использовать функцию setName(string). Обратите внимание, что setName, как и другие функции объекта, должна быть привязана к объекту. В противном случае скрипт не поймет, имя какого объекта мы хотим изменить. Строкой в setName будет то, что мы установили для имени.

object1.setName(‘Object1’) object2.setName(‘Object2’) checker.setName(‘That Stupid Checker’)

Если бы вы хотели, то нет причин, по которым вы не могли бы написать для шашки это, так:

function onload() getObjectFromGUID(‘7dc60d’).setName(‘That Stupid Checker’) end
Причина, по которой я не одобряю это ученикам является частично эстетическим выбором, и частично для ясности чтения кода. Вы хотите, чтобы кому-то было легко понять Ваш код, и как только вы начнете делать что-то более сложное, чем изменение имени Объекта, может становиться ОЧЕНЬ трудно понять, что происходит. Это улучшает восприятие кода при внесений правок в будущем.

  • click_function = Строка —Имя вызываемой функции.
  • function_owner = Объект —Объект или Global, где находится функция.
  • label = Строка —Название на кнопке.
  • position = Таблица —Координаты X, Y и Z, для которых отображается кнопка, от центра объекта, к которому он присоединен.
  • rotation = Таблица —Поворот по осям X, Y и Z в градусах относительно объекта, к которому он привязан.
  • width = Число —Ширина кнопки по отношению к масштабу объекта.
  • height = Число —Высота кнопка по отношению к масштабу объекта.
  • font_size = Число —Размер шрифта на кнопке относительно масштаба ее объекта.
  • scale = Число —Масштаб кнопки по отношении к ее объекту.

Таблицы в LUA — это динамический гетерогенный ассоциативный массив, то есть множество пар (ключ-значение). Вы можете хранить все что угодно внутри таблицы и ссылаться на нее позже в таблице по имени или по номеру индекса (в LUA нумерация индексов в таблице начинаются с 1). Все таблицы обозначаются фигурными скобками <>. Мы создадим таблицу прямо под тем, где мы установили наши GUID, а затем заполнили ее записями для использования с функцией createButton(table). Название, которое мы выбираем для нашей таблицы это button_parameters.
button_parameters = <> button_parameters.click_function = ‘buttonClicked’ button_parameters.function_owner = nil button_parameters.label = ‘Press Me’ button_parameters.position = <0,0.8,0>button_parameters.rotation = button_parameters.width = 500 button_parameters.height = 500 button_parameters.font_size = 100

Теперь мы имеем параметры, то есть таблицу со значениями. Используем функцию объекта, чтобы создать кнопку на шашке. Запишите это внутри функции onload().
function buttonClicked() print(‘Learning is fun. Sort of.’) —Обучение — это весело. Вроде. end

Повторно нажимайте его, потому что, конечно, вы это сделаете.

button_parameters = < click_function=’buttonClicked’, function_owner=nil, label=’Press Me’, position=<0,0.8,0>, rotation=, width=500, height=500, font_size=100 >

EXTRA CREDIT: Это идеальный момент для начала игры с разными вещами, которые вы можете делать с объектами. Перейдите на страницу «Объект» в Knowledge Base и попробуйте материал. Двигайте объекты, заставляйте их переключаться на позиции, менять их цвета, что бы вы ни думали.

EXTRA CREDIT: Кроме того, при каждом нажатии кнопки функция click_function запускается с двумя параметрами. Первая — это ссылка на объект, в частности ссылка на объект, к которому привязана кнопка. Второй — это цвет (например, «Blue» — синий) в строчном формате цвета игрока, который нажал на кнопку.

if CONDITION then —Активируется если условие было истинно end

Вы также можете добавить к нему «else», так что если утверждение ложно, вместо этого происходит что-то ДРУГОЕ (else). Обратите внимание, что я добавил комментарии, используя два минуса подряд. Компилятор игнорирует что-либо на линии после —.

if CONDITION then —Активируется если условие было истинно else —Активируется если условие было ложно end
Мы попробуем несколько из них. Удалите текущее содержимое в Вашей функции buttonClicked(). Теперь введите в эту функцию следующие утверждения:
if 5 > 6 then print(«5 is greater than 6») —5 больше, чем 6 end if 6 > 5 then print(‘6 is greater than 5’) —6 больше, чем 5 end if 5 == 0 then print(«Five is equal to ZERO?!») —Пять равно НУЛЮ?! else print(«No, five isn’t equal to zero.») —Нет, пять не равно нулю. end
Еще раз удалите все скрипты внутри функции buttonClicked(). Мы собираемся создать новую переменную, а затем изменить ее. Новая переменная будет булевского типа. Булевские значения могут быть только true, false. Булевские значения всегда записываются маленькими буквами. Во-первых, мы создадим нашу переменную под нашим идентификатором GUID объектов и шашки.
Затем, в buttonClicked, мы установим некоторую логику, чтобы проверить, истинно ли значение trueOrFalse. Если оно истинно, то будет печатать, что это Истина, и переключит его на Ложь. Если кнопка снова нажата, будет печатать, что это Ложь, и переключит значение на Истина.
if trueOrFalse then print(‘trueOrFalse was true.’) —trueOrFalse была истина. trueOrFalse = false else print(‘trueOrFalse was false.’) —trueOrFalse была ложна. trueOrFalse = true end

Мы могли бы также написать это так «if trueOrFalse == true then», но это необязательно. Помните, что оператору IF нужно передать булевское значение. И так как trueOrFalse уже является одним из таких, мы можем отпустить «== true».

Цикл — это секция кода, которая могут запускаться несколько раз. Это один из более сложных элементов, которые Вы будете использовать в LUA. Они часто идут со таблицами, позволяя запускать код для каждой записи в таблице.

Какой вывод будет после нажатия на кнопку:

Какой вывод будет после нажатия на кнопку:

Это ещё один тип – ipairs. Pairs нужны для таблиц без числовых ключей, а ipairs нужны для таблицы с последовательными числовыми ключами (массивы). ipairs идет по порядку, когда pairs может идти в любом порядке.

Чтобы написать скрипт непосредственно в объект, щелкните на него правой кнопкой мыши, перейдите в Scripting и выберите Lua Editor (если Вы используете Atom, это откроет для него окно в Atom).

Когда вы пишете код здесь, это похоже на global. За исключением случаев, когда Вам нужно ссылаться на объект, частью которого является скрипт, вы просто пишете «self». Чтобы создать кнопку на себе, вы должны использовать self.createButton(table_of_paramiters).

Надеюсь, что это введение в LUA помогло Вам лучше понять некоторые основные механики написания скриптов.

Помните, что в Knowledge Base есть информация обо всех функциях, которые входят в состав Tabletop Simulator. Это и базовая практика с if, else, then и for позволит Вам выполнить большинство всего, что Вы хотите. Удачи.

[Samp gamemode creation] Обзор языка программирования PAWN (часть 1)

Laine_prikol

Итак, сегодня я буду рассказывать как я пишу мод на SAMP. Для начала разберёмся на каком языке пишут скрипты и моды для SAMP. Моды и скрипты в SAMP пишутся на языке PAWN. Pawn — это С-подобный скриптовый язык (как и lua) но, в отличии от lua, в Pawn скрипты именно компилируются,в байт код для запуска на абстрактной машине, а не интерпретируются как в Lua. Скажите — ну и что это даёт? А даёт это многое, например: компилятор pawn ещё до выполнение скрипта проверяет на наличие ошибок, и поэтому у вас никогда не будет внезапных ошибок в программе, также ещё скорость работы скрипта больше чем в том-же Lua, потому-что код скомпилирован в сразу понятный для машины код. Что такое pawn мы разобрались.

blogentry-0-0-77889400-1489396166_thumb.jpg

Давайте разберёмся с средой разработки, если в lua мы могли писать скрипты хоть в блокноте, то теперь нам нужна полноценная среда разработки.
1. Pawno — Очень простой редактор, в есть необходимый минимум чтобы писать скрипты на pawn.

Плюсы:
+ Малый размер (768 кб)
+ Идёт сразу с Samp server
+ Встроенный список функций из всех инклудов
+ Сразу есть все паблики и функции samp
Минусы:
— Подсветка синтаксиса сделана чисто для галочки (всего два цвета подсветки синий и чёрный )
— На windows начиная с vista надо запускать от имени админа
— На больших скриптах может вылетать

blogentry-0-0-87342400-1489396159_thumb.jpg

2. Notepad++ (с плагином nppexec) — Самый популярный редактор скриптов. Поддерживает плагины, и также множество языков программирования

Плюсы:
+ Расширяемость
+ Нормальная подсветка синтаксиса
+ Авто-табуляция кода
+ Удобная навигация по коду (можно сразу перейти к другой строке, и есть карта документа)
Минусы:
— Для поддержки pawn надо много чего настраивать.
— Нету Встроенного списока функций
Настройка плагина NppExec:
1. Выберите plugin manager

2. Откроется окно, ищём Nppexec, выбираем галочкой, жмём install, перезапускаем.

3. Должно появится в меню пункт, жмём

4. Откроется окно

вписываем туда код:

5. Нажимаем ok и компиляция начнётся

Но давайте перейдём к написанию программы «hello world!» Как я и сказал у меня samp вариант Pawn. Вот как выглядит hello world в pawn:

blogentry-18530-0-49339800-1489396885_thumb.jpg

Компилируем:

Как видим, всё прошло успешно и компиляция завершена.

Рекомендуемые комментарии

Полезные ресурсы

Голосование

Информация

О нас

У нас играют и пишут программы как новички так и опытные программисты. А самые продвинутые участники нашего коллектива даже разрабатывают собственные авторские моды и аддоны, ресурспаки, репозитории, мощнейшие библиотеки и операционные системы.

11 комментариев

Читайте также:

      

  • Где делать смазку для полозьев клондайк
  •   

  • Как сделать гаечный ключ в industrial craft 2
  •   

  • Я что похож на абонента soda luv
  •   

  • Journey to the savage planet где лежат сохранения
  •   

  • Записи шакала far cry 2 где

Понравилась статья? Поделить с друзьями:
  • Как написать свой bootloader
  • Как написать свой bios
  • Как написать свой api на python
  • Как написать свой ahk скрипт
  • Как написать своими словами резюме