Components API

The components API provides access to UI widgets and interface elements in the game. Components can be buttons, text labels, inventory slots, dialog boxes, or any other UI element.

Overview

Components are organized hierarchically:

  • Group ID (Widget ID): Top-level interface (e.g., 149 for inventory)
  • Component ID: Specific component within the group (e.g., 0 for inventory container)
  • Child ID: Optional child components (e.g., individual inventory slots)

Methods

get

components:get(group_id: number, component_id: number, child_id: number?) -> Component?

Gets a component by its group ID and component ID. Optionally accepts a child ID to retrieve nested child components.

Parameters:

ParameterTypeRequiredDescription
group_idnumberYesThe interface group/widget ID
component_idnumberYesThe component ID within the group
child_idnumberNoThe child component ID for nested components

Returns:

  • Component?: The component, or nil if not found

Example:

-- Get parent component (inventory container)
local inventory = components:get(149, 0)
if inventory and inventory:visible() then
    logger:info("Inventory is visible")
end

-- Get child component (first inventory slot) with optional 3rd parameter
local first_slot = components:get(149, 0, 0)
if first_slot then
    local item_id = first_slot:item_id()
    if item_id ~= -1 then
        logger:info("First slot contains item: " .. item_id)
    end
end

-- Get specific inventory slot
local slot_5 = components:get(149, 0, 5)
if slot_5 then
    logger:info("Slot 5 item: " .. slot_5:item_id())
end

-- Get bank container
local bank = components:get(12, 2)
if bank and bank:visible() then
    logger:info("Bank is open")
end

Alternative: You can also use the component:child() method to navigate from a parent component to its children:

local inventory = components:get(149, 0)
if inventory then
    local first_slot = inventory:child(0)
end

find_first

components:find_first(filter: table?) -> Component?

Finds the first component matching the specified filters.

Parameters:

ParameterTypeRequiredDescription
filtertableNoFilter options (see Filter Options below)

Returns:

  • Component?: The first matching component, or nil if none found

Example:

-- Find continue button by text
local continue = components:find_first({
    text_contains = "Click here to continue"
})

-- Find visible components in a group
local dialog = components:find_first({
    group_id = 231,
    visible_only = true
})

-- Find component by action
local drop_slot = components:find_first({
    group_id = 149,
    actions_contains = "Drop"
})

find_all

components:find_all(filter: table?) -> Component[]

Finds all components matching the specified filters.

Parameters:

ParameterTypeRequiredDescription
filtertableNoFilter options (see Filter Options below)

Returns:

  • Component[]: Array of matching components (empty if none found)

Example:

-- Find all visible inventory slots
local visible_slots = components:find_all({
    group_id = 149,
    visible_only = true
})

-- Filter to only slots with items
local items = {}
for _, slot in ipairs(visible_slots) do
    if slot:item_id() ~= -1 then
        table.insert(items, slot)
    end
end

