Ивенты (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, ...);
eventName
- название ивентаdelay
- задержка в миллисекундах, чувствительна к timescale
, отсчёт продолжается и во время загрузочных экрановformat
- строка из символов, обозначающих порядок и тип передаваемых аргументов:l
- int или bool, f
- float, s
- string, a
- aref, i
- object, e
- ref2)arg1
, arg2
, arg3
, … - аргументы; типы соответствуют заданному в format
порядку
Формат и передаваемые аргументы необязательны, можно сделать ивент процедурой, не передавая ничего.
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
, пробегая список активных квестов/событий.
Тем самым, разместив скрипты каждого квеста в отдельной папке (полностью или почти полностью), мы приобретаем модульность, позволяющую проще ориентироваться в коде и добавляющую гибкость для модификации игры. Доведя эту систему до ума, можно будет добиться, например, чтобы для активации квеста было достаточно поместить его папку в соответствующий каталог, из которого всё необходимое будет подхватываться скриптами. Отдельные инит-функции могут парсить этот каталог при загрузке игры, а дополнительные правки вне папки квеста можно оформлять в виде инструкций для лончера, который, по сути, станет мод-органайзером.
Не только квесты, но и ряд базовых механик допускает оптимизацию при переходе на ивенты. Например, в ванильном ГПК это перезарядка пистолета, которая каждый кадр проверяется у всех персонажей, или состояние опьянения. Подобные вещи аналогично стоит активировать по триггеру.