Items
Lesson 7.1
Time to add something new to the game: an item of your own. An item needs two parts: a registration in your code, so the game knows it exists, and assets, so it has a name and a picture.
Registering an item
static cl_holder g_ruby;
static void on_register(void* payload, void* user) {
(void)payload;
(void)user;
cl_item_desc d = CL_ITEM_DESC_INIT;
d.id = CL_STR("my_mod:ruby");
d.creative_tab = CL_STR("minecraft:ingredients");
d.vanilla_fallback = CL_STR("minecraft:emerald");
if (cl_item_register(&d, &g_ruby) != CL_OK) {
CL_LOGF(CL_LOG_ERROR, "my_mod:ruby failed to register");
}
}
static cl_result mod_main(void) {
return CL_ON(CL_EV_LIFECYCLE_REGISTER, on_register);
}
- Items are registered in the
CL_EV_LIFECYCLE_REGISTERevent, once, while the game starts. After that the list of items is frozen, and registering returnsCL_E_WRONG_PHASE. cl_item_descis a descriptor struct, so it starts fromCL_ITEM_DESC_INIT.idmust be in your own namespace:my_mod:.... Always write the namespace: an id without one meansminecraft:, and like another mod’s namespace that givesCL_E_PERMISSION_DENIED.creative_tabputs it into a creative inventory tab.vanilla_fallbackis the vanilla item that players without your mod see instead.g_rubyreceives a holder, the item’s registry entry. You rarely need it; most functions take the id as a string.
A new or changed registration needs a game restart, like every code change in a C mod.
Item options
| Field | Default | Means |
|---|---|---|
id |
The item’s id. Required. | |
max_stack_size |
64 |
How many fit in one slot, 1 to 99. Use 1 for tools. |
creative_tab |
none | "minecraft:ingredients", "minecraft:tools_and_utilities", "minecraft:combat", "minecraft:food_and_drinks", "minecraft:building_blocks", … |
vanilla_fallback |
"minecraft:paper" |
What players without the mod see. |
copy_from |
none | A vanilla item to base this one on, when there is no fallback. |
block |
none | The block this item places (blocks usually set this up for you, see Blocks). |
behavior |
none | Your own code for using the item (below). |
user |
NULL |
Your pointer, handed back to the behaviour. |
Assets: name and picture
The game finds an item’s look by its id. For my_mod:ruby it needs these files:
assets/my_mod/
items/ruby.json which model to use
models/item/ruby.json the model
textures/item/ruby.png the picture, 16 x 16 pixels
lang/en_us.json the name
items/ruby.json points at the model:
{
"model": {
"type": "minecraft:model",
"model": "my_mod:item/ruby"
}
}
models/item/ruby.json says “a flat item with this texture”, like most vanilla items:
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "my_mod:item/ruby"
}
}
The name goes into the language file. Item keys are item.<mod id>.<name>:
{
"item.my_mod.ruby": "Ruby"
}
These are the same files a resource pack uses, so everything you know about resource packs works here. A missing texture shows the purple and black checkerboard; a missing name shows the key. Assets can be changed while the game runs: save the file and press F3 + T.
Using it in code
Your item is a normal item now. Everything from Players works with it:
cl_easy_give(player, "my_mod:ruby", 5);
cl_item_stack held = cl_player_inventory_get(player, cl_player_selected_slot(player));
if (cl_easy_stack_is(held, "my_mod:ruby")) {
cl_easy_send_message(player, "Shiny!");
}
cl_stack_release(held);
Behaviour: when the item is used
behavior gives the item its own code. Right now an item can react to use: the player right-clicks with it in hand.
static cl_override heal_crystal_use(const cl_item_ctx* ctx, cl_interaction_result* out) {
cl_living_set_health(ctx->entity, 20.0f);
cl_easy_send_action_bar(ctx->entity, "You feel better.");
cl_easy_consume_held(ctx->entity, 1); // one crystal is used up
*out = CL_RESULT_SUCCESS;
return CL_OVERRIDE_HANDLED;
}
static const cl_item_behavior heal_crystal_behavior = {
sizeof(cl_item_behavior), heal_crystal_use
};
static void on_register(void* payload, void* user) {
(void)payload;
(void)user;
cl_item_desc d = CL_ITEM_DESC_INIT;
d.id = CL_STR("my_mod:heal_crystal");
d.max_stack_size = 16;
d.creative_tab = CL_STR("minecraft:tools_and_utilities");
d.vanilla_fallback = CL_STR("minecraft:amethyst_shard");
d.behavior = &heal_crystal_behavior;
cl_item_register(&d, NULL);
}
The game copies the behaviour struct when you register, so it could be a local variable too; a static const next to its function is just the tidy way to write it. Its first field is its size, like every descriptor; CL_ITEM_BEHAVIOR_INIT gives the same with no functions set.
The use function gets a cl_item_ctx with:
| Field | Means |
|---|---|
ctx->entity |
The player using the item. |
ctx->level |
The world it happens in. |
ctx->hand |
CL_HAND_MAIN or CL_HAND_OFF. |
ctx->stack |
A copy of the stack being used. Borrowed: don’t release it. |
ctx->user |
The user pointer from the item’s descriptor. |
cl_easy_consume_held removes one crystal from the stack in the player’s main hand, so each crystal heals once. (ctx->stack is only a copy, so changing its count would do nothing.) If your item can also be used from the offhand, check ctx->hand and shrink slot 40 with cl_player_inventory_shrink instead.
The return value decides what happens next:
CL_OVERRIDE_HANDLED: your code handled the click. Vanilla does nothing more.CL_OVERRIDE_CALL_ORIGINAL: your code didn’t handle it, so the game does what the fallback item would do.
Set *out to say how it went when you handle it: CL_RESULT_SUCCESS (the arm swings), CL_RESULT_CONSUME (it worked, no swing) or CL_RESULT_FAIL.
Try it
Register an item my_mod:compass_stone with a stack size of 1 in the tools tab. When it is used, it tells the player their coordinates in the action bar, like x 12, y 64, z -30. Add the language entry too.
Show a solution
#include <clight_easy.h>
#include <math.h>
static cl_override compass_use(const cl_item_ctx* ctx, cl_interaction_result* out) {
cl_vec3 p;
if (cl_entity_position(ctx->entity, &p) != CL_OK) return CL_OVERRIDE_CALL_ORIGINAL;
char line[64];
snprintf(line, sizeof line, "x %d, y %d, z %d", (int)floor(p.x), (int)floor(p.y), (int)floor(p.z));
cl_easy_send_action_bar(ctx->entity, line);
*out = CL_RESULT_SUCCESS;
return CL_OVERRIDE_HANDLED;
}
static const cl_item_behavior compass_behavior = { sizeof(cl_item_behavior), compass_use };
static void on_register(void* payload, void* user) {
(void)payload;
(void)user;
cl_item_desc d = CL_ITEM_DESC_INIT;
d.id = CL_STR("my_mod:compass_stone");
d.max_stack_size = 1;
d.creative_tab = CL_STR("minecraft:tools_and_utilities");
d.vanilla_fallback = CL_STR("minecraft:flint");
d.behavior = &compass_behavior;
if (cl_item_register(&d, NULL) != CL_OK) CL_LOGF(CL_LOG_ERROR, "compass_stone failed");
}
static cl_result mod_main(void) {
return CL_ON(CL_EV_LIFECYCLE_REGISTER, on_register);
}
CLIGHT_MOD_ENTRY(mod_main)
assets/my_mod/lang/en_us.json:
{
"item.my_mod.compass_stone": "Compass Stone"
}
Without a texture it shows the checkerboard, which is fine for testing. Add items/, models/item/ and textures/item/ files like the ruby’s when you have a picture.
Recap: register items in CL_EV_LIFECYCLE_REGISTER with a cl_item_desc from CL_ITEM_DESC_INIT, give them a model, texture and name under assets/<mod id>/, and add a static const cl_item_behavior for right-click code.