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
| Method | Returns | Description |
|---|
get_id() | number | The 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() | string | The 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() | string | The entity category: "Player", "Mob", "Mount", "Projectile", "ItemDrop", or "WieldedItem". Handy for branching on what you were handed. |
get_entity_by_id(id) | entity or nil | Resolve 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).
| Method | Returns | Description |
|---|
get_x() | number | The entity’s current X position. |
get_y() | number | The entity’s current Y position. |
get_location() | location | The 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, number | Two 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() | boolean | Whether 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() | boolean | Whether 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() | boolean | Whether 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)
| Method | Returns | Description |
|---|
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
| Method | Returns | Description |
|---|
is_collidable() | boolean | Whether the entity currently collides with the world. |
toggle_collidable() | — | Flip the collidable flag. |
Appearance and Actions
| Method | Returns | Description |
|---|
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
| Method | Returns | Description |
|---|
get_health() | number | Current health. |
get_max_health() | number | Maximum 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() | boolean | Whether the entity is currently in the transient invulnerable state set by set_invulnerable. |
Inventory
| Method | Returns | Description |
|---|
set_inventory_size(rows, columns) | — | Resize the entity’s inventory grid. |
has_item(name, count?) | boolean | Whether the entity’s inventory holds at least count (default 1) of the named item. Use for scripted key/toll gates. |
remove_item(name, count?) | number | Remove 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.
| Method | Returns | Description |
|---|
has_capability(name) | boolean | Whether the player holds the named capability. false for non-players. |
grant_capability(name) | boolean | Grant the capability. Returns true if it was newly granted (was not already held). |
revoke_capability(name) | boolean | Remove 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
| Method | Returns | Description |
|---|
get_closest_player() | entity or nil | The 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.
| Method | Returns | Description |
|---|
is_mountable() | boolean | Whether this entity can carry riders. |
get_seat_count() | number | Total number of seats (0 if not mountable). |
get_riders() | table | A 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 nil | The 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 nil | The entity ID of the mount this entity is riding, or nil if not riding. |
get_rider_input_x() | number | The 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() | number | The rider’s vertical input. |
get_max_speed() | number | The 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() | number | The 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() | number | Current taming progress of a tameable mob (0.0 otherwise). |
set_taming_progress(p) | — | Set the taming progress. Does not itself complete taming. |
is_tamed() | boolean | Whether the mob has been tamed by someone. |
Area-of-Effect Zones
| Method | Returns | Description |
|---|
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.