Lua — всё по этой теме для программистов


Оглавление (нажмите, чтобы открыть):

Встраивание Lua для поддержки скриптов в приложениях

Реализация поддержки скриптов в приложениях при помощи небольшого специализированного языка

Lua — маленький скриптовой язык. Насколько маленький? Например, в нем вместо регулярных выражений POSIX используются особые механизмы поиска по шаблону, так как полная реализация регулярных выражений значительно превышает все его вместе взятые стандартные библиотеки. Более простой поиск в строках, предоставляемый Lua, хотя и не такой мощный, но занимает значительно меньше места.

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

Один из самых неожиданных моментов для программистов, ранее работавших с другими языками — в Lua ложными значениями считаются только false и nil ; любое значение небулевого типа в условных выражениях всегда считается истинным. Хотя этот подход может удивить людей, привыкших к соглашениям C, таким как использование 1 и 0 вместо true и false , к нему легко привыкнуть.

Lua написана на переносимом C; также его можно использовать с C++. Основа языка очень хорошо переносима; хотя некоторые возможности и требуют поддержки со стороны платформы, Lua хорошо работает без платформенных зависимостей. У этого языка нет огромного набора тестов Autoconf, так как он следует стандарту. Lua распространяется на условиях лицензии MIT и может бесплатно использоваться для любых целей, в том числе коммерческих. (Вероятно, это послужило причиной того, что многие программисты встраивают этот язык в свои приложения.)

Зачем встраивать язык?

Встраивание скриптового языка дает ряд преимуществ. Я приведу пример, с которого начал работать с Lua: это многопользовательская ролевая онлайн-игра World of Warcraft (WoW) от компании Blizzard. Пользовательский интерфейс WoW полностью реализован на Lua; разработчики предоставили несколько базовых вызовов прикладного программного интерфейса для непосредственного взаимодействия с движком отрисовки и запроса данных о мире, а затем использовали Lua для основной части кода пользовательского интерфейса.

Это значительно упрощает отделение кода пользовательского интерфейса от внутренностей игры, повышает безопасность и читаемость. В свою очередь, Blizzard в результате получила возможность открыть пользовательский интерфейс для игроков и позволить им писать свой код для изменения процесса взаимодействия с игрой.

В целом во многих задачах со скриптовыми языками работать проще, нежели с низкоуровневыми языками. Язык со сборкой мусора, неявным выделением памяти и ассоциативными массивами часто дает более простой код, который разрабатывается быстрее. Он может и не работать быстрее, но во многих случаях это не проблема; так, пользовательские интерфейсы лишь должны работать быстрее, чем пользователь вводит информацию с помощью клавиатуры или мыши.

Есть несколько способов использования скриптовых языков. Первый и самый простой — использовать язык для управляющей логики, считая код на C деталями реализации программы, которая на самом деле написана на Lua. Второй способ — писать программу в основном на C, после чего встроить Lua как механизм хранения и передачи данных и настроек. Третий и самый гибкий способ — совмещать предыдущие два, используя для скриптования определенных действий Lua, а для работы с другими — код на C. Простой интерфейс между Lua и C способствует использованию такого подхода.

Написание движка

Создание движка для программы, которая будет в основном писаться на Lua, имеет смысл в том случае, когда основное использование процессорного времени сосредоточено в отдельных операциях, а высокоуровневое управление относительно легковесно. Это помогает отделить детали реализации от высокоуровневой конструкции. Реализация основной логики программы на Lua, а не на C, может очень значительно уменьшить время разработки.

При таком подходе к проектированию основная часть интерфейса между Lua и C состоит из определения функций C для вызова из Lua, и ожидается, что после начала выполнения всё последующее использование кода на C будет сводиться к вызовам из скрипта.

Скриптуемые файлы настроек

Каждый знакомый мне программист написал хотя бы один блок кода, который занимается только тем, что пытается сохранить настройки в файле и позднее их восстановить. (Пожалуй, из того, что я использовал, мне больше всего нравятся списки свойств, разработанные Apple.) Однако в качестве формата таких файлов может использоваться встроенный скриптовой язык, что даст пользователям прекрасный набор возможностей для настройки. Так, оконный менеджер Ion использует Lua для своих файлов, позволяя пользователям создавать мощные и гибкие настройки.

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

Сочетание подходов

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

Для пользовательского интерфейса World of Warcraft в основном используется именно такая модель: вызовы Lua в пользовательском интерфейсе могут совершать обратные вызовы движка, а движок доставляет события коду пользовательского интерфейса, написанному на Lua. В результате получается гибкий интерфейс с хорошим разделением и безопасностью, что дает пользователям широкие возможности при небольшом риске вызвать выход за границы буфера в приложении или аварийное завершение работы программы. В прикладных программных интерфейсах, основанных на C, встроенный код практически всегда будет иметь возможность создания аварийной ситуации; если же пользовательский код может вызвать сбой при использовании интерфейса на Lua, это означает, что в программе имеется ошибка, которую нужно исправить.

Сборка и использование Lua

Сборка Lua происходит просто, достаточно выполнить make ; если нет желания использовать специфические возможности платформы, то можно указать posix или даже ansi После сборки получаем библиотеку liblua.a, которую можно связывать со своими программами. Поздравляю, Lua встроен! Конечно, практическое использование языка требует немного больше работы.

Реентерабельность Lua обеспечивается тем, что состояние интерпретатора сохраняется в объекте; можно иметь несколько интерпретаторов, которые не используют переменные совместно, и у них не будет общих глобальных сущностей. Для взаимодействия с интерпретатором нужно сначала создать состояние Lua:

lua_State *l;
l = lua_open();

Если вызов lua_open() происходит неудачно, возвращается нулевой указатель. В остальных случаях мы получаем пригодное для использования состояние интерпретатора Lua. Конечно, без библиотек от него мало проку. Добавить стандартные библиотеки можно при помощи функции luaL_openlibs() :

Теперь состояние Lua готово к исполнению кода. Вот пример цикла программы, просто выполняющей свои аргументы как код на Lua:

Листинг 1. Выполнение аргументов в качестве кода на Lua

Функция luaL_loadbuffer() компилирует скрипт в код Lua; если имеется синтаксическая ошибка, то сообщение о ней возвращается в стек. Иначе скомпилированный код может исполняться при помощи функции lua_pcall() . Опять же, если возникают ошибки, то они возвращаются в стек. Заметьте, что каждый вызов интерпретатора Lua на языке C, либо относящийся к интерпретатору, принимает в качестве аргумента состояние Lua; состояния по умолчанию нет.

Как работает стек Lua

Интерпретатор Lua использует для взаимодействия с вызывающим кодом стековый интерфейс. Передаваемые коду на Lua данные помещаются в стек кодом на C; ответы интерпретатора Lua также помещаются в стек. Если функции luaL_loadbuffer() передан неправильный код, то в стек помещается сообщение об ошибке.

Элементы стека имеют типы и значения. Функция lua_type() запрашивает тип объекта, а функции lua_to () (например, lua_tostring() ) дают значения, приведенные к определенному типу C. Код, написанный на Lua, всегда строго подчиняется модели стека. Однако код на C может просматривать остальную часть стека и даже вставлять туда значения.

Это простой, но удивительно мощный интерфейс. Работа с кодом для исполнения ведется таким же образом: он просто помещается в стек для выполнения функцией lua_pcall() .

Использование lua_pcall()

Функция lua_pcall() принимает три аргумента помимо состояния Lua, с которым работает. Код для выполнения не входит в эти аргументы, он помещается в стек при помощи luaL_loadbuffer() либо другой функции, получающей код. Вместо этого lua_pcall() принимает количество аргументов в стеке для передачи выполняемому коду, количество ожидаемых результатов и необязательный обработчик ошибок. Чтобы вызвать функцию, вы помещаете в стек сначала ее, а затем принимаемые аргументы (по порядку). Возвращенные значения помещаются в том же порядке; первое значение лежит в нижней части стека, а последнее — наверху.

И при отправке аргументов, и при получении возвращенных значений Lua без предупреждений корректирует количество значений таким образом, чтобы оно соответствовало числам, переданным lua_pcall() : если полученных значений недостаточно, то остальные заполняются значениями nil ; если получены лишние значения, то они просто отбрасываются. (Точно так же в Lua работает множественное присваивание.)

Обработчиком ошибок, если таковой указан, служит индекс в стеке с кодом Lua для обработки любых возникающих ошибок. В этой обзорной статье мы опустим подробное обсуждение обработки ошибок; для начала важно знать, что обработка ошибок есть и что она реализуется на Lua. Это очень удобно.

Встраивание C в Lua

Писать функции на C для последующего использования в Lua очень просто. Вы будете этим удивлены, если когда-либо писали код для встраивания другого скриптового языка. Вот функция на C, которая принимает число x и возвращает x + 1 :

Листинг 2. Функция на C для использования в Lua

Функция вызывается с состоянием Lua в качестве аргумента; всё взаимодействие между C и Lua опять производится через стек состояния Lua. Возвращаемое значение — количество объектов, помещенных функцией в стек. Чтобы сделать эту функцию доступной в Lua, вы должны создать представляющий ее объект Lua и присвоить ей имя:

lua_pushcfunction(L, l_ink);
lua_setglobal(L, «ink»);

Функция lua_pushcfunction() используется для преобразования указателя на функцию C во внутренний объект Lua. Этот объект, разумеется, помещается в стек. Далее функция lua_setglobal() присваивает верхнее значение стека именованной глобальной переменной. Так как функции в Lua являются просто значениями, при этом создается доступная для вызова функция.

Стековый интерфейс значительно всё упрощает: нет необходимости определять или объявлять аргументы, которые принимает функция. Это не дало бы ничего, так как Lua очень гибко подходит к вызову кода. Однако при желании вы можете более аккуратно проверить некоторые моменты. Функция luaL_checknumber() также может использоваться для проверки аргументов, вывода информативного сообщения об ошибке и прекращения выполнения функции.

Правильное использование встроенного скриптования

Простота встраивания Lua в код на других языках (в особенности на C) делает его простым средством для значительного улучшения функциональности программ на других языках. Это доступная альтернатива созданию собственного языка настроек или написанию своего анализатора выражений.

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

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

Ресурсы для скачивания

Похожие темы

  • Оригинал статьи Embed Lua for scriptable apps (EN).
  • На сайте Lua Users перечислены известные примеры использования Lua, особенно в видеоиграх, так как доступное для пользователей скриптование — прекрасный способ заинтересовать сообщество разработчиков модификаций и дополнений.(EN)
  • Первое издание книги Programming in Lua можно бесплатно прочитать в сети. (EN)
  • Пожалуй, World of Warcraft собрал больше Lua programmers, чем любое другое ПО. (EN)
  • Celestia может управляться при помощи скриптов на Lua. (EN)
  • В разделе Linux на сайте developerWorks, есть дополнительные ресурсы для Linux-разработчиков (в том числе и для новичков), а также можно просмотреть наши наиболее популярные статьи и учебные материалы.
  • Изучите другие советы и руководства по Linux на сайте developerWorks.
  • Ознакомительные версии ПО IBM: используйте в вашем следующем проекте программное обеспечение, которое можно загрузить непосредственно с сайта developerWorks. (EN)

Комментарии

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

Santa Simplicita

Просто писать о простом — не так и просто…

Lua за 60 минут

Я сентиментальный программист. Иногда я влюбляюсь в языки программирования, и тогда я могу говорить о них часами. Одним из этих часов я поделюсь с вами.

Lua? Что это?

Lua — простой встраиваемый язык (его можно интегрировать с вашими программами, написанными на других языках), легкий и понятный, с одним типом данных, с однообразным синтаксисом. Идеальный язык для изучения.

Зачем?

Lua может вам пригодится:

* если вы геймер (плагины для World of Warcraft и множества других игр)
* если вы пишете игры (очень часто в играх движок пишут на C/C++, а AI — на Lua)
* если вы системный программист (на Lua можно писать плагины для nmap, wireshark, nginx и других утилит)
* если вы embedded-разработчик (Lua очень быстрый, компактный и требует очень мало ресурсов)

Что надо для того, чтобы читать дальше?

