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

MethodDescription
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

MethodDescription
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

MethodDescription
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

MethodDescription
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

EnumeratorDescription
DynamicPlugin-driven / user-dragged position (default landing top-left margin)
TopCenterHorizontally centered along the top of the game viewport
LeftCenterVertically centered along the left edge of the viewport
RightCenterVertically centered along the right edge of the viewport
AboveChatboxRightAbove the chatbox on the right (XP-tracker style)
TooltipFollows 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:

LayerRenders
titan::Layer::AboveSceneAfter the 3D scene, below UI widgets
titan::Layer::AboveWidgetsAfter 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

MethodDescription
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

MethodDescription
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

MethodReturnsDescription
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_tTerrain 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:

HexColorNotes
0xFF00FF00Opaque greenA=FF, B=00, G=FF, R=00
0xFFFF0000Opaque blueA=FF, B=FF, G=00, R=00
0xFF0000FFOpaque redA=FF, B=00, G=00, R=FF
0x8000FFFF50% transparent yellowA=80, B=00, G=FF, R=FF
0xFFFFFFFFOpaque whiteAll 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.OverlayAnchorString 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.

MethodDescription
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++)