Skip to content

Features

The Feature class is the base "plugin" class in Epip. Nearly all end-user features in Epip use the Feature system, which provides simplified access to common libraries and helps keep scripts and their resources organized and easily modifiable by other mods.

Features are registered using Epip.RegisterFeature(modTable, featureID, featureTable) and fetched using Epip.GetFeature(modTable, featureID). Since Features are stored per-modtable, there's no need to prefix their ID. The feature table supports various (optional) keys with special meanings to configure the feature or initialize resources.

---@class Features.MyFeature : Feature
local Widgets = {
    -- Necessary to use a v56-style event system
    USE_LEGACY_EVENTS = false,
    USE_LEGACY_HOOKS = false,

    DEVELOPER_ONLY = true, -- If true, the feature will only be enabled in extender developer mode
    FILEPATH_OVERRIDES = { -- File overrides to apply when the feature is registered. Key is target path, value is resource to replace with.
        ["Public/Game/GUI/someUI.swf"] = "Public/EpipEncounters_7d32cb52-1cfd-4526-9b84-db4867bf9356/GUI/someUI.swf",
    },

    SupportedGameStates = _Feature.GAME_STATES.RUNNING_SESSION | _Feature.GAME_STATES.PAUSED_SESSION, -- Bitfield; if set, the feature will only be considered enabled during those game states

    TranslatedStrings = {
        Title = {
           Handle = "hf196badfg6c61g4bd5g91e8g7bfbf9874cbf",
           Text = "My Feature",
           ContextDescription = "Name of the feature, appears here and there",
        },
    },
    Settings = {
        Enabled = {
            Type = "Boolean",
            Name = "My Setting",
            Description = "Does something.",
            DefaultValue = false,
            Context = "Client",
        },
    },
    Events = {
        SomethingHappened = {}, ---@type Event<EmptyEvent>
    },
    Hooks = {
        GetSomeValue = {}, ---@type Hook<Features.MyFeature.Hooks.GetSomeValue>
    },
}
Epip.RegisterFeature("SettingWidgets", Widgets)

The table passed onto RegisterFeature() will be initialized as a Feature. This table supports multiple "magic tables" whose contents will be initialized as follows:

  • Events and Hooks: each key will become an Event or Hook respectively. You will still need to manually annotate them.
  • TranslatedStrings: each key will become TextLib_TranslatedString
  • Settings: each key will become SettingsLib_Setting. Not recommended as back-referencing translated strings here is not possible - see further down for the new recommended way of initializing settings.
  • InputActions: client-only; each key will become InputLib_Action. Not recommended for the same reason as Settings.

Fields that are in all-caps indicate fields that are only checked during feature registration; changing them later has no effect.

Using the "magic tables" is not required, but is recommended for standarization; events are expected to be in the Events table, translated strings are expected to be TranslatedStrings, etc.

The Feature system encourages designing scripts with extendability in mind, exposing events, hooks, resource and methods for usage by other mods.

Features are not only intended to be used for end-user mod aspects - they may also be used to setup utilities and APIs. In Epip, Features are used for APIs that are deemed to specific in their usage so as to be made into core global libraries, such as Features.SettingWidgets, which offers methods to render controls for settings within Generic UIs.

Enabled/Disabled Features

Features are enabled by default, and may be disabled by calling SetEnabled() or if the current game state is not supported by the feature.

There is no intrinsic meaning to disabling features - all it does is make IsEnabled() return false. This is by design, so as to allow running alternative logic when the feature is "disabled". As such, disabling a feature does not "unload" its scripts or change it in any way unless explicitly checked.

For example, if some other mod disables your feature with SetEnabled(), you could show the user a warning that the feature is unavailable. For this, IsEnabled() checks should be seeded throughout your scripts as necessary.

No events exist for features being disabled/re-enabled due to the possibility of overriding IsEnabled() to extend the checks - this practice is undetectable without excessive (and not fully-reliable) tick-based checks.

Criticism and possible improvements

The Feature system is a very old backbone of Epip, and although it fulfills the ideal of keeping script methods and their resources organized, the initialization of those resources as well as some concepts such as enabling/disabling have problems that are not easy to address:

  • No event exists for a feature being enabled/disabled, for reasons explained above
    • For client-side scripting this is a non-issue due to general lack of cleanup routines, but it may be annoying in server contexts
  • Initializing resources via the magic table keys cannot be understood by LLS
    • Though this is solved by initializing resources outside the table definition, it creates verbosity and nullifies the primary intent of these magic tables (reducing boilerplate code)