Skip to content

MobEntity

Inherits from: LivingEntity

Represents a mobile entity within the game, inheriting from LivingEntity and adding movement, looking and fighting.

A mob normally acts on its own. A spell takes over by giving it a task: while a task runs, the mob's own behavior is suspended, and when the task ends the mob carries on as before. Only one task runs at a time, so starting another one replaces it.

Starting a task

Every start... function returns true when the task started, or false and a reason when it did not, like "no_path" when no route to the target could be found within searchRange. The TaskReason type lists all reasons.

Watching a task

busy tells whether a task is running. task describes the task this entity is executing, or the last one it executed, and keeps that description after the task has ended:

  • name is the TaskName, like "path" or "attack".

  • state is the TaskState: "running", "finished", "failed" or "interrupted".

  • reason is the TaskReason of a state of "failed" or "interrupted".

  • age is the number of ticks since the task started.

  • the options the task was started with, and the values the task computed. See each function, and MobTask for all of them.

A task that fails to start does not change task: read the reason from the return value instead.

awaitTask() waits until the task has ended and reports how, the same way the start... functions do.

Hands

A mob has no inventory, only its two hands. The block tasks work with what the mob holds: startBreakBlock uses the main hand as its tool, and startPlaceBlock, startPutInto and startTakeFrom take their items from, or put them into, the hand named by their hand option. Use setEquipment to hand a mob something, and getEquipment to see what it holds.

Misspelling an option is an error, not a wish: { speeed = 2 } is rejected rather than ignored.

Example

local pig = spell:summon("pig") --[[@as MobEntity]]
local ok, reason = pig:startPathTo(pig.pos + Vec3(0, 0, 10))
if ok then
  ok, reason = pig:awaitTask() -- sleeps until the task has ended
end
print(ok and "The pig got there." or "The pig did not get there: " .. reason)

Fields

ai: boolean — Read/Write

Indicates whether this entity's own artificial intelligence is enabled. While it is disabled the entity neither acts on its own nor executes tasks, and it does not move at all, not even falling. This setting is persisted.

busy: boolean — Read-Only

Indicates whether this entity is currently executing a task.

canPickUpLoot: boolean — Read/Write

Indicates whether this entity picks up items it walks over. This setting is persisted.

lookAt: Entity|Vec3|nil — Read/Write

A reference to the entity this entity keeps looking at, a copy of the position it keeps looking at, or nil if it looks wherever it wants. Assigning a value overrides the direction its task would look in.

target: LivingEntity? — Read-Only

A reference to the entity this entity is attacking, or nil if it attacks none. The entity may have chosen this target itself.

task: MobTask? — Read-Only

A copy of the task this entity is executing, or of the last task it executed. nil if it has not executed one yet.

Methods

awaitTask(timeout?: number) → ok: boolean, reason: TaskReason|"timeout"|nil

Waits until the task this entity is executing has ended, and tells how it ended.

Returns true if the task finished, and false and the reason if it failed or was interrupted, the same way a start... function reports a task that could not start. So a failed start and a failed task can be handled in one place:

local ok, reason = zombie:startBreakBlock(pos)
if ok then
  ok, reason = zombie:awaitTask()
end

It waits for the task that runs when it is called. If something replaces that task, for example another spell, the wait ends with false and "replaced", and the new task runs on. A task that has already ended gives its result at once, and an entity that never had a task gives true.

With a timeout, the wait ends after that many ticks with false and "timeout", but the task itself keeps running. A timeout of 0 only looks.

The spell sleeps while it waits. An entity only moves while its chunk is loaded, so for a long walk away from any player, move spell.pos along with the entity in a loop instead.

In Lua, awaitTask would look much like this:

local function awaitTask(mob, timeout)
  if mob.task == nil then
    return true                  -- it never had a task, so there is nothing to wait for
  end
  local waited = 0
  while mob.busy do
    if timeout and waited >= timeout then
      return false, "timeout"    -- only the wait ends; the task keeps running
    end
    sleep(1)
    waited = waited + 1
  end
  if mob.task.state == "finished" then
    return true
  end
  return false, mob.task.reason  -- e.g. "unreachable" or "replaced"
