Как написать луа скрипт

Содержание

Сегодня мы поверхностно пройдёмся по языку Lua, его некоторым возможностям, а так же запуске наших сценариев в RakBot.
Lua — скриптовый язык программирования, предназначен для быстрой обработки данных. С помощью данного языка многие разработчики создают искусственный интелект в играх, пишут алгоритмы генерации уровней, а так же он используется для разработки ресурсов/игровых модов в Multi Theft Auto: San Andreas (аналог SA:MP). На самом деле, это простейший язык и с помощью него мы будем учиться писать собственную логику для ботов, которую будет использовать RakBot.

Пройдёмся по основам программирования, с которыми нам предстоит работать.

Обратите внимание : данная статья будет урезана в плане языка Lua, так как в RakBot используется лишь небольшая её часть. Многие возможности Lua попросту отсустствуют в RakBot, поэтому я буду ориентироваться на версию из RakBot.

Есть традиция у всех авторов книг и документаций различных языков, это первая программа, которая печатает «Hello World».
Чтож, давайте попробуем написать её, но уже в RakBot. Переходим на оффициальный сайт RakBot и ищем раздел «Доступные функции», раздел «События».

Нам необходимо событие onScriptStart() , которые вызывается автоматически при загрузке скрипта самим RakBot»ом.

В этой функции нам необходимо описать логику, которая будет писать в чат-лог RakBot»a «Hello World». Для этого, на той же странице в документации, посмотрим на раздел «Функции».

Первая фукнция printLog(text) — это то, что нам и нужно. С помощью этой функции мы отправим сообщение в чат RakBot»а. Для этого мы напишем:

Скриптинг Dota 2 — руководство для начинающих

В этом уроке мы попытаемся объяснить основы программирования пользовательских модов (кастомок/аддонов) Dota 2.

Скриптинг

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

dota 2 betagamedota_addonsназвание_вашего_модаscripts

Две основные папки cо скриптами — это NPC и vscripts. Первая может иметь следующие файлы с расширением .txt (если таковых нет, их можно создать самому):

  • npc_abilities_custom.txt — содержит все измененные и созданные способности в пользовательской игре.
  • npc_heroes_custom.txt — герои со своими способностями и статистикой.
  • npc_items_custom.txt — способности предметов, которые носят в инвентаре.
  • npc_units_custom.txt — все данные для не-героев единиц, здания или существ.
  • npc_abilities_override.txt — измененные Dota 2 способности / детали с измененными значениями.
  • herolist.txt — список героев, доступных для выбора.

Эти файлы используют систему KeyValues ​​(KV, ключ-значение) и являются ядром в системе DataDriven. Это таблицы, содержащие всевозможные данные и они помогают движку Source 2 понять, что есть что. В этих файлах очень простой синтаксис. Всё что там есть — это таблицы (начинаются и заканчиваются фигурными скобочками), в которых содержатся ключ и его параметр (значение).

KV задают базовые параметры способностей, предметов, юнитов. А вот с Lua, уже будет немного сложней.

Каждый .txt файл содержит свои особые значения key-value, и когда начинается игра, движок Source 2 будет загружать их. Например, чтобы понять, какой юнит с какими параметрами должен заспавниться. Изменения в этих файлах не вступят в силу до тех пор, пока игра не начнется снова (только после перезапуска), так что будьте очень аккуратны с синтаксисом, в случае лишней или отсутствующей скобки: » , все KeyValues, которые идут после этой ошибки не будут учтены. KV чувствительны к регистру, поэтому также обратите внимание, чтобы вы написали всё правильно, и движок не выдаст ошибку.

На этом подготовку закончим. Сейчас самое время, чтобы попробовать написать Dota 2 скрипт (сценарий). Лучшим редактором я считаю Sublime Text Editor с этими 2 сниппетами (плагинами), которые добавляют дополнения для некоторых часто используемых функций и дают подсветку синтаксиса для KV и Lua.

  • Sublime Text 3
  • Sublime Text KeyValues ​​Пакет
  • Sublime Text Lua Пакет

