Skip to content

Client and server

Lesson 14.1

Every Minecraft game has two halves, and your mod lives in both. Knowing which half runs which code saves a lot of confusion.

Two halves

  • The server owns the world. It moves mobs, grows crops, decides what happens when a block breaks, and saves everything.
  • The client is the game window: it draws the world, plays sounds, reads your keyboard and mouse, and shows menus.

On a multiplayer server, the server is another computer and every player has a client. In singleplayer, both halves run inside your game at once: the client and a small built-in server. Your mod is loaded once and gets the events of both.

Which events run where

The event list marks each event:

  • Server events, like player.join and block.break, run where the server is.
  • Client events, like client.key and tick.client_post, run in the game window.
You play… Server events Client events
Singleplayer yes yes
On a server that has your mod on the server in your game
On a server without your mod no yes

That last row is why some mods are client-only: a HUD, a keybind or a sound tweak works on any server, even a vanilla one, because it only uses the client half.

Which functions run where

Most functions belong to one half:

Server only Client only
player:give, player:send_message, player:teleport cl.hud.register_layer
world:set_block cl.screen.open
cl.cmd.register, cl.cmd.run cl.keybind.register
cl.menu.open cl.sound.play_ui, cl.music.play
cl.sched.run_server, repeat_server cl.sched.run_client
cl.entity.server_players() cl.entity.client_player(), cl.level.client_level()

Calling one on the wrong side raises a wrong_side error. Reading, like player.pos or world:get_block, works on both.

The client can’t change the world by itself. To ask the server to do something, like “give me a ruby, I pressed R”, the client sends a message to the server. Talking between client and server shows how.

Asking where you are

if cl.has("side.client") then
  -- this game has a window: HUDs, keys and screens are possible
end

print(cl.mod.environment())     --> client  (or dedicated_server)
print(cl.mod.server_running())  --> true    (a world runs in this game)

cl.has("gui") and cl.has("sound") are false on a dedicated server, which has no window.

The usual pattern

Put client code into the lifecycle.client_setup event. It only runs in a game with a window, so a dedicated server skips it, and your mod works in both places:

-- runs everywhere: registrations and server logic
cl.on("lifecycle.register", function()
  cl.items.register{ id = "ruby" }
end)

cl.on("player.join", function(event)
  event.player:give("my_mod:ruby")
end)

-- runs only where there is a window
cl.on("lifecycle.client_setup", function()
  cl.hud.register_layer{
    id = "hello",
    render = function(g)
      g:text("Ruby mod is here", 4, 4, 0xE0115F, true)
    end,
  }
end)

The manifest

"side" in clight.mod.json decides where your mod is loaded at all:

  • "both" for mods with items, blocks, commands or other server logic. Most mods.
  • "client" for mods that only change the game window: HUDs, keybinds, sounds. They are skipped on dedicated servers.
  • "server" for server-only logic, like rules or commands, that players don’t need to install.

Players without your mod

A server with your mod may have players who don’t have it. They still see something sensible:

  • Your items and blocks appear as their vanilla_fallback.
  • Commands work, because the server runs them.
  • Chest menus from cl.menu.open work, because they are vanilla menus.
  • HUDs, screens and keybinds don’t exist for them, since that code runs in the client.

Try it

Your mod should log “server ready” when a world opens and “window ready” once the game’s window is set up. Which events do you use, and in which of the three situations above does each line appear?

Show a solution
cl.on("lifecycle.server_started", function()
  cl.log("server ready")
end)

cl.on("lifecycle.client_setup", function()
  cl.log("window ready")
end)

In singleplayer you see both lines (the window one at startup, the server one when you open a world). On a server with the mod, the server’s log has “server ready” and your game’s log has “window ready”. On a server without the mod, only your game’s “window ready” appears.

Recap: the server owns the world and the client draws it; server events and functions only run where the world is simulated, and client code goes into lifecycle.client_setup.