Портирование структур Си в Lua, прерывание скриптов по времени
Раздел : Для админов и билдеров
Опубликовано Flanker [08.04.2013]
В маде (да и не только в маде) любой игровой объект представляет из себя структуру (ну или набор структур) или класс со свойствами и функциями. Чтобы получать информацию об игровых объектах или их как-то изменять из Lua необходимо огрганизовать какой-то интерфейс взаимодействия. Конечно можно это сделать через свои собственные регистрируемые функции, но это крайней неудобно, поэтому это не наш метод. Реализовать задуманное можно через метатаблицы Lua. В Lua есть тип данных под названием userdata. По сути дела это указатель Си который мы можем передать в Lua. С userdata сделать по сути дела ничего нельзя, но это переменная lua а дело в том, что каждая переменная или таблица (массив) в Lua имеют свою метатаблицу с набором полей. Среди этих полей есть два которые нас интересуют, а именно __index и __newindex. Каждый раз когда в Lua мы присваиваем переменной значение обновляется поле __newindex и каждый раз когда мы получаем значение переменной обновляется поле __index. К этим полям можно привязать функции. Вот на этом принципе и возможно организовать портирование структур из мада в Lua и наоборот.

 



Все взаимодействие Lua и Си происходит через некий стек. Для работы с этим стеком в Lua предусмотрен ряд функций которые мы и будем использовать. Я не буду описывать подробно эти функции что они делают и зачем. Разъяснения можно найти на форумах в интернете откуда я собственно это все и брал. Итак допустим нам нужно проинициализировать для Lua объект конкретного игрока или моба. У меня в маде структура такого типа носит название CHAR_DATA. Создадим функцию для инициализации этого объекта в Lua.

 void initmetach (const char* tname,lua_State *vLm,CHAR_DATA *ch)
{
     void** ptch; //Заводим войдовский указатель
     ptch=(void**)lua_newuserdata(vLm, sizeof(void*)); //Резервируем под указатель память в Lua машине
     *ptch=ch;//Присваиваем указателю значение нашего объекта
     lua_newtable(vLm);//Создаем новую таблицу в Lua
     lua_pushstring(vLm,"__newindex");
     lua_pushlightuserdata(vLm,ch); 
     lua_pushcclosure(vLm, ch_set, 1);  //Привязываем к полю __newindex функцию для установки значений объекта ch
     lua_rawset(vLm, -3); 
     lua_setmetatable(vLm,-2);
     lua_getmetatable(vLm, -1); 
     lua_pushstring(vLm, "__index"); 
     lua_pushlightuserdata(vLm,ch); 
     lua_pushcclosure(vLm, ch_get, 1);  //Привязываем к полю  __index фукнцию для получения значений
     lua_rawset(vLm, -3); 
     lua_setmetatable(vLm, -2);//Устанавливаем метатаблицу для нашей новой таблицы
     lua_setglobal(vLm, tname); //Устанавливаем имя таблицы по которому она будет доступна в Lua например «ch».
}


Теперь вызвав в каком-нибудь месте эту функцию и передав ей имя для объекта в Lua – tname  указатель на машину в Lua – vLm и объект персонажа – ch мы получим в lua  переменную с именем ch которая связана с нашим объектом ch в Си.
В Lua это будет выглядеть например так:
local hit=ch.hit  --Получаем кол-во жизни у игрока
ch.hit=hit+100 –Добавляем к текущей жизни игрока 100
Теперь о функция ch_set и ch_get. Первая из них будет у нас отвечать за присвоение значений полей объекта CHAR_DATA из Lua, а вторая для полечения значений полей CHAR_DATA в Lua.

Функция ch_set:

 static int ch_set(lua_State *vLm)
void** pch;
    CHAR_DATA   *ch;
    const char *var;
    var= luaL_checkstring(vLm, 2); //Получаем из стэка Lua имя поля которому надо присвоить значение
     pch=(void**)lua_touserdata(vLm,1);  //Получаем указатель юзердата из Луа
 if(pch==NULL)
     return 0;
     ch=*pch;

