Skip to content

Players

Lesson 11.1

Players are at the heart of most mods. You get a player from an event, a command or the list of online players, and then you can read things about them and do things to them.

Where players come from

  • From events: event.player on player.join, block.break and many more.
  • From commands: ctx:player() (see Commands).
  • From the server’s list of everybody online:
for _, player in ipairs(cl.entity.server_players()) do
  player:send_message("Server restart in 5 minutes.")
end
  • On the client, the player sitting at this computer: cl.entity.client_player(). It is nil while no world is open.

Properties

A player has a few properties you read like table fields:

Property Example Means
player.name "Alex" The player’s name.
player.uuid "069a79f4-44e9-4726-a5be-fca90e38aaf5" The account’s unique id. It never changes, even when the name does.
player.pos (12.5, 64.0, -30.2) Where they stand, with x, y and z.
player.health 20.0 Health points. 20 is full, every heart is 2.
player.world The world they are in. player.level is the same.
player.gamemode "survival" "survival", "creative", "adventure" or "spectator".
cl.on("player.join", function(event)
  local p = event.player
  cl.log("%s joined at y=%d with %.1f health", p.name, math.floor(p.pos.y), p.health)
end)

Use uuid, not name, as the key when you remember things about a player. Names can change; UUIDs can’t.

Players are entities, the game’s word for anything that moves around: players, mobs, arrows, boats. Most of what follows also works on other entities, but name is only for players. On other entities it is nil.

Giving items

player:give("minecraft:apple")       -- one apple
player:give("minecraft:arrow", 16)   -- sixteen arrows
player:give("my_mod:ruby", 3)        -- your own items work the same way

The id is namespace:name. What doesn’t fit in the inventory drops at the player’s feet, like vanilla’s /give. An id that doesn’t exist raises an error: give: unknown item 'minecraft:aple'.

Health and position

player:set_health(20)                    -- heal fully
player:teleport(cl.vec(0.5, 100, 0.5))   -- move to x=0.5, y=100, z=0.5

cl.vec(x, y, z) makes a position with decimals. To move somebody relative to where they are, add to their position:

local up = player.pos + cl.vec(0, 10, 0)
player:teleport(up)

Positions can be added, subtracted and multiplied by a number, like you did with metatables in part 1.

The inventory

player:inventory_get(slot) returns the item stack in one slot. The slot numbers are vanilla’s:

Slots Where
0 to 8 The hotbar, left to right
9 to 35 The main inventory
36 to 39 Armour: feet, legs, chest, head
40 The offhand

player:selected_slot() is the hotbar slot the player holds (0 to 8). So the item in the main hand is:

local held <close> = player:inventory_get(player:selected_slot())
if held and held:is("minecraft:diamond_sword") then
  player:send_message("Nice sword.")
end

The <close> (from Error handling) gives the stack back to the game as soon as the variable goes out of scope. Item stacks are game objects, and <close> keeps them from piling up until the next garbage collection. It is not required, but a good habit in loops.

What an item stack can tell you:

Method Gives
stack:is("minecraft:apple") true if it is that item
stack:count() How many
stack:is_empty() true for an empty slot
stack:set_count(n) Changes the count of this copy

inventory_get gives you a copy of the stack: changing it doesn’t change the player’s inventory. To read and change the real slot, see Taking items below.

You can make stacks yourself with cl.stack(id, count), and give them:

local stack <close> = cl.stack("minecraft:cookie", 8)
player:give(stack)

Taking items

Giving is half of it. These take items away again:

local taken = player:take("minecraft:diamond", 3)   -- removes up to 3, returns how many it removed
print(player:count_item("minecraft:diamond"))       -- how many diamonds are left, in all slots
if player:has_item("minecraft:emerald", 5) then     -- true with 5 or more
  player:take("minecraft:emerald", 5)
end

take looks through the slots in order (the hotbar first) and stops when it has enough. If the player has fewer, it takes what is there, so check the number it returns, or use has_item first. player:take("minecraft:diamond") takes one.

For one slot, player:slot(n) and player:held_item() give a live slot instead of a copy. Reading it always shows what is in the slot right now, and its methods change the real inventory:

local held = player:held_item()        -- the main hand; held_item("off") is the offhand
print(held.id, held.count)             -- "minecraft:bread" 7, or nil 0 for an empty hand
held:shrink(1)                         -- uses one up (in creative mode nothing is used up)
player:slot(8):set("minecraft:torch", 16)
player:slot(40):clear()                -- empties the offhand
Slot Does
slot.id The item id, or nil when the slot is empty
slot.count How many, 0 when empty
slot.empty true for an empty slot
slot:shrink(n) Uses up n (default 1) like the game does, returns how many it used. In creative mode nothing is removed, as when a creative player eats or places something.
slot:remove(n) Removes n (default 1) in every game mode, returns how many it removed
slot:set(id, count) Puts a new stack in the slot. With the item that is already there, only the count changes and the stack keeps its name, enchantments and damage.
slot:clear() Empties the slot
slot:stack() A copy of the stack, like inventory_get

These change the inventory, so they only work on the server side (server events, commands, server timers). The player sees the change at once.

Use shrink when the player uses something (a wand charge, a ticket, food), so creative players keep their items like in Minecraft. Use remove or player:take when the item really has to go, like a payment:

if player.gamemode ~= "creative" and player:count_item("my_mod:ruby") < 3 then
  player:send_message("You need 3 rubies.")
  return
end
player:held_item():shrink(1)   -- a creative player keeps the wand

Players who left

A player you saved in a variable may leave the server. player:is_valid() tells you whether the player is still there. Check it before you use a player you kept from earlier:

if saved_player and saved_player:is_valid() then
  saved_player:send_message("Still here?")
end

Try it

Make a “starter kit”: when a player joins, count the items in their whole inventory (slots 0 to 40). If the inventory is empty, give them a stone sword, 8 bread and 16 torches, and tell them in chat.

Show a solution
local function is_empty_inventory(player)
  for slot = 0, 40 do
    local stack <close> = player:inventory_get(slot)
    if stack and not stack:is_empty() then
      return false
    end
  end
  return true
end

cl.on("player.join", function(event)
  local p = event.player
  if is_empty_inventory(p) then
    p:give("minecraft:stone_sword")
    p:give("minecraft:bread", 8)
    p:give("minecraft:torch", 16)
    p:send_message("Here is a starter kit. Good luck!")
  end
end)

The return false inside the loop leaves the function early, and <close> still gives the stack back first.

Recap: read player.name, uuid, pos and health, use give, teleport and set_health to change things, read slots with inventory_get, and take items with take, held_item() and slot(n).