Skip to content

Wizards of Lua — Examples

Here are some small examples to help you get started with Lua scripting in Minecraft using the Wizards of Lua mod.


Create a 3x3 Platform Below You

Place slime blocks in a 3x3 area under the caster's feet:

local pos = spell.owner.pos + Vec3(0, -1, 0)
for x = -1, 1 do
  for z = -1, 1 do
    spell.pos = pos + Vec3(x, 0, z)
    spell.block = Block:new("slime_block")
  end
end

Delete the Block You're Looking At

Removes the block the player is pointing at:

local hit = spell.owner:raycastBlock(5)
if not hit then return end
spell.pos = hit.blockPos
spell.block = Block:new("air") -- deletes the block

Inspect a Block

Print the block ID, name, and data:

local hit = spell.owner:raycastBlock(5)
if hit then
  spell.pos = hit.blockPos
else
  spell.pos = spell.owner.eyePos + spell.owner.lookVec * 5
end
print("id", spell.block.type.id)
print("name", spell.block.type.name)
print("data", str(spell.block.data))

Auto-Build Ladder Up a Wall

Builds ladders up the targeted wall until blocked or the backing isn't solid:

local hit = spell.owner:raycastBlock(5)
if not hit or hit.side == "up" or hit.side == "down" then return end
local ladder = Block:new("ladder")
ladder.data.facing = hit.side
spell.pos = hit.blockPos
spell:move(hit.side)
while true do
  spell.block = ladder
  spell:move("up")
  if spell.block.type.id ~= "air" then break end
  spell:move(hit.side, -1)
  if not spell.block.opaque then break end
  spell:move(hit.side)
end

Make a Particle Cloud

Creates an ongoing cloud of smoke particles:

while true do
  spell.pos = spell.owner.pos
  spell:execute("/particle minecraft:smoke ~ ~2 ~ 1 2 1 0 20 force")
  sleep(20)
end

Tip: Use spell:executeSilent(...) to hide the command output. Use /wol spell break to stop all long running spells.


Make a Spell Visible

Fire a spell forward from your eyes:

spell.pos = spell.owner.eyePos
spell.visible = true
spell.velocity = spell.owner.lookVec * 0.1
sleep(20 * 10)

Inspect Player Inventory

Displays what's in the caster’s inventory:

local player = spell.owner --[[@as Player]]
print("Inventory:", str(player.inventory))

Give Players a Diamond Sword on Login

Wipes their inventory and gives a sword when they join:

local function giveOnlyDiamondSword(player)
  --- @cast player Player
  player.inventory:clear()  
  player.mainHandItem = Item:new("diamond_sword")
end

local eventQueue = spell:collect("PlayerJoinedEvent")
while true do
  local event = eventQueue:next() --[[@as PlayerJoinedEvent]]  
  giveOnlyDiamondSword(event.player)
end

Note: This spell only works in multiplayer; in singleplayer, all spells stop when you leave the world.


Block Protection Zone

Prevents block breaking or placing in a radius:

local radius = 10
spell.visible = true
spell:intercept({ "BeforePlayerBlockBreakEvent", "PlayerUseBlockEvent" }, function(event)
  local pos = type(event) == "BeforePlayerBlockBreakEvent" and event.pos or event.hitResult.blockPos
  if spell.world == event.world then
    return (pos - spell.pos):magnitude() > radius
  end
end)

while true do sleep(20) end

Auto-Toggle Gamemode Field

Switch players between survival and adventure automatically:

local radius = 10
spell.visible = true
local pos = spell.pos

while true do
  sleep(1)
  for _, p in ipairs(spell:findEntities("@a[distance=.." .. radius .. ",gamemode=survival]")) do
    ---@cast p Player
    p.gamemode = "adventure"
    spell.pos = p.pos
    spell:execute("/playsound minecraft:block.enchantment_table.use master @a[distance=..5] ~ ~ ~")
  end
  spell.pos = pos
  for _, p in ipairs(spell:findEntities("@a[distance=" .. radius .. "..,gamemode=adventure]")) do
    ---@cast p Player
    p.gamemode = "survival"
    spell.pos = p.pos
    spell:execute("/playsound minecraft:entity.illusioner.cast_spell master @a[distance=..5] ~ ~ ~")
  end
  spell.pos = pos