logger:info("Found " .. #items .. " items in inventory")

Filter Options

The find_first and find_all methods accept a filter table with the following options:

OptionTypeDescription
text_containsstringComponent text contains this string (case-insensitive)
text_equalsstringComponent text exactly matches this string (case-insensitive)
visible_onlybooleanOnly return visible components
group_idnumberFilter by specific group/widget ID
content_typenumberFilter by content type (0=graphic, 1=text, 2=filled box, etc.)
min_widthnumberMinimum width in pixels
min_heightnumberMinimum height in pixels
parent_idnumberFilter by parent component ID
with_textbooleanOnly components that have non-empty text
actions_containsstringComponent actions contain this string
actions_equalsstringComponent actions exactly match this string

Example:

-- Find large visible buttons with text
local buttons = components:find_all({
    visible_only = true,
    with_text = true,
    min_width = 50,
    min_height = 20,
    content_type = 2  -- Filled box
})

-- Find specific dialog option
local option = components:find_first({
    group_id = 219,
    text_contains = "Yes",
    visible_only = true
})

Common Component Groups

Here are some commonly used component group IDs:

Group IDInterfaceDescription
149InventoryPlayer inventory (28 slots)
12BankBank interface
162ChatboxChat messages and input
231DialogNPC dialogs and continue buttons
387EquipmentWorn equipment
218SpellbookMagic spellbook
593Grand ExchangeGE interface
219Dialog OptionsMultiple choice dialog options
465Bank InventoryInventory shown when bank is open

Common Patterns

Checking if Interface is Open

local function is_bank_open()
    local bank = components:get(12, 2)
    return bank and bank:visible()
end

local function is_inventory_open()
    local inventory = components:get(149, 0)
    return inventory and inventory:visible()
end

if is_bank_open() then
    logger:info("Bank is open")
end

Getting All Inventory Slots

local function get_all_inventory_slots()
    local slots = {}
    for i = 0, 27 do
        local slot = components:get(149, 0, i)
        if slot then
            table.insert(slots, slot)
        end
    end
    return slots
end

local slots = get_all_inventory_slots()
logger:info("Found " .. #slots .. " inventory slots")

Finding Items in Inventory

local function find_item_in_inventory(item_id)
    for i = 0, 27 do
        local slot = components:get(149, 0, i)
        if slot and slot:item_id() == item_id then
            return slot, i
        end
    end
    return nil, -1
end

-- Find coins (item ID 995)
local coin_slot, index = find_item_in_inventory(995)
if coin_slot then
    local amount = coin_slot:item_stack_size()
    logger:info("Found " .. amount .. " coins in slot " .. index)
end

Clicking Dialog Buttons

local function click_continue()
    local continue = components:find_first({
        text_contains = "Click here to continue",
        visible_only = true
    })
    
    if continue then
        continue:click()
        return true
    end
    return false
end

local function select_dialog_option(text)
    local option = components:find_first({
        group_id = 219,
        text_contains = text,
        visible_only = true
    })
    
    if option then
        option:click()
        return true
    end
    return false
end

-- Use in script
if click_continue() then
    logger:info("Clicked continue")
end

if select_dialog_option("Yes") then
    logger:info("Selected 'Yes' option")
end

Waiting for Component to Appear

local function wait_for_component(group_id, component_id, timeout)
    local start = os.time()
    
    while os.time() - start < timeout do
        local comp = components:get(group_id, component_id)
        if comp and comp:is_valid() and comp:visible() then
            return comp
        end
        sleep(100)
    end
    
    return nil
end

-- Wait up to 5 seconds for bank to open
local bank = wait_for_component(12, 2, 5)
if bank then
    logger:info("Bank opened!")
else
    logger:warn("Bank did not open within 5 seconds")
end

Reading Component Text

local function get_dialog_text()
    -- Try common dialog component IDs
    local dialog_ids = {
        {231, 5},
        {217, 5},
        {231, 6}
    }
    
    for _, ids in ipairs(dialog_ids) do
        local comp = components:get(ids[1], ids[2])
        if comp and comp:visible() then
            local text = comp:text()
            if text and text ~= "" then
                return text
            end
        end
    end
    
    return nil
end

local text = get_dialog_text()
if text then
    logger:info("NPC says: " .. text)
end

Interacting with Component Actions

local function drop_item_in_slot(slot_index)
    local slot = components:get(149, 0, slot_index)
    if slot and slot:item_id() ~= -1 then
        -- Use interact to right-click and select "Drop"
        return slot:interact("Drop")
    end
    return false
end

-- Drop items in slots 0-5
for i = 0, 5 do
    if drop_item_in_slot(i) then
        logger:info("Dropped item in slot " .. i)
        sleep(100)
    end
end

Checking Component Bounds

local function is_point_in_component(comp, x, y)
    if not comp or not comp:is_valid() then
        return false
    end
    
    local cx = comp:x()
    local cy = comp:y()
    local cw = comp:width()
    local ch = comp:height()
    
    return x >= cx and x <= cx + cw and
           y >= cy and y <= cy + ch
end

-- Check if mouse is over inventory
local inventory = components:get(149, 0)
if inventory then
    local mouse_x, mouse_y = mouse:position()
    if is_point_in_component(inventory, mouse_x, mouse_y) then
        logger:info("Mouse is over inventory")
    end
end

Finding Empty Inventory Slots

local function count_empty_slots()
    local empty = 0
    for i = 0, 27 do
        local slot = components:get(149, 0, i)
        if slot and slot:item_id() == -1 then
            empty = empty + 1
        end
    end
    return empty
end

local function find_first_empty_slot()
    for i = 0, 27 do
        local slot = components:get(149, 0, i)
        if slot and slot:item_id() == -1 then
            return slot, i
        end
    end
    return nil, -1
end

local empty_count = count_empty_slots()
logger:info("Inventory has " .. empty_count .. " empty slots")

local empty_slot, index = find_first_empty_slot()
if empty_slot then
    logger:info("First empty slot is at index " .. index)
end

Advanced Examples

Smart Component Search

local function find_clickable_button(text_hint)
    -- Find buttons that are visible, have reasonable size, and contain text
    local buttons = components:find_all({
        visible_only = true,
        with_text = true,
        min_width = 30,
        min_height = 15
    })
    
    -- Filter by text hint if provided
    if text_hint then
        for _, button in ipairs(buttons) do
            local text = button:text()
            if text and text:lower():find(text_hint:lower()) then
                return button
            end
        end
    end
    
    return buttons[1]
end

local button = find_clickable_button("Accept")
if button then
    button:click()
end

Component State Monitoring

local function monitor_component_text(group_id, component_id, callback, interval)
    local last_text = ""
    
    while true do
        local comp = components:get(group_id, component_id)
        if comp and comp:visible() then
            local current_text = comp:text()
            if current_text ~= last_text then
                callback(current_text, last_text)
                last_text = current_text
            end
        end
        sleep(interval or 100)
    end
end

-- Example usage
monitor_component_text(231, 5, function(new_text, old_text)
    logger:info("Dialog text changed from '" .. old_text .. "' to '" .. new_text .. "'")
end, 200)

Component Content Types

Component content types determine how the component is rendered:

ValueTypeDescription
0GRAPHICImage/sprite
1TEXTText label
2FILLED_BOXSolid rectangle
3MODEL3D model
4TEXT_INPUTText input field
5CONTAINERContainer for other components
6MODEL_LISTList of 3D models
9LINELine separator

Example:

local function find_text_components()
    return components:find_all({
        content_type = 1,  -- TEXT
        visible_only = true
    })
end

local text_comps = find_text_components()
for _, comp in ipairs(text_comps) do
    logger:info("Text: " .. comp:text())
end

Best Practices

Always Validate Components

-- Bad
local comp = components:get(149, 0)
comp:click()  -- May error if comp is nil

-- Good
local comp = components:get(149, 0)
if comp and comp:is_valid() and comp:visible() then
    comp:click()
else
    logger:warn("Component not available")
end

Use Appropriate Access Methods

-- For parent components - use get()
local inventory = components:get(149, 0)

-- For child components - use get_child()
local first_slot = components:get_child(149, 0, 0)

-- Or navigate from parent to child
local inventory = components:get(149, 0)
if inventory then
    local first_slot = inventory:child(0)
end

Cache Component Lookups

-- Bad: Repeated lookups
for i = 1, 10 do
    local comp = components:get(149, 0)
    if comp:visible() then
        -- Do something
    end
    sleep(100)
end

-- Good: Cache the lookup
local comp = components:get(149, 0)
for i = 1, 10 do
    if comp and comp:is_valid() and comp:visible() then
        -- Do something
    end
    sleep(100)
end

Use Filters Efficiently

-- Bad: Get all then filter manually
local all = components:find_all({})
local visible = {}
for _, comp in ipairs(all) do
    if comp:visible() and comp:text() ~= "" then
        table.insert(visible, comp)
    end
end

-- Good: Use filter options
local visible = components:find_all({
    visible_only = true,
    with_text = true
})

See Also

  • Mouse - Mouse interaction
  • Inventory - Higher-level inventory API
  • Bank - Higher-level bank API
  • Menu - Context menu interaction