A server plugin (vanilla clients welcome)
Status: Implemented for everything in this guide. Permission nodes, clickable chat lists with hover-to-teleport and region-aware scheduling are Planned; this guide stores homes in a server config file instead.
Goal: a Homes plugin: /sethome, /home with a 3-second warm-up that cancels when the player moves, /delhome, and an operator-only /homes <player>. Vanilla clients can use all of it.
Language: Lua. APIs: Server plugins, Commands, Configuration.
1. Manifest
{
"schema": 1, "id": "homes", "version": "1.0.0", "name": "Homes", "license": "MIT",
"api": 1, "side": "server", "sync": "server_only",
"runtime": { "type": "lua", "entry": "scripts/main.lua" }
}
Put the .clmod into the server’s mods/ folder.
2. Storage: one config entry per player
local store = cl.config.open("server") -- config/homes-server.json
local function key(p) return "home." .. p.uuid end
local function save_home(p)
local pos = p.pos
local value = ("%s %.2f %.2f %.2f"):format(p.level:dimension(), pos.x, pos.y, pos.z)
store:define_str(key(p), "")
store:set_str(key(p), value)
store:save()
end
local function load_home(uuid)
store:define_str("home." .. uuid, "")
local v = store:get_str("home." .. uuid)
if v == "" then return nil end
local dim, x, y, z = v:match("^(%S+) (%S+) (%S+) (%S+)$")
return { dim = dim, x = tonumber(x), y = tonumber(y), z = tonumber(z) }
end
The file survives restarts, and server owners can read or edit it.
3. Teleport with a warm-up (coroutines)
local function teleport_home(p, home)
cl.spawn(function()
local start = p.pos
p:send_action_bar("Teleporting in 3 seconds... don't move")
for _ = 1, 3 do
cl.sleep_ticks(20)
if not p:is_valid() then return end
local now = p.pos
if math.abs(now.x - start.x) > 0.5 or math.abs(now.z - start.z) > 0.5 then
p:send_action_bar("Teleport cancelled.")
return
end
end
-- works across dimensions: run vanilla's /tp as the console
cl.cmd.run(("execute in %s run tp %s %.2f %.2f %.2f"):format(home.dim, p.name, home.x, home.y, home.z))
p:send_action_bar("Welcome home.")
end)
end
4. Commands
cl.on("server.command_register", function()
local cmd = cl.cmd
cl.cmd.register(cmd.literal("sethome"):executes(function(ctx)
local p = ctx:player()
if not p then ctx:failure("Players only.") return 0 end
save_home(p)
ctx:success("Home set.")
return 1
end))
cl.cmd.register(cmd.literal("home"):executes(function(ctx)
local p = ctx:player()
local home = p and load_home(p.uuid)
if not home then ctx:failure("You have no home. Use /sethome first.") return 0 end
teleport_home(p, home)
return 1
end))
cl.cmd.register(cmd.literal("delhome"):executes(function(ctx)
local p = ctx:player()
if not p then return 0 end
store:define_str(key(p), "")
store:set_str(key(p), "")
store:save()
ctx:success("Home deleted.")
return 1
end))
cl.cmd.register(cmd.literal("homes"):requires_permission(2)
:then_(cmd.argument("player", "player"):executes(function(ctx)
local who = ctx:arg("player")
local home = load_home(who.uuid)
ctx:success(home and ("%s: %s %d %d %d"):format(who.name, home.dim, home.x, home.y, home.z) or (who.name .. " has no home."))
return 1
end)))
end)
5. A chest-GUI picker (works on vanilla clients)
A menu with one button per option, like plugin “GUIs”: see A shop block (block + menu). For homes, show a bed in slot 0 that runs teleport_home in its on_click.
Planned
Data attachments on players (no config file needed), permission nodes (homes.limit.5), clickable chat lines that run /home name (text click events work today; a list with several homes needs only more config keys), and region-aware scheduling.