NPCs API

Category: API Reference

Query and interact with NPC entities in the game world.

Functions

find_first

npcs:find_first(filters: table?) -> NPC?

Returns the first NPC matching the given filters.

Parameters:

ParameterTypeRequiredDescription
filterstableNoFilter options

Returns:

  • NPC? - First matching NPC, or nil if none found

Filters:

FilterTypeDescription
namestringExact name match
name_containsstringName contains text
name_matches_regexstringName matches regex pattern
name_anytableMatch if name equals any in array of strings
name_contains_anytableMatch if name contains any substring in array
idnumberExact ID match
idstableArray of ID numbers
within_distance_of_localnumberDistance from local player in tiles
within_distance_oftableDistance from position {x, y, floor, max_distance}
attableExact tile location {x, y, z} or [x, y, z]
at_anytableArray of tiles to check {{x, y, z}, ...}
in_combatbooleanCurrently in combat
not_in_combatbooleanNot in combat
is_interactingbooleanCurrently interacting
not_interactingbooleanNot interacting
actions_containsstringHas an action containing this text (case-insensitive)
actions_equalsstringHas an action exactly matching this text (case-insensitive)

Example:

-- Find by exact name
local guard = npcs:find_first({name = "Guard"})

-- Find by ID
local npc = npcs:find_first({id = 1234})

-- Find nearby NPC
local nearby = npcs:find_first({within_distance_of_local = 5})

-- Find NPC not in combat
local idle = npcs:find_first({
    name_contains = "Banker",
    not_in_combat = true
})

-- Find NPC with specific action
local pickpocketable = npcs:find_first({
    name = "Guard",
    actions_contains = "Pickpocket"
})

-- Find NPC at specific tile
local banker = npcs:find_first({
    name = "Banker",
    at = {3200, 3210, 0}  -- or at = {x = 3200, y = 3210, floor = 0}
})

-- Find NPC at any of several tiles
local banker_any = npcs:find_first({
    name = "Banker",
    at_any = {
        {3092, 3490, 0},
        {3092, 3489, 0}
    }
})

-- Find NPCs with any of multiple names
local npc = npcs:find_first({
    name_any = {"Cow", "Chicken", "Goblin"}
})

-- Find NPCs with names containing any substring
local guard = npcs:find_first({
    name_contains_any = {"Guard", "Knight", "Warrior"}
})

find_all

npcs:find_all(filters: table?) -> table

Returns all NPCs matching the given filters.

Parameters:

ParameterTypeRequiredDescription
filterstableNoFilter options (same as find_first)

Returns:

  • table - Array of NPC objects

Example:

-- Get all nearby NPCs
local nearby = npcs:find_all({within_distance_of_local = 20})

for _, npc in ipairs(nearby) do
    logger:info("NPC: " .. npc:name() .. " (ID: " .. npc:id() .. ")")
end

