Skip to main content

Plugin Scopes

Plugin scopes tell the client how a plugin should be scheduled with other plugins.

Scopes are lifecycle rules, not permissions. They decide whether a plugin can run beside other plugins, whether only one can be active at a time, and whether a plugin can temporarily take control from the current primary plugin.

Scope Types

TwiLite has three plugin scopes:

  • Primary: one active automation plugin. Starting another primary plugin stops the current primary plugin.
  • Background: utility plugins that can run beside other plugins, such as overlays, dev tools, loggers, and UI helpers.
  • Interrupt: a plugin that can temporarily run when a condition is met, optionally pausing or stopping the current primary plugin.

The plugin list separates these scopes so users can see which plugins are exclusive and which can run together. Primary is the default scope, to change it you can override it.

Primary Plugins

Use a primary scope for normal automation plugins: skilling scripts, combat scripts, minigame scripts, and other plugins that drive the account as the main activity.

import dev.twilite.client.plugins.Plugin;
import dev.twilite.client.plugins.PluginDescriptor;
import dev.twilite.client.plugins.scope.PluginScope;
import dev.twilite.client.plugins.scope.PluginScopes;

@PluginDescriptor(name = "Example Skiller")
public class ExampleSkillerPlugin extends Plugin {

@Override
public PluginScope scope() {
return PluginScopes.primary();
}
}

Only one primary plugin should be active. If the user starts a second primary plugin, the current primary plugin is stopped first.

Background Plugins

Use a background scope for plugins that do not own the account's main activity.

import dev.twilite.client.plugins.Plugin;
import dev.twilite.client.plugins.PluginDescriptor;
import dev.twilite.client.plugins.scope.PluginScope;
import dev.twilite.client.plugins.scope.PluginScopes;

@PluginDescriptor(name = "Example Overlay")
public class ExampleOverlayPlugin extends Plugin {

@Override
public PluginScope scope() {
return PluginScopes.background();
}
}

Good background plugins:

  • draw overlays
  • expose tools or panels
  • inspect client state
  • coordinate external services
  • handle logging or diagnostics

Do not mark an automation plugin as background just so it can run beside another automation plugin. Scope is part of the user-facing lifecycle model and should match what the plugin actually does.

Interrupt Plugins

Interrupt plugins are for temporary control. They are useful for random handlers, or other short tasks that can take priority over the active primary plugin.

import dev.twilite.client.plugins.Plugin;
import dev.twilite.client.plugins.PluginDescriptor;
import dev.twilite.client.plugins.scope.PluginScope;
import dev.twilite.client.plugins.scope.PluginScopes;

@PluginDescriptor(name = "Example Interrupt")
public class ExampleInterruptPlugin extends Plugin {

@Override
public PluginScope scope() {
return PluginScopes.interrupt()
.automatic()
.pausePrimary()
.activateWhen(context -> shouldRun())
.build();
}

private boolean shouldRun() {
return false;
}
}

This example:

  • arms the interrupt automatically when enabled
  • pauses the current primary plugin while the interrupt runs
  • resumes the previous primary plugin when the interrupt condition stops being true

Manual And Automatic Interrupts

Manual interrupts only start when explicitly enabled or started:

@Override
public PluginScope scope() {
return PluginScopes.interrupt()
.manual()
.pausePrimary()
.build();
}

Automatic interrupts are checked by the plugin manager. If the plugin is enabled and its activation condition is true, it starts automatically.

@Override
public PluginScope scope() {
return PluginScopes.interrupt()
.automatic()
.pausePrimary()
.activateWhen(context -> context.tick() == 1000)
.build();
}

Use automatic interrupts only when the condition is specific and cheap to evaluate.

Interrupt Policies

Interrupt scopes choose what happens to the current primary plugin:

PluginScopes.interrupt().runAlongsidePrimary();
PluginScopes.interrupt().pausePrimary();
PluginScopes.interrupt().stopPrimary();

runAlongsidePrimary() leaves the primary plugin running. Use it only for helpers that do not issue conflicting game actions.

pausePrimary() unregisters the primary plugin from events while the interrupt is active, then resumes it when the interrupt ends.

stopPrimary() stops the primary plugin before starting the interrupt.

You can also change what happens after the interrupt finishes:

PluginScopes.interrupt()
.pausePrimary()
.leavePrimaryPaused();

PluginScopes.interrupt()
.pausePrimary()
.stopPrimaryAfter();

Most interrupt plugins should use pausePrimary().

Pause And Resume

Primary plugins can override onPause() and onResume() to clear temporary state around interrupts.

@Override
protected void onPause() {
currentTask = null;
}

@Override
protected void onResume() {
recalculateState();
}

When a primary plugin is paused, it is not receiving event bus callbacks. Do not depend on paused() checks inside normal tick handlers; paused plugins are removed from the event flow until they resume.

Activation Context

Interrupt conditions receive an ActivationContext.

@Override
public PluginScope scope() {
return PluginScopes.interrupt()
.automatic()
.pausePrimary()
.activateWhen(context -> context.currentPrimary() != null)
.interruptWhen(context -> context.currentInterrupt() == null)
.build();
}

The context includes:

  • currentPrimary(): the active primary plugin, if any
  • currentInterrupt(): the active interrupt plugin, if any
  • pausedPrimary(): the primary plugin currently paused by an interrupt, if any
  • accountIdentifier(): the active account identifier
  • gameState(): the current game state
  • tick(): the current client tick counter

activateWhen(...) decides whether the interrupt itself should run.

interruptWhen(...) decides whether it is allowed to take control from the current primary plugin.

Licensing And Instances

Scopes do not bypass licensing. Marketplace licensing is tied to plugin start/stop and server heartbeat sessions.

Important behavior:

  • Starting a licensed plugin opens a license session.
  • Stopping it closes the session.
  • Pausing a primary plugin does not close its license session.
  • An interrupt plugin consumes its own session while it is active.
  • An enabled but inactive automatic interrupt does not consume a running session until it actually starts.

This means an interrupt cannot free the primary plugin's slot just by pausing it.

Choosing A Scope

Use this rule of thumb:

  • Main account automation: PluginScopes.primary()
  • Overlay, panel, logger, inspector, helper: PluginScopes.background()
  • Temporary takeover flow: PluginScopes.interrupt().automatic().pausePrimary()

If a plugin clicks the game as its main purpose, it should usually be primary or interrupt, not background.