if(!str_prefix(var,"hit"))  //Если в Lua идет обращение к полю hit тогда… (str_prefix) внутренняя функция мада
{
int hit=lua_tointeger(vLm,-1);//Получаем значение из стэка Lua которое мы хотим присвоить в lua это строка ch.hit=hit+100
    if(hit<=0)
      ch->hit=1;
    else
      ch->hit=hit;
    return1;
}
    return 0; 



В данной функции идет мы получаем указатель на объект нашего персонажа, получаем имя поля которому в Lua пытаются присвоить значение и затем в зависимости от имени поля мы уже производим какие-то действия.
Функция ch_get аналогична функции ch_set только так сказать с обратным знаком

 static int ch_get(lua_State *vLm)

     void** pch;
     CHAR_DATA   *ch;
     const char *var;
     var= luaL_checkstring(vLm, 2);
     pch=(void**)lua_touserdata(vLm,1); 
     if(pch==NULL)
         return 0;
     ch=*pch;
    if(!str_prefix(var,"hit")) //Если в Lua у нас запросили значение поля hit структуры ch
    {
        lua_pushnumber(vLm,ch->hit); //Пихаем в стек Lua значение поля
        return 1;
    }
     return 0; 



То есть для примера процесс можно описать так:
В игре у нас срабатывает триггер на какое-либо событие. Ну например на мобе висит триггер на вход в локацию. В этом триггере при срабатывании обычно два объекта назовем их например ch и vic – ch – это моб на котором висит триггер к которому привязан скрипт который у нас будет выполняться при срабатывании триггера, а vic это объект игрока или моба который зашел в комнату таким образом в Си при подготовке выполнения скрипта тригера у нас будет:

 initmetach (“ch”, vLm, ch);//Помещаем в Lua объект моба выполняющего скрипт
initmetach (“vic”, vLm, vic);//Помещаем в Lua объект игрока или моба на которого сработал триггер
//Подгружаем наш скрипт в – скриптв source если у нас скрипт в файле сразц вызываем //luaL_dofile(vLm,filename);
int error = luaL_loadbuffer(vLm, source, strlen(source), "строка ошибки");
if (error)
{
    sprintf(buf,"Сообщение  Lua(Прогс:%ld): %s",pvnum,(char*)lua_tostring(vLm, -1));
    wiznet(buf, NULL, NULL, WIZ_OLCDEBUG,0,0);//Функция мада
    lua_pop(vLm, 1); 
    return;
}
error=lua_pcall(vLm, 0, 0, 0);
if (error)
{
    sprintf(buf,"Сообщение  Lua(Прогс:%ld): %s",pvnum,(char*)lua_tostring(vLm, -1));
    wiznet(buf, NULL, NULL, WIZ_OLCDEBUG,0,0);//Функция мада
    lua_pop(vLm, 1); 
    return;


}



Ну а в самом Lua скрипте работаем с объектами ch и vic и их полями так как нам надо. При использовании такого механизма портирования можно передавать и работать в Lua с любыми игровыми объектами мада, что очень удобно и функционально.

Прерывание скриптов по времени.

Выполнение скриптов имеет один неприятный момент. Если вдруг в скрипте у нас ошибка не синтаксическая, а логическая и скрипт начинает например выполнять вечный цикл, то и мад соответственно у нас зависает, чтобы избежать такого развития событий в Lua предусмотрена функци  lua_sethook. Функция устанавливает отладочную функцию для Lua которая выполняется при наступлении каких-либо событий. События устанавливаются побитывомыми константами типа: LUA_MASKCALL  - подробности смотрите в руководстве Lua. В нашем случае установка хука имеет вид:

 

 lua_sethook(vLm, LuaHook, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT|LUA_MASKLINE, 1);



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

 static void LuaHook(lua_State *vLm, lua_Debug *ar)
{
   
    if(((long)time( NULL ))-script_time_start>time_out_script)
    {
        char buf[256];
        sprintf(buf,"Превышен лимит времени выполнения, выполнение преврвано.");
        luaL_error(vLm,buf);
    }
}


Перед начало выполнения скрипта мы в переменную script_time_start помещаем время начала выполенения скрипта в секундах. А в консанта time_out_script содержит значение секунд которое отводится на выполнение скрипта и если у нас разница в текущем времени и времени выполнения скрипта превышает time_out_script – выполнение прерывается.