-- Get all guards
local guards = npcs:find_all({name = "Guard"})
logger:info("Found " .. #guards .. " guards")

-- Get all NPCs in combat
local combat_npcs = npcs:find_all({in_combat = true})

find_nearest

npcs:find_nearest(filters: table?) -> NPC?

Returns the nearest NPC matching the given filters.

Parameters:

ParameterTypeRequiredDescription
filterstableNoFilter options (same as find_first)

Returns:

  • NPC? - Nearest matching NPC, or nil if none found

Example:

-- Find nearest NPC
local nearest = npcs:find_nearest()

if nearest then
    logger:info("Nearest: " .. nearest:name() .. " (" .. nearest:distance_to_local_player() .. " tiles away)")
end

-- Find nearest banker
local banker = npcs:find_nearest({name = "Banker"})

-- Find nearest enemy
local enemy = npcs:find_nearest({in_combat = true})

Common Patterns

Interacting with Specific NPCs

local function interact_with_banker()
    local banker = npcs:find_nearest({name = "Banker"})
    
    if not banker then
        logger:warn("No banker found nearby")
        return false
    end
    
    logger:info("Interacting with banker at " .. banker:distance_to_local_player() .. " tiles")
    return banker:smart_interact({action = "Bank"})
end

interact_with_banker()

Counting NPCs

local function count_npcs_by_name(name)
    local npcs_list = npcs:find_all({name = name})
    return #npcs_list
end

local guard_count = count_npcs_by_name("Guard")
logger:info("Guards nearby: " .. guard_count)

Finding Multiple NPC Types

local function find_important_npcs()
    local npcs_list = {}
    
    npcs_list.banker = npcs:find_first({name = "Banker"})
    npcs_list.merchant = npcs:find_first({name = "Shop Keeper"})
    npcs_list.quest_giver = npcs:find_first({name_contains = "Quest"})
    
    return npcs_list
end

local npcs_list = find_important_npcs()
for role, npc in pairs(npcs_list) do
    if npc then
        logger:info(role .. ": " .. npc:name())
    end
end

-- Or find any of multiple types at once
local any_important = npcs:find_first({
    name_any = {"Banker", "Shop Keeper", "Grand Exchange Clerk"}
})

if any_important then
    logger:info("Found: " .. any_important:name())
end

-- Find all NPCs of multiple types
local all_service_npcs = npcs:find_all({
    name_contains_any = {"Bank", "Shop", "Exchange"}
})

logger:info("Found " .. #all_service_npcs .. " service NPCs")

Combat NPC Detection

local function is_under_attack()
    local attacking = npcs:find_all({in_combat = true})
    
    for _, npc in ipairs(attacking) do
        if npc:distance_to_local_player() < 10 then
            return true, npc:name()
        end
    end
    
    return false
end

local under_attack, attacker = is_under_attack()
if under_attack then
    logger:warn("Under attack by: " .. attacker)
end

Finding NPCs by ID

local function find_npc_by_any_id(id_list)
    return npcs:find_first({ids = id_list})
end

-- Find any of these NPCs
local important_ids = {1, 2, 3, 123, 456}
local found = find_npc_by_any_id(important_ids)

if found then
    logger:info("Found: " .. found:name())
end

NPC Distance Analysis

local function analyze_npc_distances()
    local all_npcs = npcs:find_all()
    
    logger:info("=== NPC Distance Analysis ===")
    
    for _, npc in ipairs(all_npcs) do
        local distance = npc:distance_to_local_player()
        local status = npc:in_combat() and "(combat)" or "(idle)"
        logger:info(npc:name() .. ": " .. distance .. " tiles " .. status)
    end
end

analyze_npc_distances()

Waiting for NPC Interaction

local function wait_for_npc_interaction(npc_name, timeout)
    local start = os.time()
    
    while os.time() - start < timeout do
        local npc = npcs:find_first({name = npc_name})
        
        if npc and npc:is_interacting() then
            logger:info("NPC is interacting with something")
            return true
        end
        
        sleep(100)
    end
    
    return false
end

wait_for_npc_interaction("Banker", 30)

Safe NPC Interaction Pattern

local function safe_interact_npc(name, action)
    -- Find the NPC
    local npc = npcs:find_nearest({name = name})
    
    if not npc then
        logger:warn("NPC not found: " .. name)
        return false
    end
    
    -- Check if valid
    if not npc:is_valid() then
        logger:warn("NPC is no longer valid")
        return false
    end
    
    -- Check distance
    local distance = npc:distance_to_local_player()
    if distance > 15 then
        logger:warn("NPC too far: " .. distance .. " tiles")
        return false
    end
    
    logger:info("Interacting with " .. npc:name() .. " (" .. distance .. " tiles away)")
    return npc:smart_interact({action = action})
end

safe_interact_npc("Banker", "Bank")

NPC State Monitoring

local function monitor_npc(name)
    local npc = npcs:find_first({name = name})
    
    if not npc then
        logger:warn("NPC not found")
        return
    end
    
    logger:info("=== NPC Info ===")
    logger:info("Name: " .. npc:name())
    logger:info("ID: " .. npc:id())
    local health_pct = npc:health_percent()
    local health_ratio = npc:health_ratio()
    local health_scale = npc:health_scale()
    if health_pct then
        logger:info("Health: " .. health_pct .. "% (" .. (health_ratio or 0) .. "/" .. (health_scale or 0) .. ")")
    else
        logger:info("Health: Not visible (not in combat)")
    end
    logger:info("Combat: " .. tostring(npc:in_combat()))
    logger:info("Animating: " .. tostring(npc:is_animating()))
    logger:info("Moving: " .. tostring(npc:is_moving()))
    logger:info("Distance: " .. npc:distance_to_local_player())
    logger:info("Position: " .. npc:x() .. ", " .. npc:y())
    logger:info("================")
end

monitor_npc("Guard")

Finding NPCs in Areas

local function find_npcs_in_area(area, npc_name)
    local npcs_in_area = {}
    local all_npcs = npcs:find_all({name = npc_name})
    
    for _, npc in ipairs(all_npcs) do
        if area:contains_tile(npc:tile()) then
            table.insert(npcs_in_area, npc)
        end
    end
    
    return npcs_in_area
end

local training_area = Area.new(
    Tile.new(3200, 3200, 0),
    Tile.new(3210, 3210, 0)
)

local guards = find_npcs_in_area(training_area, "Guard")
logger:info("Guards in training area: " .. #guards)

NPC Interaction Queue

local function interact_with_npcs(npc_names, action)
    for _, name in ipairs(npc_names) do
        local npc = npcs:find_nearest({name = name})
        
        if npc then
            logger:info("Interacting with: " .. npc:name())
            npc:smart_interact({action = action})
            sleep(1000)  -- Wait between interactions
        end
    end
end

interact_with_npcs({"Guard", "Merchant"}, "Talk-to")

NPC Methods

Each NPC object returned by these functions has methods to access its properties:

  • Basic Info

    • name() → string
    • id() → number
    • animation() → number
  • Position

    • x(), y(), floor() → number
    • tile() → Tile object
    • distance_to_local_player() → number
  • State

    • is_animating() → boolean
    • is_moving() → boolean
    • is_interacting() → boolean
    • in_combat() → boolean
    • health_percent() → number or nil (0-100)
    • health_ratio() → number or nil (current health value)
    • health_scale() → number or nil (maximum health value)
  • Interaction

    • click() → boolean
    • interact(filters) → boolean
    • smart_interact(filters) → boolean

See the methods listed above for complete NPC functionality.


Filter Combinations

Filters can be combined to create precise queries:

-- Find nearby idle bankers
local idle_bankers = npcs:find_all({
    name = "Banker",
    within_distance_of_local = 10,
    not_in_combat = true,
    not_interacting = true
})

-- Find NPCs by multiple IDs and distance
local npcs_by_ids = npcs:find_all({
    ids = {1, 2, 3, 456},
    within_distance_of_local = 15
})

-- Find combat NPCs in area
local combat_zone = npcs:find_all({
    in_combat = true,
    within_distance_of = {
        x = 3200,
        y = 3200,
        floor = 0,
        max_distance = 10
    }
})

-- Find NPCs with name patterns
local quest_npcs = npcs:find_all({
    name_matches_regex = ".*Quest.*",
    not_in_combat = true
})

Related APIs

  • Players API - Query and interact with players
  • Game Objects API - Query game objects
  • Combat API - Combat-related functionality
  • Menu API - Interact with right-click menus