Это будет просто вводный пример системы DataDriven, чтобы понять, что и как устроено, где что нужно менять.

Создайте новый документ в Sublime Text Editor и убедитесь, что вы используете Dota KV синтаксис (нажмите Ctrl + Shift + P и впишите Dota KV, чтобы выбрать его быстро).

Что такое скрипты и с чем их едят — Lua #128578;

Я готов!

Когда вы поняли преимущества скриптовых языков программирования, самое время начать работать!
Скачайте из репозитория на гитхабе (низ топика) lib’у и includ’ы Lua, либо возмите их на официальном сайте.

Создаем консольный проект либо Win32 (это неважно) в Visual Studio (у меня стоит версия 2012)

Заходим в Проект->Свойства->Свойства конфигурации->Каталоги VC++ и в «каталоги включения» и «каталоги библиотек» добавьте папку Include и Lib из репозитория соответственно.

Теперь создаем файл main.cpp, пишем в нем:

int main()

Как вы догадались, у меня консольное приложение.

Теперь переходим к кодингу

Обещаю, что буду тщательно объяснять каждый момент

У нас за скрипты будет отвечать класс Script. Я буду объявлять и одновременно реализовывать функции в Script.h/.cpp
Создаем Script.cpp и пишем в нем

#include «Script.h»

Создаем Script.h и пишем в нем

#ifndef _SCRIPT_H_ #define _SCRIPT_H_ #endif

После 2 строчки и перед #endif мы определяем класс скриптов
Этот код пишется для предотвращения взаимного включения файлов. Допустим, что файл Game.h подключает Script.h, а Script.h подключает Game.h — непорядок! А с таким кодом включение выполняется только 1 раз

Теперь пишем внутри этого кода вот это

#pragma comment(lib,»lua.lib») extern «C» < #include #include #include >

Первая строчка подключает сам lua.lib из архива.
Для чего нужен extern «C»? Дело в том, что lua написан на С и поэтому такой код необходим для подключения библиотек.

Дальше идет подключение хорошо известных многим файлов для работы с консолью

#include #include #include using namespace std;

Теперь приступим к определению класса

class Script

Самый главный объект библиотеки Lua для C++ — lua_State, он необходим для выполнения скриптов

private: lua_State *lua_state;

Дальше идут публичные функции

public: void Create();

Эта функция инициализирует lua_State

Create()
Его определение в Script.cpp

void Script::Create() < lua_state = luaL_newstate(); static const luaL_Reg lualibs[] = < , , >; for(const luaL_Reg *lib = lualibs; lib->func != NULL; lib++) < luaL_requiref(lua_state, lib->name, lib->func, 1); lua_settop(lua_state, 0); > >

Первой строчкой мы инициализируем наш lua_State.
Потом мы объявляем список «подключенных библиотек». Дело в том, что в «чистом» виде в луа есть только функция print(). Для математических и прочих функций требуется подключать специальные библиотеки и потом вызывать их как math.foo, base.foo, io.foo. Для подключения других библиотек добавьте в lualibs, например, . Все названия библиотек начинаются с luaopen_…, в конце lialibs должен стоять

void Close();

Эта функция освобождает ресурсы Lua

Ее определение

void Script::Close()

Просто используем lua_close()

int DoFile(char* ScriptFileName);

А эта функция выполняет файл. На вход она принимает название файла, например, «C:\script.lua».
Почему она возвращает int? Просто некоторые скрипты могут содержать return, прерывая работу скрипта и возвращая какое-нибудь значение.

DoFile()
Ее определение

int Script::DoFile(char* ScriptFileName)

Как вы видите, я выполняю скрипт и возвращаю int. Но возращать функция может не только int, но еще и bool и char*, просто я всегда возвращаю числа (lua_toboolean, lua_tostring)

Теперь мы сделаем функцию, регистрирующую константы (числа, строки, функции)

