Skip to content

Players

Lesson 6.1

Players are at the centre of most mods. This lesson shows what you can find out about a player, how to change things about them, and how to look into their inventory.

Where players come from

A player is an entity handle, which you wrap in a clight::Entity. You get one from:

  • an event: e.player on most player and block events, e.entity or e.attacker on entity events,
  • a command: ctx.player(),
  • the server’s list: clight::entity::server_players(),
  • on the client: clight::entity::client_player(), the player at this computer.

Players are entities, so every entity method works on them too. entity.is_player() tells you whether an entity is a player.

What you can read

Method Gives
p.name() The name, like Alex ("" for other entities).
p.pos() / p.position() The position as a cl_vec3 with decimals (position() as an Expected).
p.level() The world the player is in, as a clight::Level.
p.uuid() The UUID, the id that never changes, even when the name does.
p.type() The entity type, minecraft:player, as an Expected<std::string>.
p.health() The health as a float: 20 is full, each heart is 2.
p.selected_slot() The hotbar slot in hand, 0 to 8.
p.is_valid() false once the player has left.
void describe(clight::Entity p) {
    cl_vec3 pos = p.pos();
    clight::log::info("{} at {:.0f} {:.0f} {:.0f} with {:.1f} health", p.name(), pos.x, pos.y, pos.z, p.health());
}
[my_mod] Alex at 12 64 -30 with 20.0 health

UUIDs

Names can change; a UUID can’t. To remember something about a player (a home, a score, a cooldown), use the UUID as the key. cl_uuid is two 64-bit numbers, hi and lo, and two UUIDs are the same player when both match. To use one as a key in a std::map, turn it into a pair:

using PlayerKey = std::pair<uint64_t, uint64_t>;

PlayerKey key_of(clight::Entity p) {
    cl_uuid u = p.uuid();
    return {u.hi, u.lo};
}

std::map<PlayerKey, int> g_scores;   // g_scores[key_of(player)] += 1;

std::pair can already be compared, so std::map works with it right away. W3Schools explains maps in Maps.

What you can change

These are server-side. Use them in server events, commands and server timers.

Method Does
p.give("minecraft:apple", 3) Gives items; what doesn’t fit is dropped.
p.give(stack) The same with a clight::ItemStack you made yourself.
p.teleport(pos) Moves the player to a position in their current world.
p.set_health(20.0f) Sets the health. 0 kills.
p.send_message("...") A chat message.
p.send_action_bar("...") A message above the hotbar.
// /spawn-like: back to 0, 100, 0, healed.
void send_home(clight::Entity p) {
    p.teleport({0.5, 100.0, 0.5});
    p.set_health(20.0f);
    p.send_action_bar("Welcome back.");
}

0.5 puts the player in the middle of the block instead of on its corner.

The inventory

p.inventory_get(slot) gives you a copy of the stack in one slot, as a clight::ItemStack. It releases itself when it goes out of scope.

Slots Are
0 to 8 The hotbar
9 to 35 The main inventory
36 to 39 Armour: feet, legs, chest, head
40 The offhand
// What is in the player's hand?
void log_held(clight::Entity p) {
    clight::ItemStack held = p.inventory_get(p.selected_slot());
    if (held.is_empty()) {
        clight::log::info("empty hand");
    } else {
        std::string id = clight::registry::item_name(held.item()).value_or("?");
        clight::log::info("holding {} x {}", held.count(), id);
    }
}

held.is("minecraft:apple") checks the item, held.count() gives the count and held.item() the item’s number, which clight::registry::item_name turns back into an id.

Looking at every slot is a loop over the 41 slots. This one counts an item; for that alone, count_item below is shorter:

int32_t count_item(clight::Entity p, std::string_view item_id) {
    int32_t n = 0;
    for (int32_t slot = 0; slot <= 40; ++slot) {
        if (clight::ItemStack s = p.inventory_get(slot); s.is(item_id)) n += s.count();
    }
    return n;
}

The stack you get from inventory_get is a copy: changing its count changes only the copy. To change the real inventory, use the methods below.

Counting and taking items

Method Does
p.count_item("minecraft:diamond") How many of an item the player has, in all slots.
p.take("minecraft:diamond", 3) Removes up to 3, like /clear, and returns how many it removed.
p.consume_held(1) Uses up items from the stack in the main hand like the game does: in creative mode nothing is removed. Returns how many it used.
p.inventory_consume(slot, n) The same for any slot.
p.inventory_shrink(slot, n) Removes up to n items from one slot, in every game mode.
p.inventory_set(slot, stack) Puts a copy of stack into one slot (an empty stack clears it). With the item already in the slot only the count changes; its name, enchantments and damage stay.
p.game_mode() CL_GAMEMODE_SURVIVAL, _CREATIVE, _ADVENTURE or _SPECTATOR.

Counting works on both sides (on the client, for your own player); taking and setting are server-side.

take never takes more than the player has, so “pay 3 diamonds” is: check first, then take.

// Trade 3 diamonds for 1 emerald.
bool trade(clight::Entity p) {
    if (p.count_item("minecraft:diamond") < 3) {
        p.send_action_bar("You need 3 diamonds.");
        return false;
    }
    p.take("minecraft:diamond", 3);
    p.give("minecraft:emerald");
    return true;
}

Counting first matters: take with only 2 diamonds would take those 2 and return 2, and the player would have paid without getting anything.

Everyone on the server

void heal_everyone() {
    auto players = clight::entity::server_players();
    if (!players) return;
    for (clight::Entity p : *players) {
        p.set_health(20.0f);
    }
}

server_players() gives a std::vector with everybody online, however many there are.

Try it

When a player respawns, tell them where they are now, rounded to whole blocks, and give them one bread if their hotbar slot 0 is empty.

Show a solution
#include <clight.hpp>
#include <cmath>

class RespawnMod final : public clight::Mod {
public:
    void on_construct() override {
        clight::on<clight::ev::PlayerRespawn>([](cl_ev_player& e) {
            clight::Entity p = e.player;

            if (auto pos = p.position()) {
                p.send_message(std::format("You are at {} {} {}.", std::floor(pos->x), std::floor(pos->y), std::floor(pos->z)));
            }

            if (p.inventory_get(0).is_empty()) p.give("minecraft:bread");
        }).detach();
    }
};

CLIGHT_MOD(RespawnMod)

std::floor rounds down, which is how block coordinates work: x 12.7 is inside block 12, and x -0.3 is inside block -1. A plain cast to int would round -0.3 to 0. std::format prints 12 for the floored 12.0, without decimals. std::floor is from <cmath> (Math). The stack from inventory_get(0) is a temporary here: it is checked and released in the same line.

Recap: players are clight::Entity handles from events, commands or clight::entity::server_players(); read them with name(), pos() and health(), change them with give, take, teleport and set_health, and inventory_get gives a clight::ItemStack copy that releases itself.