Server Entity API

Methods available on the entity object passed to server-side Lua hooks — movement, health, animation, mounts, and player utilities.

Most server-side hooks hand your script an entity object: the mob in a mob update hook, the player in an item-use or chat-command hook, the rider and mount in mount hooks, and so on. This page lists everything you can do with one.

An entity can be a player, a mob, a mount, or a projectile. Methods that only make sense for one kind are safe to call on any entity — on the wrong kind they quietly do nothing and return nil, 0, false, or an empty table as noted below. Your script never errors just because it got a player where it expected a mob.

You receive entity objects from:

  • Mob hooks (scripts.server-update, scripts.server-on-create) — the mob itself
  • Item hooks (server-on-primary-use) — the player using the item
  • Chat commands — the player who typed the command (see lua-chat-commands.md)
  • Interactable interaction hooks — the interacting player
  • Mount hooks (server-on-mounted / server-on-dismounted) — the rider and the mount
  • NPC dialogue hooks — the player and the NPC (see lua-npc-dialogue.md)
  • entity:get_closest_player(), entity:get_entity_by_id(...), and the globals spawn_mob_at(...) / spawn_mount(...)

Persistent Script State: lua_data

Every entity carries a lua_data table your scripts can freely read and write. It persists for the lifetime of the entity, across every hook call, so it is the standard place to keep per-entity state (timers, AI mode, counters):

-- /mobs/my_mob/scripts/server_update.lua
return function(entity, delta_ns)
    local data = entity.lua_data
    data.time_alive = (data.time_alive or 0) + delta_ns * 1e-9
end

entity.lua_data returns the live table — changes to its fields stick immediately. Assigning a whole new table (entity.lua_data = {}) replaces everything.


Identity

MethodReturnsDescription
get_id()numberThe entity’s runtime id, stable for its lifetime. This is the id returned by get_riders(), and the one to store in lua_data to refer back to another entity later.
get_name()stringThe entity’s type identifier (e.g. "Creation:zombie"), not its display name. Use set_display_name / the display shown above the entity for the player-facing name.
get_type()stringThe entity category: "Player", "Mob", "Mount", "Projectile", "ItemDrop", or "WieldedItem". Handy for branching on what you were handed.
get_entity_by_id(id)entity or nilResolve an id (from get_riders, get_id, a stored lua_data value) back to an entity object in this entity’s world. Returns nil if no such entity is loaded. This is how you turn the ids from get_riders() into entities you can drive.

Position and Movement

Positions are world coordinates in block units (1.0 = one block).

MethodReturnsDescription
get_x()numberThe entity’s current X position.
get_y()numberThe entity’s current Y position.
get_location()locationThe entity’s position as a location object with read-only .x, .y, and .world fields. This is a snapshot — changing it does not move the entity.
set_location(x, y)Teleport the entity to (x, y) in its current world. Unlike setting velocity, this snaps the position instantly and re-indexes the entity in the spatial map right away, so mob AI targeting and range checks see the new position on the same tick.
get_velocity()number, numberTwo return values: the current X and Y velocity.
set_x_velocity(v)Set the horizontal velocity directly.
set_y_velocity(v)Set the vertical velocity directly.
set_max_x_velocity(v)Cap the entity’s horizontal speed.
set_applying_x_velocity(bool)Turn the entity’s own horizontal propulsion on or off.
set_applying_y_velocity(bool)Turn vertical propulsion on or off (e.g. for flying or climbing behaviour).
set_flying(bool)Toggle gravity-exempt (flying) movement — e.g. a boss that takes off mid-fight. While flying, the entity ignores gravity, holds its altitude, and takes no fall damage; entering flight zeroes its vertical velocity so it doesn’t inherit fall momentum. mob_path_to on a flying mob routes it through the air (around obstacles) rather than walking, so you can path a flyer the same way you path a grounded mob. You can still steer it directly with the velocity setters if you prefer.
is_flying()booleanWhether the entity is currently in flying (gravity-exempt) mode.
set_phasing(bool)Toggle terrain-phasing (burrowing) movement — e.g. a boss that dives into the ground for one phase and surfaces for another. Phasing is a superset of flying: the entity is gravity-exempt and passes through solid blocks (it still collides with entities/static objects), and mob_path_to routes it straight through terrain toward the target. set_phasing(false) returns it to normal grounded movement.
is_phasing()booleanWhether the entity is currently in phasing (through-terrain) mode.
apply_impulse(vx, vy, duration_ms?)Shove the entity by a velocity impulse (vx, vy) — knockback. The push fades out over duration_ms (optional, default ~250 ms) rather than cutting off, so a single call reads as a shove. See the note below on how this differs from set_x_velocity.
pull_toward(x, y, strength, duration_ms?)Drag the entity toward the point (x, y) at strength (a velocity magnitude) — the convenience form of apply_impulse that computes the direction for you. A negative strength pushes away from the point instead. Call it every server-update tick (or pass a longer duration_ms) to sustain a pull; a boss dragging the player toward itself is player:pull_toward(entity:get_x(), entity:get_y(), strength).
grab(anchor_x, anchor_y, duration_ms)Player only. Pin this player at (anchor_x, anchor_y) for duration_ms, locking its movement input — a grab / hold / stow. A boss typically calls it after a pull_toward, passing its own position, so the player is hauled in and held at the maw: local p = entity:get_closest_player(); if p then p:pull_toward(entity:get_x(), entity:get_y(), 40); p:grab(entity:get_x(), entity:get_y(), 1500) end. The hold is server-authoritative (the player can’t walk out), but the player can struggle free early by mashing movement keys. No-op on a non-player. Call again to extend or re-anchor an active hold; capped at 30 s.
is_grabbed()booleanWhether this entity is a player currently held by a grab. Lets a boss gate follow-up behaviour on the hold still being in effect (e.g. keep chewing until the player breaks loose). False for a non-player.