1. Научитесь программировать. Хотя бы немного. Не важно на каком языке.
2. Установите Lua. Для этого либо скачайте здесь версию 5.2 (https://www.lua.org/download.html), либо ищите ее в репозиториях. Версия 5.1 тоже пойдет, но знайте, что она очень старая.

Все примеры из статьи запускайте в терминале командой наподобие «lua file.lua».

Первые впечатления

Lua — язык с динамической типизацией (переменные получают типы «на лету» в зависимости от присвоенных значений). Писать на нем можно как в императивном, так и в объектно-ориентированном или функциональном стиле (даже если вы не знаете как это — ничего страшного, продолжайте читать). Вот Hello world на Lua:

Что уже можно сказать о языке:

* однострочные комментарии начинаются с двух дефисов «—»
* скобки и точки-с-запятыми можно не писать

Операторы языка

Набор условных операторов и циклов довольно типичен:

ПОДУМАЙТЕ: что может означать цикл «for i = 1, 10, 2 do . end» ?

В выражениях можно использовать такие вот операторы над переменными:

* присваивание: x = 0
* арифметические: +, -, *, /, % (остаток от деления), ^ (возведение в степень)
* логические: and, or, not
* сравнение: >, =,

= (не-равно, да-да, вместо привычного «!=»)
* конкатенация строк (оператор «..»), напр.: s1=»hello»; s2=»world»; s3=s1..s2
* длина/размер (оператор #): s=»hello»; a = #s (‘a’ будет равно 5).
* получение элемента по индексу, напр.: s[2]

Битовых операций в языке долгое время не было, но в версии 5.2 появилась библиотека bit32, которая их реализует (как функции, не как операторы).

Типы данных

Я вам соврал, когда сказал что у языка один тип данных. Их у него много (как и у каждого серьезного языка):

* nil (ровным счетом ничего)
* булевы числа (true/false)
* числа (numbers) — без деления на целые/вещественные. Просто числа.
* строки — кстати, они очень похожи на строки в паскале
* функции — да, переменная может быть типа «функция»
* поток (thread)
* произвольные данные (userdata)
* таблица (table)

Если с первыми типами все понятно, то что же такое userdata? Вспомним о том, что Lua — язык встраиваемый, и обычно тесно работает с компонентами программ, написанными на других языках. Так вот, эти «чужие» компоненты могут создавать данные под свои нужды и хранить эти данные вместе с lua-объектами. Так вот, userdata — и есть подводная часть айсберга, которая с точки зрения языка lua не нужна, но и просто не обращать внимания на нее мы не можем.

А теперь самое важное в языке — таблицы.

Таблицы

Я вам снова соврал, когда сказал, что у языка 8 типов данных. Можете считать что он один: всё — это таблицы (это, кстати, тоже неправда). Таблица — это очень изящная структура данных, она сочетает в себе свойства массива, хэш-таблицы («ключ»-«значение»), структуры, объекта.

ПОДУМАЙТЕ: чему равно a[2] в случае разреженного массива?

В примере выше таблица ведет себя как массив, но на самом деле — у нас ведь есть ключи (индексы) и значения (элементы массива). И при этом ключами могут быть какие угодно типы, не только числа:

Кстати, раз уж у таблицы есть ключи и значения, то можно в цикле перебрать все ключи и соответствующие им значения:

А как же объекты? О них мы узнаем чуть позже, вначале — о функциях.

Функции

Вот пример обычной функции.

Функции языка позволяют принимать несколько аргументов, и возвращать несколько аргументов. Так аргументы, значения которых не указаны явно, считаются равными nil.

ПОДУМАЙТЕ: зачем может понадобиться возвращать несколько аргументов?

Функции могут принимать переменное количество аргументов:


Поскольку функции — это полноценный тип данных, то можно создавать переменные-функции, а можно передавать функции как аргументы других функций

Объекты = функции + таблицы

Раз мы можем сохранять функции в переменных, то и в полях таблиц тоже сможем. А это уже получаются как-бы-методы. Для тех, кто не знаком с ООП скажу, что основная его польза (по крайней мере в Lua) в том, что функции и данные, с которыми они работают находятся рядом — в пределах одного объекта. Для тех, кто знаком с ООП скажу, что классов здесь нет, а наследование прототипное.

Перейдем к примерам. Есть у нас объект, скажем, лампочка. Она умеет гореть и не гореть. Ну а действия с ней можно сделать два — включить и выключить:

А если лампочку сделать объектом, и функции turn_off и turn_on сделать полями объекта, то получится:

Мы вынуждены передавать сам объект лампочки в качестве первого аргумента, потому что иначе наша функция не узнает с какой именно лампочкой надо работать, чтобы сменить состояние on/off. Но чтобы не быть многословными, в Lua есть сокращенная запись, которую обычно и используют — lamp:turn_on(). Итого, мы уже знаем несколько таких упрощений синтаксиса:

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

Специальные функции

Некоторые имена функций таблиц (методов) зарезервированы, и они несут особый смысл:

* __add(a, b), __sub(a, b), __div(a, b), __mul(a, b), __mod(a, b), __pow(a, b) — вызываются, когда выполняются арифметические операции над таблицей
* __unm(a) — унарная операция «минус» (когда пишут что-то типа «x = -x»)
* __lt(a, b), __le(a, b), __eq(a, b) — вычисляют результат сравнения ( «+» , а не «..» . Для этого надо подменить функцию «+» (__add) для родительской таблицы всех строк:

Собственно, мы еще можем заменить функцию print с помощью «print = myfunction», да и много других хакерских дел можно сделать.

Области видимости

Переменные бывают глобальные и локальные. При создании все переменные в Lua являются глобальными.

Для указания локальной области видимости пишут ключевое слово local:

Не забывайте об этом слове.

Обработка ошибок

Часто, если возникают ошибки, надо прекратить выполнение определенной функции. Можно, конечно, сделать множество проверок и вызывать «return», если что-то пошло не так. Но это увеличит объем кода. В Lua используется что-то наподобие исключений (exceptions).

Ошибки порождаются с помощью функции error(x). В качестве аргумента можно передать все, что угодно (то, что имеет отношение к ошибке — строковое описание, числовой код, объект, с которым произошла ошибка и т.д.)

Обычно после этой функции вся программа аварийно завершается. А это надо далеко не всегда. Если вы вызываете функцию, которая может создать ошибку (или ее дочерние функции могут создать ошибку), то вызывайте ее безопасно, с помощью pcall():

Стандартные библиотеки

Стандартных библиотек мало, зато это позволяет запускать Lua где угодно. Подробнее можно получить их список здесь — https://www.lua.org/manual/5.2/manual.html

Нестандартных библиотек много, их можно найти на LuaForge, LuaRocks и в других репозиториях.

Между Lua и не-Lua

ВНИМАНИЕ: эту часть рекомендуется читать людям со знанием языка C.

А если нам недостаточно функциональности стандартных библиотек? Если у нас есть наша программа на C, а мы хотим вызывать ее функции из Lua? Для этого есть очень простой механизм.

Допустим, мы хотим создать свою функцию, которая возвращает случайное число (в Lua есть math.random(), но мы хотим поучиться). Нам придется написать вот такой код на C:

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

А если мы хотим вызывать код, написанный на Lua из наших программ? Тогда наши программы должны создавать виртуальную машину Lua, в которой и будут выполняться Lua-скрипты. Это намного проще:

Вы теперь можете писать на Lua. Если вы узнаете интересные моменты про Lua, которые можно было бы отразить в статье — пишите!

API/Язык программирования Lua

Урок 2. Язык программирования Lua

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

Вступление

Игра почти полностью написана на скриптовом языке программирования Lua. По крайней мере та её часть, что отвечает за логику игры, т.е. за самое интересное. Так как исходный текст скриптов доступен, то, зная Lua, можно с лёгкостью читать эти тексты и понимать, как устроена игра. Более того, можно легко менять игру или писать дополнения в виде модов к игре.

Сам язык Lua — простой. Его можно сравнить с алфавитом из нескольких десятков букв. Однако то, как устроена игра — более сложно. Устройство игры можно сравнить со словарем, где тысячи слов. Эти слова не обязательно учить все, т.к. сама игра является как бы словарем, и можно заглянуть в её код и посмотреть (прочитать), как устроен тот или иной аспект. Тем не менее, алфавит знать обязательно. Это сэкономит кучу времени, которое в противном случае придется тратить на догадки, пустые пробы, неудачи, а также на то, чтобы вам подсказали более опытные люди.

В статье речь пойдет, в основном, о самом языке Lua в настолько простом и адаптированном для простых людей виде, насколько это возможно. Однако придется упомянуть также и о том, как устроена игра, потому что понять проще на живом примере, т.е. на самой игре.

Ссылки

Хочу напомнить, что в Интернете полно информации о Lua. Этот язык позиционируется, как один из самых простых. Его предназначение — встраивание в игры и приложения, т.е. он предназначен не для разработчиков, а для пользователей, чтобы они могли добавлять желаемые эффекты в целевое приложение. Почти также дело обстоит и с Don’t Starve, с тем лишь исключением, что почти вся игра написана на Lua. И это круто.

Все материалы легко гуглятся по запросам вроде «lua» или «учебник луа» и т.п.

Хороший (лучший) учебник называется «Programming in Lua», автор Роберто Иерусалимский (Roberto Ierusalimschy). Первоисточник на английском, но читается очень легко, даже если вы плохо знаете английский. Первое издание про Lua 5.0, второе — Lua 5.1, третье — Lua 5.2. Игра использует 5.1, хотя это не принципиально для общего знания языка. Третье издание переведено на русский язык. Можете скачать (с трекера) или купить. В общем, Гугл вам в помощь.

Консоль и лог

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

(тильда) на клавиатуре. На этой же клавише находится буква «ё». Этой клавишей можно открывать и закрывать консоль, которая представляет из себя длинное поле для ввода.

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

Также вы увидите какой-то странный технический тект. Его можно отдельно включать и выключать, нажав Ctrl+L. Это стандартный вывод языка Lua, в нашем случае — игровой лог, куда скрипты «сообщают» разную полезную отладочную информацию или ошибки. Этот лог также пишется в реальном времени в файл (log.txt, либо client_log.txt, либо server_log.txt, в зависимости от версии и типа игры). Поэтому в случае очередного краша по вине вашего мода не спешите перезапускать игру — загляните в лог файл, там может быть полезная информацию об ошибке.

Таким образом, консоль и игровой лог вместе составляют простейший, но в то же время удобный, инструмент для тестирования скриптов и обучения языку Lua. Консоль — для ввода команд. Лог — для просмотра результата работы наших скриптов.

Более подробно читайте в статье про консоль. Там же вы найдете множество команд. Обычно они используются в читерных целях. Но для нас важно, что все они на языке Lua. Да, именно так, в консоль можно копипастить целые скрипты, и они должны работать.

Lua правда работает?

Для начала давайте проверим самые-самые простые примеры, что они работают. Одна из самых простых функций языка называется print. Она просто выводит в лог всё, что вы ей передаете в качестве параметров. Введите в консоль:

Посмотрите лог. Появилась ли там цифра 123? Если да, то наш первый скрипт в одну строчку работает. Иначе вы что-то делаете не так.

Попробуйте другой пример:

Какую цифру это показало в логе? Оу, это уже что-то новенькое! Lua умеет считать? Конечно умеет! Это же язык программирования.

В дальнейшем нам очень поможет функция print для отладки наших модов и изучения игры. Запомните эту функцию.

Небольшое замечание. Хотя рановато для таких замечаний, но всё же. Функция не выведет ничего, если передать ей пустое значение или несуществующую переменную. Порой это может путать. Может показаться, что сама функция не работает. Было бы удобно сделать функцию-обертку, которая выводит сообщение об ошибке, но это вы уже сами сделаете, когда выучите язык. Например, такой вариант ничего не выведет:

В этом примере abcdef не является строкой, т.к. без кавычек. А переменной с таким именем не существует.

По мере изучения с помощью консоли постоянно пытайтесь сломать игру и сам язык Lua. Только так можно лучше его понять и запомнить. Постепенно вы начнете импровизировать в своих работах, тогда начнется настоящее творчество. Например, для начала попробуйте поделить на ноль. Или сложить строку и число. Так можно или нельзя? Для каждого языка программирования — свой ответ. Консоль ответит на любые вопросы про Lua.

Память и переменные

Надеюсь, не нужно объяснять, что такое переменная. Алгебра у вас была еще в школе, так что вы в курсе, что это такое.

Оу, это что-то новенькое! Lua умеет запоминать? Конечно умеет! Это же язык программирования!

Когда вы пишете

то Lua создает переменную и помещает туда значение, которое находится справа от знака «=». Это может быть сложное выражение. Сам знак «=» называется оператором присваивания. Если такой переменной не существовало, то она создается автоматически. В отличие от других языков, нигде не нужно отдельно указывать, что мы хотим именно создать переменную.

И здесь начинаются опасности. Вы должны четко понимать, чего хотите добиться конкретной командой. Если вы здесь ошибетесь, то Lua не всегда сможет увидеть ошибку. Например, у вас уже есть переменная abcdef, а вы хотите присвоить ей новое значение:

Не заметили ошибку? И правда, ее сложно заметить. Мы случайно перепутали буквы d и c. В результате Lua создаст новую переменную, а в старой переменной abcdef будет храниться старое значение.

Вы можете создавать любые переменные, с любыми названиями. Например: a, b, c, x, y, x1, y1, xyz. Но лучше давать осмысленные названия. Это тоже важно, чтобы избежать ошибок. Например, количество здоровья лучше хранить в переменной с именем health. Но в игре не всё так просто, потому что есть много созданий, включая игрока, у которых есть своё здоровье. Поэтому нам нужно как-то структурировать всю эту информацию.

Таблицы

Это покажется странным, но таблица — один из самых простых и удобных способов структурировать информацию. Excel почти не изменился с прошлого века (еще с Windows 3.1). В Lua слово «таблица» используется в более узком значении и представляет из себя специфический тип данных. Как только вы поймете, что такое таблица, вы поймете 80% языка Lua. В школе это не проходили, но таблицы-то вы рисовали в жизни? Здесь нам понадобится лишь немного воображения.

Мастер Йода рекомендует:  Java снова на первом месте опубликован январский рейтинг языков программирования TIOBE

Введите в консоль:

Что это еще за фигурные скобки такие? Так в Lua обозначаются таблицы. Так как таблица — это данные, то ее можно поместить в переменную, что мы и сделали. В данном примере создается пустая таблица, в которой ничего нет, но всё же это таблица. Давайте посмотрим, что содержится в переменной x с точки зрения Lua:

Мда, вывело какое-то несуразное сообщение. Увы и ах, вывести таблицу в лог — это очень сложное действие и его нельзя сделать просто. Таблица может содержать разные данные, в том числе и другие таблицы. Как их вывести в виде текстовой строки? На этот вопрос нет ответа. Поэтому Lua поступает так: просто выводит информацию о том, что это таблица и адрес в памяти (он нам не нужен). Так что с помощью функции print вы можете узнать лишь сам факт того, что в переменной содержится таблица, но не ее содержимое.

Таблица — это список в виде пар ключ-значение. Чтобы поместить какую-то информацию в таблицу, нужно указать ключ и значение. Чтобы извлечь информацию, нужно указать ключ.

Чтобы лучше понять таблицы, поиграйте с ними в консоли. Сделайте таблицу, в которой отдельно хранятся ваши имя и возраст. Сделайте таблицу в таблице. Попробуйте сломать Lua, давая сложные команды, которые должны работать. А что если сработают? А что если нет? В любом случае вы получите опыт и понимание языка. Потратьте на это хотя бы 10 минут. Вот примеры, которые пока что оставляют вопросы (щупайте их функцией print):

Вот вам домашнее задание:

Что выведет? 5 или 7?

Таблицы являются основной изюминкой языка. Это единственный сложный тип данных в Lua. но с его помощью можно представить многие другие типы данных из других языков, такие как индексные массивы, ассоциативные массивы, объекты, классы и т.д. Таблицы Lua поддерживают прототипирование, как в JavaScript. А уже через него возможно полноценное ООП наследование. Последний абзац, скорее всего, прозвучал для вас, как китайский. Просто знайте, что это круто. 🙂

Игрок

Примечательно, что в игре каждый объект или даже свойство объекта представлено в виде таблицы.

Давайте поместим в переменную x объект, представляющий самого игрока (для этого нужно быть в игре).
Для DS:

Как видите, для сетевой и одиночной игры разные команды. С этим придется мириться, потому что игры разные, и это не единственное отличие. Хотя внутри устройство объектов очень схоже.
Теперь давайте выведем в лог текущее здоровье:

Что? Какая цифра? 150? Похоже на правду? Теперь попробуйте изменить здоровье. Пусть вас кто-то ударит или отравитесь слегка. И еще раз:

Это что-то новенькое! Получается, мы в наших скриптах теперь имеем доступ к здоровью персонажа. По секрету скажу, что currenthealth — это почти переменная. А если так, то ей можно присвоить значение? А ну-ка, проверим!

Здоровье персонажа поменялось? Поздравляю, теперь вы — читер!

Конечно, не всё так просто. В DST, например, у вас есть доступ лишь к локальным переменным, так что здоровье поменять не получится. А на выделенном сервере вообще нет переменной ThePlayer, потому что для каждого игрока нужна отдельная переменная. Эти нюансы вы узнаете сами, либо из следующих уроков. Пока что сосредоточимся на самом языке Lua.

Обратите внимание, что везде используются осмысленные названия. Например, currenthealth — текущее здоровье. Это важно, потому что в игре тысячи переменных, и нужно понимать смысл каждой из них. Будет лучше, если имена переменных будут интуитивно понятны. Klei Entertainment следует этому принципу, поэтому исходные тексты игры легко читаются. Вы также соблюдайте это правило, тогда вы не запутаетесь, а напротив — будете четко понимать, что есть что у вас в моде. Мод получится без ошибок и глюков. А если вы через полгода захотите что-то улучшить в своем моде, то без труда разберетесь в собственном творении. Пишите код так, словно читать будет другой человек, причем в 10 раз глупее вас.

Но что же значит эта длинная запись с точками «x.components.health.currenthealth»? Мы знаем, что x — это переменная-таблица, которая представляет игрока. А дальше? components — элемент таблицы x. И это тоже таблица. health — элемент таблицы components, и это тоже таблица. currenthealth — свойство таблицы health, и это уже, слава богу, не таблица.

Но так уж ли это сложно? Давайте подумаем. Игрок — по сути является набором неких свойств: координаты в игре, размеры, текущее действие, здоровье, сытость, текущий урон, текущая защита, (не)видимость, текущая анимация, звук и т.д. Понятно, почему игрока удобно представить в виде таблицы (а мы помним, что в Lua это просто список пар ключ-значение). Одно из таких свойств — таблица компонентов.

Разумно реализовать компоненты отдельно. Например, у игрока есть голод. Паук — тоже представлен в виде таблицы и у него тоже есть компонент здоровья. Но у паука нет компонента голода! Таким образом, мир состоит из объектов (игрок, паук, дерево, камень, трава, ягоды). И каждый объект состоит из компонентов — здоровье, голод, инвентарь, съедобность, время порчи, складываемость в стаки, созреваемость и т.д.

Каждый компонент, в свою очередь, может обладать какими-то свойствами. Для health — это текущее здоровье, максимальное здоровье, бессмертие, уровень врожденного поглощения, сопротивление огню, холоду, возможность применять лечилки и т.д.

Это архитектура игры, которую придумали Klei. И для ее реализации они использовали таблицы языка Lua.

Почему сделано так, а не иначе? Видимо потому, что это удобно. В игре тонны кода, который одному человеку весь не осилить, но зато можно без труда найти то, что нужно. В какой именно компонент поместить свойство — вопрос субъективный. Здесь стоит руководствоваться здравым смыслом и удобством понимания. Например, можно было бы сделать отдельный компонент бессмертия. Но логичнее и проще добавить булеву переменную в компонент здоровья, которая принимает значение лишь «да» и «нет». С другой стороны, это влечет дополнительные сложности. Например, компонент голода должен будет проверять наличие компонента здоровья и свойство бессмертия, но это уже издержки выбранной модели.

В своих модах вы сами планируете архитектуру и свое удобство так, чтобы не запутаться и чтобы всё было красиво.

По секрету скажу, что currenthealth — это не переменная. Мной было сказано, что это «почти переменная», потому что она ведет себя как переменная. Ей можно присваивать значение и получать значение. Но на самом деле это свойство, за которым спрятана функция. Эта функция осуществляет дополнительные проверки и передает значение «куда следует», чтобы в игре у вас и правда изменилось здоровье. Не обязательно вникать во все эти нюансы, чтобы пользоваться тем, что работает. А проверить, работает или нет, вы всегда можете с помощью консоли.

Глобальное пространство имен

Знать, что это такое, важно, чтобы понимать, что вы делаете и что вообще происходит на самом деле, когда ваш код работает.
Когда вы пишете:

То вы создаете переменную x, но где?

Когда вы учились в школе на уроке алгебры, вы тоже использовали переменную x в уравнениях. Однако в каждой задаче своя переменная x, в каждой контрольной, на каждом уроке. И это разные переменные x, хотя имя у них одно и то же.

В Lua точно также могут быть переменные x в разных местах, но это будут разные переменные. Мы сейчас не будем говорить про локальные переменные (которые как бы уничтожаются после использования за ненадобностью). Мы поговорим о глобальном пространстве, в котором хранятся все глобальные переменные, которые мы до сих пор создавали через консоль.

У мода в файле modmain.lua есть своё глобальное пространство имен. Если вы в modmain.lua напишете x = 5, то будет использовано пространство имен мода. У игры есть своё глобальное пространство. Поэтому не путайте их! Консоль работает в глобальном пространстве самой игры.

Что такое глобальное пространство имен? В Lua это просто таблица. Да, это просто-напросто таблица. Вы можете получить доступ к этой таблице, используя переменную _G. Проверьте, существует ли она:

Да, и правда существует. Эта переменная является частью стандарта языка. Постойте-ка, если это ссылка на глобальное пространство, а сама она тоже в глобальном пространстве, то получается, что она содержится в самой себе? Правда что ли? Давайте проверим:

Да, и правда существует. А ну-ка потроллим язык:


И это работает. Ну, всё логично по крайней мере. На всякий случай еще один тест «на вшивость».

Выводит true. Это означает, что элемент таблицы является в точности самой этой таблицей.
И еще один последний тест (догадайтесь, что именно он проверяет):

На всякий случай здесь мы проверяем, что нас не надули. Мы объявляем x в глобальном пространстве. Если _G и правда является ссылкой на глобал, то _G.x тоже будет равно 5.

Вот, примерно так нужно пытаться сломать язык, чтобы его понять на практике.

Теперь поговорим о modmain.lua и пространстве мода. В нем используется своё пространство, очищенное практически от всего лишнего. Оно даже не содержит переменной _G. Однако разработчики игры всё же предоставили некоторые ссылки. Самые важные из них — это env и GLOBAL. Переменная env указывает на пространство мода. Например эти две записи в modmain.lua делают одно и то же:

Переменная GLOBAL указывает на глобальное пространство имен самой игры. То есть если вы запускаете мод, в котором содержится следующее:

То мод создаст переменную в пространстве игры, и вы сможете затем в консоли набрать:

И увидите ожидаемый результат.

Все остальные файлы мода (например, свои компоненты, свои файлы префабов и любые файлы, подключенные через require) работают в пространстве игры. Исключение — файлы, подключенные с помощью функции modimport, но это уже тонкости. Пока сосредоточьтесь на синтаксисе языка.

Как протестировать многострочный скрипт

Это интересно. Есть несколько способов.

Можно банально — поместить его в modmain.lua, а затем запустить игру с включенным модом. В файле можно указывать разные print’ы, которые будут записывать подробности работы мода. Например:

После этого наличие строки «MOD BEGIN» в файле лога будет говорить о том, что мод сработал, а (внимание!) отсутствие этой строки будет говорить, что мод по каким-то причинам не сработал. Может быть, вы забыли его включить?

Второй простой способ — найти в стиме мод, улучшающий игровую консоль так, чтобы она принимала многострочный текст. Т.к. это всё же сторонний мод, то способ может оказаться не самым удобным. У всех вкусы разные.

Третий способ, указать файл для выполнения:

  1. В папке data игры создайте любой файл, например dst.lua
  2. В консоли наберите dofile(«dst.lua»)

Таким образом, всё содержимое файла будет немедленно выполнено. Если там содержится определение (замена) функции, то оно будет произведено повторно, так что осторожней с хуками на игровые функции (это такой типа хакерский прием в программировании, пока не берите в голову).

Если открыть консоль и нажимать клавиши вверх и вниз, то можно листать предыдущие команды. Так можно очень быстро снова выполнить dofile(«dst.lua») после редактирования файла. Пользуйтесь.

Заключение

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

  • Как вставлять комментарии прямо в скриптах.
  • Локальные переменные и области видимости.
  • Операторы языка Lua, в том числе операторы сравнения, ветвления, цикла.
  • Типы данных в Lua, в том числе особое значение nil.
  • Функции, в том числе важно знать о рекурсии и замыканиях и что переменным можно присваивать функции.
  • Классы в Lua (как они реализованы)

Всё это есть в статье «Lua за 15 минут». Она же, кстати, является lua файлом, т.е. скомпилируется без ошибок, если скопипастить в modmain.lua. Результат, правда, вы найдете только в файле логов. Но весь смысл раскрыт в комментариях. Это как бы самодокументируемый текст скрипта. И это хороший пример того, как нужно комментировать свои собственные скрипты, когда вы делаете сложные вещи.

Каждый нюанс языка, который вы узнаете из примера или учебника, сразу тестируйте на практике — в игровой консоли. Лучше делать это в отдельном файле, т.к. текстовый редактор удобнее. Пробуйте, тролльте язык, ломайте игру! Удачи!

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

DonHomka

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

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

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

Atom
Atom — полнофункциональный и свободно расширяемый бесплатный редактор кода, поддерживающий большое количество языков программирования, включая Lua. Для начала работы с Lua нужно установить сам Atom.
Скачать Atom
После установки Atom обязательно установить пакет для удобства редактирования скриптов.
Нажимаем Ctrl + , и переходим во вкладку Install, в поле поиска вводим «moonloader» и устанавливаем первый в списке пакет.

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

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

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

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

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

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

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

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

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

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

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

Базовые указания по повышению качества кода
Чтобы ваш код был чист и понятен, необходимо придерживаться некоторых правил, вот основные из них:

  • Добавляйте информацию о скрипте при помощи директив
  • Соблюдайте единый стиль кода
  • Соблюдайте табуляцию (отступы)
  • Отделяйте блоки кода логически: пустые строки между функциями и блоками переменных, пробелы между блоками кода, осуществляющими логически завершённое действие и т.д.
  • Называйте переменные и функции внятными именами
  • Комментируйте неочевидные участки кода

Следование этим простым правилам уже значительно повлияет на качество вашего кода, но если вам этого не хватает, вот отличный гайд по стилю кода (на английском): luarocks/lua-style-guide (https://github.com/luarocks/lua-style-guide)

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

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

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

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

Создание модулей
Модули делятся на два типа: Lua и DLL. Lua-модули пишутся, как вы уже могли догадаться, на языке Lua и в результате представляют из себя привычные Lua-скрипты, только с некоторыми особенностями.
Давайте рассмотрим пример простого модуля, назовём его example:

Lua — всё по этой теме для программистов

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

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

593 уникальных посетителей
16 добавили в избранное

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

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

Во-первых, я бы настоятельно рекомендовал установить Atom, если вы собираетесь делать скрипты в Tabletop Simulator. Он знает, какие функции можно использовать и будет импортировать/экспортировать код в/из TTS.
Инструкции по установке и настройке Atom [berserk-games.com]

Затем Вы должны добавить в закладках Knowledge Base [berserk-games.com] . Вы будете часто ссылаться на этот сайт, как только начнете писать свои скрипты. Здесь Вы найдете специальные функции в Tabletop Simulator и как они работают. Вы чаще всего будете использовать страницы API и Object, по крайней мере, на моем опыте.

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

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

Для этого упражнения возьмите пустой стол и создайте два объекта (я использовал квадратный блок и прямоугольный блок), а также красную шашку.

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

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

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

Функции [berserk-games.com] – участок кода, который вызывается. Некоторые из них встроены в систему (например, onload()), а другие могут быть созданы пользователем. Каждая функция начинается со словом function и заканчивается словом end.

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

Команда print() также является функцией. Но вместо запуска секции кода в LUA она активирует уже встроенный в Tabletop Simulator для получения желаемого эффекта. В данном случае печатает сообщение хосту игры в чате.

Сообщение является строкой и всегда окружено кавычками, чтобы указать это. Строка представляет собой последовательность символов. (Пример: «Это строка» или ‘Это так!’). Когда Вы сохраняете и загружаете свой скрипт, теперь он должен печатать «Hello, World» в чат.

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

Objects [berserk-games.com] – физические объекты, которые существуют на столе. В нашем случае объектами являются два блока и шашка (какую ужасную игру мы делаем). Используя скрипты, мы можем манипулировать объектами, перемещать их, добавлять к ним кнопки или выполнять другие различные действия. Мы запускаем наш Global.lua заново. Удалите весь текст.

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

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

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

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

Extra Credit: Вам может быть интересно узнать, почему мы не поместили GU )). Мы могли бы, и это сработало бы. Этот пример должен был показать Вам, что иногда удобнее устанавливать переменную на раннем этапе, поэтому вы можете ссылаться на нее позже. Таким образом, если эта переменная должна измениться (новый GUID), Вам не нужно пытаться ее отслеживать, чтобы исправить ее во всем коде.

Мастер Йода рекомендует:  Агрегатор Exactis хранил данные 340 млн американцев в открытом доступе

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

Сохраните и примените свой код. Теперь у Вас должна быть кнопка, которая парит на несколько дюймов выше Вашей шашкой. Если вы не видите ее и не получили сообщение об ошибке, попробуйте перевернуть свою шашку. Она может быть перевернута, так что кнопка скрывается внутри стола! Если вы перевернули шашку, то не забудьте перезаписать сохранение с правильно установленной шашкой.

После загрузки нашего скрипта нажатие кнопки должно печатать наше сообщение один раз для каждого щелчка.

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

EXTRA CREDIT: Когда вы создаете таблицы, есть несколько способов сделать этоhttps://www.lua.org/pil/3.6.html. Способ, используемый здесь, заключался в том, чтобы обеспечить визуальную ясность. Однако такой способ создания параметров кнопки, как этот, занимает не мало места, если у Вас будет много кнопок. Я предпочитаю создавать свои таблицы таким образом, чтобы экономить место, но не выходить за правых край. Используя наш пример, я бы создал таблицу параметров следующим образом:

button_parameters = < click_function='buttonClicked', function_owner=nil, label='Press Me', position=<0,0.8,0>, rotation=<0,0,0>, w >EXTRA CREDIT: Это идеальный момент для начала игры с разными вещами, которые вы можете делать с объектами. Перейдите на страницу «Объект» в Knowledge Base и попробуйте материал. Двигайте объекты, заставляйте их переключаться на позиции, менять их цвета, что бы вы ни думали.

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

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

То, что вы размещаете в области, которую я обозначил CONDITION (условие) в этих примерах, называется условными операторами или операторами отношениями. [www.tutorialspoint.com] Используя их, вы можете сравнивать много вещей друг с другом. Они производят так называемое булевское (boolian) значение (переменное значение, которое является либо true — истина, либо false — ложь).

Эти строки выполнится, как кнопка будет нажата. Вы увидите, что напечатались только те сообщения, находящихся в ИСТИННЫХ утверждениях. Кроме того, поскольку 5==0 является ложным утверждением, он активировал print(), расположенны в «else» части логики.

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

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

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

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

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

Break
Break [www.lua.org] завершит цикл for, как только он будет выполнен. Например, если Вы добавили в свой числовой цикл for, сразу после его функции печати строка «if i==3 then break end», она закончит цикл после того, как напечатала 1, 2, 3.

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

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

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

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


Lua — всё по этой теме для программистов

Группа: Forum members
Сообщений: 350
Регистрация: 26.12.2006
Из: TTK(Domolink)
Пользователь №: 11 559

function SaveToFile(file , table , tablename)
local handle = io.open(file,»w+»)
handle:write(Serialize(table, tablename))
handle:flush()
handle:close()
end

function Serialize(tTable, sTableName, sTab)
assert(tTable, «tTable equals nil»);
assert(sTableName, «sTableName equals nil»);
assert(type(tTable) == «table», «tTable must be a table!»);
assert(type(sTableName) == «string», «sTableName must be a string!»);
sTab = sTab or «»;
sTmp = «»
sTmp = sTmp..sTab..sTableName..» = <\n"
for key, value in pairs(tTable) do
local sKey = (type(key) == «string») and string.format(«[%q]»,key) or string.format(«[%d]»,key);
if(type(value) == «table») then
sTmp = sTmp..Serialize(value, sKey, sTab..»\t»);
else
local sValue = (type(value) == «string») and string.format(«%q»,value) or tostring(value);
sTmp = sTmp..sTab..»\t»..sKey..» = «..sValue
end
sTmp = sTmp..»,\n»
end
sTmp = sTmp..sTab..»>»
—collectgarbage()
return sTmp
end

Группа: Forum members
Сообщений: 350
Регистрация: 26.12.2006
Из: TTK(Domolink)
Пользователь №: 11 559

Группа: Forum members
Сообщений: 177
Регистрация: 7.11.2006
Пользователь №: 10 132

Группа: Forum members
Сообщений: 350
Регистрация: 26.12.2006
Из: TTK(Domolink)
Пользователь №: 11 559

function IPAccessory(IP_zero,Cur_IP)
local AcIp = 1
local _,_,ZP1,ZP2,ZP3,ZP4 = string.find(IP_zero,»(%d*)%.(%S*)%.(%d*)%.(%d*)»)
local _,_,UP1,UP2,UP3,UP4 = string.find(Cur_IP,»(%d*)%.(%d*)%.(%d*)%.(%d*)»)
ZP1 = tonumber(ZP1) ZP3 = tonumber(ZP3) ZP4 = tonumber(ZP4)
UP1 = tonumber(UP1) UP2 = tonumber(UP2) UP3 = tonumber(UP3) UP4 = tonumber(UP4)
if tonumber(ZP2) then ZP2 = tonumber(ZP2) end

if (ZP2 == 0) or (ZP2 == UP2) then
AcIp = AcIp + 1
else
if string.find(ZP2,»%-«) then
local _,_,For,To = string.find(ZP2,»(%d*)%-(%d*)»)
For = tonumber(For) To = tonumber(To)
if (UP2 >= For) and (UP2 = CalculateIP(FromIP)) and (ResultIP X-Disa

Группа: Forum members
Сообщений: 837
Регистрация: 24.8.2006
Пользователь №: 8 755

Извращение. Будет дико лагать и тормозить при нагрузке. И никто не задает адреса регекспами. Либо прямой, либо по маске.

Все гораздо проще.

Имеем диапазон адресов, заданный A и B. И адрес C, который надо проверить.

Принадлежность = ipcalc© >= ipcalc(A) and ipcalc© Setuper

Группа: Forum members
Сообщений: 128
Регистрация: 27.8.2006
Пользователь №: 8 781

Группа: Forum members
Сообщений: 837
Регистрация: 24.8.2006
Пользователь №: 8 755

И если тебе надо пару сотен юзеров проверить будешь читать из файла каждый раз? И заново пересчитывать?

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

Группа: Forum members
Сообщений: 350
Регистрация: 26.12.2006
Из: TTK(Domolink)
Пользователь №: 11 559

Группа: Forum members
Сообщений: 837
Регистрация: 24.8.2006
Пользователь №: 8 755

Функциональнее, не спорю. Но. Зачем? Любой диапазон ип можно задать либо a1.b1.c1.d1 — a2.b2.c2.d2, либо a.b.c.d/z. Все остальное создает только лишние тормоза, и при куче юзеров это явно не принесет пользы)

Кстати, я нашел способ в несколько раз сократить объем трафика, при передаче MyInfo, когда например нужно динамически менять описание юзера.

Делается так — создается две таблицы. В таблицу A загоняется ник юзера при вызывании им события NewUser (Op) Connected и GetNickListArrival. На User (Op) Disconnected он из таблицы удаляется.

В таблицу B пихаем имя юзера, только если он вызвал NewUser (Op) Connected или MyInfoArrival. Убирается оттуда так же как A.

Далее делаем слоедующий финт — раз в N секунд отправляем ВСЕМ юзерам измененное MyInfo тех, кто в таблице B, и полный список измененных майинфо — тем кто в A.

Плюсы — работает быстрее, не засоряет лишним трафиком, и наконец, такой системе абсолютно пофиг на количество юзеров на хабе. Проверено — работает офигенно

Группа: Forum members
Сообщений: 350
Регистрация: 26.12.2006
Из: TTK(Domolink)
Пользователь №: 11 559

GetItemByIP(IP)
По IP возвратит Item (если юзер с таким IP онлайн)
пример

Группа: Forum members
Сообщений: 350
Регистрация: 26.12.2006
Из: TTK(Domolink)
Пользователь №: 11 559

Группа: Forum members
Сообщений: 837
Регистрация: 24.8.2006
Пользователь №: 8 755

Группа: Forum members
Сообщений: 350
Регистрация: 26.12.2006
Из: TTK(Domolink)
Пользователь №: 11 559

Группа: Forum members
Сообщений: 837
Регистрация: 24.8.2006
Пользователь №: 8 755

Кину вам еще парочку функций.

Инвертирует массив, меняя индексы на значения, и наоборот. Одинаковые значения не отслеживаются.

Объединяет массив ValArray в строку, разделяя элементы с помощью Sep, и пропуская значение Excl.
Пример: tbl = <"asd","dfg","df">str = array_implode(tbl,»; «,»»). В str будет такое: «asd; dfg; df»

= «») then
if (cnt > 0) then result = result..Sep end
result = result..v
cnt = cnt + 1
end
end
return result
end

То же самое, но с индексами.

Делает в точности наоборот, в отличие от предыдущей функфции.
Превращает строку ValString типа «ы, ы1, ы5, ы6» в массив. Sep — разделитель элементов.

= «») then
table.insert(retarr,fnd)
end
iii = iii + 1
end
return retarr
else
return
end
end

