Skip to content

Script Plugin System

Mahtra edited this page Aug 2, 2026 · 1 revision

Script Plugin System

Some scripts (currently combat-trainer and hunting-buddy) expose a small, vendor-neutral plugin system so you can observe and influence a script's behavior from your own code, without editing (forking) the script itself.

This page documents the shared model that every plugin-capable script follows. Each such script also has its own page listing the specific hooks it fires:

What a plugin is

A plugin is any plain Ruby object that implements one or more hook methods. There is no base class to inherit and no interface to satisfy: the host script discovers which hooks your plugin cares about at runtime with respond_to?, and calls only those. A hook you do not implement is simply skipped.

This keeps the dependency one-directional - your plugin depends on the host's hook names, never the other way around.

Where plugins live and how they load

Create a file named <script>-plugin-<name>.rb in your Lich scripts/custom/ directory, for example:

scripts/custom/combat-trainer-plugin-mything.rb
scripts/custom/hunting-buddy-plugin-mything.rb

At startup the host loads every matching file, in sorted filename order, before it begins its work, and each file is expected to register one instance of its plugin:

CombatTrainer.register_plugin(MyThing.new)   # combat-trainer
HuntingBuddy.register_plugin(MyThing.new)    # hunting-buddy

Use the class name that matches the host script (see each script's page). A plugin file that fails to load - for example, a syntax error or a missing require - is skipped with a warning and does not stop the host script from starting.

Two kinds of hook

Every hook is one of two kinds, which differ in how the host uses your return value.

Decision hooks let a plugin replace a built-in decision. Registered plugins are polled in registration order and the first non-nil return value wins; the host then uses that value instead of its own default. Return nil (or don't implement the hook) to defer to the built-in behavior.

Note: false is a real answer and stops the poll. It does not mean "defer" - only nil defers.

Notification hooks let a plugin react to something that happened. Every registered plugin that implements the hook is called, purely for its side effects; the return value is ignored.

In both cases, an exception raised inside one plugin is caught and isolated (and echoed when the host's debug flag is on), so a misbehaving plugin degrades gracefully instead of breaking the host or the other plugins.

Calling into a plugin from outside

Each host forwards unknown method calls to the first registered plugin that responds, via its global handle. So an external script (or a ;e one-liner) can drive your plugin directly:

;e $COMBAT_TRAINER.my_custom_command(arg)
;e $HUNTING_BUDDY.my_custom_command(arg)

If no registered plugin responds, a NoMethodError is raised as usual.

A minimal example

# scripts/custom/combat-trainer-plugin-hello.rb
class HelloPlugin
  def after_initialize(_host)
    echo '[hello] plugin loaded'
  end
end

CombatTrainer.register_plugin(HelloPlugin.new)

Every plugin-capable script fires after_initialize once at startup and cleanup once at teardown, so those two hooks are a safe starting point on any host. For everything else, see the per-script hook catalog:

Clone this wiki locally