Skip to content

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_REGISTER event, once, while the game starts. After that the list of items is frozen, and registering returns CL_E_WRONG_PHASE.
  • cl_item_desc is a descriptor struct, so it starts from CL_ITEM_DESC_INIT.
  • id must be in your own namespace: my_mod:.... Always write the namespace: an id without one means minecraft:, and like another mod’s namespace that gives CL_E_PERMISSION_DENIED.
  • creative_tab puts it into a creative inventory tab.
  • vanilla_fallback is the vanilla item that players without your mod see instead.
  • g_ruby receives 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.