Описание
# Custom Item Data
  
A **Fabric library mod** for Minecraft **1.20.6** that lets you store persistent, typed, schema-validated data on any `ItemStack`.
### Two ways to use it:
- As a **mod developer**, 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` interface.
---
## Installation (end users)
1. Install **Fabric Loader** 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
/itemdata define [default]
/itemdata remove
/itemdata set
/itemdata get
/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` 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` (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 {
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 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 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 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` |
| `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 |
---