end

There is one difference: awaitTask keeps to the task that was running when it was called. If another task replaces it, awaitTask returns false and "replaced" at once, while the loop above would go on waiting for the new task.

Example: Fetching coal and fueling a furnace

local zombie = spell:summon("zombie") --[[@as MobEntity]]
local chest, furnace = spell.pos + Vec3(3, 0, 0), spell.pos + Vec3(3, 0, 3)
local ok, reason = zombie:startTakeFrom(chest, { item = "coal" })
if ok then
  ok, reason = zombie:awaitTask(20 * 20)
end
if ok then
  ok, reason = zombie:startPutInto(furnace)
  if ok then
    ok, reason = zombie:awaitTask(20 * 20)
  end
end
print(ok and "The furnace is fueled." or "Gave up: " .. reason)

Parameters

  • timeout?: number — The maximum number of ticks to wait, an integer. Defaults to no limit.

Returns

  • ok: boolean — true if the task finished, false otherwise.
  • reason: TaskReason|"timeout"|nil — Why the task did not finish, or "timeout" if the wait ended first.

findPath(target: Vec3|Vec3[ ], options?: {targetRadius?:number, searchRange?:number}) → path: MobPath?

Searches for the shortest path from where this entity is to the given position, and returns it without moving: the entity stays where it is, and its task, if it has one, goes on.

The path is the one startPathTo would start on with the same options, found the way this kind of entity finds its ways: a spider climbs, a fish swims, a villager goes through doors. So a spell can look before the entity goes: how long the way is, whether it gets there at all, and where it leads. startPathAlong then moves the entity along it.

Given a list of positions, the path leads to each of them in order, and ends at the last: a route of the spell's own choice, like a patrol, or a way around a place the entity should avoid. The positions only say where the path comes by. The way from each to the next is searched like any path, with the same options, and the entity walks all of it in one go. targetRadius tells how close the path comes to each position; with 0 it leads through their blocks. If a position can't be reached, the path ends there, short of it, and reachable is false.

An entity can only search a path where it can set off: a walking one on the ground, a fish in water. Otherwise, and when no path exists at all, the function returns nil.

Example: The nearer of two places

local a, b = villager:findPath(well), villager:findPath(farm)
local path = a
if not (a and a.reachable) or (b and b.reachable and b.length < a.length) then
  path = b
end
if path and path.reachable then
  villager:startPathAlong(path)
  villager:awaitTask()
end

Example: A patrol around a square

local golem = spell:summon("iron_golem") --[[@as MobEntity]]
local corner = golem.pos
local route = { corner + Vec3(8, 0, 0), corner + Vec3(8, 0, 8), corner + Vec3(0, 0, 8), corner }
while true do
  local path = golem:findPath(route, { targetRadius = 0 })
  if not (path and path.reachable and golem:startPathAlong(path) and golem:awaitTask()) then
    break
  end
end

Parameters

  • target: Vec3|Vec3[ ] — The position to search a path to, or a list of positions to come by in order, the last being the target.
  • options?: {targetRadius?:number, searchRange?:number} — targetRadius and searchRange are as for startPathTo.

Returns

  • path: MobPath? — The path, or nil if there is none at all. A path that stops short of the target has reachable set to false.

startAttack(target: LivingEntity, options?: StartAttackOptions) → ok: boolean, reason: TaskReason?

Starts a task that walks this entity to the given entity and attacks it.

The entity chases its target, recomputing its route every few ticks as the target moves, and attacks once every cooldown ticks while the target is close enough. Where its path ends short of the target, it searches wider before it gives up, as for startFollow.

An entity that hits walks as close as it can get. An entity that shoots stops at distance blocks and backs off when the target comes nearer, so a skeleton keeps its bow at a useful range instead of walking into the enemy. Set chase to false to make the entity hold its ground: it then never moves, turns to face its target and attacks whatever comes within range, which is what a guard on a wall or a tower does.

