Getting Started with Atomicoffe
Atomicoffe is a Fabric mod for Minecraft 1.21.11 that lets you write server and client-side logic in Lua without compiling Java. Drop .lua files into a folder, reload, and your changes are live.
Requirements
| Requirement | Version |
|---|---|
| Minecraft | 1.21.11 |
| Fabric Loader | 0.18.4+ |
| Fabric API | 0.141.3+1.21.11+ |
| Java | 21+ |
Installation
- Install Fabric Loader for Minecraft 1.21.11.
- Drop Fabric API and atomicoffe into your
mods/folder. - Launch the game once — Atomicoffe will create its script directories automatically.
Script Directories
After the first launch, open your game directory (.minecraft/ or your server root). You will find:
<gameDir>/
└── atomicoffe/
├── startup_scripts/ ← runs at mod init (registration)
├── server_scripts/ ← runs when a server/world starts
├── client_scripts/ ← runs on the client only
├── playerdata/ ← auto-managed per-player JSON storage
├── config.json ← mod config (managed by the config API)
└── data/ ← generated tag files (managed by the Tags API)
Every .lua file inside a phase directory is loaded automatically in alphabetical order.
Your First Script
Create the file atomicoffe/server_scripts/hello.lua:
-- Greet players when they join
onEvent("player.join", function(e)
e.player.tell("§aWelcome to the server, §e" .. e.player.name .. "§a!")
server.broadcast("§b" .. e.player.name .. " §fhas joined the game.")
end)
-- Register a simple command
command.register("ping", function(ctx)
ctx.reply("Pong! The server has " .. server.getPlayerCount() .. " player(s) online.")
end)
Start your server (or open a singleplayer world). When a player joins, they'll be greeted, and /ping will be available to everyone.
Reloading Scripts
You never need to restart the server to apply changes.
| Action | Effect |
|---|---|
/atomicoffe reload | Re-runs all server_scripts/ |
| F3+T | Re-runs all client_scripts/ (resource pack reload) |
/reload | Full data-pack reload (re-applies recipes, tags, loot) |
Event System
Everything in Atomicoffe is driven by events. Use onEvent(id, fn) to subscribe:
onEvent("player.death", function(e)
-- e.player — the player who died
-- e.damage — damage amount
-- e.source — damage source ID string
server.broadcast(e.player.name .. " died! (" .. e.source .. ")")
end)
Full Event Reference
Player Events
| Event ID | Phase | Context Fields |
|---|---|---|
player.join | Server | player |
player.logged_in | Server | player (alias for player.join) |
player.leave | Server | player |
player.logged_out | Server | player (alias for player.leave) |
player.death | Server | player, damage, source |
player.respawn | Server | player, alive |
player.chat | Server | player, message |
player.tick | Server | player (fires every tick — be careful!) |
player.break_block | Server | player, block, pos {x,y,z} |
Block Events
| Event ID | Phase | Context Fields | Cancellable |
|---|---|---|---|
block.right_clicked | Server | player, block, pos, hand, face, sneaking, item, itemStack, hitPos | Yes |
block.sneak_right_clicked | Server | Same as above | Yes |
block.left_clicked | Server | player, block, pos, hand, face, sneaking, item, itemStack | Yes |
block.sneak_left_clicked | Server | Same as above | Yes |
Item Events
| Event ID | Phase | Context Fields | Cancellable |
|---|---|---|---|
item.used | Server | player, hand, sneaking, item, itemStack | Yes |
item.sneak_used | Server | Same as above | Yes |
Entity Events
| Event ID | Phase | Context Fields | Cancellable |
|---|---|---|---|
entity.spawned | Server | entity | |
entity.killed | Server | killer, killed, player (if player killed) | |
entity.hurt | Server | entity, damage, source, attacker, directSource, projectile, isExplosion, isFire, isProjectile | Yes |
entity.death | Server | Same as entity.hurt | Yes |
Server / World Events
| Event ID | Phase | Context Fields |
|---|---|---|
server.started | Server | — |
server.stopping | Server | — |
server.tick | Server | — (fires every tick) |
level.loaded | Server | dimension |
level.unloaded | Server | dimension |
level.tick | Server | dimension (fires every tick per dimension) |
Data Events (Startup Phase)
| Event ID | Phase | Context |
|---|---|---|
item.registry | Startup | Item registration context |
block.registry | Startup | Block registration context |
fluid.registry | Startup | Fluid registration context |
mob_effect.registry | Startup | MobEffectRegistry context |
creative_tab.registry | Startup | Creative tab context |
recipes | Server | Recipe context |
loot.modify | Server | tableId, addDrop(itemId, count) |
Return false from a handler registered for a cancellable event to prevent the default action.
onEvent("entity.hurt", function(e)
-- Make all players immune to fire
if e.isFire and e.entity.type == "minecraft:player" then
return false -- cancel the damage
end
end)
Script Examples
Give a diamond on first join
-- server_scripts/welcome.lua
onEvent("player.join", function(e)
local p = e.player
if not data.has(p.uuid, "welcomed") then
data.set(p.uuid, "welcomed", true)
p.give("minecraft:diamond", 1)
p.sendTitle("§6Welcome!", "§eHere's a diamond to start.", 10, 70, 20)
end
end)
Kill counter scoreboard
-- server_scripts/kills.lua
scoreboard.createObjective("kills", "Player Kills")
onEvent("entity.killed", function(e)
if e.player then
local newKills = scoreboard.add(e.player.name, "kills", 1)
e.player.sendActionBar("§cKills: §f" .. newKills)
end
end)
Custom recipe
-- server_scripts/recipes.lua
onEvent("recipes", function(e)
-- 4 diamonds from a diamond block
e.addShaped("mymod:diamonds_from_block", "minecraft:diamond 4",
{"X"},
{ X = "minecraft:diamond_block" }
)
-- Remove the default creeper-face banner pattern recipe
e.remove({ id = "minecraft:creeper_banner_pattern" })
end)
Repeating announcement
-- server_scripts/announcements.lua
local messages = {
"§bRemember to vote!",
"§aDiscord: discord.gg/example",
"§eRules: /rules",
}
local index = 1
-- Announce every 5 minutes (6000 ticks)
schedule.every(6000, function()
server.broadcast(messages[index])
index = (index % #messages) + 1
end)
Client HUD
-- client_scripts/hud.lua
client.onHudRender(function(e)
local p = client.getPlayer()
if not p then return end
-- Dark bar at top-left
e.drawRect(2, 2, 120, 12, 0x88000000)
e.drawText("Pos: " .. math.floor(p.x) .. " " .. math.floor(p.y) .. " " .. math.floor(p.z), 4, 4, 0xFFFF55)
end)
API Reference
Explore the full API documentation:
- Globals —
onEvent,print,logger - server — players, broadcast, TPS, weather
- world — blocks, entities, particles, time
- Player Context — health, inventory, effects, messages
- command — custom slash commands
- schedule — tick-based timers
- network — custom client↔server packets
- scoreboard — objectives and scores
- data — persistent per-player storage
- config — shared mod config
- Recipes — add/remove crafting recipes
- loot — modify loot tables
- Tags — modify data-pack tags
- MobEffectRegistry — register status effects
- client — HUD, keybinds, client networking