Содержание

Event Management

Введение

Ивенты (events) — способ опционально выполнять код из разных методов. Как и во многих других языках программирования, это система обратного вызова. Она позволяет зарегистрировать ивент с любым названием, при запуске которого в порядке очереди (FIFO) вызываются и выполняются предписанные обработчики события (event handlers). За счёт использования ивентов код становится более гибким, децентрализованным, позволяя управлять его структурой из разных мест, оперативно внедряя код одних методов в другие. Для дополнительной технической информации ознакомьтесь с официальной документацией.

Описание для скриптов

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

Для мгновенного вызова используется функция Event, для отложенного PostEvent, они могут содержать множественное число аргументов.

Event(string eventName, string format, arg1, arg2, arg3, ...);
PostEvent(string eventName, int delay, string format, arg1, arg2, arg3, ...);

Формат и передаваемые аргументы необязательны, можно сделать ивент процедурой, не передавая ничего.
NB: Отложенные ивенты активируются и после смены локации, но забываются после перезагрузки, если не успели отработать. Это отличает их от стандартных методов отложенного вызова функций (DoQuestFunctionDelay, LAi_MethodDelay, …), которые деактивируются в обоих случаях.
Для постоянной привязки функции к соответствующему ивенту следует использовать:

#event_handler(string eventName, string functionName);

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

SetEventHandler(eventName, functionName, int post);
SetEventHandlerConst(eventName, functionName, int post);
DelEventHandler(eventName, functionName);
DelEventHandlerConst(eventName, functionName);

Аргумент post в случае единицы не даёт обработчику включиться сразу, только со следующего кадра3).
Обработчик, активированный через SetEventHandlerConst, будет реагировать на вызов ивента и после перезагрузки, пока не будет отключён через DelEventHandlerConst.
При этом обработчик, указанный в #event_handler, убрать через DelEventHandler нельзя.

Вместо собственных аргументов обработчики получают их по одному внутри тела с помощью GetEventData():

void LI_SetWindowSize()
{
	int w = GetEventData();
	int h = GetEventData();
	int bTVused = GetEventData();
	if(bTVused) SetShowWindowParameters(bTVused,w,h,40,24,w-40,h-24);
	else SetShowWindowParameters(bTVused,w,h,0,0,w,h);
}

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

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

Применение

Помимо существующего использования в скриптах, ивенты могут помочь в оптимизации и рефакторинге кода, включая всевозможные квесты.
Всякий винконд (win condition - триггер) можно оформить в виде ивента и далее вместо того, чтобы плодить огромное количество атрибутов, достаточно в нужных местах включать и отключать (привязывать/отвязывать) соответствующие обработчики. Тем самым, в отличие от проверки через QuestsCheck, проверка из ивента будет проходить только по целевым условиям. Добавление новых триггеров в этом случае осуществляется значительно проще, чем прописывание дополнительных кейсов в ProcessCondition, а их выбор расширяется до огромного количества всевозможных параметров из движка. Если по квесту для вызова функции предполагается несколько условий, то её можно подключить к нескольким ивентам, проверяя выполнение остальных условий уже внутри самой функции. Когда все проверки пройдены, и соответствующие скрипты отработали, можно по надобности отключить обработчик, по надобности вновь активировать его в другом месте.

С учётом того, что все дополнительные иниты, вроде новых предметов, также можно писать внутри скриптов конкретного квеста, то, обзаведясь необходимым количеством ивент-кондов, можно компактно хранить все скрипты квеста в одной папке, то есть без использования совершенно чудовищных мегафайлов, вроде reaction_functions и quests_reaction. При этом диалоги могут подгружать актуальные квестовые ноды через loadsegment, пробегая список активных квестов/событий.

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

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

Категории

1) , 3)
Уточнить
2)
EvgAnat: Мой опыт говорит, что a - это чётко aref; i - чётко основной объект aka экземпляр класса, к которому обращаются; e обязательно ставить, если хочешь возвращать значение; в остальном стабильнее со ссылкой на объект работает aref.