Custom Item Data

A useful mod for Minecraft servers or framework for mod developers to store easily data on item.

54

Custom Item Data

Custom Item Data

Version Loader Status

A 1.20.6 for Minecraft 1.20.6 that lets you store persistent, typed, schema-validated data on any `ItemStack`.

Two ways to use it:

- As a server admin, call the `ItemDataAPI` from your own mod to read/write values on items. - As a server admin, use the `/itemdata` commands in-game to define and edit item data without writing code.

---

Features

- Store typed data (`int`, `float`, `string`, `boolean` + your own types for framework) on any `ItemStack`. - Schema-based validation — the framework rejects values that don't match the registered type. - Automatic persistence (survives save/load, gives/clones, etc.). - Stacks with different data don't stack together. - In-game admin commands (`/itemdata […]`) persisted in the world save. - Pluggable custom types via a simple `CustomDataType<T>` interface.

---

Installation (end users)

1. Install Fabric API for Minecraft 1.20.6: https://fabricmc.net/use/installer/ 2. Download Fabric API for 1.20.6 and drop the `.jar` into your `mods/` folder. 3. Drop `customitemdata-1.0.0.jar` into the same `mods/` folder. 4. Launch the game (or server). You should see `[ItemDataFramework] Initialisé avec succès.` in the log.

That's it — the mod is now active. By itself it does nothing visible; it becomes useful when another mod calls its API, or when an OP uses the `/itemdata` commands.

---

Admin commands tutorial

All commands require OP level 2. They operate on the item currently held in the main hand.

```text /itemdata define <param_name> <type> /itemdata define <param_name> <type> <visible> <nullable> [default] /itemdata remove <param_name> /itemdata set <param_name> <value> /itemdata get <param_name> /itemdata list /itemrename [name] ```

Example session — give a stick a "points" counter:

```text /itemdata define nb_point int /itemdata set nb_point 42 /itemdata get nb_point → 42 /itemdata list → nb_point (int) ```

Schemas defined this way are saved in the world save, so they persist across restarts.

---

Framework tutorial (for mod developers)

This section walks you through using ItemDataFramework from your own Fabric mod — from zero to a working example in four steps.

Step 1 — Add the dependency

Until the mod is published to a Maven repository, the simplest approach is to drop its `.jar` into your project and reference it locally.

Option A — local jar (fastest):

1. Build this mod: `./gradlew build` — the jar lands in `build/libs/customitemdata-1.0.0.jar`. 2. Copy that jar into a `libs/` folder in your own mod project. 3. In your `build.gradle`:

```gradle dependencies { modImplementation files("libs/customitemdata-1.0.0.jar") // or: modImplementation fileTree(dir: "libs", include: ["*.jar"]) } ```

4. In your `fabric.mod.json`, declare it as a dependency so it loads first:

```json "depends": { "fabricloader": ">=0.18.4", "minecraft": "~1.20.6", "fabric-api": "*", "customitemdata": "*" } ```

Step 2 — Register a schema

A schema tells the framework which parameters an item type is allowed to carry, and of what type. Register your schemas once, in your mod's `onInitialize()`, *after* Minecraft's registries are ready.

```java package com.example.mymod;

import fr.hdi.customitemdata.schema.SchemaEntry; import fr.hdi.customitemdata.schema.SchemaManager; import net.fabricmc.api.ModInitializer;

public class MyMod implements ModInitializer {

@Override public void onInitialize() { SchemaManager schemas = SchemaManager.getInstance();

// A simple integer counter on sticks, visible in the tooltip, // defaulting to "0" and required (not nullable). schemas.registerSchema("minecraft:stick", new SchemaEntry.Builder("nb_point", "int") .visible(true) .nullable(false) .defaultValue("0") .build());

// A string "owner" field on diamond swords. schemas.registerSchema("minecraft:diamond_sword", new SchemaEntry.Builder("owner", "string") .visible(true) .build()); } } ```

Available primitive types: `int`, `float`, `string`, `boolean`.

> Schemas registered via the API are re-applied every time the server starts — they are not persisted to the world. Only schemas created via `/itemdata define` are persisted.

Step 3 — Read and write with `ItemDataAPI`

Once a schema exists, use `ItemDataAPI` anywhere you have an `ItemStack`. The API validates against the schema automatically; if validation fails, `set*` returns `false` and the stack is left unchanged.

```java import fr.hdi.customitemdata.api.ItemDataAPI; import net.minecraft.item.ItemStack;

ItemStack stack = player.getMainHandStack();

ItemDataAPI.setCustomName(stack, "My Item");

// Write ItemDataAPI.setInt(stack, "nb_point", 42); ItemDataAPI.setString(stack, "owner", "Steve");

// Read (with default fallback) int points = ItemDataAPI.getInt(stack, "nb_point", 0); String owner = ItemDataAPI.getString(stack, "owner", "unknown");

// Read as Optional (absent if the key isn't present) ItemDataAPI.getInt(stack, "nb_point").ifPresent(value -> ...);

// Check / delete boolean has = ItemDataAPI.has(stack, "nb_point"); ItemDataAPI.remove(stack, "nb_point"); ```

Under the hood, every value is serialized as a `String` inside a single `ItemDataComponent` attached to the stack. Two stacks with different data will not merge, because that's how vanilla Data Components work.

