- **Breaking Change: `startFollowPathTo` replaced by mob tasks**
- A spell now gives a mob a **task** instead of taking its AI away. While a task runs, the mob's own behavior is suspended; when the task ends, the mob carries on as before — it wanders, panics and defends itself again. Before, the mob was left standing there for good.
- `startFollowPathTo` became `startPathTo`, `followingPath` became `busy`, and `stopFollowPath` became `stopTask`.
- Every `start...` function takes its subject first and everything else in an options table, and returns `true`, or `false` and a reason why the task did not start. A misspelled option raises an error rather than being ignored.
- An unreachable target now fails the task. Before, the mob walked as far as it could get and the call reported success either way.
- **Migration:**
```lua
-- before
pig:startFollowPathTo(waypoint, 0, 100, 2)
while pig.followingPath do sleep(20) end
if (waypoint - pig.pos):magnitude() > 1 then print("did not make it") end
-- after
pig:startPathTo(waypoint, { targetRadius = 0, searchRange = 100, speed = 2 })
while pig.busy do sleep(20) end
if pig.task.state ~= "finished" then print("did not make it: " .. pig.task.reason) end
```
- To keep a mob standing still, use `mob:startStay()` for as long as the spell runs, or `mob.ai = false` to keep it that way for good. The latter is saved with the entity, and such a mob does not even fall.
- **Breaking Change: Hands and hit types are lowercase**
- A hand is now `"mainhand"` or `"offhand"`, as in `getEquipment` and the task options, instead of `"MAIN_HAND"` or `"OFF_HAND"`. This is what the `hand` field of `PlayerAttackBlockEvent`, `PlayerAttackEntityEvent`, `PlayerHandSwingEvent`, `PlayerUseBlockEvent`, `PlayerUseEntityEvent` and `PlayerUseItemEvent` returns.
- `HitResult.type` is now `"block"`, `"entity"` or `"miss"` instead of `"BLOCK"`, `"ENTITY"` or `"MISS"`.
- `getItemInHand` and `setItemInHand` reject the old spelling with an error that names the new one. A comparison with the old spelling, though, is simply no longer true, and no error tells you so.
- **Migration:**
```lua
-- before
if event.hand == "MAIN_HAND" then ... end
if hit.type == "MISS" then ... end
-- after
if event.hand == "mainhand" then ... end
if hit.type == "miss" then ... end
```
- **New: Support for Minecraft 26.1.1, 1.21.2, 1.21.3, 1.21.5, 1.21.6, 1.21.7 and 1.21.9**
- These versions get their first Wizards of Lua release with 6.0.0, so every Minecraft version from 1.21.1 to 26.3 is now supported.
- **New: Mobs can be sent to attack, follow, flee and stay**
- `startAttack(entity, options?)` walks the mob to an entity and attacks it, with the weapon it holds and the damage that weapon gives it. Options: `damage`, `reach`, `cooldown`, `distance`, `chase`, `searchRange`, `speed`, `mode` ("auto", "melee" or "ranged") and `wearTool`. A peaceful mob such as a pig has no attack damage of its own, so it needs `damage`. Like any mob, it does not wear out its weapon, unless `wearTool` is `true`.
- `startFollow(entity, options?)` follows an entity at a distance, `startFleeFrom(entity, options?)` runs away until far enough, and `startStay(options?)` keeps the mob where it is, for a `duration` if one is given.
- `stopTask()` ends the task and hands the mob back to its own behavior.
```lua
local wolf = spell:summon("wolf")
wolf:startFollow(spell.owner, { distance = 3, speed = 1.2 })
wolf.lookAt = spell.owner
```
- **New: `busy` and `task` tell what a mob is doing**
- `busy` is `true` while the mob executes a task. `task` is a copy of the task it is executing, or of the last one it executed: `name`, `state` ("running", "finished", "failed" or "interrupted"), `reason`, `age`, the options it was started with, and for a path how far it has `traveled`, the `remainingDistance` and whether the target is `reachable`.
- Because `task` outlives the task, a failure can be reported with the where and how far, not only that it happened.
- **New: `MobEntity.lookAt`, `ai`, `target` and `canPickUpLoot`**
- `lookAt` is the entity or position a mob keeps looking at, independent of its task, so it can watch you while walking somewhere else.
- `ai` enables or disables the mob's artificial intelligence. It is persisted, and a mob with `ai = false` neither acts, nor executes tasks, nor falls.
- `target` is the entity the mob is attacking, whether a spell told it to or it picked the target itself.
- `canPickUpLoot` tells whether the mob picks up items it walks over.
- **New: Mobs can break and place blocks, and fill and empty containers**
- `startBreakBlock(pos, options?)` walks the mob to a block and breaks it the way a player mines it: the time it takes depends on the tool in its main hand, players see the block crack, and the block drops what it would drop for a player with that tool. The tool wears out unless `wearTool` is `false`.
- `startPlaceBlock(pos, options?)` places the block the mob holds, facing the mob. `against` tells which neighbor it rests against, e.g. a torch on the floor or on a wall; unlike a player, the mob needs no neighbor at all.
- `startPutInto(pos, options?)` puts the items the mob holds into a container, and `startTakeFrom(pos, options?)` takes items out, chosen by an item id, a tag or a list of them. Both follow the rules a hopper follows, so coal goes into a furnace as fuel and raw iron as the item to smelt.
- A mob has only its two hands: the `hand` option tells which one gives or takes the items.
```lua
zombie:setEquipment("mainhand", Item:new("iron_pickaxe"))
zombie:startBreakBlock(spell.pos + Vec3(3, 0, 0))
```
- **New: `awaitTask(timeout?)` waits for a mob's task to end**
- Returns `true` if the task finished, otherwise `false` and the reason, the way the `start...` functions report a task that could not start. So a failed start and a failed task are handled in one place:
```lua
local ok, reason = pig:startPathTo(waypoint)
if ok then
ok, reason = pig:awaitTask()
end
```
- With a `timeout` the wait ends after that many ticks with "timeout"; the task itself keeps running.
- **New: `removeNbt(path, ...)` on `Entity`, `BlockEntity` and `Item`**
- Removes every tag the given NBT paths match and returns how many were removed. Paths use the syntax of the vanilla `/data` command:
```lua
pig:removeNbt("CustomName", 'active_effects[{id:"minecraft:speed"}]')
chest:removeNbt("Items[{Slot:0b}]")
```
- Minecraft only reads some values when they are present, so omitting them does not clear them. `removeNbt` clears the known cases itself (custom name, tags, effects, attributes and their modifiers, health) and raises an error if a removed value is still there afterwards, instead of reporting success.
- **New: `putNbt(path, value)` on `Entity`, `BlockEntity` and `Item`**
- Merges `value` at every tag the NBT path matches, with the same rules as `putNbt(table)`, and returns the number of changed tags. A path can select list elements by content, e.g. `chest:putNbt("Items[{Slot:0b}].count", 16)`.
- In a path, filters must match the stored type exactly (`Slot:0b`, not `Slot:0`) and ids are namespaced. A filter that matches nothing changes nothing.
- **New: `logToConsole(...)` writes to the server console**
- Works like `log(...)`, but always writes to the server console and the server log, whatever `WizardsOfLua.log` is set to. Useful for spells started by the server console, a command block or RCON, where `print(...)` has no one to show its output to.
- **New: `spell:findBlocks(criteria)` and `spell:findBlockEntities(criteria)`**
- Search a sphere around the spell for blocks or block entities, nearest first, filtered by a block id, a tag or a list of them. `findBlocks` returns positions, `findBlockEntities` the block entities:
```lua
local logs = spell:findBlocks({ block = "#minecraft:logs", maxradius = 32, limit = 1 })
local chests = spell:findBlockEntities({ block = { "chest", "barrel" }, maxradius = 16 })
```
- Only loaded chunks are searched.
- **New: Spells can be tagged**
- `spell.tags` is the list of a spell's tags, `spell:addTag(tag)` and `spell:removeTag(tag)` add and remove one. `findSpells({ tag = ... })` finds spells by tag, which it could not before, since no spell could have a tag.
- `/wol spell list byTag <tag>` and `/wol spell break byTag <tag>` list and break the spells with a tag.
```lua
spell:addTag("miner")
for _, s in ipairs(spell:findSpells({ tag = "miner" })) do s:kill() end
```
- **New: `spell.forceChunkRadius` keeps a larger area loaded**
- The number of chunks around the spell's chunk that stay loaded and ticking like the chunk itself. It defaults to 0, the area a spell has always kept loaded.
- **New: Guards example**
- Zombified piglins in iron armor that hold a post and fight off hostile mobs within a radius, without straying further than their leash. Placed with `/guards place <pos> [<radius> <leash>]`. A spell hands the guards their tasks and takes them over again whenever their chunk is loaded, since a mob's task is not saved with it. Installed by `/wol extract examples`; see `examples/guards/Guards.md`.
- **New: Miner example**
- A zombified piglin that digs a tunnel, hangs torches on the wall, and fetches pickaxes and just the torches it needs from the chests near the tunnel entrance, waiting when there are none. Blocks put into the tunnel meanwhile it breaks again. When it is done, it puts its tools back and vanishes. Created with `/miner create miner <pos> <direction> <length>`; see `examples/miner/Miner.md`.
- **New: Named types for the API's values and tables**
- The API files name the values a string accepts, like `Hand` ("mainhand" or "offhand"), `Side`, `EquipmentSlot`, `TaskReason` and `TaskState`, and the tables passed to or returned by functions, like the task options (`StartAttackOptions`, ...), the search criteria and `MobEntity.task` (`MobTask`). Editors with the Lua language server complete these values and fields and flag a wrong or missing one. Run `/wol extract api` again to get the new files.
- The API reference has a new [Types](https://mickkay.codeberg.page/Wizards-of-Lua-Fabric/api/_types/) page that lists them, with a description of every value and field.
- **Change: The undocumented globals `Types` and `Check` are gone**
- `type`, `instanceOf` and `declare` are now global functions of their own, like `log` and `sleep`, instead of wrappers around the `Types` table. `Check`, a leftover nobody used, is gone as well.
- `type`, and the formatting `error(fmt, ...)` and `assert(value, fmt, ...)`, are now documented, so that editors know them too.
- **Change: Assigning `nbt` removes the keys the assigned table omits on every MC version**
- Before, whether an omitted custom name, tag list, effect or attribute was removed depended on the MC version. It is now always removed, so assign a full snapshot (`local nbt = obj.nbt; ...; obj.nbt = nbt`) to keep everything else.
- **Fix: Effects written through NBT did not take effect**
- Adding or removing effects with `putNbt`, `removeNbt` or by assigning `nbt` changed the stored effects, but the client was not told (no icon, particles left over) and the effect's attribute changes were not applied or removed, e.g. Speed did not change the movement speed. Effects now behave as if added or removed with `/effect`.
- **Fix: Writing NBT could change an entity's UUID**
- `putNbt` or assigning `nbt` with a different `UUID` changed the identity of a live entity. The UUID is now kept, as with the vanilla `/data` command; a new one is ignored.
- **Fix: `putNbt()` could not change the NBT type of an existing value**
- Renaming an entity or block entity twice with `putNbt({ CustomName = { text = "..." } })` failed with `Can't convert nbt.CustomName! string expected, but got table` whenever the first name had no formatting, because Minecraft stores an unformatted name as a string and a formatted one as a compound.
- A table is still merged into a stored compound or list. A value of a different kind now replaces the stored value instead of raising an error.
- **Fix: `putNbt()` on `Items`, `Inventory` and `EnderItems` did not update an existing slot**
- Each entry is now merged into the stored entry with the same `Slot`, regardless of its position in the list. Entries for other slots are left alone.
- Partial entries such as `{ Slot = 1, count = 7 }` were silently ignored before and now change only the given fields.
- Because entries are now merged, a new item written into an occupied slot keeps the old item's `components` (e.g. its custom name) unless the entry sets them. To replace a slot completely, assign the full snapshot via `.nbt = ...`.
- **Fix: `putNbt()` on `active_effects` merged effects by list position**
- Effects are now matched by their `id`, so `putNbt({ active_effects = { { id = "speed", amplifier = 3 } } })` changes the stored speed effect and leaves the others alone. Before, it could overwrite whichever effect came first in the list, and a new effect was dropped if the entity already had effects.
- The id may be given with or without the `minecraft:` namespace.
- **Fix: `putNbt()` on `attributes` merged attributes by list position**
- Attributes are now matched by their `id`. Before, `putNbt({ attributes = { { id = "max_health", base = 20 } } })` was merged into whichever attribute came first, so a newly written attribute could inherit that attribute's base value and modifiers.
- **Fix: A spell leaving a chunk released it, even when `/forceload` kept it loaded** ([#41](https://codeberg.org/mickkay/Wizards-of-Lua-Fabric/issues/41))
- A spell keeps the chunk it is in loaded. When it moved on, it also undid a `/forceload` of that chunk, so the chunk could unload and its mobs stopped moving. A spell now uses its own chunk ticket and leaves `/forceload` alone.
- The chunks a spell keeps loaded no longer show up in `/forceload query` and `/forceload list`, and are no longer written to the save, so a server crash cannot leave them force-loaded forever.
- **Fix: Clearing table fields while iterating with `pairs` raised an error** ([rembulan#14](https://github.com/wizards-of-lua/rembulan/issues/14))
- `for k in pairs(t) do t[k] = nil end` failed with `invalid key to 'next'`, or with an error showing only the removed key. Lua allows clearing existing fields during a traversal, and this now works.
- **Fix: Players still saw an item taken from a mob in the tick it spawned**
- A zombified piglin summoned by a spell kept showing its golden sword after `setEquipment("mainhand", nil)`, although it no longer held it. `setEquipment` and `setItemInHand` now always tell the players who see the entity.
- **Fix: `findSpells({ owner = ... })` compared with the wrong owner**
- It compared the name with the owner of the spell doing the search, not with the owner of each spell found, so it found every spell with an owner or none at all. A search from a spell without an owner, e.g. one started from the server console, raised an error. It now finds the spells owned by the named entity.
- **Fix: `Server:getWorld()` raised an error for an unknown dimension**
- It failed with `javaObject == null!`. It now returns `nil`.
Developer information
Projects on Modrinth are automatically available through a Maven repository for use with JVM build tools such as Gradle.
maven { name = "Modrinth"; url = "https://api.modrinth.com/maven" }