Группа: Forum members
Сообщений: 350
Регистрация: 26.12.2006
Из: TTK(Domolink)
Пользователь №: 11 559

Группа: Forum members
Сообщений: 177
Регистрация: 7.11.2006
Пользователь №: 10 132

Доброго времени суток.
Кусочек скрипта:

function NewUserConnected(user)
local Prof
local Topic
local Teg
local Desc
Prof = GetProfileName(user.iProfile) or «Unregistered User»
Topic = frmHub:GetHubTopic() or «Топик не установлен.»
Teg = GetEnable(user.sTag)
Desc = GetEnable(user.sDescription)

Подскажите как переменной (к примеру Speed) задать информацию о скорости закачки
клиента юзера? (Пример: В тэге — число 80).
Может есть другие способы?

Группа: Forum members
Сообщений: 837
Регистрация: 24.8.2006
Пользователь №: 8 755

Прочитать про регулярные выражения в луа что мешает?

Должно работать, не проверял.

Группа: Forum members
Сообщений: 177
Регистрация: 7.11.2006
Пользователь №: 10 132

Прочитать про регулярные выражения в луа что мешает?

Должно работать, не проверял.

Группа: Forum members
Сообщений: 124
Регистрация: 12.3.2007
Пользователь №: 13 858

function SaveToFile(file , table , tablename)
local handle = io.open(file,»w+»)
handle:write(Serialize(table, tablename))
handle:flush()
handle:close()
end