Step 4 — Create a custom data type

When `int` / `float` / `string` / `boolean` aren't enough, implement `CustomDataType<T>` to plug in your own type. You need to provide:

- a unique string ID (`namespace:name`), - an `encode` that turns your object into a string, - a `decode` that parses it back, - a `Codec<T>` (used for Minecraft data integration, e.g. datapacks).

```java package com.example.mymod;

import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import fr.hdi.customitemdata.api.CustomDataType;

public final class PointDataType implements CustomDataType<PointDataType.Point> {

public static final String TYPE_ID = "mymod:point"; public static final PointDataType INSTANCE = new PointDataType();

private PointDataType() {}

@Override public String getId() { return TYPE_ID; }

@Override public Codec<Point> getCodec() { return Point.CODEC; }

@Override public String encode(Point value) { return value.x() + "," + value.y(); }

@Override public Point decode(String stored) { String[] parts = stored.split(",", 2); return new Point(Integer.parseInt(parts[0]), Integer.parseInt(parts[1])); }

// Nicer tooltip display: "(3, 7)" instead of "Point[x=3, y=7]" @Override public String display(String stored) { Point p = decode(stored); return "(" + p.x() + ", " + p.y() + ")"; }

public record Point(int x, int y) { public static final Codec<Point> CODEC = RecordCodecBuilder.create(i -> i.group( Codec.INT.fieldOf("x").forGetter(Point::x), Codec.INT.fieldOf("y").forGetter(Point::y) ).apply(i, Point::new)); } } ```

Register it before any schema that uses it:

```java SchemaManager schemas = SchemaManager.getInstance();

schemas.registerCustomType(PointDataType.INSTANCE);

schemas.registerSchema("minecraft:compass", new SchemaEntry.Builder("target", PointDataType.TYPE_ID).build()); ```

Then read/write through the generic `get`/`set` overloads:

```java ItemDataAPI.set(stack, "target", new PointDataType.Point(3, 7), PointDataType.INSTANCE);

Optional<PointDataType.Point> target = ItemDataAPI.get(stack, "target", PointDataType.INSTANCE); ```

Full working example

A minimal mod that defines two parameters on a stick, sets them on the first tick, and reads them back:

```java package com.example.mymod;

import fr.hdi.customitemdata.api.ItemDataAPI; import fr.hdi.customitemdata.schema.SchemaEntry; import fr.hdi.customitemdata.schema.SchemaManager; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.player.UseItemCallback; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.util.ActionResult; import net.minecraft.util.TypedActionResult;

public class MyMod implements ModInitializer {

@Override public void onInitialize() { SchemaManager schemas = SchemaManager.getInstance();

schemas.registerSchema("minecraft:stick", new SchemaEntry.Builder("nb_point", "int") .nullable(false).defaultValue("0").build());

schemas.registerSchema("minecraft:stick", new SchemaEntry.Builder("owner", "string").build());

// Right-click a stick to increment its counter. UseItemCallback.EVENT.register((player, world, hand) -> { ItemStack stack = player.getStackInHand(hand); if (stack.getItem() != Items.STICK) { return TypedActionResult.pass(stack); }

int current = ItemDataAPI.getInt(stack, "nb_point", 0); ItemDataAPI.setInt(stack, "nb_point", current + 1); ItemDataAPI.setString(stack, "owner", player.getName().getString());

player.sendMessage( net.minecraft.text.Text.literal("Points: " + (current + 1)), true); return TypedActionResult.success(stack); }); } } ```

---

Reference

`ItemDataAPI` (static methods)

| Method | Purpose | |------------------------------------------------------------|------------------------------------------| | `getRaw(stack, key)` / `setRaw(stack, key, value)` | Raw string access | | `getInt` / `setInt` | Integer values | | `getFloat` / `setFloat` | Float values | | `getString` / `setString` | String values | | `getBoolean` / `setBoolean` | Boolean values | | `get(stack, key, customType)` / `set(stack, key, v, t)` | Custom typed values | | `has(stack, key)` / `hasData(stack)` | Presence checks | | `remove(stack, key)` | Delete a key (removes component if empty)| | `getComponent(stack)` | Access the raw `ItemDataComponent` |

`SchemaEntry.Builder`

| Method | Default | Description | |----------------------|---------|------------------------------------------------| | `visible(boolean)` | `true` | Show in item tooltip | | `nullable(boolean)` | `true` | Allow the parameter to be absent | | `defaultValue(String)`| `null` | Default encoded value (e.g. `"0"`, `"false"`) | | `fromCommand(boolean)`| `false`| Mark as command-sourced (triggers persistence) |

`SchemaManager` (singleton via `getInstance()`)

| Method | Purpose | |--------------------------------------------------|--------------------------------------| | `registerSchema(itemId, entry)` | API-side schema registration | | `registerCustomType(customDataType)` | Register a `CustomDataType<T>` | | `getEntry(itemId, paramName)` | Look up a single entry | | `getEntries(itemId)` | Look up all entries for an item | | `hasSchema(itemId)` | Does the item have any schema? | | `isKnownType(typeId)` | Is the type ID primitive or custom? | | `validateValue(entry, value)` | Validate a string against an entry |

---

ADS