The Event System - Ruin0x11/OpenNefia GitHub Wiki

Events are a very important part of OpenNefia. They allow you to insert custom script behavior at certain important times during the game.

How Events Work

An event is a data type in the data table, with ID base.event.

You can register one or more callback functions with a specific event ID. Each callback is assigned a numeric priority value, which determines the order in which the callback will run relative to all other callbacks registered for the event. Callbacks with lower priority are run first.

Events can be triggered using the Event API or by calling :emit() on a game object. If a game object emits the event, it will be passed as the "receiver" of the event to the event's callbacks.

Each callback function should accept a specific set of arguments and may return a result, like so:

local function callback(receiver, params, result)
   -- callback body
   return result
end

Here is an explanation of each parameter.

  • receiver: The object that triggered the event, if any. If the Event API was used to trigger the event, this argument will be nil.
  • params: A table of parameters passed to the event. The specific parameters used differ depending on the event type.
  • result: A value that is returned from the event being triggered. The value that is returned from each callback function is passed to the next callback in order, using the result argument. The final result returned is the return value received by the location that triggered the event, so you can affect the output of things like stat calculations. Certain events do not return anything, so the value of result is ignored for those events. The initial value of result passed to the first callback can be set using the event APIs.

Note that if no result or nil is returned from a callback function, the overall result will remain unchanged. This is to avoid confusion when a callback function does not explicitly return a value, which causes the value returned from it to become nil. This would lead to unexpected behavior when the result would become nil despite not returning anything from the function.

Using Events

Here is how to use events in the code.

Registering/Unregistering Event Callbacks

You can register an event callback in one of two ways:

  • Globally, so it will be run when Event.trigger() or IEventEmitter:emit() are called
  • On an instance of a game object, so it will only be run when :emit() is called on that object

Globally

To register a global callback for an event, use Event.register():

Event.register(event_id, name, callback, opts)
  • event_id: The event to register a callback for.
  • name: A uniquely identifying name for the callback. This can be as long as you want, so you can include a description of what the event does also. No two names can collide for a single event ID.
  • callback: An event callback function, as described above.
  • opts: Optional. A table of extra parameters for the event's registration.
    • priority: The callback's priority. Lower priority callbacks get called first. Defaults to 200000.

Here is an example of how to use Event.register().

local function callback(receiver, params, result)
   Gui.mes("Event triggered.")
end

Event.register("my_mod.some_event", "My event callback.", callback)

To unregister a callback, use Event.unregister(). It takes an event ID and the callback's name, and removes it from the callbacks to be run.

Event.unregister("my_mod.some_event", "My event callback.")

On Game Objects

To register an event on a game object, call object:connect_self().

local player = Chara.player()
player:connect_self("my_mod.some_event", "My event callback.", callback)

To unregister an event, call object:disconnect_self().

local player = Chara.player()
player:disconnect_self("my_mod.some_event", "My event callback.")

One important thing to note when registering events on game objects is the list of events will not be serialized when the game is saved. Thus, you should only call :connect_self() from within another event like base.on_map_entered to ensure the events are set up every time the object is loaded.

Also, if you register an event on an item, it will be taken into account when the item is stacked with other items of the same type, so items with different sets of event callbacks will show up as separate item stacks. This also means that registering a callback on an item stack will cause the events to be preserved for all items if you separate the stack into two parts.

Triggering Events

Events can be triggered in one of two places:

  • The Event API (api.Event), with Event.trigger()
  • A game object or other object that implements the IEventEmitter interface, with object:emit().

The basic syntax for both functions is the same:

Event.trigger(event_id[, event_args[, default_result]])

Here is an example of using each function.

local result = Event.trigger("my_mod.some_custom_event")

local player = Chara.player()
local result = player:emit("my_mod.some_custom_event", { argument = "Scut!", other_argument = 42 }, "default_result")

Here is an explanation of each argument to Event.trigger()/IEventEmitter:emit():

  • event_id: An ID of a data entry of base.event. This determines the event to run.
  • event_args: Optional. A table of arguments to pass to the event handler. The specific arguments vary depending on the event type being triggered, so you need to look at either the documentation or existing places in the code that Event.trigger() is being called to determine what arguments should be used. If omitted, defaults to an empty table.
  • default_result: Optional. The default return value of the event. If omitted, defaults to nil.

Debugging callbacks

You can print a list of the globally registered callbacks of any event in the REPL by using Event.list().

Event.list("base.on_chara_created")

Commonly Used Events

The documentation for events is rather lacking at present, since the interface for each event is still unstable. Usually it helps to search through the code for instances of Event.trigger()/:emit() or Event.register() to see how events are triggered or used.

Next Steps

Proceed to Localization.