Chapter 5, Items
A one-line item, then the Jump Stick, where the small builder ends and plain old subclassing takes over.
Items are the deliberately-thin sibling of blocks. RetroAPI's item builder is tiny on purpose: there are very few things every item needs, and everything interesting, right-click behavior, durability, sub-items, comes from subclassing vanilla's Item, exactly as it always did. So this chapter is two halves: the whole builder in a breath, then a real custom item.
The simplest item
Here is SUSPICIOUS_SUBSTANCE from init(), three lines, the same shape as a block:
// The simplest possible item. Texture: assets/example_mod/textures/item/suspicious_substance.png
// Display name: "item.@.suspicious_substance.name" in the lang file.
SUSPICIOUS_SUBSTANCE = RetroItemAccess.create()
.maxStackSize(64)
.texture(id("suspicious_substance"))
.register(id("suspicious_substance"));create a plain item, set its stack size, point it at a texture, register it. Done. It loads textures/item/suspicious_substance.png and reads its name from item.@.suspicious_substance.name in the lang file, both covered in Chapter 3.
The whole item builder
This is the entire item builder, and we mean entire. There is no .durability(), no .food(), no behavior hooks. If you want any of that, you subclass Item (next section). Being honest about this saves you hunting for methods that don't exist.
| Method | What it does |
|---|---|
RetroItemAccess.create() | start a builder for a plain Item |
RetroItemAccess.of(Item) | wrap a Item subclass instance you constructed yourself |
RetroItemAccess.AUTO_ID | pass this into a subclass constructor instead of a real id; RetroAPI allocates a free slot for you, atomically and safely (preferred) |
RetroItemAccess.allocateId() | get a fresh numeric id to pass into your subclass constructor (the older, manual form of AUTO_ID) |
maxStackSize(int) | how many stack (64 for most, 1 for tools) |
texture(NamespacedIdentifier) | load textures/item/<name>.png as the icon |
tool(RetroTool) | declare the tool KIND for the mineable/<tool> tag system (vanilla tool classes infer this) |
tier(RetroToolTier) | declare the tool TIER for needs_<tier>_tool gating (vanilla tools infer from their material) |
handheld() | hold the item like a tool, the diagonal through-the-fist pose (vanilla tools and sticks have it; flat sprite pose otherwise) |
register(NamespacedIdentifier) | finalize and register; returns the Item |
The two tool rows feed the tag system from Chapter 4: a plain Item chained through .tool(RetroTool.PICKAXE).tier(RetroToolTier.IRON) harvests everything an iron pickaxe would, no ToolItem subclass required. The showcase's Ruby Pick is exactly that, see the tool-tiers section of the blocks chapter.
Same id story as blocks: numeric item ids are allocated above vanilla's range, remapped per-world, and kept safe in vanilla saves, you only ever touch NamespacedIdentifiers. (The full id mechanics are in Chapter 4.)
Subclassing an item? Pass RetroItemAccess.AUTO_ID as the constructor's id. Vanilla's Item(int) constructor writes the new item into the global Item.ITEMS array, so it needs a free slot to construct at all, the classic chicken-and-egg of legacy registration. AUTO_ID is a sentinel: RetroAPI intercepts the constructor and fills in a free, reserved slot for you, in one atomic step, so two items can never collide on a slot. The older allocateId() still works (it now reserves too), but AUTO_ID is the simpler and safer default, just write super(RetroItemAccess.AUTO_ID) and forget about numbers.
The Jump Stick, a custom item class
For anything with behavior, you write an Item subclass and hand it to RetroItemAccess.of(...). The Jump Stick is the smallest interesting example: right-click it and you hop. Here's the whole class:
package com.example.example_mod;
import com.example.example_mod.mixin.examples.Example06JumpInvoker;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
/**
* A custom Item CLASS: the Jump Stick, right-click to hop.
*
* Two things this demonstrates:
* - Items with behavior: override use(stack, world, player) for right-click
* (useOnBlock / useOnEntity exist for targeted clicks). Return the stack
* (possibly shrunk/damaged) when you're done.
* - Mixin example 6 in action: LivingEntity.jump() is protected, so normal
* code can't call it, the @Invoker interface in
* mixin/examples/Example06JumpInvoker makes it callable from outside.
*
* Like custom blocks, custom items take a placeholder id in their constructor.
* Pass RetroItemAccess.AUTO_ID and RetroAPI fills in a free slot for you; the
* instance is wrapped with RetroItemAccess.of(...) in ExampleMod.init().
*
* No texture file: overriding getTextureId lets it reuse vanilla's stick sprite.
*/
public class JumpStickItem extends Item {
public JumpStickItem(int id) {
super(id);
}
@Override
public int getTextureId(int damage) {
return Item.STICK.getTextureId(0); // borrow the vanilla stick's sprite
}
@Override
public ItemStack use(ItemStack stack, World world, PlayerEntity player) {
// The whole trick: call the protected jump() through the invoker bridge.
((Example06JumpInvoker) player).example_mod$jump();
return stack;
}
}And its registration in init(), of(...) around a new JumpStickItem(RetroItemAccess.AUTO_ID), just like a custom block:
JUMP_STICK = RetroItemAccess.of(new JumpStickItem(RetroItemAccess.AUTO_ID))
.maxStackSize(1)
// Hold it like a tool: the diagonal through-the-fist pose vanilla sticks
// get. Items with an item/handheld model JSON (the Ruby Pick below) get
// this automatically; everything else opts in with one chained call.
.handheld()
.register(id("jump_stick"));Three details earn a closer look.
1. Right-click behavior: use(...)
use(stack, world, player) fires when the player right-clicks holding the item in open air. You return the stack you want them left holding, unchanged here, but you might shrink or damage it. Two siblings handle targeted clicks, and both are real methods you can override on Item:
useOnBlock(...), right-clicking a block (placing, activating, tilling).useOnEntity(...), right-clicking another entity (shears, name tags, saddles).
2. Borrowing a sprite: getTextureId(damage)
Overriding getTextureId(damage) returns the item's sprite index directly, and here it returns Item.STICK.getTextureId(0), vanilla's stick. So the Jump Stick ships no PNG of its own and just wears the stick icon. (This is the item-side trick from Chapter 3; the damage argument would let you vary the icon by metadata if you wanted.)
3. The invoker bridge
The actual hop is ((Example06JumpInvoker) player).example_mod$jump(). The catch: LivingEntity.jump() is protected, so ordinary code can't call it. Example06JumpInvoker is an @Invoker mixin, an interface mixin makes LivingEntity implement it, turning the protected method into a callable public bridge. Cast the player to the interface, call the bridge, done. How that works mechanically, and why a reusable bridge sometimes belongs in the shared library instead, is Chapter 13, example 6.
ItemStacks
Once items exist, you move them around as ItemStacks. Beta exposes most of the stack's state as public fields rather than getters, which feels blunt but is just how the era worked:
| Expression | Meaning |
|---|---|
new ItemStack(item, count) | a stack of an item |
new ItemStack(block, count) | a stack of a block's item form |
stack.itemId | the numeric item id (public field) |
stack.count | how many are in the stack (public field) |
stack.getDamage() | the metadata / damage value |
You can build a stack from a Block or an Item reference interchangeably, RetroAPI guarantees every registered block has a valid item form, so new ItemStack(EXAMPLE_BLOCK, 64) just works.
Items in the world
An item nobody can obtain isn't much fun. The showcase hands you everything at once through ExamplePlayerSetup, a one-shot per-player starter kit (demo scaffolding you'd delete in a real mod). It builds stacks straight from the block and item references and drops them in the inventory:
PlayerInventory inventory = player.inventory;
inventory.addStack(new ItemStack(ExampleMod.EXAMPLE_BLOCK, 64));
inventory.addStack(new ItemStack(ExampleMod.SUSPICIOUS_SUBSTANCE, 16));
inventory.addStack(new ItemStack(ExampleMod.JUMP_STICK, 1));
// … plus the blocks, buckets, and coal so the freezer can run right away …That's the throwaway way. The durable way to get an item into players' hands is a recipe that outputs it, the showcase smelts and crafts SUSPICIOUS_SUBSTANCE several ways, and that's Chapter 11.
Only new players get the kit
A starter kit you hand out on every join would refill the inventory each time you log back in. The showcase wants it once, the first time a player exists. The trick is telling a brand-new player apart from a returning one, and that's a one-line mixin. A player's saved data is read back through readNbt, which only runs when there is something on disk to read; a freshly created player never goes through it. So the showcase marks every player that does load from disk, and ExamplePlayerSetup gives the kit only to the players left unmarked:
@Mixin(PlayerEntity.class)
public class PlayerLoadMixin {
@Inject(method = "readNbt", at = @At("HEAD"))
private void exampleMod$markLoadedFromDisk(NbtCompound nbt, CallbackInfo ci) {
ExamplePlayerSetup.markLoadedFromDisk((PlayerEntity) (Object) this);
}
}Like every mixin, this one only runs if it's listed in example_mod.mixins.json, where it already sits under "mixins" (the common list, since PlayerEntity exists on both sides). Injecting at HEAD of readNbt means the mark is set before any saved field is parsed, so by the time the setup logic runs the new-vs-returning answer is already known. For the full vocabulary of @Inject, @At, and the (PlayerEntity) (Object) this cast idiom, see the mixins chapter.
Items done. Next we give blocks a memory: block entities that store data, tick, and open GUIs.