end

Smite Nearby Zombies

Strikes all nearby zombies with lightning:

local nearbyZombies = spell:findEntities("@e[type=zombie,distance=..20]")
for i = 1, 3 do
  for _, zombie in ipairs(nearbyZombies) do
    spell.pos = zombie.pos
    spell:execute("/execute run summon lightning_bolt")
  end
  sleep(10)
end

Night Vision for Golden Helmet Wearers

Continuously grants Night Vision to any player wearing a golden helmet and removes it once they take it off.

local tracked = {}
while true do
  for _, player in ipairs(spell:findEntities("@a")) do
    ---@cast player Player
    local head = player:getEquipment("head")
    local wearing = head and head.type.id == "golden_helmet"
    local id = player.name
    if wearing then
      spell:executeSilent(
        "effect give " .. id
        .. " minecraft:night_vision 20 0 true"
      )
      tracked[id] = true
    elseif tracked[id] then
      spell:executeSilent(
        "effect clear " .. id .. " minecraft:night_vision"
      )
      tracked[id] = nil
    end
  end
  sleep(20)
end

Thunder Stick: Lightning Bolt Spell

Assigns a special stick to the player. When swung, it strikes nearby hostile mobs with lightning.

local spellName = "thunder-spell"
-- Prevent duplicates by stopping existing spell with same name
spell:executeSilent("wol spell break byName " .. spellName)
spell.name = spellName

local function castLightningBolt(player)
  local oldPos = spell.pos
  spell.pos = player.pos
  spell.world = player.world
  local nearbyEntities = spell:findEntities("@e[type=!player,distance=..20]")
  local hostileEntities = {}
  for _, entity in ipairs(nearbyEntities) do
    if instanceOf(LivingEntity, entity) then
      ---@cast entity LivingEntity
      if entity.hostile then
        table.insert(hostileEntities, entity)
      end
    end
  end
  if #hostileEntities > 0 then
    spell.pos = player.pos
    spell:execute("/particle minecraft:instant_effect ~ ~1 ~ 0 1 0 2 50 force")
  end
  for i = 1, 3 do
    for _, entity in ipairs(hostileEntities) do
      spell.pos = entity.pos
      spell:execute("/execute run summon lightning_bolt")
    end
    sleep(10)
  end
  spell.pos = oldPos
end

local stick = Item:new("stick")
stick.name = "Thunder Stick"
local player = spell.owner --[[@as Player]]
player.mainHandItem = stick
local eventQueue = spell:collect("PlayerHandSwingEvent")
while true do
  local event = eventQueue:next() --[[@as PlayerHandSwingEvent]]
  if event.hand == "mainhand" then
    if event.player.mainHandItem and event.player.mainHandItem.name == stick.name then
      local hit = event.player:raycastBlock(5)
      if not hit then
        castLightningBolt(event.player)
      end
    end
  end
end

Move Minecarts Up a Ladder and Launch Them Horizontally

This spell detects minecarts (all types) and moves them upward while they are inside a ladder block. If they reach the top of the ladder, they are launched away from the ladder, in the direction opposite to the ladder's facing. This can be used to build vertical minecart elevators with automatic directional exits at the top.

local name = "vertical-minecart-spell"

-- Prevent duplicates by stopping existing spell with same name
spell:executeSilent("wol spell break byName " .. name)
spell.name = name

print("Starting Vertical Minecart Spell")

local verticalSpeed = 0.1
local exitSpeed = 0.2

local exitDirection = {
  north = Vec3(0, 0, 1),
  south = Vec3(0, 0, -1),
  west = Vec3(1, 0, 0),
  east = Vec3(-1, 0, 0)
}

local types = { "minecart", "chest_minecart", "furnace_minecart",
 "hopper_minecart", "tnt_minecart" }

local function addAll(t1, t2)
  for k, v in ipairs(t2) do
    table.insert(t1, v)
  end
end