The task finishes when the target dies. It fails when the target can no longer be reached, which includes the target moving further away than searchRange, and when the target dies out of sight, leaves the world or changes dimension. An entity holding its ground never fails for distance: it waits for the target to come back.

This entity attacks with the weapon it is holding and deals the damage its attack damage attribute says, enchantments included. Peaceful entities such as pigs have no attack damage attribute; for them damage is required, and calling this function without it raises an error.

Like any entity other than a player, this entity does not wear out its weapon. Set wearTool to true to make hitting and shooting wear it out as they would in a player's hand.

With a mode of "auto" this entity shoots if it is able to shoot and holds a bow, a crossbow or a trident, and hits otherwise. An entity that shoots but cannot hit at all, like a snow golem, always shoots, and needs no damage. Use "melee" or "ranged" to decide instead of it, for example to make a witch throw potions rather than punch. An entity that cannot shoot hits even with "ranged": a zombie, and a skeleton without a bow.

While this task runs, task also carries target, mode, damage, reach, cooldown, distance, chase, wearTool, speed and searchRange, and hits, the number of hits that landed and shots fired.

Example: A wolf that hunts down the nearest zombie

local wolf = spell:summon("wolf") --[[@as MobEntity]]
local zombies = spell:findEntities("@e[type=zombie,limit=1,sort=nearest]")
if #zombies > 0 then
  wolf:startAttack(zombies[1], { searchRange = 30 })
  wolf:awaitTask()
  print("Hits landed: " .. wolf.task.hits)
end

Example: A skeleton that shoots from a safe distance

local skeleton = spell:summon("skeleton") --[[@as MobEntity]]
skeleton:setItemInHand("mainhand", Item:new("bow"))
skeleton:startAttack(spell.owner, { distance = 12 })

Example: A guard that holds its post

local skeleton = spell:summon("skeleton") --[[@as MobEntity]]
skeleton:startAttack(spell.owner, { chase = false })

Example: A pig with a grudge

local pig = spell:summon("pig") --[[@as MobEntity]]
pig:startAttack(spell.owner, { damage = 4, speed = 1.3 })

Parameters

Returns

  • ok: boolean — true if the task started, false otherwise.
  • reason: TaskReason? — The reason the task did not start.

startBreakBlock(pos: Vec3, options?: StartBreakBlockOptions) → ok: boolean, reason: TaskReason?

Starts a task that walks this entity to the given block and breaks it.

The entity walks until the block is within reach, then hits it until it breaks. Players see the block crack as it does when they mine it. The time it takes is computed the way it is for a player, from the block's hardness and the tool in this entity's main hand: a zombie needs 15 ticks for dirt, 150 for stone without a pickaxe and 8 with an iron one. duration overrides it.

The block drops what it would drop for a player with the same tool: stone broken without a pickaxe drops nothing, and enchantments like silk touch and fortune on the tool apply. The tool wears out as it would in a player's hand. This task ignores the mobGriefing game rule.

The task finishes when the block is gone, also when something else removed it. It fails with "target_changed" if the block is replaced by a different one. Gravel and sand above the block fall into its place right after it breaks, so check the block again before moving on.

While this task runs, task also carries target, duration (the break time in ticks), progress (from 0 to 1), drops, wearTool, reach, searchRange and speed.

Example

local zombie = spell:summon("zombie") --[[@as MobEntity]]
zombie:setEquipment("mainhand", Item:new("iron_pickaxe"))
local ok, reason = zombie:startBreakBlock(spell.pos + Vec3(3, 0, 0))
if ok then
  ok, reason = zombie:awaitTask()
end
print(ok and "Broken." or "The zombie could not break it: " .. reason)

Parameters

Returns

  • ok: boolean — true if the task started, false otherwise.
  • reason: TaskReason? — The reason the task did not start: "unbreakable" for blocks like bedrock, "invalid_target" for air and fluids, or one of the reasons every task has.

