Panels & Overlays
Controller panels, anchored in-game overlay panels (SDK v46+), and freeform OpenGL overlays.
Panels
Panels are custom UI tabs rendered in the controller window. They use a fluent builder API — your plugin appends commands (text, buttons, checkboxes, sliders, tables) and the controller replays them with ImGui.
Declaring a Panel
Add the TITAN_PANEL macro and override buildPanel:
class MyPlugin : public titan::Plugin {
TITAN_PLUGIN("my_plugin", "My Plugin")
TITAN_PANEL("My Panel")
int counter = 0;
void buildPanel(titan::Panel& p) override {
p.text("Hello from my plugin!");
p.separator();
p.label("Counter", std::to_string(counter));
p.button("Increment", 1);
p.button("Reset", 2);
}
void onPanelAction(int32_t actionId, const PluginProtocol::Value& value) override {
if (actionId == 1) counter++;
if (actionId == 2) counter = 0;
}
};
Threading: buildPanel and onPanelAction run in the
controller process, not the game process. You cannot call titan::* queries
from panel code. Use panels for pure UI display and intent queueing only.
Panel Builder API
The titan::Panel fluent builder supports these elements:
Text
| Method | Description |
|---|---|
text(s) | Normal text |
wrapped(s) | Text with word wrapping |
disabled(s) | Greyed-out text |
bullet(s) | Bulleted text |
colored(s, color) | Text with ABGR color (uint32_t) |
label(label, value) | Key-value label pair |
Layout
| Method | Description |
|---|---|
separator() | Horizontal rule |
separatorText(s) | Separator with centered label |
spacing() | Vertical spacing |
sameLine() | Place next element on the same line |
newLine() | Force line break |
indent() / unindent() | Adjust indentation level |
dummy(w, h) | Invisible spacer with fixed dimensions |
Interactive Controls
| Method | Description |
|---|---|
button(label, actionId) | Clickable button |
smallButton(label, actionId) | Compact button |
selectable(label, actionId, selected) | Selectable item |
checkbox(label, actionId, value) | Toggle checkbox |
sliderInt(label, actionId, value, min, max) | Integer slider |
sliderFloat(label, actionId, value, min, max) | Float slider |
inputText(label, actionId, value) | Text input field |
inputText(label, actionId, submitActionId, value) | Text input with Enter-to-submit |
progress(fraction, overlay) | Progress bar (0.0 to 1.0) |
Tree / Collapsing
| Method | Description |
|---|---|
collapsing(label, open?) | Collapsible header (optional default-open state) |
treeNode(label) | Tree node (call treePop() to close) |
treePop() | Close the current tree node |
tooltip(text) | Tooltip on the previous element |
push(type, text) | Raw element escape hatch for custom element types |
Handling Panel Actions
Each interactive control has an actionId. When the user interacts with it, onPanelAction
fires with that ID and the control's current value:
void onPanelAction(int32_t actionId, const PluginProtocol::Value& value) override {
switch (actionId) {
case 1: // "Start" button
running = true;
break;
case 2: // Checkbox toggled
verbose = (value.boolValue != 0);
break;
case 3: // Slider changed
threshold = value.intValue;
break;
}
}
Value types: Buttons send no meaningful value. Checkboxes provide value.boolValue.
Sliders provide value.intValue or value.floatValue. Text inputs provide
value.stringValue. Selectables provide value.boolValue (selected state).
Comprehensive Panel Example
This example demonstrates multiple control types in a single panel:
class FarmingPlugin : public titan::Plugin {
TITAN_PLUGIN("farming_helper", "Farming Helper")
TITAN_PANEL("Farming")
enum Action { ToggleOverlay = 1, SetRadius, ModeSelect, SearchInput, ResetBtn };
bool showOverlay = true;
int radius = 5;
int mode = 0;
std::string filter;
void buildPanel(titan::Panel& p) override {
p.text("Farming Helper v1.2");
p.disabled("Monitors nearby farming patches");
p.separator();
p.checkbox("Show overlay", showOverlay, ToggleOverlay);
p.sliderInt("Scan radius", radius, 1, 15, SetRadius);
p.spacing();
p.separatorText("Patch Filter");
p.inputText("Search", filter, SearchInput);
p.spacing();
p.collapsing("Mode", true);
p.indent();
p.selectable("Herbs", mode == 0, ModeSelect);
p.selectable("Trees", mode == 1, ModeSelect + 100);
p.selectable("Allotments", mode == 2, ModeSelect + 200);
p.unindent();
p.spacing();
p.colored("Status: Active", 0xFF00FF00);
p.sameLine();
p.smallButton("Reset", ResetBtn);
}
void onPanelAction(int32_t actionId, const PluginProtocol::Value& value) override {
switch (actionId) {
case ToggleOverlay: showOverlay = value.boolValue; break;
case SetRadius: radius = value.intValue; break;
case ModeSelect: mode = 0; break;
case ModeSelect+100: mode = 1; break;
case ModeSelect+200: mode = 2; break;
case SearchInput: filter = value.stringValue; break;
case ResetBtn:
showOverlay = true;
radius = 5;
mode = 0;
filter.clear();
break;
}
}
};
Overlay panels (in-game HUD)
SDK v46+. Structured, anchor-based HUD panels inside the game client — similar to
RuneLite overlay panels. They use the host layout manager (not raw OpenGL calls): titles, two-column lines,
progress bars, theming, and automatic width. Panels always render on titan::Layer::AboveWidgets.
Include <titan/overlay_panel.h> in C++ (pulls in titan::Anchor,
titan::OverlayPanel, and the Plugin::overlayPanel factory).
Positioning: Choose an initial titan::Anchor. Users can Alt-drag
any panel to reposition it; free-placed panels behave like Dynamic. Layout overrides persist per
machine in %USERPROFILE%\.titanclient\overlay_layout.json.
titan::Anchor
| Enumerator | Description |
|---|---|
Dynamic | Plugin-driven / user-dragged position (default landing top-left margin) |
TopCenter | Horizontally centered along the top of the game viewport |
LeftCenter | Vertically centered along the left edge of the viewport |
RightCenter | Vertically centered along the right edge of the viewport |
AboveChatboxRight | Above the chatbox on the right (XP-tracker style) |
Tooltip | Follows the mouse cursor (drag disabled) |
C++: Plugin::overlayPanel
Register a lambda-driven panel from your plugin constructor. Optional priority defaults to
50 (higher stacks above lower when anchors stack panels).
#include <titan/overlay_panel.h>
class HudPlugin : public titan::Plugin {
TITAN_PLUGIN("hud", "HUD")
HudPlugin() {
overlayPanel("status", titan::Anchor::AboveChatboxRight, [this](titan::OverlayPanel& p) {
p.title("Status");
p.line("Tick", std::to_string(titan::state::client().tick()).c_str());
p.line("Energy",
(std::to_string(titan::state::client().runEnergy() / 100) + "%").c_str());
p.progressBar(titan::state::client().runEnergy(), 0, 10000);
});
}
};
The builder API on titan::OverlayPanel includes title, line
(two-column or single-column), progressBar, setPreferredWidth (clamped 80–600),
setStyle / per-field colour setters, and setOpacity. Colours are
ARGB uint32_t as used by the host panel renderer (see defaults in
overlay_panel_defaults).
Alternatively subclass titan::OverlayPanel and override render() for stateful
panels — same builder methods apply inside render().
Overlays
Overlays let your plugin draw on the game window. The client injects into the game's OpenGL rendering pipeline and provides drawing primitives for world-space and screen-space rendering.
Registering Overlays
Two rendering layers are available:
| Layer | Renders |
|---|---|
titan::Layer::AboveScene | After the 3D scene, below UI widgets |
titan::Layer::AboveWidgets | After all game UI, on top of everything |
Register overlays in the constructor using the callback style:
MyPlugin() {
onRender(titan::Layer::AboveScene, [this] { drawWorld(); });
onRender(titan::Layer::AboveWidgets, [this] { drawHud(); });
}
Or use the declarative member style:
titan::Overlay boxes{this, titan::Layer::AboveScene, [this] {
titan::queries::npcs()
.nameContains("Chicken")
.forEach([](const titan::Npc& n) {
titan::overlay().entityBox(n, 0xFF00FF00);
});
}};
Draw Primitives
All drawing goes through titan::overlay() which returns an OverlayDraw instance.
Import <titan/render.h>:
World Space
| Method | Description |
|---|---|
tileQuad(tileX, tileZ, plane, fill, outline) | Colored quad on a single tile |
tileRegion(minTX, minTZ, maxTX, maxTZ, plane, fill, outline) | Colored rectangle over a tile region |
entityBox(npc, color) | Box around an NPC |
entityBox(player, color) | Box around a player |
entityClickbox(npc, outline, fill) | Accurate world-space AABB clickbox |
entityHull(npc, outline, fill) | 2D convex hull of projected AABB |
tileObjectClickbox(obj, outline, fill) | Clickbox for scene objects |
tileObjectHull(obj, outline, fill) | Hull for scene objects |
textAtWorld(worldX, worldY, worldZ, text, color) | Text anchored at a world position |
Screen Space
| Method | Description |
|---|---|
screenText(x, y, text, color) | Fixed-position text on screen |
screenRect(x, y, w, h, color) | Filled rectangle |
screenLine(x1, y1, x2, y2, color, thickness) | Line between two screen points |
Projection
| Method | Returns | Description |
|---|---|---|
worldToScreen(x, y, z) | optional<ScreenPoint> | Project a world point to screen coords |
tileToScreen(tileX, tileZ, plane) | optional<ScreenPoint> | Project a tile centre to screen coords |
tileHeight(preciseX, preciseZ, plane) | int32_t | Terrain height at a precise coordinate |
Color Format
Colors are uint32_t in ABGR format (alpha in the high byte, blue-green-red in
descending bytes). This matches ImGui's internal format. Examples:
| Hex | Color | Notes |
|---|---|---|
0xFF00FF00 | Opaque green | A=FF, B=00, G=FF, R=00 |
0xFFFF0000 | Opaque blue | A=FF, B=FF, G=00, R=00 |
0xFF0000FF | Opaque red | A=FF, B=00, G=00, R=FF |
0x8000FFFF | 50% transparent yellow | A=80, B=00, G=FF, R=FF |
0xFFFFFFFF | Opaque white | All channels max |
Common mistake: ABGR is not ARGB. If your red and blue look swapped, you
likely have the byte order reversed. Think of it as 0xAABBGGRR.
Layer Enum
The rendering layer is defined as:
enum class Layer : uint8_t {
AboveScene, // renders after the 3D world, below game UI widgets
AboveWidgets // renders after all game UI, on top of everything
};
Use AboveScene for world-space highlights (tile quads, entity boxes) that should appear
integrated into the game world. Use AboveWidgets for HUD elements, screen-space text, and
info panels that must remain visible regardless of game UI.
Register overlays on different layers via onRender in your constructor:
MyPlugin() {
// World-space drawing — tiles, entity highlights
onRender(titan::Layer::AboveScene, [this] { drawWorld(); });
// Screen-space HUD — info text, bars, status indicators
onRender(titan::Layer::AboveWidgets, [this] { drawHud(); });
}
Threading
Overlay callbacks run on the render thread (inside the SwapBuffers hook). You can safely
read game state via titan::* queries, but mutations (walk, interact, invokeMenuAction) must
be dispatched to the game thread:
onRender(titan::Layer::AboveScene, [this] {
// Safe: reading game state
auto npcs = titan::queries::npcs().nameContains("Target").toVector();
// Unsafe from render thread — dispatch to game thread
titan::runOnClientTick([npcs] {
if (!npcs.empty()) npcs[0].interact("Attack");
});
});
Practical Examples
1. Drawing Tile Highlights Under NPCs
onRender(titan::Layer::AboveScene, [this] {
titan::queries::npcs()
.nameContains("Guard")
.forEach([](const titan::Npc& npc) {
const auto t = npc.tile();
titan::overlay().tileQuad(t.x, t.y, t.plane,
0x4000FF00, // semi-transparent green fill
0xFF00FF00); // opaque green outline
});
});
2. Clickboxes with Conditional Coloring
onRender(titan::Layer::AboveScene, [this] {
auto self = titan::state::client().localPlayer();
if (!self) return;
titan::queries::npcs()
.within(10, self->tile())
.forEach([](const titan::Npc& npc) {
uint32_t outline, fill;
if (npc.isInteracting()) {
outline = 0xFF0000FF; // red — interacting
fill = 0x300000FF;
} else {
outline = 0xFF00FF00; // green
fill = 0x3000FF00;
}
titan::overlay().entityClickbox(npc, outline, fill);
});
});
3. Custom Text via tileToScreen
onRender(titan::Layer::AboveWidgets, [this] {
titan::queries::npcs()
.nameContains("Banker")
.forEach([](const titan::Npc& npc) {
const auto t = npc.tile();
auto pt = titan::overlay().tileToScreen(t.x, t.y, t.plane);
if (pt) {
titan::overlay().screenText(
pt->x, pt->y - 20,
std::format("{} [{}]", npc.name(), npc.id()),
0xFFFFFFFF);
}
});
});
4. Hull Outlines with Screen-Space Info Text
onRender(titan::Layer::AboveScene, [this] {
auto self = titan::state::client().localPlayer();
if (!self) return;
titan::queries::npcs()
.within(8, self->tile())
.forEach([](const titan::Npc& npc) {
titan::overlay().entityHull(npc, 0xFFFF8800, 0x30FF8800);
const auto t = npc.tile();
auto pt = titan::overlay().tileToScreen(t.x, t.y, t.plane);
if (pt) {
titan::overlay().screenText(
pt->x, pt->y - 30,
std::format("{} (id {})", npc.name(), npc.id()),
0xFFFFFFFF);
}
});
});
JavaScript (in-game)
The sections below use the JavaScript plugin API (titan.* in QuickJS). They follow the C++ overlay material above.
For namespace details and types, see JavaScript API Guide.
Overlay panels (SDK v46)
Use this.overlayPanel({ ... }) on your titan.Plugin subclass. The host registers the
panel, applies layout from titan.OverlayAnchor, and invokes your render callback
each frame on the AboveWidgets pass — alongside other overlay panels if defined.
class HudPlugin extends titan.Plugin {
id = "hud_js";
name = "HUD";
constructor() {
super();
this.overlayPanel({
name: "run",
anchor: titan.OverlayAnchor.AboveChatboxRight,
priority: 50,
render: (op) => {
op.title("Run energy");
const pct = Math.floor(titan.state.client.runEnergy / 100);
op.line("Percent", pct + "%");
op.progressBar(titan.state.client.runEnergy, 0, 10000);
}
});
}
}
The object passed to overlayPanel supports optional style (merged with defaults),
preferredWidth, and priority (default 50). The chained helpers
op.title, op.line, op.progressBar, and op.set* mirror the C++
builder — colours are unsigned ARGB integers (same hex literals as the defaults above).
titan.OverlayAnchor | String value |
|---|---|
.Dynamic | "Dynamic" |
.TopCenter | "TopCenter" |
.LeftCenter | "LeftCenter" |
.RightCenter | "RightCenter" |
.AboveChatboxRight | "AboveChatboxRight" |
.Tooltip | "Tooltip" |
Overlay drawing (titan.overlay)
The JS scripting environment exposes the same overlay primitives through titan.overlay.
Methods mirror the C++ titan::overlay() facade. Use queries
via titan.queries.npcs(), titan.queries.objects(), etc.
Registering overlays
titan.onRender("AboveScene", () => {
drawWorldHighlights();
});
titan.onRender("AboveWidgets", () => {
drawHud();
});
World-space drawing
titan.onRender("AboveScene", () => {
titan.queries.npcs().nameContains("Goblin").forEach((npc) => {
const tile = npc.tile;
titan.overlay.tileQuad(tile.x, tile.y, tile.plane, 0x4000FF00, 0xFF00FF00);
titan.overlay.entityBox(npc, 0xFF00FFFF);
titan.overlay.entityClickbox(npc, 0xFFFF8800, 0x30FF8800);
titan.overlay.entityHull(npc, 0xFFFFFF00, 0x30FFFF00);
});
const local = titan.state.client.localPlayer;
if (local) {
const t = local.tile;
titan.overlay.tileRegion(t.x - 3, t.y - 3, t.x + 3, t.y + 3,
t.plane, 0x200000FF, 0xFF0000FF);
}
});
Screen-space drawing
titan.onRender("AboveWidgets", () => {
// Fixed HUD text
titan.overlay.screenText(10, 10, "My Plugin Active", 0xFF00FF00);
// Rectangle background
titan.overlay.screenRect(5, 5, 200, 25, 0x80000000);
// Divider line
titan.overlay.screenLine(10, 35, 200, 35, 0xFFFFFFFF, 1.0);
});
Projection helpers
titan.onRender("AboveWidgets", () => {
const local = titan.state.client.localPlayer;
if (local) {
titan.queries.npcs().within(6, local.tile).forEach((npc) => {
const tile = npc.tile;
const pt = titan.overlay.tileToScreen(tile.x, tile.y, tile.plane);
if (pt) {
titan.overlay.screenText(pt.x, pt.y - 20, npc.name, 0xFFFFFFFF);
}
});
}
const wp = titan.overlay.worldToScreen(3200, 3200, 0); // world X, Y, plane
if (wp) {
titan.overlay.screenText(wp.x, wp.y, "Marker", 0xFF00FFFF);
}
const h = titan.overlay.tileHeight(8320, 8320, 0); // precise scene X/Z + plane; mirrors C++ OverlayDraw::tileHeight
});
Scene object overlays
titan.onRender("AboveScene", () => {
titan.queries.objects().nameContains("Oak").forEach((tree) => {
titan.overlay.tileObjectClickbox(tree, 0xFF00FF00, 0x3000FF00);
titan.overlay.tileObjectHull(tree, 0xFFFF8800, 0x30FF8800);
});
});
Overlay method quick reference
Authoritative signatures and behaviour live in shared/titan/render.h (C++) and
JavaScript API Guide. The table below is a concise checklist.
| Method | Description |
|---|---|
titan.overlay.tileQuad(tileX, tileZ, plane, fill, outline) | Colored quad on a single tile (params match C++; scene Y is the second axis) |
titan.overlay.tileRegion(minTX, minTZ, maxTX, maxTZ, plane, fill, outline) | Colored rectangle over a tile region |
titan.overlay.entityBox(entity, color) | Box around an NPC or player |
titan.overlay.entityClickbox(entity, outline, fill) | Clickbox shape for entity |
titan.overlay.entityHull(entity, outline, fill) | Convex hull outline for entity |
titan.overlay.tileObjectClickbox(obj, outline, fill) | Clickbox for scene objects |
titan.overlay.tileObjectHull(obj, outline, fill) | Hull for scene objects |
titan.overlay.textAtWorld(worldX, worldY, worldZ, text, color) | Text at a world position |
titan.overlay.screenText(x, y, text, color) | 2D text at screen coordinates |
titan.overlay.screenRect(x, y, w, h, color) | 2D filled rectangle |
titan.overlay.screenLine(x1, y1, x2, y2, color, thickness?) | 2D line between points |
titan.overlay.worldToScreen(x, y, z) | Returns { x, y } or null |
titan.overlay.tileToScreen(tileX, tileZ, plane) | Returns { x, y } or null |
titan.overlay.tileHeight(preciseX, preciseZ, plane) | Terrain height at precise scene coordinates (matches C++) |