SaveToFile(file , table , tablename)
сохранит в «file» таблицу «table» и назовёт её «tablename»
обычно используют table = tablename

Доработал в своих целях код.

=nil then
sRepear=sRepear..sNewLine..sTab..sLongKey..»=»..tTablesCollector[tTable]..»;»
return nil, sRepear
end
tTablesCollector[tTable]=sLongKey
sTmp = sTmp..sTabs..sTableName..»= <"..sNewLine
local bJump=false
for key, value in pairs(tTable) do
local sKey
local bToRepear = false
if (type(key) == «table») then
if tTablesCollector[key] then
sKey=»[«..tTablesCollector[key]..»]»
else
k > sKey=»tKey[«..kidx..»]»
local sTmp2,sRepear2=SerializeInternal(key, sKey, sTab)
sRepear = sRepear..sNewLine..sTmp2..»;»..sRepear2
sKey=»[«..sKey..»]»
end
bToRepear = true
bJump=true
elseif (type(key) == «string») then
sKey = string.format(«[%q]»,key)
else
sKey = string.format(«[%d]»,key);
end
if(type(value) == «table») then
local sTmp2,sRepear2=SerializeInternal(value, sKey,(bToRepear and sTab) or sTabs..sTab, sLongKey..sKey)
if bToRepear then
if sTmp2

