Violet

Mob

On this page

The Mob object represents a mob entity in the game world.

Functions

mob:is_valid() -> boolean

Available on GMS and CW

Validates the object exists in the game world. Always call this before using other methods.


mob:get_id() -> number | nil

Available on GMS and CW

Returns the mob template ID or nil if unavailable.

The template ID identifies the mob species (e.g. every Blue Snail shares the same template ID). To tell two mobs of the same species apart, use mob:get_unique_id().


mob:get_unique_id() -> number | nil

Available on GMS and CW

Returns the unique instance ID of this specific spawned mob, or nil if unavailable.

Unlike the template ID, the unique ID is assigned when the mob spawns: every mob in the map has a different one, it stays the same for the mob's whole lifetime, and it is not reused when the mob dies. This makes it the right key for tracking a specific mob across ticks.

lua
-- Lock onto a single mob and keep following that exact instance
local target_uid = nil

local function find_target(map)
    for _, mob in ipairs(map:get_mobs()) do
        if mob and mob:is_valid() then
            local uid = mob:get_unique_id()
            if target_uid == nil then
                target_uid = uid
            end
            if uid == target_uid then
                return mob
            end
        end
    end
    target_uid = nil -- target died or despawned, pick a new one next tick
    return nil
end

mob:get_position() -> table | nil

Available on GMS and CW

Returns position as a table with x and y fields, or nil if unavailable.

lua
local pos = mob:get_position()
if pos then
    -- pos.x, pos.y
end

mob:anchor() -> number | nil

Available on GMS and CW

Returns an anchor handle for core.render, or nil if the mob has no position. Pass it as the anchor draw option so shapes follow the sprite between logic steps instead of trailing it by a tick.

The handle is only valid for the on_render() call it was created in, so call anchor() again every frame. Pass the number itself, not the mob object.

lua
function on_render()
    local map = core.object_manager.get_current_map()
    if not map then return end
    for _, mob in ipairs(map:get_mobs()) do
        local pos, a = mob:get_position(), mob:anchor()
        if pos and a then
            core.render.rect(pos.x - 30, pos.y - 80, 60, 80, { anchor = a })
        end
    end
end

mob:get_render_position() -> table | nil

Available on GMS and CW

Returns the mob's position as it was drawn in the latest rendered frame, as a table with x and y, or nil if no frame has captured it yet. Unlike get_position(), this includes between-tick interpolation, so it matches where the sprite is on screen.

The first call for a mob usually returns nil: it registers interest, and the position becomes available on the following frames. Prefer an anchor when you only need a shape to follow the mob; use this when your logic needs the coordinate itself.


mob:get_name() -> string | nil

Available on GMS and CW

Returns the mob name or nil if unavailable.


mob:distance() -> number | nil

Available on GMS and CW

Returns the Euclidean distance from the local player to this mob, or nil if the mob or the player is unavailable.


mob:get_health_percent() -> number | nil

Available on GMS and CW

Returns the mob's remaining health as a whole number from 0 to 100, or nil if unavailable.

Exact HP values are not available. Multi-body bosses that share one bar all report the shared bar.

lua
-- Burst only once the boss is below 30%
for _, mob in ipairs(map:get_mobs()) do
    if mob and mob:is_valid() and mob:is_boss() then
        local hp = mob:get_health_percent()
        if hp and hp <= 30 then
            core.log(mob:get_name() .. " is at " .. hp .. "%")
        end
    end
end

mob:get_health_gauge() -> number, number | nil

Available on GMS and CW

Returns the raw pair behind get_health_percent() — the HP bar value and what it counts up to — or nil if unavailable.

Reach for this only when whole percent is too coarse: against a boss whose gauge max is 1000 it gives you 0.1% resolution. A max of 0 means the mob has no subdivided bar and the value is already a 0–100 percentage. Note this pair is the mob's own bar, so unlike get_health_percent() it does not follow a multi-body boss to the body that owns the shared bar.

lua
local value, max = mob:get_health_gauge()
local precise = (max and max > 0) and (value * 100 / max) or value

mob:get_rank() -> number | nil

Available on GMS and CW

Returns the mob's Elite Monster-system tier, or nil if unavailable.

RankMeaning
0not part of the Elite Monster system
1elite monster
2elite champion
3elite boss

Use this instead of is_elite() when you need to tell the tiers apart — is_elite() is true for all three.


mob:is_elite() -> boolean | nil

Available on GMS and CW

Returns true if the mob belongs to the Elite Monster system — an elite monster or an elite boss — or nil if unavailable.