startFleeFrom(entity: Entity, options?: StartFleeFromOptions) → ok: boolean, reason: TaskReason?

Starts a task that moves this entity away from the given entity.

The entity repeatedly looks for a reachable spot leading away from the given entity and runs there, as an animal does when it panics. The task finishes as soon as the two are distance blocks apart, and fails when there is nowhere to run, for example in a closed room.

While this task runs, task also carries target, distance, searchRange and speed.

Example

local sheep = spell:summon("sheep") --[[@as MobEntity]]
sheep:startFleeFrom(spell.owner, { distance = 15, speed = 1.5 })
if sheep:awaitTask() then
  print("The sheep is safe again.")
end

Parameters

Returns

  • ok: boolean — true if the task started, false otherwise.
  • reason: TaskReason? — The reason the task did not start.

startFollow(target: Entity, options?: StartFollowOptions) → ok: boolean, reason: TaskReason?

Starts a task that follows the given entity at a distance of distance blocks.

The entity walks after its target whenever the gap grows larger than distance and waits when it is closer. The task runs until it is stopped or replaced. It fails if the target dies, leaves the world or moves somewhere this entity cannot reach.

Where its path ends short of the target, at the foot of a cliff or at a wall, the entity doesn't give up at once: it searches again from there, up to 4 times as far as searchRange, since a way around may be longer than that. Only when that search finds no way either, the task fails with "unreachable".

While this task runs, task also carries target, distance, searchRange and speed.

Example

local wolf = spell:summon("wolf") --[[@as MobEntity]]
wolf:startFollow(spell.owner, { distance = 3, speed = 1.2 })
wolf.lookAt = spell.owner
sleep(60 * 20)
wolf:stopTask()

Parameters

Returns

  • ok: boolean — true if the task started, false otherwise.
  • reason: TaskReason? — The reason the task did not start.

startPathAlong(path: MobPath, options?: {speed?:number}) → ok: boolean, reason: TaskReason?

Starts a task that moves this entity along a path that findPath returned.

The entity walks this path and no other. It doesn't search again: if it is held up, for example by a block placed in its way, the task fails with "unreachable", and the spell decides what to do next. The task finishes when the entity is at the end of the path, also at the end of a path that stops short of its target, as path.reachable tells.

The entity must be on the path to start: at its first node, or at any other, from where it walks the rest. So a path can be walked again, or by another entity of the same kind, as long as the blocks along it stay as they were.

While this task runs, task carries the same fields as for startPathTo, with the target and the search options of the path.

Example

local pig = spell:summon("pig") --[[@as MobEntity]]
local path = pig:findPath(pig.pos + Vec3(20, 0, 0))
if path and path.reachable and path.length < 40 then
  local ok, reason = pig:startPathAlong(path, { speed = 1.2 })
  if ok then
    ok, reason = pig:awaitTask()
  end
  print(ok and "The pig got there." or "The pig did not get there: " .. reason)
end

Parameters

  • path: MobPath — The path to walk along.
  • options?: {speed?:number} — speed is as for startPathTo.

Returns

  • ok: boolean — true if the task started, false otherwise.
  • reason: TaskReason? — The reason the task did not start: "off_path" if the entity isn't on the path, or "other_dimension" if the path was searched in another world.

startPathTo(target: Vec3, options?: StartPathToOptions) → ok: boolean, reason: TaskReason?

Searches for the shortest path to the given position and starts a task that moves this entity along it.

The task finishes as soon as the entity is within targetRadius of the target. A path search looks searchRange blocks ahead, so a path to a target further away, or along a long, winding way, may end short of it. Then the entity searches again from where that path ends, and so on, as long as each such end is at least a block nearer the target than the last. The task fails if the entity gets stuck, or if it gets no nearer, which happens when the target cannot be reached. On a winding way, the end of a short path can be a dead end, which costs a detour or fails; a larger searchRange finds the better way at once, but takes longer to search.

