Skip to content

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.