=nil then
sRepear = sRepear..sNewLine..sTab..sLongKey..sTmp2..»;»
end
bJump=true
else
if sTmp2==nil then
bJump=true
else
sTmp = sTmp..sTmp2;
end
end
sRepear = sRepear..sRepear2;
else
local sValue = (type(value) == «string») and string.format(«%q»,value) or tostring(value);
if bToRepear then
sRepear = sRepear..sNewLine..sTab..sLongKey..sKey..»=»..sValue..»;»
bJump=true
else
sTmp = sTmp..sTabs..sTab..sKey..»=»..sValue
end
end
if bJump then bJump=false; else sTmp = sTmp..»,»..sNewLine; end
end
sTmp = sTmp..sTabs..»>»
return sTmp, sRepear
end

local sResult, sRepear = SerializeInternal(tTable,»tTable»,sTab)
sResult=sTableName..»=function()»..sNewLine..»local tKey=<>;»..sNewLine..»local «..sResult..»;»..sNewLine..sRepear..sNewLine..»return tTable;»..sNewLine..»end;»..sNewLine..sTableName..»=»..sTableName..»();»
collectgarbage()
return sResult
end

В данном случае предотвращает переполнение стека при циклических вложениях таблиц и сохраняет связи.

Пример корректно обрабатываемой таблицы.

Код рабочий. Результат проверен циклической(до 1000 раз) обработкой методами loadstring и собственно Serialize.

Я использовал этот код для просмотра содержания таблицы.

С английским у меня тоже не очень так что може где накосячил с названиями переменных.

Введение в Lua.

Недавно мой близкий друг ходил на собеседование по устройству на работу в местную компанию разработки игр. Я не собираюсь здесь называть имена, скажу только, что это был своего рода большой бутик Разработки Игр в Ванкувере. Он не получил работу, но сегодня речь не о нем. Лично я полагаю, что одна из причин была из-за его недостаточно дружественных отношений со скрипт-языком Lua, который они используют.

Автор: Ash Matheson

Введение

Я занимаюсь этой областью, так как обучаю студентов программированию игр, но именно этой теме я уделил не достаточно внимания в прошлом. Мы охватываем Unreal Script как часть курса «Использование существующих игровых движков». Но мы фактически не рассматривали скрипт-движок, как часть утилит или часть движка. Так, вооружившись вебсайтом, я решил сломать этот небольшой барьер. Результат описан в этом документе.

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

Почему и почему бы нет?

Прежде всего, зачем использовать скрипт-язык? Большая часть игровой логики может быть описана на скрипт-языке для различных целей, вместо того, чтобы программировать ее как часть игрового движка. Например, загрузка или инициализации уровня. После загрузки уровня, возможно Вы захотите перевести сцену к игровому плану или может быть захотите показать некоторый предварительный текст. Используя скрипт-систему, Вы могли бы заставить некоторые объекты игры выполнять определенные задачи. Также, подумайте о реализации искусственного интеллекта. Не Игровые Персонажи должны знать, что делать. Программирование каждого NPC «вручную», в теле игрового движка излишне усложнит задачу. Когда Вы захотите изменить поведение NPC, Вам придется перекомпилировать ваш проект. С скрипт-системой, Вы можете делать это в интерактивном режиме, изменяя поведение и сохраняя настройки.

Я немного затронул эту проблему в последнем параграфе, мы еще поговорим об этом немного позже. Вопрос, почему бы не написать логику исключительно на C/C++? Проще говоря, что в перспективе у программиста то, что все ложится непосредственно на него и начнет он соответственно с игрового кода, заодно придется писать и движок и утилиты и т.д. Но мы теперь можем с простым скрипт-языком переложить некоторые задачи функциональных возможностей на дизайнеров уровней. Они могут начать возиться с уровнем и оптимизировать геймплей. Вот собственно пример:

Давайте представим, что Джо, наш несчастный программист, пишет весь игровой движок, инструменты и логику игры сам. Да, Джо придется туго, но давайте предположим, что ему все нипочем. У нас так же имеется Брендон, игровой дизайнер. Брендон довольно развитый парнишка с шикарными идеями насчет игры. И так, наш кодер Джо, уползает и осуществляет всю игровую логику используя инструментарий, который он разработал основываясь на начальном проекте Брендона. Все хорошо в конторке. Первый этап закончен и Джо с Брендоном сидят в зале заседаний и проверяют свои немалые труды. Брендон замечает несколько проблем в геймплее, который ведет себя не должным образом. Так что Джо возвращается к коду и делает требуемые изменения. Этот процесс может занять день, по крайней мере, если это не тривиальное изменение. Затем еще день для перекомпилирования проекта. Чтобы не терять лишние сутки большинство контор оставляют процесс сборки на ночь. Так, как мы видим проходит 24 часа прежде, чем Брендон увидит изменения, которое он требовал.

Теперь, давайте представим, что наш главный герой Джо решил, что реализация игровой логики использует скрипт-движок в его интересах. Это займет в начале некоторое время, но он чувствует, что в конечном счете это принесет пользу. И так, он перекладывает с игрового движка некоторые функциональные возможности на скрипт-систему игры. Он также пишет всю игровую логику в упомянутой ранее скрипт-системе. И так, когда он встречается с Брендоном и дизайнер замечает кое-что, не отвечающее его задумке, Джо быстренько открывает консоль, делает некоторые изменения в скрипте, перезапускает игру и уже видит новое поведение. Изменения могут быть сразу внесены и показаны немедленно, вместо того, чтобы ждать рекомпиллинг. И если Джо был особенно выразителен, скрипт-система могла быть использована для утилит и доступна левел-дизайнерам при построении уровней. Если двигаться по такому пути, то при небольшом обучении проектировщики уровней могли бы сами устанавливать игровые события, такие как триггеры, двери, другие игровые события и радоваться жизни не напрягая программиста.

Это довольно надуманный пример и может быть немного преувеличен, но я надеюсь, что он покажет разницу между подходами. Так что мы попробуем сделать с такой моделью, что означает движение к большему количеству автоматически управляемых данных. Так, по существу, куда мы движемся:

  1. Кодер заинтересован в написании кода движка/инструментов, а не логики игры.
  2. Время было потрачено на написание движка/инструментов игры.
  3. Дизайнерам нравится «баловаться» с вещами. Скриптинг открывает им свободу в проектировании уровней и функциональных возможностей. Это также добавляет им больше гибкости, чтобы экспериментировать с вещами, для которых они обычно привлекали программиста.
  4. Вы не должны перекомпилировать, если хотите изменить функциональные возможности игры. Просто измените скрипт.
  5. Вы хотите разрушить связь между машинным и игровым кодом. Они должны быть двумя отдельными частями. Таким образом, будет удобно использовать движок для последующих сиквелов (я надеюсь).

Здесь я сделаю несколько прогнозов. В течение 5 лет, дизайнеры уровней должны будут делать больше, чем просто строить уровни. Они должны быть способными использовать скрипт для игровых сцен. Несколько компаний с передовыми взглядами уже применили этот подход. Также, Вы можете увидеть этот способ интеграции в редакторах подобно UnrealEd и Aurora toolset Bioware.

Разъяснение и разглагольствования

Надеюсь сейчас Вы уже купились на мои слова и захотели включить скрипт-компонент в вашу игру. И так, следующий вопрос: как, черт возьми, Вы это делаете?

Что я собираюсь использовать для моего скрипт-компонента — это внедряемый скрипт-движок Lua. В начале скажу, что я не спец в Lua, но это относительно простой язык и не потребует утомительного изучения для овладения им. Некоторые последующие примеры, по которым я буду пробегаться, довольно просты. В конце этого документа, я собираюсь включить некоторый дополнительный справочный материал. По справедливости, есть и другие скрипт-языки, типа Small, Simkin, Python, Perl. Однако Lua приятный и чистый язык. Это действительно хорошее преимущество.

Lua имеет открытый исходный код. Это хорошо, потому что: (a) Вы получаете исходники языка и можете рыться в них сколько вздумается, (b) он бесплатен. Вы можете использовать его в коммерческих приложениях, и не раскидываться деньгами. Ну а для некоммерческих проектов сами понимаете бесплатно == хорошо.

Так, кто в настоящее время использует Lua? Lua написан шарашкиной конторкой и его используют только бедные? Ммм. не совсем так. Lua появился не вчера и использовался достаточно известными личностями:

  • Lucasarts
    • Grim Fandango
    • Escape from Monkey Island
  • Bioware
    • Neverwinter Nights
    • MDK2

Ок, достаточно с кто-есть-кто из lua разработчиков. Вы можете это сами увидеть на вебсайте lua.

Давайте начнем с действительно простого. Первая вещь, которую мы должны построить, покажет нам как используется lua интерпретатор. Что для этого потребуется:

  1. Получение кода интерпретатора Lua.
  2. Настройка вашей среды разработки.
  3. Сборка интерпретатора с нуля.

Эй, я подумал, Вы сказали достаточно разглагольствований?

Ну что, достаточно? Так, давайте перейдем к делу. Вы можете получить весь исходный код Lua на офиуиальном сайте. Я также хотел бы взять секунду и обратить Ваше внимание, что на горизонте есть новая версия lua 5.0. Я не собираюсь обсуждать эту версию в этой статье. Я разберусь с ней позднее, а пока, мы будем использовать 4.0.1.

Первая вещь, которую мы сделаем — соберем библиотеку lua. Таким образом, нам не понадобится включать исходники каждый раз при сборке проекта. Это не сложно и это не цель наших уроков. Поэтому я заранее включил библиотеку как часть этой статьи. Я использовал статическую библиотеку для этого примера. Да, возможно я собрал бы ее как DLL, но для скрипт-системы статическая библиотека работает немного быстрее. Заметьте, не на много, но быстрее.

Встраивание Lua для поддержки скриптов в приложениях

