Mob
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.
-- 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
endmob:get_position() -> table | nil
Available on GMS and CW
Returns position as a table with x and y fields, or nil if unavailable.
local pos = mob:get_position()
if pos then
-- pos.x, pos.y
endmob: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.
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
endmob: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.
-- 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
endmob: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.
local value, max = mob:get_health_gauge()
local precise = (max and max > 0) and (value * 100 / max) or valuemob:get_rank() -> number | nil
Available on GMS and CW
Returns the mob's Elite Monster-system tier, or nil if unavailable.
| Rank | Meaning |
|---|---|
0 | not part of the Elite Monster system |
1 | elite monster |
2 | elite champion |
3 | elite 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).
-- 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
endmob: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.
| Field | Description |
|---|---|
name | The 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" |
index | The number in attackN or skillN; nil for attackF, skillF and other actions |
skill_id | The mob skill a skill action uses, when known; otherwise nil |
skill_level | That skill's level, when skill_id is set |
facing_left | true while the mob faces left |
started | Game 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_id | This 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.
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
endmob: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.
| Field | Type | Description |
|---|---|---|
id | number | Skill ID of the effect, such as 63110011 for Kain's Death's Blessing |
name | string | Readable skill name |
stacks | number? | 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_remaining | number? | 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.
-- 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
endmob: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.
-- 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
endmob: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.
| Field | Description |
|---|---|
invincible | No attack can damage it |
health_locked | Its 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 |
physical | Immune to physical attacks |
magic | Immune to magic attacks |
reflect_physical | Can reflect damage from physical attacks back at the attacker |
reflect_magic | Can reflect damage from magic attacks back at the attacker |
Example
-- 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