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