Architecture

_images/architecture.png

Overview

The framework is built from two kinds of scripts:

  • Include files (ff_i_*) are libraries of functions and constants shared by every script. They contain no entry point and do nothing on their own. ff_i_core includes all of them, so a script only needs a single #include "ff_i_core".

  • Unit files (ff_u_*) are small, self-contained scripts. Each one provides a single feature, for example respawning destroyed placeables, logging chat, or limiting inventory size.

Units

A unit is one script file that implements one feature and reacts to events. It holds everything the feature needs: its constants, variables, functions and event handlers. No other script ever calls a unit directly.

Every unit follows the same pattern:

  1. When the module starts, the framework sends the ON_REGISTER event to the unit.

  2. On ON_REGISTER, the unit subscribes to the events it needs with SubscribeToEvent().

  3. Each time one of those events fires, the unit is executed again, reads the current event with NWNX_Events_GetCurrentEvent(), and acts on it.

#include "ff_i_core"

void main()
{
  string sEvent = NWNX_Events_GetCurrentEvent();
  if (sEvent == ON_REGISTER)
    SubscribeToEvent(ON_DEFAULT_ITEM_ACQUIRE, __FILE__);
  else if (sEvent == ON_DEFAULT_ITEM_ACQUIRE)
  {
    // do something
  }
}

Script prefixes

Prefix

Purpose

ff_i_

Framework include files.

ff_u_

Framework units.

ff_d_

Framework user-defined scripts, set directly in an object’s event slots (for example a trash can’s OnClose).

ff_c_

Conversation actions and conditions.

ff_x_

Framework system scripts (module startup, overrides, …).

us_u_

User units.

us_d_

User user-defined scripts.

us_x_

User system scripts (for example us_x_overrides).

Adding and removing units

Units are discovered automatically. On module start, ff_x_modstartex searches the resource manager for every compiled script matching ff_u_*, ff_d_*, us_u_* and us_d_*, and sends each one the ON_REGISTER event. Script names containing a double underscore (__) are skipped.

This means that:

  • adding a unit only requires adding its file to the module or a hak;

  • removing a unit only requires removing its file;

  • a unit can also be turned off without removing it, through its module variable ENABLE__<UNIT>. Most units are opt-out (set the variable to “N” to disable them); a few are opt-in (set it to “Y” to enable them). Each unit’s page lists its variable and its default.

User units

Builders can extend the framework without touching its files: adding a new us_u_* script to the module is enough. User units are registered right after the framework units, and work exactly like them.

Events

Event dispatching

Every event is sent and received through the NWNX event NWNX_ON_RUN_EVENT_SCRIPT_BEFORE. On module start, the framework subscribes the single dispatcher script on_event to it. Whenever the engine is about to run an event script (module, area, creature, door, placeable, store, trigger, …), on_event signals the matching ON_DEFAULT_* event to every unit subscribed to it.

Event scripts are whitelisted on demand: SubscribeToEvent() adds an engine event to the NWNX whitelist only when a unit subscribes to it. An engine event nobody listens to therefore costs nothing.

On top of the engine events, the framework sends events of its own, such as ON_REGISTER, ON_MODULE_START, ON_DELAYED_REGISTER, the real-time minute, hour and day events, and console commands. See ff_i_events_c for the full list.

Existing event scripts

The framework runs before the engine event script and never skips it. Scripts already set in an object’s event slots keep working as before, so the framework can be added to an existing module without rewriting its scripts.