Реализация поддержки скриптов в приложениях при помощи небольшого специализированного языка

Lua — маленький скриптовой язык. Насколько маленький? Например, в нем вместо регулярных выражений POSIX используются особые механизмы поиска по шаблону, так как полная реализация регулярных выражений значительно превышает все его вместе взятые стандартные библиотеки. Более простой поиск в строках, предоставляемый Lua, хотя и не такой мощный, но занимает значительно меньше места.

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

Один из самых неожиданных моментов для программистов, ранее работавших с другими языками — в Lua ложными значениями считаются только false и nil ; любое значение небулевого типа в условных выражениях всегда считается истинным. Хотя этот подход может удивить людей, привыкших к соглашениям C, таким как использование 1 и 0 вместо true и false , к нему легко привыкнуть.

Lua написана на переносимом C; также его можно использовать с C++. Основа языка очень хорошо переносима; хотя некоторые возможности и требуют поддержки со стороны платформы, Lua хорошо работает без платформенных зависимостей. У этого языка нет огромного набора тестов Autoconf, так как он следует стандарту. Lua распространяется на условиях лицензии MIT и может бесплатно использоваться для любых целей, в том числе коммерческих. (Вероятно, это послужило причиной того, что многие программисты встраивают этот язык в свои приложения.)

Зачем встраивать язык?


Встраивание скриптового языка дает ряд преимуществ. Я приведу пример, с которого начал работать с Lua: это многопользовательская ролевая онлайн-игра World of Warcraft (WoW) от компании Blizzard. Пользовательский интерфейс WoW полностью реализован на Lua; разработчики предоставили несколько базовых вызовов прикладного программного интерфейса для непосредственного взаимодействия с движком отрисовки и запроса данных о мире, а затем использовали Lua для основной части кода пользовательского интерфейса.

Это значительно упрощает отделение кода пользовательского интерфейса от внутренностей игры, повышает безопасность и читаемость. В свою очередь, Blizzard в результате получила возможность открыть пользовательский интерфейс для игроков и позволить им писать свой код для изменения процесса взаимодействия с игрой.

В целом во многих задачах со скриптовыми языками работать проще, нежели с низкоуровневыми языками. Язык со сборкой мусора, неявным выделением памяти и ассоциативными массивами часто дает более простой код, который разрабатывается быстрее. Он может и не работать быстрее, но во многих случаях это не проблема; так, пользовательские интерфейсы лишь должны работать быстрее, чем пользователь вводит информацию с помощью клавиатуры или мыши.

Есть несколько способов использования скриптовых языков. Первый и самый простой — использовать язык для управляющей логики, считая код на C деталями реализации программы, которая на самом деле написана на Lua. Второй способ — писать программу в основном на C, после чего встроить Lua как механизм хранения и передачи данных и настроек. Третий и самый гибкий способ — совмещать предыдущие два, используя для скриптования определенных действий Lua, а для работы с другими — код на C. Простой интерфейс между Lua и C способствует использованию такого подхода.

Написание движка

Создание движка для программы, которая будет в основном писаться на Lua, имеет смысл в том случае, когда основное использование процессорного времени сосредоточено в отдельных операциях, а высокоуровневое управление относительно легковесно. Это помогает отделить детали реализации от высокоуровневой конструкции. Реализация основной логики программы на Lua, а не на C, может очень значительно уменьшить время разработки.

При таком подходе к проектированию основная часть интерфейса между Lua и C состоит из определения функций C для вызова из Lua, и ожидается, что после начала выполнения всё последующее использование кода на C будет сводиться к вызовам из скрипта.

Скриптуемые файлы настроек

Каждый знакомый мне программист написал хотя бы один блок кода, который занимается только тем, что пытается сохранить настройки в файле и позднее их восстановить. (Пожалуй, из того, что я использовал, мне больше всего нравятся списки свойств, разработанные Apple.) Однако в качестве формата таких файлов может использоваться встроенный скриптовой язык, что даст пользователям прекрасный набор возможностей для настройки. Так, оконный менеджер Ion использует Lua для своих файлов, позволяя пользователям создавать мощные и гибкие настройки.

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

Сочетание подходов

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

Для пользовательского интерфейса World of Warcraft в основном используется именно такая модель: вызовы Lua в пользовательском интерфейсе могут совершать обратные вызовы движка, а движок доставляет события коду пользовательского интерфейса, написанному на Lua. В результате получается гибкий интерфейс с хорошим разделением и безопасностью, что дает пользователям широкие возможности при небольшом риске вызвать выход за границы буфера в приложении или аварийное завершение работы программы. В прикладных программных интерфейсах, основанных на C, встроенный код практически всегда будет иметь возможность создания аварийной ситуации; если же пользовательский код может вызвать сбой при использовании интерфейса на Lua, это означает, что в программе имеется ошибка, которую нужно исправить.

Сборка и использование Lua

Сборка Lua происходит просто, достаточно выполнить make ; если нет желания использовать специфические возможности платформы, то можно указать posix или даже ansi После сборки получаем библиотеку liblua.a, которую можно связывать со своими программами. Поздравляю, Lua встроен! Конечно, практическое использование языка требует немного больше работы.

Реентерабельность Lua обеспечивается тем, что состояние интерпретатора сохраняется в объекте; можно иметь несколько интерпретаторов, которые не используют переменные совместно, и у них не будет общих глобальных сущностей. Для взаимодействия с интерпретатором нужно сначала создать состояние Lua:

lua_State *l;
l = lua_open();

Если вызов lua_open() происходит неудачно, возвращается нулевой указатель. В остальных случаях мы получаем пригодное для использования состояние интерпретатора Lua. Конечно, без библиотек от него мало проку. Добавить стандартные библиотеки можно при помощи функции luaL_openlibs() :

Теперь состояние Lua готово к исполнению кода. Вот пример цикла программы, просто выполняющей свои аргументы как код на Lua:

Листинг 1. Выполнение аргументов в качестве кода на Lua

Функция luaL_loadbuffer() компилирует скрипт в код Lua; если имеется синтаксическая ошибка, то сообщение о ней возвращается в стек. Иначе скомпилированный код может исполняться при помощи функции lua_pcall() . Опять же, если возникают ошибки, то они возвращаются в стек. Заметьте, что каждый вызов интерпретатора Lua на языке C, либо относящийся к интерпретатору, принимает в качестве аргумента состояние Lua; состояния по умолчанию нет.

Как работает стек Lua

Интерпретатор Lua использует для взаимодействия с вызывающим кодом стековый интерфейс. Передаваемые коду на Lua данные помещаются в стек кодом на C; ответы интерпретатора Lua также помещаются в стек. Если функции luaL_loadbuffer() передан неправильный код, то в стек помещается сообщение об ошибке.

Элементы стека имеют типы и значения. Функция lua_type() запрашивает тип объекта, а функции lua_to () (например, lua_tostring() ) дают значения, приведенные к определенному типу C. Код, написанный на Lua, всегда строго подчиняется модели стека. Однако код на C может просматривать остальную часть стека и даже вставлять туда значения.

Это простой, но удивительно мощный интерфейс. Работа с кодом для исполнения ведется таким же образом: он просто помещается в стек для выполнения функцией lua_pcall() .

Использование lua_pcall()

Функция lua_pcall() принимает три аргумента помимо состояния Lua, с которым работает. Код для выполнения не входит в эти аргументы, он помещается в стек при помощи luaL_loadbuffer() либо другой функции, получающей код. Вместо этого lua_pcall() принимает количество аргументов в стеке для передачи выполняемому коду, количество ожидаемых результатов и необязательный обработчик ошибок. Чтобы вызвать функцию, вы помещаете в стек сначала ее, а затем принимаемые аргументы (по порядку). Возвращенные значения помещаются в том же порядке; первое значение лежит в нижней части стека, а последнее — наверху.

И при отправке аргументов, и при получении возвращенных значений Lua без предупреждений корректирует количество значений таким образом, чтобы оно соответствовало числам, переданным lua_pcall() : если полученных значений недостаточно, то остальные заполняются значениями nil ; если получены лишние значения, то они просто отбрасываются. (Точно так же в Lua работает множественное присваивание.)

Обработчиком ошибок, если таковой указан, служит индекс в стеке с кодом Lua для обработки любых возникающих ошибок. В этой обзорной статье мы опустим подробное обсуждение обработки ошибок; для начала важно знать, что обработка ошибок есть и что она реализуется на Lua. Это очень удобно.

Встраивание C в Lua

Писать функции на C для последующего использования в Lua очень просто. Вы будете этим удивлены, если когда-либо писали код для встраивания другого скриптового языка. Вот функция на C, которая принимает число x и возвращает x + 1 :

Листинг 2. Функция на C для использования в Lua

Функция вызывается с состоянием Lua в качестве аргумента; всё взаимодействие между C и Lua опять производится через стек состояния Lua. Возвращаемое значение — количество объектов, помещенных функцией в стек. Чтобы сделать эту функцию доступной в Lua, вы должны создать представляющий ее объект Lua и присвоить ей имя:

lua_pushcfunction(L, l_ink);
lua_setglobal(L, «ink»);

Функция lua_pushcfunction() используется для преобразования указателя на функцию C во внутренний объект Lua. Этот объект, разумеется, помещается в стек. Далее функция lua_setglobal() присваивает верхнее значение стека именованной глобальной переменной. Так как функции в Lua являются просто значениями, при этом создается доступная для вызова функция.

Стековый интерфейс значительно всё упрощает: нет необходимости определять или объявлять аргументы, которые принимает функция. Это не дало бы ничего, так как Lua очень гибко подходит к вызову кода. Однако при желании вы можете более аккуратно проверить некоторые моменты. Функция luaL_checknumber() также может использоваться для проверки аргументов, вывода информативного сообщения об ошибке и прекращения выполнения функции.

Правильное использование встроенного скриптования

Простота встраивания Lua в код на других языках (в особенности на C) делает его простым средством для значительного улучшения функциональности программ на других языках. Это доступная альтернатива созданию собственного языка настроек или написанию своего анализатора выражений.

Мастер Йода рекомендует:  Include, Require и комментарии PHP

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

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

Ресурсы для скачивания

Похожие темы

  • Оригинал статьи Embed Lua for scriptable apps (EN).
  • На сайте Lua Users перечислены известные примеры использования Lua, особенно в видеоиграх, так как доступное для пользователей скриптование — прекрасный способ заинтересовать сообщество разработчиков модификаций и дополнений.(EN)
  • Первое издание книги Programming in Lua можно бесплатно прочитать в сети. (EN)
  • Пожалуй, World of Warcraft собрал больше Lua programmers, чем любое другое ПО. (EN)
  • Celestia может управляться при помощи скриптов на Lua. (EN)
  • В разделе Linux на сайте developerWorks, есть дополнительные ресурсы для Linux-разработчиков (в том числе и для новичков), а также можно просмотреть наши наиболее популярные статьи и учебные материалы.
  • Изучите другие советы и руководства по Linux на сайте developerWorks.
  • Ознакомительные версии ПО IBM: используйте в вашем следующем проекте программное обеспечение, которое можно загрузить непосредственно с сайта developerWorks. (EN)

Комментарии

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

API/Язык программирования Lua

Урок 2. Язык программирования Lua

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

Вступление

Игра почти полностью написана на скриптовом языке программирования Lua. По крайней мере та её часть, что отвечает за логику игры, т.е. за самое интересное. Так как исходный текст скриптов доступен, то, зная Lua, можно с лёгкостью читать эти тексты и понимать, как устроена игра. Более того, можно легко менять игру или писать дополнения в виде модов к игре.

Сам язык Lua — простой. Его можно сравнить с алфавитом из нескольких десятков букв. Однако то, как устроена игра — более сложно. Устройство игры можно сравнить со словарем, где тысячи слов. Эти слова не обязательно учить все, т.к. сама игра является как бы словарем, и можно заглянуть в её код и посмотреть (прочитать), как устроен тот или иной аспект. Тем не менее, алфавит знать обязательно. Это сэкономит кучу времени, которое в противном случае придется тратить на догадки, пустые пробы, неудачи, а также на то, чтобы вам подсказали более опытные люди.

В статье речь пойдет, в основном, о самом языке Lua в настолько простом и адаптированном для простых людей виде, насколько это возможно. Однако придется упомянуть также и о том, как устроена игра, потому что понять проще на живом примере, т.е. на самой игре.

Ссылки

Хочу напомнить, что в Интернете полно информации о Lua. Этот язык позиционируется, как один из самых простых. Его предназначение — встраивание в игры и приложения, т.е. он предназначен не для разработчиков, а для пользователей, чтобы они могли добавлять желаемые эффекты в целевое приложение. Почти также дело обстоит и с Don’t Starve, с тем лишь исключением, что почти вся игра написана на Lua. И это круто.