A speed well above 1 can make the entity overshoot turns and the target: the task finishes when the entity reaches the target, and the entity may slide on from there. In tight spaces, keep it close to 1.

An entity that can open doors, like a villager, opens wooden doors on its way and closes them behind itself.

While this task runs, task also carries target, targetRadius, searchRange and speed as they were passed, traveled, the distance in blocks the entity has come along its path so far, remainingDistance, the length in blocks of the rest of the path, and reachable, which is false when the path stops short of the target; the entity may still get there, see above.

The path is computed anew when blocks change along the way, or when the entity was held up. So remainingDistance and reachable can change while the entity walks, while traveled only grows.

Example

local pig = spell:summon("pig") --[[@as MobEntity]]
local waypoint = pig.pos + Vec3(0, 0, 10)

local ok, reason = pig:startPathTo(waypoint, { targetRadius = 1, speed = 1.2 })
if not ok then
  error("The pig cannot get there: " .. reason)
end

while pig.busy do
  if not pig.task.reachable then
    print("It will not make it all the way.")
  end
  sleep(20)
end

if pig.task.state == "finished" then
  print("The pig walked " .. math.floor(pig.task.traveled) .. " blocks in " .. pig.task.age .. " ticks.")
else
  print("The pig gave up: " .. pig.task.reason)
end

Parameters

Returns

  • ok: boolean — true if the task started, false otherwise.
  • reason: TaskReason? — The reason the task did not start.

startPlaceBlock(pos: Vec3, options?: StartPlaceBlockOptions) → ok: boolean, reason: TaskReason?

Starts a task that walks this entity to the given position and places the block it holds there.

The entity walks until the position is within reach and places one item of the stack in its hand, as a player would. A block with a direction, like a furnace or stairs, faces this entity. against tells which neighbor the block rests against, which decides between a torch on the floor and one on a wall, for example.

Unlike a player, this entity needs no neighboring block to place a block against: it can put a stone in mid-air. If it stands where the block goes, it steps aside first.

The task fails with "occupied" if a block was put there in the meantime or another entity is in the way, and with "cannot_place" if the block cannot stay there, like a torch above a hole. Nothing is used up then.

While this task runs, task also carries target, hand, against, reach, searchRange and speed.

Example: A torch on the wall, from the off hand

local zombie = spell:summon("zombie") --[[@as MobEntity]]
zombie:setEquipment("offhand", Item:new("torch"))
zombie:startPlaceBlock(spell.pos + Vec3(2, 1, 0), { hand = "offhand", against = "east" })

Parameters

Returns

  • ok: boolean — true if the task started, false otherwise.
  • reason: TaskReason? — The reason the task did not start: "hand_empty", "not_a_block", "occupied", or one of the reasons every task has.

startPutInto(pos: Vec3, options?: StartPutIntoOptions) → ok: boolean, reason: TaskReason?

Starts a task that walks this entity to the given container and puts the items it holds into it.

The entity walks until the container is within reach, then moves the items from its hand into the container, following the rules a hopper follows. The container is found the way a hopper finds it: a container block, or else a container entity like a chest minecart in that block space. Many containers, like chests and barrels, take anything from any side. A few decide by the side an item comes from:

Container from above from a side from below
furnace, blast furnace, smoker the item to smelt fuel -
brewing stand the ingredient bottles and blaze powder -
composter compostable items - -

The side is not a place the entity has to stand at: it only decides which slots the items go to, so a covered side works as well. Without side, the items go in as if from the side this entity stands at, and what that side refuses as if from above. So coal goes into a furnace as fuel and raw iron as the item to smelt. Use side = "up" to smelt logs rather than burn them.

The task finishes when at least one item went in. What does not fit stays in the hand, and task.moved tells how many went in. The task fails with "container_full" if nothing fit, and with "target_changed" if the container is gone.

While this task runs, task also carries target, hand, side, count, moved, reach, searchRange and speed.

Example: Fueling a furnace

