Added by , published

Программирование Корсаров: продвинутый уровень

Contents:
Код Корсаров, как и большинства других игр, в некоторой степени событийно-ориентирован.
Справка:
Событи́йно-ориенти́рованное программи́рование (англ. «event-driven programming») — парадигма программирования, в которой выполнение программы определяется событиями — действиями пользователя (клавиатура, мышь, сенсорный экран), сообщениями других программ и потоков, событиями операционной системы (например, поступлением сетевого пакета).
Что это значит? При совершении какого-то действия приложение рассылает определённое событие, после чего оно (событие) попадает в т.н. «Event pool» (из уровня скриптов эта куча недоступна). В этой куче находятся все необработанные, но разосланные события. При обработке каждого события (т.е. через определенное время) оно удаляется из пула событий и происходит вызов процедуры, связанной с событием - слушателя события.
На примере:
Игрок совершает выстрел из пушек корабля. Выстрел - это вполне себе событие. Но само по себе, это событие, ничего в себе не несёт. Ну вот событие и событие, что дальше?
А чтобы происходили какие-то действия дальше - на это событие должны быть подписаны какие-то слушатели. Какие слушатели могут быть у события выстрела? Допустим, у нас есть крутая система с генерацией дыма из дула пушки. На какое событие ей быть подписанной, если не на это?
Вот совершили вы выстрел. Игра рассылает сообщение о том, что данное событие произошло, на всех слушателей. В нашем случае - это генерация дыма. И дальше происходят действия, записанные в функции, которую мы указали как коллбек данного слушателя - т.е. генерация дыма.
Справка:
Колбэк-функция (англ. «callback function») или обратный вызов - это функция, переданная в другую функцию в качестве аргумента, которая затем вызывается по завершению какого-либо действия.

Event Management

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

SetEventHandler

// синтаксис
void SetEventHandler(string eventName, string functionName, bool post);
#event_handler(string eventName, string functionName, bool post);

// пример 
SetEventHandler("frame", "RefreshTableByFrameEvent", false);    // выполняется при вызове ф-ции "Event()"
SetEventHandler("frame", "ProcessFrame", true);                 // выполняется при вызове ф-ции "PostEvent()"
SetEventHandler - добавляет обработчик (слушателя) к событию.
Данная функция принимает три аргумента:
string eventName - название события, на которое выполняется подписка. Если события с таким названием не существует - оно будет создано автоматически.
string functionName - название колбэк-функции. Указывается без скобок. Также эта функция не должна принимать аргументов.
bool post - вариант вызова. Посредством Event(), или PostEvent().
Как видим, есть два способа повесить слушателя на событие: при помощи функции или при помощи директивы.
И здесь есть нюанс: слушателя, добавленного при помощи директивы #event_handler, нельзя удалить.

DelEventHandler

// синтаксис
void DelEventHandler(string eventName, string functionName);

// пример
DelEventHandler("frame", "RefreshTableByFrameEvent");
DelEventHandler - удалить обработчик (отменить подписку) с события.
string eventName - название события.
string functionName - название колбэк-функции.
Здесь всё в точности наоборот: указываем событие и название колбэка, который нужно снять с подписки.

Event

// синтаксис
void Event(string eventName, [MSG]); 

// пример 
Event("NextDay");                               // без сообщения
Event("ControlActivation", "s", "ChrAction");   // передача аргумента строкового типа
Справка:
В С++ функции могут иметь необязательные аргументы. Они указываются в квадратных скобках.
Event - запустить событие и вызвать все его обработчики.
string eventName - название события.
MSG - передаваемое сообщение. Оно состоит из перечисления типов передаваемых аргументов, а также самих аргументов.
В качестве сообщения могут быть переданы следующие типы данных:
l: int/bool
f: float
s: string
a, i: object или aref
e: ref
Как видим, данная функция может быть вызвана двумя способами: с передачей дополнительных аргументов и без них. Аргументов может быть любое количество - их типы перечисляются без разделительных знаков, а сами аргументы через запятую в той же последовательности, что и перечисленные типы.

PostEvent

// синтаксис
void PostEvent(string eventName, int delay, [MSG]);

// пример
PostEvent("My_eventMoveImg", 100);               // без сообщения
int charIndex = sti(CharacterRef.index);
PostEvent("eventDialogExit", 1, "l", charIndex); // передача ID персонажа
PostEvent - запуск события с задержкой на вызов обработчиков.
string eventName - название события.
int delay - задержка вызова, в милисекундах (1s = 1000ms).
MSG - передаваемое сообщение (аналогично предыдущей ф-ции).
Данный вызов колбэков также может быть с передачей аргументов и без. Отличается от предыдущего тем, что вызов обработчиков происходит не моментально, а через указанное кол-во времени.

GetEventData

// синтаксис
[any type] GetEventData(); 

// пример
SetEventHandler("Control Activation", "InfoShow_Control", 0);
Event("Control Activation", "sl", "ChrAction", 123);