Все материалы легко гуглятся по запросам вроде «lua» или «учебник луа» и т.п.

Хороший (лучший) учебник называется «Programming in Lua», автор Роберто Иерусалимский (Roberto Ierusalimschy). Первоисточник на английском, но читается очень легко, даже если вы плохо знаете английский. Первое издание про Lua 5.0, второе — Lua 5.1, третье — Lua 5.2. Игра использует 5.1, хотя это не принципиально для общего знания языка. Третье издание переведено на русский язык. Можете скачать (с трекера) или купить. В общем, Гугл вам в помощь.

Консоль и лог

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

(тильда) на клавиатуре. На этой же клавише находится буква «ё». Этой клавишей можно открывать и закрывать консоль, которая представляет из себя длинное поле для ввода.

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

Также вы увидите какой-то странный технический тект. Его можно отдельно включать и выключать, нажав Ctrl+L. Это стандартный вывод языка Lua, в нашем случае — игровой лог, куда скрипты «сообщают» разную полезную отладочную информацию или ошибки. Этот лог также пишется в реальном времени в файл (log.txt, либо client_log.txt, либо server_log.txt, в зависимости от версии и типа игры). Поэтому в случае очередного краша по вине вашего мода не спешите перезапускать игру — загляните в лог файл, там может быть полезная информацию об ошибке.

Таким образом, консоль и игровой лог вместе составляют простейший, но в то же время удобный, инструмент для тестирования скриптов и обучения языку Lua. Консоль — для ввода команд. Лог — для просмотра результата работы наших скриптов.

Более подробно читайте в статье про консоль. Там же вы найдете множество команд. Обычно они используются в читерных целях. Но для нас важно, что все они на языке Lua. Да, именно так, в консоль можно копипастить целые скрипты, и они должны работать.

Lua правда работает?

Для начала давайте проверим самые-самые простые примеры, что они работают. Одна из самых простых функций языка называется print. Она просто выводит в лог всё, что вы ей передаете в качестве параметров. Введите в консоль:

Посмотрите лог. Появилась ли там цифра 123? Если да, то наш первый скрипт в одну строчку работает. Иначе вы что-то делаете не так.

Попробуйте другой пример:

Какую цифру это показало в логе? Оу, это уже что-то новенькое! Lua умеет считать? Конечно умеет! Это же язык программирования.

В дальнейшем нам очень поможет функция print для отладки наших модов и изучения игры. Запомните эту функцию.

Небольшое замечание. Хотя рановато для таких замечаний, но всё же. Функция не выведет ничего, если передать ей пустое значение или несуществующую переменную. Порой это может путать. Может показаться, что сама функция не работает. Было бы удобно сделать функцию-обертку, которая выводит сообщение об ошибке, но это вы уже сами сделаете, когда выучите язык. Например, такой вариант ничего не выведет:

В этом примере abcdef не является строкой, т.к. без кавычек. А переменной с таким именем не существует.

По мере изучения с помощью консоли постоянно пытайтесь сломать игру и сам язык Lua. Только так можно лучше его понять и запомнить. Постепенно вы начнете импровизировать в своих работах, тогда начнется настоящее творчество. Например, для начала попробуйте поделить на ноль. Или сложить строку и число. Так можно или нельзя? Для каждого языка программирования — свой ответ. Консоль ответит на любые вопросы про Lua.

Память и переменные

Надеюсь, не нужно объяснять, что такое переменная. Алгебра у вас была еще в школе, так что вы в курсе, что это такое.

Оу, это что-то новенькое! Lua умеет запоминать? Конечно умеет! Это же язык программирования!

Когда вы пишете

то Lua создает переменную и помещает туда значение, которое находится справа от знака «=». Это может быть сложное выражение. Сам знак «=» называется оператором присваивания. Если такой переменной не существовало, то она создается автоматически. В отличие от других языков, нигде не нужно отдельно указывать, что мы хотим именно создать переменную.

И здесь начинаются опасности. Вы должны четко понимать, чего хотите добиться конкретной командой. Если вы здесь ошибетесь, то Lua не всегда сможет увидеть ошибку. Например, у вас уже есть переменная abcdef, а вы хотите присвоить ей новое значение:

Не заметили ошибку? И правда, ее сложно заметить. Мы случайно перепутали буквы d и c. В результате Lua создаст новую переменную, а в старой переменной abcdef будет храниться старое значение.

Вы можете создавать любые переменные, с любыми названиями. Например: a, b, c, x, y, x1, y1, xyz. Но лучше давать осмысленные названия. Это тоже важно, чтобы избежать ошибок. Например, количество здоровья лучше хранить в переменной с именем health. Но в игре не всё так просто, потому что есть много созданий, включая игрока, у которых есть своё здоровье. Поэтому нам нужно как-то структурировать всю эту информацию.

Таблицы

Это покажется странным, но таблица — один из самых простых и удобных способов структурировать информацию. Excel почти не изменился с прошлого века (еще с Windows 3.1). В Lua слово «таблица» используется в более узком значении и представляет из себя специфический тип данных. Как только вы поймете, что такое таблица, вы поймете 80% языка Lua. В школе это не проходили, но таблицы-то вы рисовали в жизни? Здесь нам понадобится лишь немного воображения.

Введите в консоль:

Что это еще за фигурные скобки такие? Так в Lua обозначаются таблицы. Так как таблица — это данные, то ее можно поместить в переменную, что мы и сделали. В данном примере создается пустая таблица, в которой ничего нет, но всё же это таблица. Давайте посмотрим, что содержится в переменной x с точки зрения Lua:

Мда, вывело какое-то несуразное сообщение. Увы и ах, вывести таблицу в лог — это очень сложное действие и его нельзя сделать просто. Таблица может содержать разные данные, в том числе и другие таблицы. Как их вывести в виде текстовой строки? На этот вопрос нет ответа. Поэтому Lua поступает так: просто выводит информацию о том, что это таблица и адрес в памяти (он нам не нужен). Так что с помощью функции print вы можете узнать лишь сам факт того, что в переменной содержится таблица, но не ее содержимое.

Таблица — это список в виде пар ключ-значение. Чтобы поместить какую-то информацию в таблицу, нужно указать ключ и значение. Чтобы извлечь информацию, нужно указать ключ.

Чтобы лучше понять таблицы, поиграйте с ними в консоли. Сделайте таблицу, в которой отдельно хранятся ваши имя и возраст. Сделайте таблицу в таблице. Попробуйте сломать Lua, давая сложные команды, которые должны работать. А что если сработают? А что если нет? В любом случае вы получите опыт и понимание языка. Потратьте на это хотя бы 10 минут. Вот примеры, которые пока что оставляют вопросы (щупайте их функцией print):

Вот вам домашнее задание:

Что выведет? 5 или 7?

Таблицы являются основной изюминкой языка. Это единственный сложный тип данных в Lua. но с его помощью можно представить многие другие типы данных из других языков, такие как индексные массивы, ассоциативные массивы, объекты, классы и т.д. Таблицы Lua поддерживают прототипирование, как в JavaScript. А уже через него возможно полноценное ООП наследование. Последний абзац, скорее всего, прозвучал для вас, как китайский. Просто знайте, что это круто. 🙂

Игрок


Примечательно, что в игре каждый объект или даже свойство объекта представлено в виде таблицы.

Давайте поместим в переменную x объект, представляющий самого игрока (для этого нужно быть в игре).
Для DS:

Как видите, для сетевой и одиночной игры разные команды. С этим придется мириться, потому что игры разные, и это не единственное отличие. Хотя внутри устройство объектов очень схоже.
Теперь давайте выведем в лог текущее здоровье:

Что? Какая цифра? 150? Похоже на правду? Теперь попробуйте изменить здоровье. Пусть вас кто-то ударит или отравитесь слегка. И еще раз:

Это что-то новенькое! Получается, мы в наших скриптах теперь имеем доступ к здоровью персонажа. По секрету скажу, что currenthealth — это почти переменная. А если так, то ей можно присвоить значение? А ну-ка, проверим!

Здоровье персонажа поменялось? Поздравляю, теперь вы — читер!

Конечно, не всё так просто. В DST, например, у вас есть доступ лишь к локальным переменным, так что здоровье поменять не получится. А на выделенном сервере вообще нет переменной ThePlayer, потому что для каждого игрока нужна отдельная переменная. Эти нюансы вы узнаете сами, либо из следующих уроков. Пока что сосредоточимся на самом языке Lua.

Обратите внимание, что везде используются осмысленные названия. Например, currenthealth — текущее здоровье. Это важно, потому что в игре тысячи переменных, и нужно понимать смысл каждой из них. Будет лучше, если имена переменных будут интуитивно понятны. Klei Entertainment следует этому принципу, поэтому исходные тексты игры легко читаются. Вы также соблюдайте это правило, тогда вы не запутаетесь, а напротив — будете четко понимать, что есть что у вас в моде. Мод получится без ошибок и глюков. А если вы через полгода захотите что-то улучшить в своем моде, то без труда разберетесь в собственном творении. Пишите код так, словно читать будет другой человек, причем в 10 раз глупее вас.

Но что же значит эта длинная запись с точками «x.components.health.currenthealth»? Мы знаем, что x — это переменная-таблица, которая представляет игрока. А дальше? components — элемент таблицы x. И это тоже таблица. health — элемент таблицы components, и это тоже таблица. currenthealth — свойство таблицы health, и это уже, слава богу, не таблица.

Но так уж ли это сложно? Давайте подумаем. Игрок — по сути является набором неких свойств: координаты в игре, размеры, текущее действие, здоровье, сытость, текущий урон, текущая защита, (не)видимость, текущая анимация, звук и т.д. Понятно, почему игрока удобно представить в виде таблицы (а мы помним, что в Lua это просто список пар ключ-значение). Одно из таких свойств — таблица компонентов.

Разумно реализовать компоненты отдельно. Например, у игрока есть голод. Паук — тоже представлен в виде таблицы и у него тоже есть компонент здоровья. Но у паука нет компонента голода! Таким образом, мир состоит из объектов (игрок, паук, дерево, камень, трава, ягоды). И каждый объект состоит из компонентов — здоровье, голод, инвентарь, съедобность, время порчи, складываемость в стаки, созреваемость и т.д.

Каждый компонент, в свою очередь, может обладать какими-то свойствами. Для health — это текущее здоровье, максимальное здоровье, бессмертие, уровень врожденного поглощения, сопротивление огню, холоду, возможность применять лечилки и т.д.

Это архитектура игры, которую придумали Klei. И для ее реализации они использовали таблицы языка Lua.

Почему сделано так, а не иначе? Видимо потому, что это удобно. В игре тонны кода, который одному человеку весь не осилить, но зато можно без труда найти то, что нужно. В какой именно компонент поместить свойство — вопрос субъективный. Здесь стоит руководствоваться здравым смыслом и удобством понимания. Например, можно было бы сделать отдельный компонент бессмертия. Но логичнее и проще добавить булеву переменную в компонент здоровья, которая принимает значение лишь «да» и «нет». С другой стороны, это влечет дополнительные сложности. Например, компонент голода должен будет проверять наличие компонента здоровья и свойство бессмертия, но это уже издержки выбранной модели.

В своих модах вы сами планируете архитектуру и свое удобство так, чтобы не запутаться и чтобы всё было красиво.

По секрету скажу, что currenthealth — это не переменная. Мной было сказано, что это «почти переменная», потому что она ведет себя как переменная. Ей можно присваивать значение и получать значение. Но на самом деле это свойство, за которым спрятана функция. Эта функция осуществляет дополнительные проверки и передает значение «куда следует», чтобы в игре у вас и правда изменилось здоровье. Не обязательно вникать во все эти нюансы, чтобы пользоваться тем, что работает. А проверить, работает или нет, вы всегда можете с помощью консоли.

Глобальное пространство имен

Знать, что это такое, важно, чтобы понимать, что вы делаете и что вообще происходит на самом деле, когда ваш код работает.
Когда вы пишете:

То вы создаете переменную x, но где?

Когда вы учились в школе на уроке алгебры, вы тоже использовали переменную x в уравнениях. Однако в каждой задаче своя переменная x, в каждой контрольной, на каждом уроке. И это разные переменные x, хотя имя у них одно и то же.

В Lua точно также могут быть переменные x в разных местах, но это будут разные переменные. Мы сейчас не будем говорить про локальные переменные (которые как бы уничтожаются после использования за ненадобностью). Мы поговорим о глобальном пространстве, в котором хранятся все глобальные переменные, которые мы до сих пор создавали через консоль.