while true do
  -- Collect all types of minecarts
  local minecarts = {}
  for _, t in ipairs(types) do
    addAll(minecarts, spell:findEntities("@e[type=" .. t .. "]"))
  end

  for _, minecart in ipairs(minecarts) do
    local currentPos = minecart.pos
    spell.pos = currentPos

    if spell.block.type.id == "ladder" then
      -- Move minecart upward on ladder
      local velocity = currentPos:floor() + Vec3(0.5, 0, 0.5) - currentPos
      velocity.y = verticalSpeed
      minecart.velocity = velocity
    else
      -- If not on a ladder, check if the block below is a ladder for exit launch
      spell.pos = currentPos + Vec3(0, -0.5, 0)
      if spell.block.type.id == "ladder" then
        minecart.velocity = exitDirection[spell.block.data.facing] * exitSpeed
      end
    end
  end
  sleep(1)
end

Safe Zone: Disable PvP in Area

Creates a circular safe zone (radius = 10 meters) around the spell’s position where players cannot damage each other:

local name = "safezone-spell"

-- Prevent duplicates by stopping existing spell with same name
spell:executeSilent("wol spell break byName " .. name)
spell.name = name

print("Starting Safezone Spell")
local radius = 10
spell:intercept({ "PlayerAttackEntityEvent" }, function(event)
  ---@cast event PlayerAttackEntityEvent
  local attacker = event.player
  local target = event.entity
  if spell.world == event.world then
    if instanceOf(Player, target) then
      local distance = (attacker.pos - spell.pos):magnitude()
      if distance <= radius then
        spell:execute("/title " .. attacker.name .. " actionbar \"§cPvP is disabled here!\"")
        return false -- block the attack
      end
    end
  end
  return true
end)

while true do
  sleep(20)
end

Send a Pig on an Errand

Walks a pig to a spot ten blocks north and reports what became of it:

spell.pos = spell.owner.eyePos + spell.owner.lookVec * 5
local pig = spell:summon("pig") --[[@as MobEntity]]
sleep(20) -- let it land, so that the waypoint is on the ground too
local waypoint = pig.pos + Vec3(0, 0, -10)

local ok, reason = pig:startPathTo(waypoint, { speed = 1.2 })
if ok then
  ok, reason = pig:awaitTask() -- sleeps until the pig got there or gave up
end

if ok then
  print("Walked " .. math.floor(pig.task.traveled) .. " blocks in " .. pig.task.age .. " ticks.")
else
  print("Gave up: " .. reason)
end

awaitTask() reports the same way startPathTo() does, so one if handles both a pig that cannot start and one that gives up on the way.


Gather Mobs at a Spot

Registers a dynamic command that sends every mob an entity selector finds to a spot. Each mob walks until it is within the given distance of the spot, and the command reports how many got there. Run this once, for example from your startup spell:

WizardsOfLua.setCommand({
  id      = "gather",
  level   = 2,
  pattern = "gather targets:%entities to spot:%v within distance:%i",
  code    = [[
    local mobs = {}
    for _, entity in ipairs(args.targets) do
      -- A selector can also find players, items and the like; only mobs take tasks.
      if instanceOf(MobEntity, entity) then
        ---@cast entity MobEntity
        if entity:startPathTo(args.spot, { targetRadius = args.distance }) then
          mobs[#mobs + 1] = entity
        end
      end
    end

    local arrived = 0
    for _, mob in ipairs(mobs) do
      if mob:awaitTask() then
        arrived = arrived + 1
      end
    end
    print(arrived .. " of " .. #mobs .. " mobs got there.")
  ]]
})

Then gather all sheep within 30 blocks at a spot ten blocks south of you:

/gather @e[type=sheep,distance=..30] to ~ ~ ~10 within 3

Once a mob has got there, its task is done and it goes back to its own behavior, so the sheep start grazing and wandering again. To keep them there, give each one mob:startStay() after it arrived.


Village Patrol

Sends a villager on patrol between two posts at a strolling pace, for as long as you stay nearby. Step away and he returns to his own business — with his job site and his bed intact, because a task only borrows his attention:

spell.name = "patrol"
local owner = spell.owner
local villager = spell:summon("villager") --[[@as MobEntity]]

spell:move(owner.facing, 16)
local posts = { villager.pos, spell.pos }

local function ownerIsNear()
  return owner.alive and (owner.pos - villager.pos):magnitude() < 32
end

local function waitForTask()
  while villager.busy do
    if not ownerIsNear() then
      villager:stopTask()
      return false
    end
    sleep(10)
    spell.pos = villager.pos -- ensure that the chunk stays loaded
  end
  return true
end

local function patrol()
  while villager.alive and ownerIsNear() do
    for _, post in ipairs(posts) do
      villager.lookAt = nil -- look where he walks
      local ok, reason = villager:startPathTo(post, { targetRadius = 0, speed = 0.5, searchRange = 200 })
      if not ok then
        print("The villager can't get to his post:", reason)
        return
      end
      if not waitForTask() then return end
      villager:startStay({ duration = 3 * 20 })
      villager.lookAt = owner
      if not waitForTask() then return end
    end
  end
end

patrol()
if villager.alive then
  villager.lookAt = nil
  print("The villager goes back to work.")
end

Tarman, the Hit-and-Run Zombie

Summons a zombie ten blocks ahead of you. It charges, runs off as soon as it takes a hit, and comes back once it has caught its breath. The helmet keeps it from burning in daylight, and it swings the axe it is holding - a mob attacks with its weapon and the damage that weapon gives it:

spell.name = "tarman"
local victim = spell.owner --[[@as Player]]
spell.pos = victim.eyePos + victim.lookVec * 10
local zombie = spell:summon("zombie") --[[@as MobEntity]]
local oldName = zombie.name
zombie.name = "Tarman"
zombie:setEquipment("head", Item:new("leather_helmet"))
zombie:setEquipment("mainhand", Item:new("iron_axe"))
local hits = spell:collect("AfterLivingEntityDamageEvent")

while zombie.alive and victim.alive do
  local ok, reason = zombie:startAttack(victim)
  if not ok then
    print("Tarman can't attack you: " .. reason) -- e.g. "invalid_target" in creative mode
    break
  end

  local hurt = false
  while zombie.busy and not hurt do
    local evt = hits:next(10) --[[@as AfterLivingEntityDamageEvent]] 
    hurt = evt ~= nil and evt.entity == zombie
    spell.pos = zombie.pos -- ensure that the chunk stays loaded
  end

  if hurt then
    zombie:startFleeFrom(victim, { distance = 12, speed = 1.4 })
    zombie:awaitTask()
    sleep(3 * 20) -- catch breath
    hits:latest() -- forget the hits taken while running
  end
end

if zombie.alive then
  zombie.name = oldName
  zombie:dropItem(zombie:getEquipment("head"))
  zombie:setEquipment("head", nil)
  zombie:dropItem(zombie:getEquipment("mainhand"))
  zombie:setEquipment("mainhand", nil)
end

Spider Bodyguard

Gives you a spider that stays at your side and attacks whoever hurts you. When the fight is over it returns to you. If you die, it dies with you — and if you stop the spell, it is an ordinary spider again, daylight and all:

spell.name = "bodyguard"
local owner = spell.owner
spell.pos = owner.eyePos + owner.lookVec * 3
local spider = spell:summon("spider") --[[@as MobEntity]]
spider.name = "Bodyguard"

local function guard()
  spider:startFollow(owner, { distance = 5, speed = 1.3 })
  spider.lookAt = owner
end

guard()
local hits = spell:collect("AfterLivingEntityDamageEvent")
while spider.alive and owner.alive do
  local evt = hits:next(20) --[[@as AfterLivingEntityDamageEvent]]  
  if evt and evt.entity == owner then
    local attacker = evt.damageSource.attacker --[[@as LivingEntity?]]
    if attacker and attacker ~= owner and attacker ~= spider then
      spider.lookAt = nil
      if spider:startAttack(attacker, { searchRange = 30 }) then
        spider:awaitTask()
      end
      guard()
    end
  end
end

if spider.alive then
  spider:kill()
end

What's next?

  • Explore complete examples:
  • The Lumberjack Spell that makes the whole tree fall when any player breaks a single log
  • The Guards who hold their posts against hostile mobs, and take them up again whenever their chunk is loaded
  • The Miner who digs a tunnel, lights it with torches, and fetches new tools from a chest when its pickaxe breaks
  • The Magic Hoe is a magical chain-harvesting tool
  • The Journey mini-game
  • Read the Commands documentation.