local zombie = spell:summon("zombie") --[[@as MobEntity]]
local coal = Item:new("coal")
coal.count = 16
zombie:setEquipment("mainhand", coal)
zombie:startPutInto(spell.pos + Vec3(3, 0, 0))

Parameters

Returns

  • ok: boolean — true if the task started, false otherwise.
  • reason: TaskReason? — The reason the task did not start: "hand_empty", "no_container", or one of the reasons every task has.

startStay(options?: StartStayOptions) → ok: boolean, reason: TaskReason?

Starts a task that keeps this entity where it is, or at a given position.

The entity stands still and does not act on its own until the task is stopped or replaced. Without duration the task runs until then; with one it finishes once the duration has passed. The entity still falls, burns and takes damage, and it defends itself against nothing: attacking, panicking and fleeing are all part of the behavior a task suspends.

Setting ai to false also keeps an entity still, but it is a different thing and it is persisted: such an entity stops moving entirely, does not fall, and executes no tasks until ai is true again.

With pos or radius, the entity holds its spot: when something moves it further away than radius, like a hit, a push or a current, it walks back once it has landed, along a path as startPathTo walks. Its spot is the middle of the block at pos, where it walks first, or else the place where it stands when the task starts. An entity comes to rest anywhere on the block of its spot, so with a radius below 1 it goes back when it is a block off. When it finds no way back, it waits where it is and tries again every two seconds; only on its way to pos at the start the task fails, with "unreachable", as a path task does.

While this task runs, task also carries duration and remaining, the ticks left, if a duration was given. An entity that holds its spot also carries pos, its spot, radius, away, which is true while it is on its way to the spot, searchRange and speed.

Example

Without options, the entity stays where it stands, with no time limit, until the task is stopped or replaced. It doesn't hold its spot: when something moves it, it stays where it ends up.

local villager = spell:summon("villager") --[[@as MobEntity]]
villager:startPathTo(spell.pos + Vec3(8, 0, 0))
villager:awaitTask()
villager:startStay()          -- guards the spot instead of wandering off
villager.lookAt = spell.owner

A guard that walks to its post and keeps it, and one that gives way up to 3 blocks:

guard:startStay({ pos = post, speed = 1.3 })
other:startStay({ radius = 3 })

Parameters

Returns

  • ok: boolean — true if the task started, false otherwise.
  • reason: TaskReason? — The reason the task did not start: "no_path" if no path leads to pos.

startTakeFrom(pos: Vec3, options?: StartTakeFromOptions) → ok: boolean, reason: TaskReason?

Starts a task that walks this entity to the given container and takes items out of it.

The entity walks until the container is within reach, then takes items into its hand, following the rules a hopper follows: by default from below, which gives a furnace's output but not its fuel. item chooses what to take: an item id like "coal", a tag like "#minecraft:logs", or a list of them. A hand that already holds something only takes more of the same.

The task finishes when at least one item was taken; task.moved tells how many. It fails with "nothing_to_take" if nothing matched, and with "target_changed" if the container is gone.

While this task runs, task also carries target, hand, side, item, count, moved, reach, searchRange and speed.

Example: Fetching a new pickaxe

local zombie = spell:summon("zombie") --[[@as MobEntity]]
local chest = spell.pos + Vec3(3, 0, 0)
zombie:startTakeFrom(chest, { item = "#minecraft:pickaxes", count = 1 })
zombie:awaitTask()
print(str(zombie:getEquipment("mainhand")))

Parameters

Returns

  • ok: boolean — true if the task started, false otherwise.
  • reason: TaskReason? — The reason the task did not start: "hand_full", "no_container", or one of the reasons every task has.

stopTask()

Stops the task this entity is executing and hands it back to its own behavior.

Does nothing if this entity is executing no task. The stopped task gets the state "interrupted" and the reason "stopped".

Example

local pig = spell:summon("pig") --[[@as MobEntity]]
pig:startPathTo(spell.owner.pos + Vec3(20, 0, 0))
sleep(5 * 20)
pig:stopTask()  -- five seconds of walking is enough