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:
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:
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.