void InfoShow_Control()
{
    string controlName = GetEventData();    // "ChrAction"
    int num = GetEventData();               // 123
    // ...
}
GetEventData - извлекает аргумент из сообщения, переданного в событие.
Эта функция обычно вызывается из функции, зарегистрированной как обработчик событий. Аргументы возвращаются в том порядке, в котором они были переданы в сообщение.

ClearEvents

// синтаксис
void ClearEvents(); 
ClearEvents - удаляет все обычные обработчики из всех событий.
Не влияет на обработчики событий «post».

ClearPostEvents

// синтаксис
void ClearPostEvents();
ClearPostEvents - удаляет все обработчики «post» из всех событий.
Не влияет на обычные обработчики событий.

EventsBreak

// синтаксис
void EventsBreak();
EventsBreak - останавливает выполнение всех событий для оставшейся части кадра.
В настоящее время эта функция не имеет никакого эффекта, поскольку bEventsBreak всегда имеет значение false при входе в цикл событий.

Contents
`
LOADING AD...
0
29
0
Одну и туже функцию можно несколько раз добавить каллбэком в одно и тоже событие.
Replies (3)
0
22
0
nazarpunk, не похоже
uint32_t S_EVENTTAB::AddEventHandler(const char *event_name, uint32_t func_code, uint32_t func_segment_id, int32_t flag, bool bStatic)
{
    uint32_t i;

    const auto hash = MakeHashValue(event_name);

    const auto ti = HASH2INDEX(hash);

    for (uint32_t n = 0; n < Event_num[ti]; n++)
    {
        if (pTable[ti][n].hash == hash)
        {
            if (!storm::iEquals(event_name, pTable[ti][n].name))
                continue;
            // event already in list
            for (i = 0; i < pTable[ti][n].elements; i++)
            {
                // event handler function already set
                if (pTable[ti][n].pFuncInfo[i].func_code == func_code)
                {
                    /*if(pTable[ti][n].pFuncInfo[i].status == FSTATUS_DELETED)
                    {
                      trace("pTable[ti][n].pFuncInfo[i].status == FSTATUS_DELETED : %s",pTable[ti][n].name);
                    }*/
                    // return n;
                    pTable[ti][n].pFuncInfo[i].status = FSTATUS_NORMAL;

                    return (((ti << 24) & 0xff000000) | (n & 0xffffff));
                }
            }
            // add function
            i = pTable[ti][n].elements;
            pTable[ti][n].elements++;
            pTable[ti][n].pFuncInfo.resize(pTable[ti][n].elements);

            pTable[ti][n].pFuncInfo[i].func_code = func_code;
            pTable[ti][n].pFuncInfo[i].segment_id = func_segment_id;
            if (flag)
                pTable[ti][n].pFuncInfo[i].status = FSTATUS_NEW;
            else
                pTable[ti][n].pFuncInfo[i].status = FSTATUS_NORMAL;
            pTable[ti][n].pFuncInfo[i].bStatic = bStatic;
            // return n;
            return (((ti << 24) & 0xff000000) | (n & 0xffffff));
        }
    }

    // add new event
    if (Event_num[ti] >= Buffer_size[ti])
    {
        Buffer_size[ti] += BUFFER_BLOCK_SIZE;
        pTable[ti].resize(Buffer_size[ti]);
    }

    pTable[ti][Event_num[ti]].elements = 1;
    pTable[ti][Event_num[ti]].hash = hash;
    pTable[ti][Event_num[ti]].name = nullptr;

    pTable[ti][Event_num[ti]].pFuncInfo.push_back(EVENT_FUNC_INFO{});
    pTable[ti][Event_num[ti]].pFuncInfo[0].func_code = func_code;
    pTable[ti][Event_num[ti]].pFuncInfo[0].segment_id = func_segment_id;
    if (flag)
        pTable[ti][Event_num[ti]].pFuncInfo[0].status = FSTATUS_NEW;
    else
        pTable[ti][Event_num[ti]].pFuncInfo[0].status = FSTATUS_NORMAL;
    pTable[ti][Event_num[ti]].pFuncInfo[0].bStatic = bStatic;

    if constexpr (true) // bKeepName)
    {
        if (event_name)
        {
            const auto len = strlen(event_name) + 1;
            pTable[ti][Event_num[ti]].name = new char[len];
            memcpy(pTable[ti][Event_num[ti]].name, event_name, len);
        }
    }
    Event_num[ti]++;
    // return (Event_num[ti] - 1);
    return (((ti << 24) & 0xff000000) | ((Event_num[ti] - 1) & 0xffffff));
}
0
29
0
avuremybe, хм, интересно, а исходники игры открыты или дизасемблированы?
0
22
0
nazarpunk, открыли недавно. Пацаны всем селом уже успели там многое перепилить, но основа на месте
To leave a comment please sign in to the site.