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->playeron most player and block events,e->entityore->attackeron 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.