Forced movement vs. setting velocity. set_x_velocity / set_y_velocity replace the entity’s velocity and only work on server-driven entities (mobs, mounts, projectiles) — on a player they do nothing, because the player’s own client is authoritative over its position. apply_impulse / pull_toward are the way to move a player from a script: the push is sent to the player’s client and folded into its movement for the duration. They also work on mobs (added straight to velocity). The push is capped at the target’s own maximum velocity, so it accelerates hard but never launches faster than the entity could normally move; use a longer duration_ms or a repeated pull_toward for a sustained drag. Typical use: local p = entity:get_closest_player(); if p then p:apply_impulse(dir_x * 40, dir_y * 40) end.

Pathfinding (Mobs Only)

MethodReturnsDescription
mob_path_to(x, y, time_seconds, max_checks, delay_ms)Ask a mob to pathfind to (x, y). time_seconds is how long the traversal should take, max_checks caps how much searching the pathfinder may do, and delay_ms is the minimum time between path requests. The path is computed over the following ticks, not instantly. The call is ignored if the mob is already following a path or if delay_ms has not elapsed since the last request — so it is safe to call every update tick. Does nothing on non-mobs.
stop_pathing()Cancel the current path and any queued path movement.

Collision

MethodReturnsDescription
is_collidable()booleanWhether the entity currently collides with the world.
toggle_collidable()Flip the collidable flag.

Appearance and Actions

MethodReturnsDescription
animate(name, reverse)Play the named animation on every client that can see the entity. reverse (boolean) plays it backwards.
set_state(state)Set the entity’s state string (drives state-dependent visuals defined in texture configs).
set_display_name(name)Change the name shown above the entity.
primary_use(x, y)Make the entity perform its primary-use action aimed at world position (x, y) — for mobs this triggers their attack behaviour. Clients are notified so the use animation plays.

Health

MethodReturnsDescription
get_health()numberCurrent health.
get_max_health()numberMaximum health.
heal(amount)Restore amount health, capped at max health. Healing always applies — it is not blocked by invincibility frames or damage reduction.
damage(amount, attacker_id)Deal amount damage to the entity — the counterpart to heal, and the way to script entity→entity damage without an AoE zone. attacker_id is optional (pass the attacker’s get_id() to credit the hit for aggro/kill purposes, or omit it). The hit isn’t blocked by per-attacker invincibility frames but still respects the victim’s global post-hit window, and armor/damage reduction still applies. If it drops the entity to zero health, death (drops, on_death hooks) is handled on the same tick, exactly as any other kill.
set_invulnerable(bool)Turn a transient armor state on or off. While true, every damage source is rejected — melee, projectiles, AoE zones, scripted damage(), and environmental (fall/oxygen) — the entity simply takes no damage. This is the primitive for armored / weak-point bosses: keep the boss invulnerable while its body is armored, then set_invulnerable(false) to open a vulnerable window (and re-arm it after). Distinct from the permanent takes-damage config flag and from invincibility frames; it is runtime-only and not saved, so a boss that reloads starts vulnerable — re-arm it from your server-update hook. heal() still works while invulnerable.
is_invulnerable()booleanWhether the entity is currently in the transient invulnerable state set by set_invulnerable.

Inventory

MethodReturnsDescription
set_inventory_size(rows, columns)Resize the entity’s inventory grid.
has_item(name, count?)booleanWhether the entity’s inventory holds at least count (default 1) of the named item. Use for scripted key/toll gates.
remove_item(name, count?)numberRemove up to count (default 1) of the named item, returning how many were actually removed. Broadcasts the change so the client stays in sync — use it to consume a key or pay a cost.

Capabilities

Persistent per-player unlocks — the Metroidvania ability set checked by sealed barriers. Capabilities are free-form string ids you choose; grant them from progression scripts (a boss death, a consumed item, a mount, an interactable). They are saved with the player and carry across world transfers. Players only.

