2. Events and storage
In part 1 you built commands. Now let’s make your script react to the world and remember things between reloads.
In this guide you will learn how to:
- Listen to server events with
mc.on. - Cancel events with
e:cancel()and order handlers with priorities. - Fire your own events with
mc.emit. - Save data globally with
mc.dataand per-player withplayer.data.
All examples use Nova lambda syntax, so the first line of each file is:
--# nova syntax1. Reacting to events with mc.on
Section titled “1. Reacting to events with mc.on”Minecraft fires events constantly — when a player joins, breaks a block, chats, dies, and so on. mc.on lets you run code whenever one of those events happens.
Syntax:
mc.on(eventName, handler)eventNameis a string like"player.join".handleris a function that receives one argument: the event tablee, with the event’s fields.mc.onreturns a numeric ID. Save it if you want to stop listening later withmc.off(id).
Example:
--# nova syntaxmc.on("player.join") \{ e -> mc.broadcast("Welcome, " .. e.player.name .. "!")}e.player is the player who joined. Each event has its own fields, see the Events reference.
To unsubscribe later:
--# nova syntaxlocal joinId = mc.on("player.join") \{ e -> mc.broadcast("Welcome!")}
-- later...mc.off(joinId)2. Cancelling events
Section titled “2. Cancelling events”Some events are cancellable. Call e:cancel() and Minecraft stops the action.
--# nova syntaxmc.on("block.break") \{ e -> if not e.player:hasPermission("build") then e.player:sendMessage("You cannot break blocks here.") e:cancel() end}In this example, hasPermission("build") checks whether the player has the build permission. You can use any permission node you have configured on your server.
Cancellable events include block.break, block.place, player.chat, entity.hurt, and several others. See the Events reference for the full list.
Once an event is cancelled, the handlers after it are skipped. Handlers run by priority (higher first), so put checks that may cancel before the rest:
--# nova syntaxmc.on("block.break", { priority = "high" }) \{ e -> if isProtected(e.pos) then e:cancel() end}
mc.on("block.break") \{ e -> -- not called for protected blocks e.player.data.mined = (e.player.data.mined or 0) + 1}A handler that wants to see cancelled events too passes { receiveCancelled = true }. If a handler of a cancellable
event fails with an error, the event is cancelled, so a bug in a protection script does not open the door.
3. Custom events with mc.emit
Section titled “3. Custom events with mc.emit”You can fire your own events from Lua. This is useful when you want separate scripts to react to the same thing.
--# nova syntax-- rewards.luamc.on("my_mod:boss_killed") \{ player, bossName -> player:sendMessage("You defeated " .. bossName .. "!") player.data.bossKills = (player.data.bossKills or 0) + 1}Then elsewhere:
--# nova syntax-- bossfight.luamc.emit("my_mod:boss_killed", somePlayer, "Ender Dragon")4. Global persistent data with mc.data
Section titled “4. Global persistent data with mc.data”mc.data is a special table that is saved to config/ignis/storage/global.json. Anything you put in it survives /ignis reload and server restarts.
--# nova syntaxmc.data.joins = (mc.data.joins or 0) + 1
mc.on("player.join") \{ e -> mc.broadcast("Visitor #" .. mc.data.joins .. "!")}Allowed values: numbers, strings, booleans, tables, and nil (to delete a key).
Not allowed: functions, userdata, threads, and tables that reference themselves (cyclic tables). If you try to save those, the server logs an error.
5. Per-player data with player.data
Section titled “5. Per-player data with player.data”Every player wrapper has its own persistent table at player.data. It works exactly like mc.data, but each player gets a separate file on disk.
--# nova syntaxmc.on("player.join") \{ e -> local visits = (e.player.data.visits or 0) + 1 e.player.data.visits = visits e.player:sendMessage("This is visit #" .. visits)}Per-player data is saved when the player disconnects and when /ignis reload runs. It is removed from memory on disconnect, but stays on disk so it is available next time the player joins.
6. Putting it together: a tiny coin system
Section titled “6. Putting it together: a tiny coin system”This example combines commands, events, and per-player storage.
--# nova syntaxlocal function giveCoins(player, amount) player.data.coins = (player.data.coins or 0) + amount player:sendMessage("You have " .. player.data.coins .. " coins")end
-- /coins gives 10 coinsregister("coins") \{ ctx -> giveCoins(ctx.player, 10)}
-- Killing another player gives 50 coinsmc.on("player.kill") \{ e -> giveCoins(e.player, 50)}
-- Dying makes you lose 10% of your coinsmc.on("entity.death") \{ e -> local player = e.player if not player then return end -- a mob died local coins = player.data.coins or 0 local lost = math.floor(coins * 0.1) player.data.coins = coins - lost player:sendMessage("You lost " .. lost .. " coins")}How it works:
giveCoinsreads the player’s saved coin count, adds the amount, and saves it back.- The
coinscommand calls it for the player who ran the command. - The
player.killevent gives a reward. - The
entity.deathevent takes a penalty when the one who died is a player.
Because player.data persists, your coin total survives disconnects and reloads.
7. Notes and limitations
Section titled “7. Notes and limitations”- Storage saves automatically. You do not need to call a save method.
- Deep nested tables work directly:
mc.data.guilds.mine.members.leader = player.name. - Event handlers run as coroutines, so they can wait on the
asyncmodule directly:
--# nova syntaxlocal async = require "async"
mc.on("player.join") \{ e -> async.sleep(100) -- 5 seconds e.player:sendMessage("Delayed hello!")}- Handlers run on the server thread. A handler that runs too long (5 seconds by default, e.g. an endless loop) is stopped with an error so it cannot freeze the server.
Next steps
Section titled “Next steps”Keep going with the topics that match what you want to build:
Build a whole game
- Tutorial: an arena minigame — commands, regions, rounds, kits, statistics and more, in one script that grows chapter by chapter
More about events and data
- Events reference — complete event list and handler signatures
- Storage reference — allowed types and save behavior
- Examples: persistence — advanced data patterns
- Examples: events — larger event-driven scripts
Built-in helpers
Game systems
- Region API — protect areas and react to movement
- Sidebar API — show persistent on-screen info
- Hologram API — floating text displays
- Inventory API — custom menus and storage UIs
- Mob AI — script custom mob behavior