Paint

Paint System

The Paint system allows scripts to create custom overlay windows that display runtime information, statistics, and progress tracking. These overlays appear on top of the game window and provide real-time feedback to users about script execution.

What is Paint?

Paint overlays are draggable, customizable windows that can display:

  • Static text (script name, version)
  • Dynamic text (current status, player health)
  • Skill XP tracking (automatic XP gained and XP/hour)
  • Item tracking (automatic item quantity changes)
  • Counters (custom counters you update programmatically)
  • Progress bars (visual progress indicators)

Basic Concepts

Builder Pattern

The Paint API uses a builder pattern. Use dot notation for paint.builder() (not colon), then chain builder methods:

local builder = paint.builder()
local paint_id = builder
    :position(10, 10)
    :size(300, 200)
    :add_static("My Script v1.0")
    :track_skill("Mining")
    :build()

Paint Lifecycle

  1. Create: Use paint.builder() to start building
  2. Configure: Chain methods to set position, size, colors, and content
  3. Build: Call build() to create and register the paint (returns paint ID)
  4. Show/Hide: Use paint.show(id, true/false) to toggle visibility
  5. Remove: Call paint.remove(id) when done

Common Use Cases

Displaying Script Status

Show users what the script is currently doing:

local builder = paint.builder()
builder:position(10, 10)
builder:add_labeled("Status", function()
    if inventory:full() then
        return "Banking..."
    else
        return "Mining..."
    end
end)
local paint_id = builder:build()

Tracking Progress

Display skill XP and item gains:

local builder = paint.builder()
builder:track_skill("Mining")  -- Shows: "Mining: +123 XP (456 XP/hr)"
builder:track_item({name = "Iron ore"}, "Iron ore", { per_hour = true })  -- Shows: "Iron ore: +5 (120/hr)"
local paint_id = builder:build()

Visual Feedback

Use dynamic rows to show completion status:

local builder = paint.builder()
builder:add_labeled("Inventory", function()
    local count = inventory:count()
    return count .. "/28"
end)
local paint_id = builder:build()

See the Paint API Reference for all builder methods including add_runtime(), add_static(), and header options.

Best Practices

Performance

  • Use dynamic functions: For frequently changing values, use add_labeled() with a function so the value is evaluated at render time
  • Shared strings for events: Use add_shared_string / add_labeled_shared_string with shared.string("key") when values are updated from event handlers or other threads
-- Good: Function is called at render time
builder:add_labeled("HP", function()
    return combat:current_health() .. "/" .. combat:max_health()
end)

Organization

  • Group related information: Use multiple paints for different purposes (status, stats, debug)
  • Consistent styling: Use the same colors and sizes across your paints
  • Clear labels: Make sure all paint elements have descriptive labels

User Experience

  • Default positions: Set sensible default positions that don't obstruct gameplay
  • Allow customization: Don't disable dragging unless necessary
  • Provide context: Include script name and version in your paint

Example: Complete Mining Script Paint

local paint_id

local function create_mining_paint()
    local builder = paint.builder()
    
    -- Position and appearance
    builder:position(10, 10)
    builder:size(350, 300)
    builder:bg_color(15, 15, 15, 220)
    builder:text_color(220, 220, 220, 255)
    
    -- Static and dynamic content
    builder:add_static("Mining Script v2.1")
    builder:add_runtime()
    builder:track_skill("Mining")
    builder:track_item({name = "Iron ore"}, "Iron ore", { per_hour = true })
    builder:track_item({name = "Coal"}, "Coal", { per_hour = true })
    
    builder:add_labeled("Inventory", function()
        local count = inventory:count()
        return count .. "/28"
    end)
    
    builder:add_labeled("Status", function()
        if inventory:full() then
            return "Banking..."
        elseif movement:is_moving() then
            return "Walking..."
        else
            return "Mining..."
        end
    end)
    
    paint_id = builder:build()
    return paint_id
end

-- In your script
create_mining_paint()

function script:on_stop()
    if paint_id then
        paint.remove(paint_id)
    end
end

Next Steps