MethodReturnsDescription
has_capability(name)booleanWhether the player holds the named capability. false for non-players.
grant_capability(name)booleanGrant the capability. Returns true if it was newly granted (was not already held).
revoke_capability(name)booleanRemove the capability. Returns true if it was present and removed.
-- A shrine that permanently grants an ability. Block interact handlers receive
-- (world, x, y, layer, actor_entity_id, actor); `actor` is the interacting player.
return function(world, x, y, layer, actor_entity_id, actor)
    if actor and actor:grant_capability("storm_ward") then
        actor:send_message("You feel the storm's blessing.")
    end
end

Players

MethodReturnsDescription
get_closest_player()entity or nilThe nearest player in the same world, or nil if there is none. Works from any entity.
send_message(text)Send a private chat message to this entity’s player. In a chat-command hook this replies to whoever ran the command; elsewhere it messages the player entity directly. Does nothing for non-player entities.
set_custom_spawn(x, y)Set the player’s respawn point to (x, y) in their current world (e.g. a bed). Players only.
clear_custom_spawn()Remove the custom respawn point, reverting to the world’s default spawn. Players only.
transfer_to_world(world_name)Move the player to another world, arriving at that world’s spawn. Players only. The destination world is created (with its registered type) on first visit. The transfer runs on the next server tick.
transfer_to_world(world_name, x, y)As above, but arrive at exactly world position (x, y) (the same coordinates the debug overlay uses; placed as given — make sure your exit is open space, not inside terrain). Players only.

Mounts, Riders, and Taming

These work on mounts and on mobs configured as rideable. On anything else they return the neutral value listed.

MethodReturnsDescription
is_mountable()booleanWhether this entity can carry riders.
get_seat_count()numberTotal number of seats (0 if not mountable).
get_riders()tableA list (1-indexed) of the entity IDs currently riding this entity. Empty seats are skipped, so the list is compacted — index does not equal seat number. Empty table if none.
get_mount_type()string or nilThe mount type name, or nil for anything that is not a mount.
mount_entity(rider, seat_index)Put rider (another entity object) onto this entity. seat_index is 0-based; pass nil to use the first free seat. Ignored if the seat is taken or no seat is free. All clients are updated.
dismount()Remove this entity (a rider) from whatever it is riding. Ignored if it is not riding anything. All clients are updated. Note: this does not fire the mount’s server-on-dismounted hook.
get_mounted_on()number or nilThe entity ID of the mount this entity is riding, or nil if not riding.
get_rider_input_x()numberThe current horizontal steering input from this mount’s rider (0.0 when unridden or not a mount). Use in a mount/mob update hook to drive movement.
get_rider_input_y()numberThe rider’s vertical input.
get_max_speed()numberThe max-speed value from this entity’s mount/mount config (0.0 if not a mount). The engine does not apply it — read it in your update hook to decide how fast to drive the mount.
get_jump_strength()numberThe jump-strength value from this entity’s mount/mount config (0.0 if not a mount). Also engine-inert — read it and apply the jump yourself.
get_taming_progress()numberCurrent taming progress of a tameable mob (0.0 otherwise).
set_taming_progress(p)Set the taming progress. Does not itself complete taming.
is_tamed()booleanWhether the mob has been tamed by someone.

Area-of-Effect Zones

MethodReturnsDescription
create_aoe_zone(zone_name, x, y)Spawn the AoE zone named zone_name (as defined in an aoe-zone-config.md file, qualified as "ModName:zone_name") at world position (x, y), owned by this entity. Clients are notified automatically. Logs a warning and does nothing if the zone name is unknown.

Example — A Mob That Chases the Nearest Player

-- a mob's scripts.server-update
return function(entity, delta_ns)
    local player = entity:get_closest_player()
    if player == nil then
        entity:stop_pathing()
        return
    end

    local target = player:get_location()
    -- Safe to call every tick: re-path requests are rate-limited by delay_ms.
    entity:mob_path_to(target.x, target.y, 2.0, 200, 500)
end

Notes

  • Timing: update hooks receive delta_ns in nanoseconds. Convert with delta_ns * 1e-9 for seconds.
  • Replication: animate, primary_use, mount_entity, dismount, create_aoe_zone, and send_message notify clients immediately. set_location re-indexes the entity on the server at once and reaches clients through the regular entity sync. Plain state setters (velocity, state, display name, collision) take effect on the server and reach clients through the regular entity sync.
  • Long-running scripts: each hook call has a server-side time budget (a few seconds). Keep per-tick work small; spread heavy work across ticks using lua_data.
  • Numeric inputs are validated: methods that take coordinates, velocities, or amounts (set_x_velocity, set_y_velocity, set_max_x_velocity, set_location, heal, damage, set_taming_progress, set_custom_spawn, create_aoe_zone, primary_use, and the x/y/time_seconds of mob_path_to) raise a Lua error if passed a non-finite number (NaN or infinity — e.g. from 0/0 or math.huge), so a bad value fails loudly instead of corrupting entity state. set_inventory_size clamps each dimension to a sane maximum (256), and mob_path_to’s max_checks is clamped to 100000, so oversized values are bounded rather than allocating or searching without limit.