template void RegisterConstant(T value, char* constantname);
RegisterConstant()
Мы действуем через шаблоны. Пример вызова функции:
RegisterConstant(13,»goodvalue»);
template void Script::RegisterConstant(int value, char* constantname) < lua_pushinteger(lua_state, value); lua_setglobal(lua_state,constantname); >template void Script::RegisterConstant(double value, char* constantname) < lua_pushnumber(lua_state, value); lua_setglobal(lua_state,constantname); >template void Script::RegisterConstant(char* value, char* constantname) < lua_pushstring(lua_state, value); lua_setglobal(lua_state,constantname); >template void Script::RegisterConstant(bool value, char* constantname) < lua_pushboolean(lua_state, value); lua_setglobal(lua_state,constantname); >template void Script::RegisterConstant(lua_CFunction value, char* constantname)

Для каждого возможного значения class T мы определяем свои действия.
*Капитан* последнее определение — регистрация функции
Функции, годные для регистрации, выглядят так:

int Foo(lua_State*) < // . return n; >

Где n — количество возвращаемых значений. Если n = 2, то в Луа можно сделать так:

a, b = Foo()

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

Следующая функция создает таблицу для Луа. Если непонятно, что это значит, то тамошная таблица все равно что массив

void Array(int size);
Ее описание
void Script::Array(int size)

Следующая функция регистрирует элемент в таблице.

template void RegisterConstantArray(T value, int index);
RegisterConstantArray()
Ее описание
template void Script::RegisterConstantArray(int value, int index) < lua_pushnumber(lua_state, index); lua_pushinteger(lua_state, value); lua_settable(lua_state, -3); >template void Script::RegisterConstantArray(double value, int index) < lua_pushnumber(lua_state, index); lua_pushnumber(lua_state, value); lua_settable(lua_state, -3); >template void Script::RegisterConstantArray(char* value, int index) < lua_pushnumber(lua_state, index); lua_pushstring(lua_state, value); lua_settable(lua_state, -3); >template void Script::RegisterConstantArray(bool value, int index) < lua_pushnumber(lua_state, index); lua_pushboolean(lua_state, value); lua_settable(lua_state, -3); >template void Script::RegisterConstantArray(lua_CFunction value, int index)

Если вы не знаете Lua, вы, наверное, удивлены тем, что в один массив помещается столько типов?
На самом деле в элементе таблицы может содержаться еще и таблица, я так никогда не делаю.

Наконец, заполненную таблицу нужно зарегистрировать

void RegisterArray(char* arrayname);
RegisterArray()
Ее описание
void Script::RegisterArray(char* arrayname)

Ничего особенного нет

Следующие функции предназначены в основном только для функций типа int foo(lua_State*), которые нужны для регистрации в Луа.

Первая из них — получает количество аргументов

int GetArgumentCount();
Create()
Ее описание
int Script::GetArgumentCount()

Эта функция нужна, например, для функции Write(), куда можно запихать сколь угодно аргументов, а можно и ни одного
Подобную функцию мы реализуем позже

Следующая функция получает аргумент, переданный функции в скрипте

template T GetArgument(int index);
GetArgument()
Ее описание
template int Script::GetArgument(int index) < return lua_tointeger(lua_state,index); >template double Script::GetArgument(int index) < return lua_tonumber(lua_state,index); >template char* Script::GetArgument(int index) < return (char*)lua_tostring(lua_state,index); >template bool Script::GetArgument(int index)

Можно получить все типы, описывавшиеся ранее, кроме таблиц и функций
index — это номер аргумента. И первый аргумент начинается с 1.

Наконец, последняя функция, которая возвращает значение в скрипт

template void Return(T value);
Return()
Ее описание
template void Script::Return(int value) < lua_pushinteger(lua_state,value); >template void Script::Return(double value) < lua_pushnumber(lua_state,value); >template void Script::Return(char* value) < lua_pushstring(lua_state,value); >template void Script::Return(bool value)

