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 a cl_entity handle. You get one from:

  • an event: e->player on most player and block events, e->entity or e->attacker on entity events,
  • a command: cl_cmd_ctx_player(ctx),
  • the server’s list: cl_server_players(...),
  • on the client: cl_client_player(), the player at this computer.

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

What you can read

Function Gives
cl_player_name(p, buf, cap, &len) The name, like Alex. (Or cl_easy_player_name(p, buf, cap).)
cl_entity_position(p, &pos) The position as a cl_vec3 with decimals.
cl_entity_level(p) The world the player is in.
cl_entity_uuid(p) The UUID, the id that never changes, even when the name does.
cl_entity_type(p, buf, cap, &len) The entity type: minecraft:player.
cl_living_health(p) The health as a float: 20 is full, each heart is 2.
cl_player_selected_slot(p) The hotbar slot in hand, 0 to 8.
cl_entity_is_valid(p) false once the player has left.
static void describe(cl_entity p) {
    char name[64];
    cl_easy_player_name(p, name, sizeof name);
    cl_vec3 pos = {0, 0, 0};
    cl_entity_position(p, &pos);
    float hp = cl_living_health(p);
    CL_LOG("%s at %.0f %.0f %.0f with %.1f health", name, pos.x, pos.y, pos.z, hp);
}
[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, and two UUIDs are the same player when both numbers match:

static bool same_player(cl_uuid a, cl_uuid b) {
    return a.hi == b.hi && a.lo == b.lo;
}

What you can change

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

Function Does
cl_easy_give(p, "minecraft:apple", 3) Gives items; what doesn’t fit is dropped.
cl_player_give(p, stack) The same with a stack you made yourself.
cl_entity_teleport(p, pos) Moves the player to a position in their current world.
cl_living_set_health(p, 20.0f) Sets the health. 0 kills.
cl_easy_send_message(p, "...") A chat message.
cl_easy_send_action_bar(p, "...") A message above the hotbar.
// /spawn-like: back to 0, 100, 0, healed.
static void send_home(cl_entity p) {
    cl_vec3 home = {0.5, 100.0, 0.5};
    cl_entity_teleport(p, home);
    cl_living_set_health(p, 20.0f);
    cl_easy_send_action_bar(p, "Welcome back.");
}

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

The inventory

cl_player_inventory_get(p, slot) gives you a copy of the stack in one slot. You own it, so release it:

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?
static void log_held(cl_entity p) {
    int32_t slot = cl_player_selected_slot(p);
    cl_item_stack held = cl_player_inventory_get(p, slot);
    if (cl_stack_is_empty(held)) {
        CL_LOG("empty hand");
    } else {
        char id[64];
        size_t len = 0;
        cl_item_name(cl_stack_item(held), id, sizeof id, &len);
        CL_LOG("holding %d x %s", cl_stack_count(held), id);
    }
    cl_stack_release(held);
}

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

static int32_t count_item(cl_entity p, const char* item_id) {
    int32_t n = 0;
    for (int32_t slot = 0; slot <= 40; ++slot) {
        cl_item_stack s = cl_player_inventory_get(p, slot);
        if (cl_easy_stack_is(s, item_id)) n += cl_stack_count(s);
        cl_stack_release(s);
    }
    return n;
}

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

Counting and taking items

Function Does
cl_easy_count(p, "minecraft:diamond") How many of an item the player has, in all slots.
cl_easy_take(p, "minecraft:diamond", 3) Removes up to 3, like /clear, and returns how many it removed.
cl_easy_consume_held(p, 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.
cl_player_inventory_consume(p, slot, n) The same for any slot.
cl_player_inventory_shrink(p, slot, n) Removes up to n items from one slot, in every game mode.
cl_player_inventory_set(p, 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.
cl_player_game_mode(p) CL_GAMEMODE_SURVIVAL, _CREATIVE, _ADVENTURE or _SPECTATOR.

The easy helpers are short forms of cl_player_count_item and cl_player_take, which take a cl_str. Counting works on both sides (on the client, for your own player); taking and setting are server-side.

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

// Trade 3 diamonds for 1 emerald.
static bool trade(cl_entity p) {
    if (cl_easy_count(p, "minecraft:diamond") < 3) {
        cl_easy_send_action_bar(p, "You need 3 diamonds.");
        return false;
    }
    cl_easy_take(p, "minecraft:diamond", 3);
    cl_easy_give(p, "minecraft:emerald", 1);
    return true;
}

Counting first matters: cl_easy_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

static void heal_everyone(void) {
    cl_entity players[128];
    size_t count = 0;
    if (cl_server_players(players, 128, &count) != CL_OK) return;
    if (count > 128) count = 128;
    for (size_t i = 0; i < count; ++i) {
        cl_living_set_health(players[i], 20.0f);
    }
}

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_easy.h>
#include <math.h>

static void on_respawn(void* payload, void* user) {
    (void)user;
    cl_entity p = CL_EVENT(cl_ev_player, payload)->player;

    cl_vec3 pos;
    if (cl_entity_position(p, &pos) == CL_OK) {
        cl_easy_send_messagef(p, "You are at %d %d %d.", (int)floor(pos.x), (int)floor(pos.y), (int)floor(pos.z));
    }

    cl_item_stack first = cl_player_inventory_get(p, 0);
    if (cl_stack_is_empty(first)) cl_easy_give(p, "minecraft:bread", 1);
    cl_stack_release(first);
}

static cl_result mod_main(void) {
    return CL_ON(CL_EV_PLAYER_RESPAWN, on_respawn);
}

CLIGHT_MOD_ENTRY(mod_main)

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 (int) cast would round -0.3 to 0. floor is from <math.h> (Math functions).

Recap: players are entity handles from events, commands or cl_server_players; read them with cl_player_name, cl_entity_position and cl_living_health, change them with cl_easy_give, cl_easy_take, cl_entity_teleport and cl_living_set_health, and release stacks from cl_player_inventory_get.