У мода в файле modmain.lua есть своё глобальное пространство имен. Если вы в modmain.lua напишете x = 5, то будет использовано пространство имен мода. У игры есть своё глобальное пространство. Поэтому не путайте их! Консоль работает в глобальном пространстве самой игры.

Что такое глобальное пространство имен? В Lua это просто таблица. Да, это просто-напросто таблица. Вы можете получить доступ к этой таблице, используя переменную _G. Проверьте, существует ли она:

Да, и правда существует. Эта переменная является частью стандарта языка. Постойте-ка, если это ссылка на глобальное пространство, а сама она тоже в глобальном пространстве, то получается, что она содержится в самой себе? Правда что ли? Давайте проверим:

Да, и правда существует. А ну-ка потроллим язык:

И это работает. Ну, всё логично по крайней мере. На всякий случай еще один тест «на вшивость».

Выводит true. Это означает, что элемент таблицы является в точности самой этой таблицей.
И еще один последний тест (догадайтесь, что именно он проверяет):

На всякий случай здесь мы проверяем, что нас не надули. Мы объявляем x в глобальном пространстве. Если _G и правда является ссылкой на глобал, то _G.x тоже будет равно 5.

Вот, примерно так нужно пытаться сломать язык, чтобы его понять на практике.

Теперь поговорим о modmain.lua и пространстве мода. В нем используется своё пространство, очищенное практически от всего лишнего. Оно даже не содержит переменной _G. Однако разработчики игры всё же предоставили некоторые ссылки. Самые важные из них — это env и GLOBAL. Переменная env указывает на пространство мода. Например эти две записи в modmain.lua делают одно и то же:

Переменная GLOBAL указывает на глобальное пространство имен самой игры. То есть если вы запускаете мод, в котором содержится следующее:

То мод создаст переменную в пространстве игры, и вы сможете затем в консоли набрать:

И увидите ожидаемый результат.

Все остальные файлы мода (например, свои компоненты, свои файлы префабов и любые файлы, подключенные через require) работают в пространстве игры. Исключение — файлы, подключенные с помощью функции modimport, но это уже тонкости. Пока сосредоточьтесь на синтаксисе языка.

Как протестировать многострочный скрипт

Это интересно. Есть несколько способов.

Можно банально — поместить его в modmain.lua, а затем запустить игру с включенным модом. В файле можно указывать разные print’ы, которые будут записывать подробности работы мода. Например:

После этого наличие строки «MOD BEGIN» в файле лога будет говорить о том, что мод сработал, а (внимание!) отсутствие этой строки будет говорить, что мод по каким-то причинам не сработал. Может быть, вы забыли его включить?

Второй простой способ — найти в стиме мод, улучшающий игровую консоль так, чтобы она принимала многострочный текст. Т.к. это всё же сторонний мод, то способ может оказаться не самым удобным. У всех вкусы разные.

Третий способ, указать файл для выполнения:

  1. В папке data игры создайте любой файл, например dst.lua
  2. В консоли наберите dofile(«dst.lua»)

Таким образом, всё содержимое файла будет немедленно выполнено. Если там содержится определение (замена) функции, то оно будет произведено повторно, так что осторожней с хуками на игровые функции (это такой типа хакерский прием в программировании, пока не берите в голову).

Если открыть консоль и нажимать клавиши вверх и вниз, то можно листать предыдущие команды. Так можно очень быстро снова выполнить dofile(«dst.lua») после редактирования файла. Пользуйтесь.

Заключение

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

  • Как вставлять комментарии прямо в скриптах.
  • Локальные переменные и области видимости.
  • Операторы языка Lua, в том числе операторы сравнения, ветвления, цикла.
  • Типы данных в Lua, в том числе особое значение nil.
  • Функции, в том числе важно знать о рекурсии и замыканиях и что переменным можно присваивать функции.
  • Классы в Lua (как они реализованы)

Всё это есть в статье «Lua за 15 минут». Она же, кстати, является lua файлом, т.е. скомпилируется без ошибок, если скопипастить в modmain.lua. Результат, правда, вы найдете только в файле логов. Но весь смысл раскрыт в комментариях. Это как бы самодокументируемый текст скрипта. И это хороший пример того, как нужно комментировать свои собственные скрипты, когда вы делаете сложные вещи.

Каждый нюанс языка, который вы узнаете из примера или учебника, сразу тестируйте на практике — в игровой консоли. Лучше делать это в отдельном файле, т.к. текстовый редактор удобнее. Пробуйте, тролльте язык, ломайте игру! Удачи!

Santa Simplicita

Просто писать о простом — не так и просто…

Lua за 60 минут

Я сентиментальный программист. Иногда я влюбляюсь в языки программирования, и тогда я могу говорить о них часами. Одним из этих часов я поделюсь с вами.

Lua? Что это?

Lua — простой встраиваемый язык (его можно интегрировать с вашими программами, написанными на других языках), легкий и понятный, с одним типом данных, с однообразным синтаксисом. Идеальный язык для изучения.

Зачем?

Lua может вам пригодится:

* если вы геймер (плагины для World of Warcraft и множества других игр)
* если вы пишете игры (очень часто в играх движок пишут на C/C++, а AI — на Lua)
* если вы системный программист (на Lua можно писать плагины для nmap, wireshark, nginx и других утилит)
* если вы embedded-разработчик (Lua очень быстрый, компактный и требует очень мало ресурсов)

Что надо для того, чтобы читать дальше?

1. Научитесь программировать. Хотя бы немного. Не важно на каком языке.
2. Установите Lua. Для этого либо скачайте здесь версию 5.2 (https://www.lua.org/download.html), либо ищите ее в репозиториях. Версия 5.1 тоже пойдет, но знайте, что она очень старая.

Все примеры из статьи запускайте в терминале командой наподобие «lua file.lua».

Первые впечатления

Lua — язык с динамической типизацией (переменные получают типы «на лету» в зависимости от присвоенных значений). Писать на нем можно как в императивном, так и в объектно-ориентированном или функциональном стиле (даже если вы не знаете как это — ничего страшного, продолжайте читать). Вот Hello world на Lua:

Что уже можно сказать о языке:

* однострочные комментарии начинаются с двух дефисов «—»
* скобки и точки-с-запятыми можно не писать

Операторы языка

Набор условных операторов и циклов довольно типичен:

ПОДУМАЙТЕ: что может означать цикл «for i = 1, 10, 2 do . end» ?

В выражениях можно использовать такие вот операторы над переменными:

* присваивание: x = 0
* арифметические: +, -, *, /, % (остаток от деления), ^ (возведение в степень)
* логические: and, or, not
* сравнение: >, =,

= (не-равно, да-да, вместо привычного «!=»)
* конкатенация строк (оператор «..»), напр.: s1=»hello»; s2=»world»; s3=s1..s2
* длина/размер (оператор #): s=»hello»; a = #s (‘a’ будет равно 5).
* получение элемента по индексу, напр.: s[2]

Битовых операций в языке долгое время не было, но в версии 5.2 появилась библиотека bit32, которая их реализует (как функции, не как операторы).

Типы данных

Я вам соврал, когда сказал что у языка один тип данных. Их у него много (как и у каждого серьезного языка):

* nil (ровным счетом ничего)
* булевы числа (true/false)
* числа (numbers) — без деления на целые/вещественные. Просто числа.
* строки — кстати, они очень похожи на строки в паскале
* функции — да, переменная может быть типа «функция»
* поток (thread)
* произвольные данные (userdata)
* таблица (table)

Если с первыми типами все понятно, то что же такое userdata? Вспомним о том, что Lua — язык встраиваемый, и обычно тесно работает с компонентами программ, написанными на других языках. Так вот, эти «чужие» компоненты могут создавать данные под свои нужды и хранить эти данные вместе с lua-объектами. Так вот, userdata — и есть подводная часть айсберга, которая с точки зрения языка lua не нужна, но и просто не обращать внимания на нее мы не можем.

А теперь самое важное в языке — таблицы.

Таблицы

Я вам снова соврал, когда сказал, что у языка 8 типов данных. Можете считать что он один: всё — это таблицы (это, кстати, тоже неправда). Таблица — это очень изящная структура данных, она сочетает в себе свойства массива, хэш-таблицы («ключ»-«значение»), структуры, объекта.

ПОДУМАЙТЕ: чему равно a[2] в случае разреженного массива?

В примере выше таблица ведет себя как массив, но на самом деле — у нас ведь есть ключи (индексы) и значения (элементы массива). И при этом ключами могут быть какие угодно типы, не только числа:

Кстати, раз уж у таблицы есть ключи и значения, то можно в цикле перебрать все ключи и соответствующие им значения:

А как же объекты? О них мы узнаем чуть позже, вначале — о функциях.

Функции

Вот пример обычной функции.

Функции языка позволяют принимать несколько аргументов, и возвращать несколько аргументов. Так аргументы, значения которых не указаны явно, считаются равными nil.

ПОДУМАЙТЕ: зачем может понадобиться возвращать несколько аргументов?

Функции могут принимать переменное количество аргументов:

Поскольку функции — это полноценный тип данных, то можно создавать переменные-функции, а можно передавать функции как аргументы других функций

Объекты = функции + таблицы

Раз мы можем сохранять функции в переменных, то и в полях таблиц тоже сможем. А это уже получаются как-бы-методы. Для тех, кто не знаком с ООП скажу, что основная его польза (по крайней мере в Lua) в том, что функции и данные, с которыми они работают находятся рядом — в пределах одного объекта. Для тех, кто знаком с ООП скажу, что классов здесь нет, а наследование прототипное.

Перейдем к примерам. Есть у нас объект, скажем, лампочка. Она умеет гореть и не гореть. Ну а действия с ней можно сделать два — включить и выключить:

А если лампочку сделать объектом, и функции turn_off и turn_on сделать полями объекта, то получится:

Мы вынуждены передавать сам объект лампочки в качестве первого аргумента, потому что иначе наша функция не узнает с какой именно лампочкой надо работать, чтобы сменить состояние on/off. Но чтобы не быть многословными, в Lua есть сокращенная запись, которую обычно и используют — lamp:turn_on(). Итого, мы уже знаем несколько таких упрощений синтаксиса:

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

Специальные функции

Некоторые имена функций таблиц (методов) зарезервированы, и они несут особый смысл:

* __add(a, b), __sub(a, b), __div(a, b), __mul(a, b), __mod(a, b), __pow(a, b) — вызываются, когда выполняются арифметические операции над таблицей
* __unm(a) — унарная операция «минус» (когда пишут что-то типа «x = -x»)
* __lt(a, b), __le(a, b), __eq(a, b) — вычисляют результат сравнения ( «+» , а не «..» . Для этого надо подменить функцию «+» (__add) для родительской таблицы всех строк:

Собственно, мы еще можем заменить функцию print с помощью «print = myfunction», да и много других хакерских дел можно сделать.

Области видимости

Переменные бывают глобальные и локальные. При создании все переменные в Lua являются глобальными.

Для указания локальной области видимости пишут ключевое слово local:

Не забывайте об этом слове.

Обработка ошибок

Часто, если возникают ошибки, надо прекратить выполнение определенной функции. Можно, конечно, сделать множество проверок и вызывать «return», если что-то пошло не так. Но это увеличит объем кода. В Lua используется что-то наподобие исключений (exceptions).

Ошибки порождаются с помощью функции error(x). В качестве аргумента можно передать все, что угодно (то, что имеет отношение к ошибке — строковое описание, числовой код, объект, с которым произошла ошибка и т.д.)

Обычно после этой функции вся программа аварийно завершается. А это надо далеко не всегда. Если вы вызываете функцию, которая может создать ошибку (или ее дочерние функции могут создать ошибку), то вызывайте ее безопасно, с помощью pcall():

Стандартные библиотеки

Стандартных библиотек мало, зато это позволяет запускать Lua где угодно. Подробнее можно получить их список здесь — https://www.lua.org/manual/5.2/manual.html

Нестандартных библиотек много, их можно найти на LuaForge, LuaRocks и в других репозиториях.

Между Lua и не-Lua

ВНИМАНИЕ: эту часть рекомендуется читать людям со знанием языка C.

А если нам недостаточно функциональности стандартных библиотек? Если у нас есть наша программа на C, а мы хотим вызывать ее функции из Lua? Для этого есть очень простой механизм.

Допустим, мы хотим создать свою функцию, которая возвращает случайное число (в Lua есть math.random(), но мы хотим поучиться). Нам придется написать вот такой код на C:

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

А если мы хотим вызывать код, написанный на Lua из наших программ? Тогда наши программы должны создавать виртуальную машину Lua, в которой и будут выполняться Lua-скрипты. Это намного проще:

Вы теперь можете писать на Lua. Если вы узнаете интересные моменты про Lua, которые можно было бы отразить в статье — пишите!

Добавить комментарий