Боевой код

Пора что-нибудь сделать!
Изменяем main.cpp

#include «Script.h» int main()

Компилируем. Теперь можно приступить к тестированию нашего класса

Помните, я обещал сделать функцию Write?
Видоизменяем main.cpp

#include «Script.h» // Нужен для _getch() #include // Объект скрипта Script script; // Функция Write для текста int Write(lua_State*) < // Тут мы считываем количество аргументов и каждый аргумент выводим for(int i = 1; i (i); // После вывода ставим консоль на паузу _getch(); return 0; >int main() < script.Create(); // Имя у луашной функции такое же, как у сишной script.RegisterConstant(Write,»Write»); script.DoFile(«script.lua»); script.Close(); >

А в папке с проектом создаем файл script.lua

Write(1,2,3,4);

image

Компилируем и запускаем проект.

image

Теперь изменяем script.lua

for i = 1, 4 do Write(i, «n», «Hier kommt die Sonne», «n») end

Теперь программа будет выводить по 2 строки («n» — создание новой строки), ждать нажатия Enter и снова выводить строки.

image

Экспериментируйте со скриптами!

Вот пример main.cpp с функциями и пример script.lua

#include «Script.h» #include #include #include Script script; int Write(lua_State*) < // Тут мы считываем количество аргументов и каждый аргумент выводим for(int i = 1; i (i); cout >str; script.Return(str); // Не забудьте! У нас возвращается 1 результат -> return 1 return 1; > int Message(lua_State*) < // Выводим обычное сообщение MessageBox из Windows.h // Кстати, вам домашнее задание — сделайте возможность вывода сообщений с несколькими аргументами 🙂 char* msg = script.GetArgument(1); MessageBox(0,msg,»Сообщение»,MB_OK); return 0; > int GetTwoRandomNumbers(lua_State*) < // Возвращаем два рандомных числа до 1000 srand(time(NULL)); for(int i = 0; i (rand()%1000); // Вовзращаем 2 значения return 2; >int GetLotOfRandomNumbers(lua_State*) < // Возвращаем много рандомных чисел до 1000 srand(time(NULL)); for(int i = 0; i (1); i++) script.Return(rand()%1000); // Вовзращаем столько значений, сколько задано в аргументе return script.GetArgument(1); > int main() < script.Create(); script.RegisterConstant(Write,»Write»); script.RegisterConstant(GetString,»GetString»); script.RegisterConstant(Message,»Message»); script.RegisterConstant(GetTwoRandomNumbers,»Rand1″); script.RegisterConstant(GetLotOfRandomNumbers,»Rand2″); script.DoFile(«script.lua»); script.Close(); // Пауза после скрипта _getch(); >
for i = 1, 4 do Write(i, «n», «Hier kommt die Sonne», «n») end Write(2*100-1) Message(«Привет!») a, b = Rand1() Write(a, «n», b, «n») Write(Rand1(), «n») a, b, c, d = Rand2(4) Write(a, «n», b, «n», c, «n», d, «n») return 1

Полезные советы
  • Для класса Script все равно, в каком расширении находится скрипт, хоть в .txt, хоть в .lua, хоть в .bmp, просто .lua открывается множеством редакторов именно ЯП Луа
  • Используйте редакторы Lua кода, очень трудно писать код, можно забыть написать end, do, либо что-нибудь еще. Программа из-за ошибки в луа скрипте не вылетит, но просто не выполнит код
  • Lua может оказаться намного гибче, чем вам могло показаться. К примеру, числа свободно преобразуются в строки, он нетипизирован. Если передать в функцию 100 параметров, а она в С++ считывает только первые 2, то программа не вылетит. Есть еще много подобных допущений.
Вопросы и ответы
  • Вопрос: Почему мы не используем луа стейт, который есть в каждой подобной функции — int foo(lua_State* L)?
    Ответ: За всю программу мы используем только один стейт в Script, где регистрируем функции, инициализируем его и делаем прочие штучки. К тому же просто невыгодно было бы, написав целый класс, опять обращаться начистоту к lua_State через lua_pushboolean и прочие функции.