This is true for every special mob alike: both a trivial elite (e.g. Killer Bee, Failed Subject) and a much stronger boss-type elite (e.g. Odium's Guard). Use mob:is_boss() to tell those apart.


mob:is_boss() -> boolean | nil

Available on GMS and CW

Returns true if the mob's template is flagged as a boss — a designated boss-type mob (field boss, mini-boss, or a boss-type elite) that shows the boss HP bar and runs boss mechanics — or nil if unavailable.

This is independent of mob:is_elite(); a mob can be both. On a normal farming map a trivial elite and a stronger boss-type elite are both is_elite(), but only the boss-type one is is_boss() — so this is what distinguishes the tougher target when the map isn't a dedicated boss fight (where map:get_bosses() returns nothing).

lua
-- Prefer a boss-type mob on the map, otherwise fall back to any elite
local function pick_target(map)
    local elite_fallback = nil
    for _, mob in ipairs(map:get_mobs()) do
        if mob and mob:is_valid() then
            if mob:is_boss() then
                return mob            -- strongest: field boss / boss-type elite
            elseif mob:is_elite() and not elite_fallback then
                elite_fallback = mob  -- weaker elite, keep as a backup
            end
        end
    end
    return elite_fallback
end

mob:get_action() -> table | nil

Available on GMS and CW

Returns what the mob is doing right now, or nil if unavailable: the attack, skill or other one-off action it is playing, otherwise its movement or current state.

FieldDescription
nameThe action's name in the mob's game data, such as "attack2", "skill1", "stand", "move", "fly" or "hit1"
kind"attack" (attack1–attack16, attackF), "skill" (skill1–skill16, skillF), "movement" (move, stand, jump, fly, rope, chase, sleep, runaway) or "other"
indexThe number in attackN or skillN; nil for attackF, skillF and other actions
skill_idThe mob skill a skill action uses, when known; otherwise nil
skill_levelThat skill's level, when skill_id is set
facing_lefttrue while the mob faces left
startedGame update time in milliseconds when the action began, on the same clock as core.get_update_time(); nil for movement and when the start was not seen, for example an action already playing when you entered the map
mob_idThis mob's get_unique_id()

For a given mob an action name always plays the same animation, so get_id() together with name identifies a boss pattern. Several skills can share one skill animation; skill_id tells them apart. To react as actions begin and end, use on_mob_action and on_mob_action_end.

lua
for _, mob in ipairs(map:get_mobs()) do
    if mob and mob:is_valid() and mob:is_boss() then
        local action = mob:get_action()
        if action and action.kind == "skill" then
            core.log(mob:get_name() .. " is using " .. action.name
                .. (action.skill_id and (" (skill " .. action.skill_id .. ")") or ""))
        end
    end
end

mob:get_debuffs() -> table<debuff> | nil

Available on GMS and CW

Returns the debuffs players' skills have on the mob as an array, one entry per skill, or nil if unavailable.

FieldTypeDescription
idnumberSkill ID of the effect, such as 63110011 for Kain's Death's Blessing
namestringReadable skill name
stacksnumber?Stack count, for effects that stack, such as Death's Blessing, Divine Judgment or Another Bite. Only stacks you built are counted; absent for other effects
time_remainingnumber?Milliseconds until the effect ends, when that is known

The mob's own buffs, such as invincibility, are not debuffs; get_immunity() covers those. Some effects can't be read this way and never appear.


mob:get_debuff(skill_id: number) -> debuff | nil

Available on GMS and CW

Returns the debuff for that skill, or nil if the mob doesn't have it or is unavailable. Where the game treats a skill's HEXA version as the same skill, either ID finds it: 63110011 also finds HEXA Death's Blessing (63141015), and the returned id is the one on the mob. Otherwise pass the ID get_debuffs() shows, such as 1240001 for HEXA Divine Judgment.

lua
-- Kain: find the mobs carrying Death's Blessing, the targets for an Execute skill
local DEATHS_BLESSING = 63110011

for _, mob in ipairs(map:get_mobs()) do
    if mob and mob:is_valid() then
        local blessing = mob:get_debuff(DEATHS_BLESSING)
        if blessing and (blessing.stacks or 0) > 0 then
            core.log(mob:get_name() .. " has " .. blessing.stacks .. " Death's Blessing stacks")
        end
    end
end

mob:has_debuff(skill_id: number) -> boolean | nil

Available on GMS and CW

Returns true if the mob has that skill's debuff, matching HEXA versions like get_debuff(), or nil if unavailable.


mob:can_take_damage(attack_type?: string) -> boolean | nil

Available on GMS and CW

Returns false while attacks can't damage the mob, or nil if unavailable.

With no argument it is false only when nothing can damage the mob: it is invincible, its health is held at a floor it can't drop below yet, or it is immune to both physical and magic attacks. Pass "physical" or "magic", the type of damage your attacks deal, to also count an immunity to that type alone. Any other value raises an error. Reflect doesn't count; check get_immunity() for it.

Some mobs and boss mechanics block damage in ways these states don't cover, and those are not detected.

lua
-- Hold burst while the boss can't be hurt by physical attacks
for _, mob in ipairs(map:get_mobs()) do
    if mob and mob:is_valid() and mob:is_boss() and mob:can_take_damage("physical") == false then
        return
    end
end

mob:get_immunity() -> table | nil

Available on GMS and CW

Returns what is blocking or reflecting damage on the mob, or nil if unavailable. Every field is a boolean.

FieldDescription
invincibleNo attack can damage it
health_lockedIts health is held at a floor it can't drop below yet, so hits show no damage; it can turn on up to about a percent before the floor
physicalImmune to physical attacks
magicImmune to magic attacks
reflect_physicalCan reflect damage from physical attacks back at the attacker
reflect_magicCan reflect damage from magic attacks back at the attacker

Example

lua
-- Find closest mob to player
local map = core.object_manager.get_current_map()

if map and map:is_valid() then
    local closest_mob = nil
    local closest_dist = math.huge

    for _, mob in ipairs(map:get_mobs()) do
        if mob and mob:is_valid() then
            local dist = mob:distance()
            if dist and dist < closest_dist then
                closest_dist = dist
                closest_mob = mob
            end
        end
    end

    if closest_mob then
        core.log("Closest: " .. (closest_mob:get_name() or "Unknown")
            .. " at " .. (closest_mob:get_health_percent() or "?") .. "%")
    end
end