Overlay System
The annotation overlay system lets plugins draw HUD text, rates, skill stats, world tiles, and world areas without writing a custom renderer.
Attach an overlay to a plugin with @PluginDescriptor#overlay.
@PluginDescriptor(
name = "Example",
overlay = ExampleOverlay.class
)
public class ExamplePlugin extends Plugin {
}
Overlay classes referenced by @PluginDescriptor#overlay implement OverlayBinding. For annotation-backed overlays, OverlayBinding is only the marker that lets the plugin manager discover the class; @OverlayPanel tells the manager to wrap it in an internal AnnotatedOverlay.
Use the annotation-backed style for ordinary HUD text and simple world highlights. Implement Overlay yourself only when you need custom rendering logic.
Text HUD
import dev.twilite.client.ui.OverlayBinding;
import dev.twilite.client.ui.overlay.OverlayPanel;
import dev.twilite.client.ui.overlay.OverlayText;
import java.util.function.Supplier;
@OverlayPanel(title = "Example")
public class ExampleOverlay implements OverlayBinding {
@OverlayText(label = "State")
private String state = "Running";
@OverlayText(label = "Kills", rate = true, suffix = " kills")
private int kills = 42;
@OverlayText(label = "Mode")
private Supplier<String> mode = () -> "Idle";
}
rate = true tracks numeric values from the first rendered sample and displays the per-hour delta.
Annotated panels can be dragged by their title area and collapsed with the small header button.
ImGui Panels
Annotated overlay panel rows render with Java2D by default. Override imgui() to render the panel through native ImGui instead.
import dev.twilite.client.ui.OverlayBinding;
import dev.twilite.client.ui.overlay.OverlayPanel;
import dev.twilite.client.ui.overlay.OverlayText;
@OverlayPanel(title = "Debug")
public class DebugOverlay implements OverlayBinding {
@Override
public boolean imgui() {
return true;
}
@OverlayText(label = "State")
private String state = "Running";
}
ImGui panels use the same @OverlayPanel position and style values, support dragging, resizing, collapsing, and tabs, and stack with Java2D panels that share the same anchor. World geometry annotations such as @OverlayCoord and @OverlayRect still render through Java2D.
Updating On Tick
Keep overlay fields as cached display state and update them from the plugin's server tick handler. The annotated overlay reads the fields when it paints; it does not need getter methods.
import com.google.inject.Inject;
import com.google.inject.Provider;
import dev.twilite.client.eventbus.Subscribe;
import dev.twilite.client.events.ServerTickEvent;
import dev.twilite.client.plugins.Plugin;
import dev.twilite.client.plugins.PluginDescriptor;
@PluginDescriptor(
name = "Example",
overlay = ExampleOverlay.class
)
public class ExamplePlugin extends Plugin {
private final Provider<ExampleOverlay> overlay;
private int ticks;
@Inject
public ExamplePlugin(Provider<ExampleOverlay> overlay) {
this.overlay = overlay;
}
@Subscribe
private void onServerTick(ServerTickEvent event) {
ticks++;
overlay.get().update("Running", ticks);
}
}
import dev.twilite.client.ui.OverlayBinding;
import dev.twilite.client.ui.overlay.OverlayPanel;
import dev.twilite.client.ui.overlay.OverlayText;
@OverlayPanel(title = "Example")
public class ExampleOverlay implements OverlayBinding {
@OverlayText(label = "State")
private String state = "Starting";
@OverlayText(label = "Ticks")
private int ticks;
public void update(String state, int ticks) {
this.state = state;
this.ticks = ticks;
}
}
Tabs
Use @OverlayTab on fields or no-argument methods to split a panel into selectable tabs. The tab bar only appears when the panel has more than one tab.
import dev.twilite.client.ui.OverlayBinding;
import dev.twilite.client.ui.overlay.OverlayPanel;
import dev.twilite.client.ui.overlay.OverlayTab;
import dev.twilite.client.ui.overlay.OverlayText;
@OverlayPanel(title = "Gauntlet")
public class GauntletOverlay implements OverlayBinding {
@OverlayTab("Prep")
@OverlayText(label = "Stage")
private String stage = "Gathering";
@OverlayTab("Prep")
@OverlayText(label = "Supplies")
private String supplies = "8 food, 1 potion";
@OverlayTab("Fight")
@OverlayText(label = "Prayer")
private String prayer = "Ranged";
}
Use @OverlayTab on the overlay class to change the default tab for unannotated rows and class-level stats.
@OverlayPanel(title = "Fighter")
@OverlayTab("Stats")
@OverlayStats({Stat.ATTACK, Stat.STRENGTH, Stat.DEFENCE})
public class FighterOverlay implements OverlayBinding {
}
Panel Styling
@OverlayPanel controls the HUD position and visual style.
@OverlayPanel(
title = "Example",
position = OverlayPosition.TOP_RIGHT,
x = 12,
y = 12,
columns = 2,
text = 0xffd7dde8,
accent = 0xff69c7d6,
background = 0xd80b1118,
border = 0x7a31465c
)
Text Binding Types
@OverlayText can bind to fields or no-argument methods. Prefer fields for normal HUD rows because fields render in declaration order and make the overlay layout predictable.
Supported values:
- Any object
Optional<T>Supplier<T>IntSupplierLongSupplierDoubleSupplierBooleanSupplierIterable<T>Iterator<T>- Object arrays and primitive arrays
Suppliers are evaluated when the overlay reads the value. Collections and arrays are expanded into one line per element.
@OverlayText
private List<String> lines = List.of("First line", "Second line");
import java.util.function.IntSupplier;
import java.util.function.Supplier;
@OverlayText(label = "State")
private Supplier<String> state = () -> currentState;
@OverlayText(label = "Kills", rate = true)
private IntSupplier kills = () -> killCount;
Formatters
Use OverlayFormatter to customize rendered values.
import dev.twilite.client.ui.overlay.OverlayFormatter;
public class GpFormatter implements OverlayFormatter {
@Override
public String format(Object value) {
return value + " gp";
}
}
@OverlayText(label = "Profit", formatter = GpFormatter.class)
private int profit = 125000;
Formatter classes need an accessible no-argument constructor.
Skill Stats
Use @OverlayStats on the overlay class to render skill progress.
import dev.twilite.client.ui.overlay.OverlayStats;
import dev.twilite.game.facade.Stat;
@OverlayPanel(title = "Fighter")
@OverlayStats({Stat.ATTACK, Stat.STRENGTH, Stat.DEFENCE})
public class FighterOverlay implements OverlayBinding {
}
By default it shows levels, XP gained, and XP/hour.
World Tiles
Use @OverlayCoord for one-tile highlights.
import dev.twilite.client.ui.overlay.OverlayCoord;
import dev.twilite.game.common.Coord;
@OverlayCoord(fill = 0x553fc7ff, outline = 0xdd3fc7ff)
private List<Coord> tiles = List.of(new Coord(3164, 3487));
Supported values:
CoordOptional<Coord>Iterable<Coord>Iterator<Coord>Coord[]
Duplicate coords are drawn once.
World Areas
Use @OverlayRect for rectangular world areas.
import dev.twilite.client.ui.overlay.OverlayRect;
import dev.twilite.game.common.Coord;
import dev.twilite.game.common.Rect;
@OverlayRect(fill = 0x443fc7ff, outline = 0xdd3fc7ff)
private Rect area = Rect.from(new Coord(3164, 3487), 3, 3);
Supported values:
RectOptional<Rect>Iterable<Rect>Iterator<Rect>Rect[]Map<Rect, ?>
When returning a map, each key is the drawn area and the value is label text. Iterable, iterator, and array labels are split into multiple labels.
@OverlayRect
private Map<Rect, List<String>> targets = Map.of(
Rect.from(new Coord(3164, 3487), 1, 1),
List.of("Bank", "Safe")
);
Duplicate rects are drawn once and labels are merged in encounter order.