Полный листинг Script.h и Script.cpp

Script.h

#ifndef _SCRIPT_H_ #define _SCRIPT_H_ #pragma comment(lib,»lua.lib») extern «C» < #include #include #include > class Script < private: lua_State *lua_state; public: void Create(); void Close(); int DoFile(char* ScriptFileName); templatevoid RegisterConstant(T value, char* constantname); void Array(int size); template void RegisterConstantArray(T value, int index); void RegisterArray(char* arrayname); int GetArgumentCount(); template T GetArgument(int index); template void Return(T value); >; #endif

Я удалил инклуды для работы с консолью

Script.cpp

#include «Script.h» void Script::Create() < lua_state = luaL_newstate(); static const luaL_Reg lualibs[] = < , , >; for(const luaL_Reg *lib = lualibs; lib->func != NULL; lib++) < luaL_requiref(lua_state, lib->name, lib->func, 1); lua_settop(lua_state, 0); > > void Script::Close() < lua_close(lua_state); >int Script::DoFile(char* ScriptFileName) < luaL_dofile(lua_state,ScriptFileName); return lua_tointeger(lua_state, lua_gettop(lua_state)); >template void Script::RegisterConstant(int value, char* constantname) < lua_pushinteger(lua_state, value); lua_setglobal(lua_state,constantname); >template void Script::RegisterConstant(double value, char* constantname) < lua_pushnumber(lua_state, value); lua_setglobal(lua_state,constantname); >template void Script::RegisterConstant(char* value, char* constantname) < lua_pushstring(lua_state, value); lua_setglobal(lua_state,constantname); >template void Script::RegisterConstant(bool value, char* constantname) < lua_pushboolean(lua_state, value); lua_setglobal(lua_state,constantname); >template void Script::RegisterConstant(int(*value)(lua_State*), char* constantname) < lua_pushcfunction(lua_state, value); lua_setglobal(lua_state,constantname); >void Script::Array(int size) < lua_createtable(lua_state, 2, 0); >template void Script::RegisterConstantArray(int value, int index) < lua_pushnumber(lua_state, index); lua_pushinteger(lua_state, value); lua_settable(lua_state, -3); >template void Script::RegisterConstantArray(double value, int index) < lua_pushnumber(lua_state, index); lua_pushnumber(lua_state, value); lua_settable(lua_state, -3); >template void Script::RegisterConstantArray(char* value, int index) < lua_pushnumber(lua_state, index); lua_pushstring(lua_state, value); lua_settable(lua_state, -3); >template void Script::RegisterConstantArray(bool value, int index) < lua_pushnumber(lua_state, index); lua_pushboolean(lua_state, value); lua_settable(lua_state, -3); >template void Script::RegisterConstantArray(lua_CFunction value, int index) < lua_pushnumber(lua_state, index); lua_pushcfunction(lua_state, value); lua_settable(lua_state, -3); >void Script::RegisterArray(char* arrayname) < lua_setglobal(lua_state, arrayname); >int Script::GetArgumentCount() < return lua_gettop(lua_state); >template int Script::GetArgument(int index) < return lua_tointeger(lua_state,index); >template double Script::GetArgument(int index) < return lua_tonumber(lua_state,index); >template char* Script::GetArgument(int index) < return (char*)lua_tostring(lua_state,index); >template bool Script::GetArgument(int index) < return lua_toboolean(lua_state,index); >template void Script::Return(int value) < lua_pushinteger(lua_state,value); >template void Script::Return(double value) < lua_pushnumber(lua_state,value); >template void Script::Return(char* value) < lua_pushstring(lua_state,value); >template void Script::Return(bool value)

Источник: savepearlharbor.com

Рейтинг
( Пока оценок нет )
Загрузка ...
Заработок в интернете или как начать работать дома