diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java
index 19f743f2b77..acf740e24ac 100644
--- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java
+++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/GeyserDefineCustomItemsEvent.java
@@ -41,29 +41,34 @@
/**
* Called on Geyser's startup when looking for custom items. Custom items must be registered through this event.
*
- * This event will not be called if the "add non-Bedrock items" setting is disabled in the Geyser config.
+ * This event will not be called if the "add-non-bedrock-items" setting is disabled in the Geyser config.
*/
public interface GeyserDefineCustomItemsEvent extends Event {
/**
- * A multimap of all the already registered (using the deprecated method) custom items indexed by the item's extended java item's identifier.
+ * A multimap of all the already registered custom items indexed by the item's extended java item's identifier.
+ * The map returned here will only contain items registered with the deprecated
+ * {@link GeyserDefineCustomItemsEvent#register(String, CustomItemData)} method.
*
- * @deprecated use {@link GeyserDefineCustomItemsEvent#customItemDefinitions()}
+ * @deprecated replaced with {@link GeyserDefineCustomItemsEvent#customItemDefinitions()}
*/
@Deprecated
@NonNull
Map> getExistingCustomItems();
/**
- * A multimap of all the already registered custom item definitions indexed by the item's extended java item's identifier.
+ * A multimap of all the already registered custom item definitions
+ * indexed by the identifier of the Java item which the item is based on.
*/
@NonNull
Map> customItemDefinitions();
/**
- * A list of the already registered (using the deprecated method) non-vanilla custom items.
+ * A list of the already registered non-vanilla custom items.
+ * The map returned here will only contain items registered with the deprecated
+ * {@link GeyserDefineCustomItemsEvent#register(NonVanillaCustomItemData)} method.
*
- * @deprecated use {@link GeyserDefineCustomItemsEvent#nonVanillaCustomItemDefinitions()}
+ * @deprecated replaced with {@link GeyserDefineCustomItemsEvent#nonVanillaCustomItemDefinitions()}
*/
@Deprecated
@NonNull
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java
index 999f70a6df2..e8d84edab15 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/NonVanillaCustomItemData.java
@@ -32,13 +32,13 @@
import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions;
import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition;
import org.geysermc.geyser.api.item.custom.v2.NonVanillaCustomItemDefinition;
-import org.geysermc.geyser.api.item.custom.v2.component.BlockPlacer;
-import org.geysermc.geyser.api.item.custom.v2.component.Chargeable;
-import org.geysermc.geyser.api.item.custom.v2.component.Consumable;
-import org.geysermc.geyser.api.item.custom.v2.component.DataComponent;
-import org.geysermc.geyser.api.item.custom.v2.component.Equippable;
-import org.geysermc.geyser.api.item.custom.v2.component.FoodProperties;
-import org.geysermc.geyser.api.item.custom.v2.component.GeyserDataComponent;
+import org.geysermc.geyser.api.item.custom.v2.component.geyser.BlockPlacer;
+import org.geysermc.geyser.api.item.custom.v2.component.geyser.Chargeable;
+import org.geysermc.geyser.api.item.custom.v2.component.java.Consumable;
+import org.geysermc.geyser.api.item.custom.v2.component.java.Equippable;
+import org.geysermc.geyser.api.item.custom.v2.component.java.FoodProperties;
+import org.geysermc.geyser.api.item.custom.v2.component.geyser.GeyserDataComponent;
+import org.geysermc.geyser.api.item.custom.v2.component.java.ItemDataComponents;
import org.geysermc.geyser.api.util.CreativeCategory;
import org.geysermc.geyser.api.util.Identifier;
@@ -203,39 +203,39 @@ default NonVanillaCustomItemDefinition.Builder toDefinition() {
.tags(tags().stream().map(Identifier::of).collect(Collectors.toSet()))
.protectionValue(protectionValue())
)
- .component(DataComponent.MAX_STACK_SIZE, stackSize())
- .component(DataComponent.MAX_DAMAGE, maxDamage())
+ .component(ItemDataComponents.MAX_STACK_SIZE, stackSize())
+ .component(ItemDataComponents.MAX_DAMAGE, maxDamage())
.component(GeyserDataComponent.ATTACK_DAMAGE, attackDamage())
.translationString(translationString());
if (isHat()) {
- definition.component(DataComponent.EQUIPPABLE, new Equippable(Equippable.EquipmentSlot.HEAD));
+ definition.component(ItemDataComponents.EQUIPPABLE, Equippable.builder().slot(Equippable.EquipmentSlot.HEAD).build());
} else if (armorType() != null) {
switch (armorType()) {
- case "helmet" -> definition.component(DataComponent.EQUIPPABLE, new Equippable(Equippable.EquipmentSlot.HEAD));
- case "chestplate" -> definition.component(DataComponent.EQUIPPABLE, new Equippable(Equippable.EquipmentSlot.CHEST));
- case "leggings" -> definition.component(DataComponent.EQUIPPABLE, new Equippable(Equippable.EquipmentSlot.LEGS));
- case "boots" -> definition.component(DataComponent.EQUIPPABLE, new Equippable(Equippable.EquipmentSlot.FEET));
+ case "helmet" -> definition.component(ItemDataComponents.EQUIPPABLE, Equippable.builder().slot(Equippable.EquipmentSlot.HEAD));
+ case "chestplate" -> definition.component(ItemDataComponents.EQUIPPABLE, Equippable.builder().slot(Equippable.EquipmentSlot.CHEST));
+ case "leggings" -> definition.component(ItemDataComponents.EQUIPPABLE, Equippable.builder().slot(Equippable.EquipmentSlot.LEGS));
+ case "boots" -> definition.component(ItemDataComponents.EQUIPPABLE, Equippable.of(Equippable.EquipmentSlot.FEET));
}
}
if (isEdible()) {
- definition.component(DataComponent.CONSUMABLE, new Consumable(1.6F, Consumable.Animation.EAT)); // Default values
+ definition.component(ItemDataComponents.CONSUMABLE, Consumable.builder().consumeSeconds(1.6F).animation(Consumable.Animation.EAT)); // Default values
if (canAlwaysEat()) {
- definition.component(DataComponent.FOOD, new FoodProperties(0, 0, true));
+ definition.component(ItemDataComponents.FOOD, FoodProperties.builder().canAlwaysEat(true));
}
}
if (isChargeable() && toolType() != null) {
if (toolType().equals("bow")) {
- definition.component(GeyserDataComponent.CHARGEABLE, new Chargeable(1.0F, true, Identifier.of("arrow")));
+ definition.component(GeyserDataComponent.CHARGEABLE, Chargeable.builder().maxDrawDuration(1.0F).chargeOnDraw(true).ammunition(Identifier.of("arrow")));
} else {
- definition.component(GeyserDataComponent.CHARGEABLE, new Chargeable(0.0F, false, Identifier.of("arrow")));
+ definition.component(GeyserDataComponent.CHARGEABLE, Chargeable.builder().ammunition(Identifier.of("arrow")));
}
}
if (block() != null) {
- definition.component(GeyserDataComponent.BLOCK_PLACER, new BlockPlacer(Identifier.of(block()), false));
+ definition.component(GeyserDataComponent.BLOCK_PLACER, BlockPlacer.builder().block(Identifier.of(block())));
}
return definition;
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java
index d65bfde5493..f2f44770850 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/CustomItemDefinition.java
@@ -26,15 +26,19 @@
package org.geysermc.geyser.api.item.custom.v2;
import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.common.returnsreceiver.qual.This;
import org.geysermc.geyser.api.GeyserApi;
-import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap;
import org.geysermc.geyser.api.item.custom.v2.component.DataComponent;
+import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap;
+import org.geysermc.geyser.api.item.custom.v2.component.java.ItemDataComponents;
import org.geysermc.geyser.api.predicate.MinecraftPredicate;
import org.geysermc.geyser.api.predicate.PredicateStrategy;
import org.geysermc.geyser.api.predicate.context.item.ItemPredicateContext;
+import org.geysermc.geyser.api.util.GenericBuilder;
import org.geysermc.geyser.api.util.Identifier;
import java.util.List;
+import java.util.Objects;
/**
* This is used to define a custom item and its properties for a specific Java item and item model definition combination.
@@ -59,11 +63,15 @@ public interface CustomItemDefinition {
/**
* The Bedrock identifier for this custom item. It cannot be in the {@code minecraft} namespace.
+ *
+ * @return the Bedrock item identifier
*/
@NonNull Identifier bedrockIdentifier();
/**
* The display name of the item. If none is set, the display name is taken from the item's Bedrock identifier.
+ *
+ * @return the display name shown to Bedrock clients
*/
@NonNull String displayName();
@@ -71,6 +79,8 @@ public interface CustomItemDefinition {
* The item model this definition is for. If the model is in the {@code minecraft} namespace, then the definition must have at least one predicate.
*
* If multiple item definitions for a model are registered, then only one can have no predicate.
+ *
+ * @return the identifier of the Java item model
*/
@NonNull Identifier model();
@@ -82,6 +92,8 @@ public interface CustomItemDefinition {
*
* {@code my_datapack:my_custom_item} => {@code my_datapack.my_custom_item}
* {@code my_datapack:cool_items/cool_item_1} => {@code my_datapack.cool_items_cool_item_1}
+ *
+ * @return the icon shown to Bedrock players
*/
default @NonNull String icon() {
String setIcon = bedrockOptions().icon();
@@ -103,7 +115,7 @@ public interface CustomItemDefinition {
/**
* The predicate strategy to be used. Determines if one of, or all of the predicates have to pass for this item definition to be used. Defaults to {@link PredicateStrategy#AND}.
*/
- PredicateStrategy predicateStrategy();
+ @NonNull PredicateStrategy predicateStrategy();
/**
* The priority of this definition. For all definitions for a single Java item model, definitions with a higher priority will be matched first. Defaults to 0.
@@ -121,16 +133,16 @@ public interface CustomItemDefinition {
* Currently, the following components are (somewhat) supported:
*
*
- * - {@code minecraft:consumable} ({@link DataComponent#CONSUMABLE})
- * - {@code minecraft:equippable} ({@link DataComponent#EQUIPPABLE})
- * - {@code minecraft:food} ({@link DataComponent#FOOD})
- * - {@code minecraft:max_damage} ({@link DataComponent#MAX_DAMAGE})
- * - {@code minecraft:max_stack_size} ({@link DataComponent#MAX_STACK_SIZE})
- * - {@code minecraft:use_cooldown} ({@link DataComponent#USE_COOLDOWN})
- * - {@code minecraft:enchantable} ({@link DataComponent#ENCHANTABLE})
- * - {@code minecraft:tool} ({@link DataComponent#TOOL})
- * - {@code minecraft:repairable} ({@link DataComponent#REPAIRABLE})
- * - {@code minecraft:enchantment_glint_override} ({@link DataComponent#ENCHANTMENT_GLINT_OVERRIDE})
+ * - {@code minecraft:consumable} ({@link ItemDataComponents#CONSUMABLE})
+ * - {@code minecraft:equippable} ({@link ItemDataComponents#EQUIPPABLE})
+ * - {@code minecraft:food} ({@link ItemDataComponents#FOOD})
+ * - {@code minecraft:max_damage} ({@link ItemDataComponents#MAX_DAMAGE})
+ * - {@code minecraft:max_stack_size} ({@link ItemDataComponents#MAX_STACK_SIZE})
+ * - {@code minecraft:use_cooldown} ({@link ItemDataComponents#USE_COOLDOWN})
+ * - {@code minecraft:enchantable} ({@link ItemDataComponents#ENCHANTABLE})
+ * - {@code minecraft:tool} ({@link ItemDataComponents#TOOL})
+ * - {@code minecraft:repairable} ({@link ItemDataComponents#REPAIRABLE})
+ * - {@code minecraft:enchantment_glint_override} ({@link ItemDataComponents#ENCHANTMENT_GLINT_OVERRIDE})
*
*
* Note: some components, for example {@code minecraft:rarity} and {@code minecraft:attribute_modifiers}, are translated automatically, and do not have to be specified here.
@@ -148,33 +160,131 @@ public interface CustomItemDefinition {
*/
@NonNull List removedComponents();
- static Builder builder(Identifier bedrockIdentifier, Identifier itemModel) {
+ /**
+ * Creates a builder for the custom item definition.
+ *
+ * @param bedrockIdentifier the Bedrock item identifier
+ * @param itemModel the Java item model identifier
+ * @return a new builder
+ */
+ static Builder builder(@NonNull Identifier bedrockIdentifier, @NonNull Identifier itemModel) {
return GeyserApi.api().provider(Builder.class, bedrockIdentifier, itemModel);
}
- interface Builder {
+ /**
+ * The builder for the custom item definition.
+ */
+ interface Builder extends GenericBuilder {
+ /**
+ * Sets the display name, as shown to the Bedrock client.
+ * When not set, the display name will be derived from the Bedrock item identifier.
+ *
+ * @param displayName the display name to show for Bedrock clients.
+ * @return this builder
+ */
+ @This
Builder displayName(@NonNull String displayName);
+ /**
+ * Sets the priority of this definition, used for definition matching.
+ * @see CustomItemDefinition#priority()
+ *
+ * @param priority the priority
+ * @return this builder
+ */
+ @This
Builder priority(int priority);
+ /**
+ * Sets the Bedrock item options for this definition.
+ * Those determine the icon seen on Bedrock edition, whether the item
+ * can be placed in the offhand slot, and other options.
+ *
+ * @see CustomItemBedrockOptions
+ * @param options the bedrock item options
+ * @return this builder
+ */
+ @This
Builder bedrockOptions(CustomItemBedrockOptions.@NonNull Builder options);
+ /**
+ * Adds a predicate that must match for Geyser to use this item definition.
+ * See {@link CustomItemDefinition#predicates()} for details.
+ *
+ * @param predicate a predicate that must match for this item to be used
+ * @return this builder
+ */
+ @This
Builder predicate(@NonNull MinecraftPredicate super ItemPredicateContext> predicate);
+ /**
+ * Sets the predicate strategy that should be used for item definition matching.
+ *
+ * @param strategy the predicate strategy to use
+ * @return this builder
+ */
+ @This
Builder predicateStrategy(@NonNull PredicateStrategy strategy);
+ /**
+ * Sets data components that determine the item behavior. These are assumed to also be
+ * present server-side on the Java server. See {@link CustomItemDefinition#components()}
+ * for more information.
+ *
+ * @param component the type of the component - found in {@link ItemDataComponents}
+ * @param value the value of the component
+ * @return this builder
+ * @param the value held by the component
+ */
+ @This
Builder component(@NonNull DataComponent component, @NonNull T value);
- Builder removeComponent(Identifier component);
+ /**
+ * Convenience method for {@link CustomItemDefinition.Builder#component(DataComponent, Object)}
+ *
+ * @param component the type of the component - found in {@link ItemDataComponents}
+ * @param builder the builder of the component
+ * @return this builder
+ * @param the value held by the component
+ */
+ @This
+ default Builder component(@NonNull DataComponent component, @NonNull GenericBuilder builder) {
+ return component(component, builder.build());
+ }
+
+ /**
+ * Indicates a removed item component that will not be present on the custom item despite
+ * existing on the vanilla item. This must match server-side behavior, otherwise, issues
+ * will occur. See {@link CustomItemDefinition#removedComponents()} for more information.
+ *
+ * @param component the identifier of the vanilla base component to remove
+ * @return this builder
+ */
+ @This
+ Builder removeComponent(@NonNull Identifier component);
- default Builder removeComponent(DataComponent> component) {
+ /**
+ * Convenience method for {@link CustomItemDefinition.Builder#removeComponent(Identifier)}.
+ *
+ * @param component the component type to remove
+ * @return this builder
+ */
+ @This
+ default Builder removeComponent(@NonNull DataComponent> component) {
+ Objects.requireNonNull(component);
if (!component.vanilla()) {
throw new IllegalArgumentException("Cannot remove non-vanilla component");
}
return removeComponent(component.identifier());
}
+ /**
+ * Creates the custom item definition.
+ *
+ * @return the new custom item definition
+ */
+ @Override
CustomItemDefinition build();
}
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/NonVanillaCustomItemDefinition.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/NonVanillaCustomItemDefinition.java
index 00d820a94e3..4e20f9e2c5f 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/NonVanillaCustomItemDefinition.java
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/NonVanillaCustomItemDefinition.java
@@ -29,6 +29,7 @@
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.GeyserApi;
+import org.geysermc.geyser.api.item.custom.v2.component.geyser.GeyserDataComponent;
import org.geysermc.geyser.api.item.custom.v2.component.DataComponent;
import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap;
import org.geysermc.geyser.api.predicate.MinecraftPredicate;
@@ -39,13 +40,13 @@
import java.util.List;
/**
- * Defines an entirely custom item, introduced by mods and therefore not based on a vanilla item, and its properties.
+ * Defines an entirely custom item, introduced by mods and therefore not based on a vanilla item and its properties.
*
* A definition will be used when an item is received with the ID of the definition. Predicate matching, as is possible
* right now with vanilla custom item definitions, is currently not implemented, so only one definition can be created for each
* Java non-vanilla item.
*
- * Non-vanilla item definitions can be configured with additional components defined in {@link org.geysermc.geyser.api.item.custom.v2.component.GeyserDataComponent}.
+ * Non-vanilla item definitions can be configured with additional components defined in {@link GeyserDataComponent}.
*/
public interface NonVanillaCustomItemDefinition extends CustomItemDefinition {
@@ -80,6 +81,7 @@ public interface NonVanillaCustomItemDefinition extends CustomItemDefinition {
* Trying to use predicates will result in an error.
*/
@Override
+ @NonNull
PredicateStrategy predicateStrategy();
/**
@@ -91,23 +93,23 @@ public interface NonVanillaCustomItemDefinition extends CustomItemDefinition {
int priority();
/**
- * On top of vanilla Minecraft's item components, custom ones defined by Geyser in {@link org.geysermc.geyser.api.item.custom.v2.component.GeyserDataComponent} can
+ * On top of vanilla Minecraft's item components, custom ones defined by Geyser in {@link GeyserDataComponent} can
* also be used. Like with vanilla data components, it is still expected that the item always has the behaviour defined by its components.
*
* Default component removals are not supported for non-vanilla items, since here the data component map defines default components, instead of
* a patch on top of a vanilla base item.
*
* @see CustomItemDefinition#components()
- * @see org.geysermc.geyser.api.item.custom.v2.component.GeyserDataComponent
+ * @see GeyserDataComponent
*/
@Override
@NonNull DataComponentMap components();
- static Builder builder(Identifier javaIdentifier, int javaId) {
+ static Builder builder(@NonNull Identifier javaIdentifier, int javaId) {
return builder(javaIdentifier, javaIdentifier, javaId);
}
- static Builder builder(Identifier javaIdentifier, Identifier bedrockIdentifier, int javaId) {
+ static Builder builder(@NonNull Identifier javaIdentifier, @NonNull Identifier bedrockIdentifier, int javaId) {
return GeyserApi.api().provider(Builder.class, javaIdentifier, bedrockIdentifier, javaId);
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java
deleted file mode 100644
index 561b2dc7c42..00000000000
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Consumable.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (c) 2025 GeyserMC. http://geysermc.org
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * @author GeyserMC
- * @link https://github.com/GeyserMC/Geyser
- */
-
-package org.geysermc.geyser.api.item.custom.v2.component;
-
-import org.checkerframework.checker.index.qual.Positive;
-
-public record Consumable(@Positive float consumeSeconds, Animation animation) {
-
- public Consumable {
- if (consumeSeconds <= 0.0F) {
- throw new IllegalArgumentException("Consume seconds must be above 0");
- }
- }
-
- /**
- * Not all animations work perfectly on bedrock. Bedrock behaviour is noted per animation. The {@code toot_horn} animation doesn't exist on bedrock, and is therefore not listed here.
- */
- public enum Animation {
- /**
- * Does nothing in 1st person, appears as eating in 3rd person.
- */
- NONE,
- /**
- * Appears to look correctly.
- */
- EAT,
- /**
- * Appears to look correctly.
- */
- DRINK,
- /**
- * Does nothing in 1st person, eating in 3rd person.
- */
- BLOCK,
- /**
- * Does nothing in 1st person, eating in 3rd person.
- */
- BOW,
- /**
- * Does nothing in 1st person, but looks like spear in 3rd person.
- */
- SPEAR,
- /**
- * Does nothing in 1st person, eating in 3rd person.
- */
- CROSSBOW,
- /**
- * Does nothing in 1st person, but looks like spyglass in 3rd person.
- */
- SPYGLASS,
- /**
- * Brush in 1st and 3rd person. Will look weird when not displayed handheld.
- */
- BRUSH
- }
-}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java
index 91486d3bb8f..32ecc6b8b6e 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/DataComponent.java
@@ -25,125 +25,44 @@
package org.geysermc.geyser.api.item.custom.v2.component;
+import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition;
+import org.geysermc.geyser.api.item.custom.v2.component.geyser.GeyserDataComponent;
+import org.geysermc.geyser.api.item.custom.v2.component.java.ItemDataComponents;
import org.geysermc.geyser.api.util.Identifier;
import java.util.function.Predicate;
/**
- * Data components are used to indicate item behaviour of custom items. It is expected that any components set on a {@link CustomItemDefinition} are always present on the item server-side.
+ * Data components are used to indicate item behaviour of custom items.
+ * It is expected that any components set on a {@link CustomItemDefinition} are always present on the item server-side.
*
+ * @see ItemDataComponents
* @see GeyserDataComponent
* @see CustomItemDefinition#components()
*/
-public final class DataComponent {
- /**
- * Marks the item as consumable. Of this component, only {@code consume_seconds} and {@code animation} properties are translated. Consume effects are done server side,
- * and consume sounds and particles aren't possible.
- *
- * Note that due to a bug on Bedrock, not all consume animations appear perfectly. See {@link Consumable.Animation}
- *
- * @see Consumable
- */
- public static final DataComponent CONSUMABLE = create("consumable");
- /**
- * Marks the item as equippable. Of this component, only the {@code slot} property is translated. Other properties are done server-side, are done differently on Bedrock (e.g. {@code asset_id} is done via attachables),
- * or are not possible on Bedrock at all (e.g. {@code camera_overlay}).
- *
- * Note that on Bedrock, equippables can't have a stack size above 1.
- *
- * @see Equippable
- */
- public static final DataComponent EQUIPPABLE = create("equippable");
- /**
- * Food properties of the item. All properties properly translate over to Bedrock.
- *
- * @see FoodProperties
- */
- public static final DataComponent FOOD = create("food");
- /**
- * Max damage value of the item. Must be at or above 0. Items with a max damage value above 0 can't have a stack size above 1.
- */
- public static final DataComponent MAX_DAMAGE = create("max_damage", i -> i >= 0);
- /**
- * Max stack size of the item. Must be between 1 and 99. Items with a max stack size value above 1 can't have a max damage value above 0.
- */
- public static final DataComponent MAX_STACK_SIZE = create("max_stack_size", i -> i >= 1 && i <= 99); // Reverse lambda
- /**
- * Marks the item to have a use cooldown. To properly function, the item must be able to be used: it must be consumable or have some other kind of use logic.
- *
- * The cooldown group can be {@code null}, in this case the identifier of the vanilla item (in case of vanilla custom items),
- * or the item itself (in case of non-vanilla custom items) will be used.
- *
- * @see UseCooldown
- */
- public static final DataComponent USE_COOLDOWN = create("use_cooldown");
+public interface DataComponent {
+
/**
- * Marks the item to be enchantable. Must be at or above 0.
+ * The identifier of the data component.
*
- * This component does not translate over perfectly, due to the way enchantments work on Bedrock. The component will be mapped to the {@code minecraft:enchantable} bedrock component with {@code slot=all}.
- * This should, but does not guarantee, allow for compatibility with vanilla enchantments. Non-vanilla enchantments are unlikely to work.
+ * @return the identifier
*/
- public static final DataComponent ENCHANTABLE = create("enchantable", i -> i >= 0);
+ @NonNull
+ Identifier identifier();
+
/**
- * This component is only used for the {@link ToolProperties#canDestroyBlocksInCreative()} option.
+ * The predicate used to validate the component.
*
- * Like other components, when not set this will fall back to the default value.
- *
- * @see ToolProperties
+ * @return the validator
*/
- public static final DataComponent TOOL = create("tool");
+ @NonNull
+ Predicate validator();
+
/**
- * Marks which items can be used to repair the item.
+ * Whether the component exists in vanilla Minecraft.
*
- * @see Repairable
+ * @return whether this component is vanilla
*/
- public static final DataComponent REPAIRABLE = create("repairable");
- /**
- * Overrides the item's enchantment glint.
- */
- public static final DataComponent ENCHANTMENT_GLINT_OVERRIDE = create("enchantment_glint_override");
-
- private final Identifier identifier;
- private final Predicate validator;
- private final boolean vanilla;
-
- private DataComponent(Identifier identifier, Predicate validator, boolean vanilla) {
- this.identifier = identifier;
- this.validator = validator;
- this.vanilla = vanilla;
- }
-
- private static DataComponent create(String name) {
- return new DataComponent<>(Identifier.of(name), t -> true, true);
- }
-
- private static DataComponent create(String name, Predicate validator) {
- return new DataComponent<>(Identifier.of(name), validator, true);
- }
-
- static DataComponent createGeyser(String name) {
- return new DataComponent<>(Identifier.of("geyser", name), t -> true, false);
- }
-
- static DataComponent createGeyser(String name, Predicate validator) {
- return new DataComponent<>(Identifier.of("geyser", name), validator, false);
- }
-
- public Identifier identifier() {
- return identifier;
- }
-
- public boolean vanilla() {
- return vanilla;
- }
-
- public boolean validate(T value) {
- return validator.test(value);
- }
-
- @Override
- public String toString() {
- return "data component " + identifier.toString() + (vanilla ? "" : " (not vanilla)");
- }
+ boolean vanilla();
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Equippable.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Equippable.java
deleted file mode 100644
index b0589fd77ce..00000000000
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Equippable.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (c) 2025 GeyserMC. http://geysermc.org
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * @author GeyserMC
- * @link https://github.com/GeyserMC/Geyser
- */
-
-package org.geysermc.geyser.api.item.custom.v2.component;
-
-public record Equippable(EquipmentSlot slot) {
-
- public enum EquipmentSlot {
- HEAD,
- CHEST,
- LEGS,
- FEET,
- BODY,
- SADDLE
- }
-}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/ToolProperties.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/ToolProperties.java
deleted file mode 100644
index eb04e09daab..00000000000
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/ToolProperties.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (c) 2025 GeyserMC. http://geysermc.org
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * @author GeyserMC
- * @link https://github.com/GeyserMC/Geyser
- */
-
-package org.geysermc.geyser.api.item.custom.v2.component;
-
-public record ToolProperties(boolean canDestroyBlocksInCreative) {
-}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/BlockPlacer.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/BlockPlacer.java
new file mode 100644
index 00000000000..3f0e2650828
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/BlockPlacer.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.item.custom.v2.component.geyser;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.common.returnsreceiver.qual.This;
+import org.geysermc.geyser.api.GeyserApi;
+import org.geysermc.geyser.api.util.GenericBuilder;
+import org.geysermc.geyser.api.util.Identifier;
+
+/**
+ * Allows modifying items so these can place blocks or take on the
+ * icon of the block they place.
+ */
+public interface BlockPlacer {
+
+ /**
+ * The block placed by the item, used by the
+ * Bedrock client to predict block placing.
+ * This is a Bedrock edition block identifier.
+ *
+ * @return the identifier of the block to place
+ */
+ @NonNull Identifier block();
+
+ /**
+ * Whether to use the block's rendering
+ * as the icon for the item. Defaults to {@code false}.
+ *
+ * @return whether to use the 3d block rendering for the
+ * item icon
+ */
+ boolean useBlockIcon();
+
+ /**
+ * Creates a builder for the block placer component.
+ *
+ * @return a new builder
+ */
+ static Builder builder() {
+ return GeyserApi.api().provider(BlockPlacer.Builder.class);
+ }
+
+ /**
+ * Creates a block placer component.
+ *
+ * @param block the identifier of the block to place
+ * @param useBlockIcon whether to use the 3d block rendering for the item icon
+ * @return the block placer component
+ */
+ static BlockPlacer of(Identifier block, boolean useBlockIcon) {
+ return BlockPlacer.builder().block(block).useBlockIcon(useBlockIcon).build();
+ }
+
+ /**
+ * Builder for the block placer component.
+ */
+ interface Builder extends GenericBuilder {
+
+ /**
+ * The identifier of the block to place.
+ * This should be the block identifier as it is
+ * known to the Bedrock client.
+ *
+ * @param block the identifier of the block
+ * @see BlockPlacer#block()
+ * @return this builder
+ */
+ @This
+ Builder block(@NonNull Identifier block);
+
+ /**
+ * Whether to use the block's icon over the item icon.
+ * Block items have a 3d-generated block icon.
+ *
+ * @param useBlockIcon whether to use the block icon
+ * @see BlockPlacer#useBlockIcon()
+ * @return this builder
+ */
+ @This
+ Builder useBlockIcon(boolean useBlockIcon);
+
+ /**
+ * Creates the block placer component.
+ *
+ * @return the new component
+ */
+ @Override
+ BlockPlacer build();
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/Chargeable.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/Chargeable.java
new file mode 100644
index 00000000000..a39d739f4d2
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/Chargeable.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.item.custom.v2.component.geyser;
+
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.common.returnsreceiver.qual.This;
+import org.geysermc.geyser.api.GeyserApi;
+import org.geysermc.geyser.api.util.GenericBuilder;
+import org.geysermc.geyser.api.util.Identifier;
+
+import java.util.List;
+
+// TODO projectile component (?)
+
+/**
+ * The chargeable component allows creating crossbows
+ * or bows. This includes the draw duration, whether the item
+ * charges on being drawn, and the ammunition that can be
+ * used by the item.
+ */
+public interface Chargeable {
+
+ /**
+ * The maximum draw duration determines how long the weapon
+ * can be drawn before releasing automatically. Defaults to {@code 0.0}.
+ *
+ * @return the maximum draw duration
+ */
+ @NonNegative float maxDrawDuration();
+
+ /**
+ * Whether the item is being charged when being drawn, like a crossbow. Defaults to {@code false}.
+ *
+ * @return whether drawing the item charges it
+ */
+ boolean chargeOnDraw();
+
+ /**
+ * The identifiers of the Bedrock items that can be
+ * used as ammunition by this bow.
+ * For example, this can contain {@code minecraft:arrow} to allow arrows to be shot.
+ *
+ * @return all valid ammunition items
+ */
+ List<@NonNull Identifier> ammunition();
+
+ /**
+ * Creates a builder for the Chargeable component.
+ *
+ * @return a new builder
+ */
+ static Builder builder() {
+ return GeyserApi.api().provider(Chargeable.Builder.class);
+ }
+
+ /**
+ * Builder for the chargeable component.
+ */
+ interface Builder extends GenericBuilder {
+
+ /**
+ * Sets the maximum draw duration before the item is released.
+ *
+ * @param maxDrawDuration the non-negative maximum charging duration
+ * @see Chargeable#maxDrawDuration()
+ * @return this builder
+ */
+ @This
+ Builder maxDrawDuration(@NonNegative float maxDrawDuration);
+
+ /**
+ * Sets whether the item is charged when drawing.
+ *
+ * @param chargeOnDraw whether drawing charges the item
+ * @see Chargeable#chargeOnDraw()
+ * @return this builder
+ */
+ @This
+ Builder chargeOnDraw(boolean chargeOnDraw);
+
+ /**
+ * Adds an item that can be used as ammunition, such as {@code minecraft:arrow}.
+ * This will throw when trying to add an item that was already added.
+ *
+ * @param ammunition the Bedrock item identifier of possible ammunition
+ * @see Chargeable#ammunition()
+ * @return this builder
+ */
+ @This
+ Builder ammunition(@NonNull Identifier ammunition);
+
+ /**
+ * Creates the chargeable component.
+ *
+ * @return the new component
+ */
+ @Override
+ Chargeable build();
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/GeyserDataComponent.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/GeyserDataComponent.java
similarity index 70%
rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/GeyserDataComponent.java
rename to api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/GeyserDataComponent.java
index eae709e7ddf..1ca32b11c25 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/GeyserDataComponent.java
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/geyser/GeyserDataComponent.java
@@ -23,9 +23,14 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.api.item.custom.v2.component;
+package org.geysermc.geyser.api.item.custom.v2.component.geyser;
+import org.geysermc.geyser.api.GeyserApi;
import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition;
+import org.geysermc.geyser.api.item.custom.v2.component.DataComponent;
+import org.geysermc.geyser.api.util.Identifier;
+
+import java.util.function.Predicate;
/**
* Geyser data components are data components used for non-vanilla items only. Like vanilla data components, they indicate behaviour of custom items, and like vanilla data components, it is expected
@@ -36,24 +41,33 @@
* @see DataComponent
* @see CustomItemDefinition#components()
*/
-public final class GeyserDataComponent {
+public interface GeyserDataComponent {
/**
- * Marks this item as chargeable, meaning an item functions as a bow or a crossbow. A list of bedrock item identifiers can be given as ammunition.
+ * Marks this item as chargeable, meaning an item functions as a bow or a crossbow.
+ * A list of bedrock item identifiers can be given as ammunition.
*
* @see Chargeable
*/
- public static final DataComponent CHARGEABLE = DataComponent.createGeyser("chargeable");
+ DataComponent CHARGEABLE = createGeyser("chargeable");
+
/**
* Places a visual indicator (=tooltip) of the item's attack damage. Must be at or above 0.
*
* Attribute modifiers are automatically translated for custom vanilla items, but not for non-vanilla ones, which is why this component is here.
*/
- public static final DataComponent ATTACK_DAMAGE = DataComponent.createGeyser("attack_damage", i -> i >= 0);
+ DataComponent ATTACK_DAMAGE = createGeyser("attack_damage", i -> i >= 0);
+
/**
* Indicates which block the item should place and whether it should replace the original item for that block.
*/
- public static final DataComponent BLOCK_PLACER = DataComponent.createGeyser("block_placer");
+ DataComponent BLOCK_PLACER = createGeyser("block_placer");
+
+ private static DataComponent createGeyser(String id) {
+ return createGeyser(id, t -> true);
+ }
- private GeyserDataComponent() {}
+ private static DataComponent createGeyser(String id, Predicate predicate) {
+ return GeyserApi.api().provider(DataComponent.class, Identifier.of("geysermc", id), predicate, false);
+ }
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/Consumable.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/Consumable.java
new file mode 100644
index 00000000000..102c3000806
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/Consumable.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.item.custom.v2.component.java;
+
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.common.returnsreceiver.qual.This;
+import org.geysermc.geyser.api.GeyserApi;
+import org.geysermc.geyser.api.util.GenericBuilder;
+
+/**
+ * The consumable component is used to mark
+ * an item as consumable. Further, it allows specifying
+ * the consume duration and animation to play when consuming.
+ */
+public interface Consumable {
+
+ /**
+ * The seconds it takes to consume the item.
+ * This it the amount of time the animation will play for. Defaults to {@code 1.6}.
+ *
+ * @return the consume duration, in seconds
+ */
+ @Positive float consumeSeconds();
+
+ /**
+ * The animation that should play when consuming the item. Defaults to {@link Animation#EAT}.
+ *
+ * @return the animation to play
+ */
+ @NonNull Animation animation();
+
+ /**
+ * Creates a builder for the consumable component.
+ *
+ * @return a new builder
+ */
+ static Builder builder() {
+ return GeyserApi.api().provider(Consumable.Builder.class);
+ }
+
+ /**
+ * Creates a consumable component.
+ *
+ * @param consumeSeconds the consume duration, in seconds
+ * @param animation the animation to play when consuming
+ * @return the consumable component
+ */
+ static Consumable of(float consumeSeconds, Animation animation) {
+ return Consumable.builder().consumeSeconds(consumeSeconds).animation(animation).build();
+ }
+
+ /**
+ * Not all animations work perfectly on bedrock. Bedrock behaviour is noted per animation. The {@code toot_horn} animation doesn't exist on bedrock, and is therefore not listed here.
+ */
+ enum Animation {
+ /**
+ * Does nothing in 1st person, appears as eating in 3rd person.
+ */
+ NONE,
+ /**
+ * Appears to look correctly.
+ */
+ EAT,
+ /**
+ * Appears to look correctly.
+ */
+ DRINK,
+ /**
+ * Does nothing in 1st person, eating in 3rd person.
+ */
+ BLOCK,
+ /**
+ * Does nothing in 1st person, eating in 3rd person.
+ */
+ BOW,
+ /**
+ * Does nothing in 1st person, but looks like spear in 3rd person.
+ */
+ SPEAR,
+ /**
+ * Does nothing in 1st person, eating in 3rd person.
+ */
+ CROSSBOW,
+ /**
+ * Does nothing in 1st person, but looks like spyglass in 3rd person.
+ */
+ SPYGLASS,
+ /**
+ * Brush in 1st and 3rd person. Will look weird when not displayed handheld.
+ */
+ BRUSH;
+ }
+
+ /**
+ * Builder for the consumable component.
+ */
+ interface Builder extends GenericBuilder {
+ /**
+ * Sets the time in seconds that consumption takes. This also
+ * determines the animation length.
+ *
+ * @param consumeSeconds the seconds it takes to consume the item
+ * @see Consumable#consumeSeconds()
+ * @return this builder
+ */
+ @This
+ Builder consumeSeconds(@Positive float consumeSeconds);
+
+ /**
+ * Sets the animation to play when consuming the item.
+ * See {@link Animation} for more details - some animations
+ * do not work correctly.
+ *
+ * @param animation the animation to play
+ * @see Consumable#animation()
+ * @return this builder
+ */
+ @This
+ Builder animation(@NonNull Animation animation);
+
+ /**
+ * Creates the consumable component.
+ *
+ * @return the new component
+ */
+ @Override
+ Consumable build();
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/Equippable.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/Equippable.java
new file mode 100644
index 00000000000..d28ab19f6fd
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/Equippable.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.item.custom.v2.component.java;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.common.returnsreceiver.qual.This;
+import org.geysermc.geyser.api.GeyserApi;
+import org.geysermc.geyser.api.util.GenericBuilder;
+
+/**
+ * The equippable component is used to mark an item as equippable.
+ * Bedrock allows specifying the slot where an item can be worn.
+ */
+public interface Equippable {
+
+ /**
+ * The equipment slot where this item
+ * can be worn.
+ *
+ * @return the equipment slot
+ */
+ @NonNull EquipmentSlot slot();
+
+ /**
+ * Creates a builder for the equippable component.
+ *
+ * @return a new builder
+ */
+ static Builder builder() {
+ return GeyserApi.api().provider(Equippable.Builder.class);
+ }
+
+ /**
+ * Creates an equippable component for an equipment slot.
+ *
+ * @param slot the slot in which the item can be equipped
+ * @return the Equippable component
+ */
+ static Equippable of(EquipmentSlot slot) {
+ return builder().slot(slot).build();
+ }
+
+ /**
+ * Builder for the equippable component
+ */
+ interface Builder extends GenericBuilder {
+
+ /**
+ * The equipment slot where the item can be equipped
+ *
+ * @param slot the equipment slot
+ * @see Equippable#slot()
+ * @return this builder
+ */
+ @This
+ Builder slot(@NonNull EquipmentSlot slot);
+
+ /**
+ * Creates the equippable component.
+ *
+ * @return the new component
+ */
+ @Override
+ Equippable build();
+ }
+
+ /**
+ * The slot in which the equipment can be worn.
+ */
+ enum EquipmentSlot {
+ HEAD,
+ CHEST,
+ LEGS,
+ FEET,
+ BODY,
+ SADDLE
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/FoodProperties.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/FoodProperties.java
new file mode 100644
index 00000000000..97fe528769a
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/FoodProperties.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.item.custom.v2.component.java;
+
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.common.returnsreceiver.qual.This;
+import org.geysermc.geyser.api.GeyserApi;
+import org.geysermc.geyser.api.util.GenericBuilder;
+
+/**
+ * The food properties component can be used to define properties
+ * for consumable items. This includes setting the nutrition and
+ * saturation values, and whether the item can always be eaten.
+ */
+public interface FoodProperties {
+
+ /**
+ * The nutrition of the item. Defaults to {@code 0}.
+ *
+ * @return the nutrition
+ */
+ @NonNegative int nutrition();
+
+ /**
+ * The saturation of the item. Defaults to {@code 0.0}.
+ *
+ * @return the saturation
+ */
+ @NonNegative float saturation();
+
+ /**
+ * Whether this item can always be eaten,
+ * even when not hungry. In vanilla, this would
+ * include items such as golden apples. Defaults to {@code false}.
+ *
+ * @return whether the item can always be eaten
+ */
+ boolean canAlwaysEat();
+
+ /**
+ * Creates a builder for the food properties component.
+ *
+ * @return a new builder
+ */
+ static Builder builder() {
+ return GeyserApi.api().provider(FoodProperties.Builder.class);
+ }
+
+ /**
+ * Creates a food properties component.
+ *
+ * @param nutrition the nutrition of the item
+ * @param saturation the saturation of the item
+ * @param canAlwaysEat whether the item can always be eaten
+ * @return the food properties component
+ */
+ static FoodProperties of(int nutrition, float saturation, boolean canAlwaysEat) {
+ return FoodProperties.builder().nutrition(nutrition).saturation(saturation).canAlwaysEat(canAlwaysEat).build();
+ }
+
+ /**
+ * Builder for the food properties component.
+ */
+ interface Builder extends GenericBuilder {
+
+ /**
+ * Sets the nutrition of the item which is added to the hunger bar.
+ *
+ * @param nutrition the nutrition of the item.
+ * @see FoodProperties#nutrition()
+ * @return this builder
+ */
+ @This
+ Builder nutrition(@NonNegative int nutrition);
+
+ /**
+ * Sets the saturation of the item.
+ *
+ * @param saturation the saturation of the item
+ * @see FoodProperties#saturation()
+ * @return this builder
+ */
+ @This
+ Builder saturation(@NonNegative float saturation);
+
+ /**
+ * Sets whether this item can always be eaten,
+ * even when the hunger bar is full.
+ *
+ * @param canAlwaysEat whether the item can always be eaten
+ * @see FoodProperties#canAlwaysEat()
+ * @return this builder
+ */
+ @This
+ Builder canAlwaysEat(boolean canAlwaysEat);
+
+ /**
+ * Creates the food properties component.
+ *
+ * @return the new component
+ */
+ @Override
+ FoodProperties build();
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/ItemDataComponents.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/ItemDataComponents.java
new file mode 100644
index 00000000000..c612b15cea0
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/ItemDataComponents.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.item.custom.v2.component.java;
+
+import org.geysermc.geyser.api.GeyserApi;
+import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition;
+import org.geysermc.geyser.api.item.custom.v2.component.DataComponent;
+import org.geysermc.geyser.api.item.custom.v2.component.geyser.GeyserDataComponent;
+import org.geysermc.geyser.api.util.Identifier;
+
+import java.util.function.Predicate;
+
+/**
+ * Represents various Java item data components to the extent
+ * that these can be translated to custom items for Bedrock edition players.
+ * These can be set in {@link CustomItemDefinition.Builder#component(DataComponent, Object)}
+ * to specify the item's behavior. It's expected that these components would also
+ * be present on the item server-side to avoid de-syncs.
+ *
+ * @see CustomItemDefinition#components()
+ * @see GeyserDataComponent
+ */
+public interface ItemDataComponents {
+
+ /**
+ * Marks the item as consumable. Of this component, only {@code consume_seconds} and {@code animation} properties are translated. Consume effects are done server side,
+ * and consume sounds and disabling consume particles aren't possible.
+ *
+ * Note that due to a bug on Bedrock, not all consume animations appear perfectly. See {@link Consumable.Animation}.
+ *
+ * @see Consumable
+ */
+ DataComponent CONSUMABLE = create("consumable");
+
+ /**
+ * Marks the item as equippable. Of this component, only the {@code slot} property is translated. Other properties are done server-side, are done differently on Bedrock (e.g. {@code asset_id} is done via attachables),
+ * or are not possible on Bedrock at all (e.g. {@code camera_overlay}).
+ *
+ * Note that on Bedrock, equippables can't have a stack size above 1.
+ *
+ * @see Equippable
+ */
+ DataComponent EQUIPPABLE = create("equippable");
+
+ /**
+ * Food properties of the item. All properties properly translate over to Bedrock.
+ *
+ * @see FoodProperties
+ */
+ DataComponent FOOD = create("food");
+
+ /**
+ * Max damage value of the item. Must be at or above 0. Items with a max damage value above 0 cannot have a stack size above 1.
+ */
+ DataComponent MAX_DAMAGE = create("max_damage", i -> i >= 0);
+
+ /**
+ * Max stack size of the item. Must be between 1 and 99. Items with a max stack size value above 1 cannot have a max damage value above 0.
+ */
+ DataComponent MAX_STACK_SIZE = create("max_stack_size", i -> i >= 1 && i <= 99); // Reverse lambda
+
+ /**
+ * Marks the item to have a use cooldown. To properly function, the item must be able to be used: it must be consumable or have some other kind of use logic.
+ *
+ * The cooldown group can be {@code null}, in this case the identifier of the vanilla item (in case of vanilla custom items),
+ * or the item itself (in case of non-vanilla custom items) will be used.
+ *
+ * @see UseCooldown
+ */
+ DataComponent USE_COOLDOWN = create("use_cooldown");
+
+ /**
+ * Marks the item to be enchantable. Must be at or above 0.
+ *
+ * This component does not translate over perfectly, due to the way enchantments work on Bedrock. The component will be mapped to the {@code minecraft:enchantable} bedrock component with {@code slot=all}.
+ * This should, but does not guarantee, allow for compatibility with vanilla enchantments. Non-vanilla enchantments are unlikely to work.
+ */
+ DataComponent ENCHANTABLE = create("enchantable", i -> i >= 0);
+
+ /**
+ * This component is only used for the {@link ToolProperties#canDestroyBlocksInCreative()} option.
+ *
+ *
+ * @see ToolProperties
+ */
+ DataComponent TOOL = create("tool");
+
+ /**
+ * Marks which items can be used to repair the item.
+ *
+ * @see Repairable
+ */
+ DataComponent REPAIRABLE = create("repairable");
+
+ /**
+ * Overrides the item's enchantment glint.
+ */
+ DataComponent ENCHANTMENT_GLINT_OVERRIDE = create("enchantment_glint_override");
+
+ private static DataComponent create(String id) {
+ return create(id, t -> true);
+ }
+
+ private static DataComponent create(String id, Predicate consumer) {
+ return GeyserApi.api().provider(DataComponent.class, Identifier.of(id), consumer, true);
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/Repairable.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/Repairable.java
new file mode 100644
index 00000000000..579784997b2
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/Repairable.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.item.custom.v2.component.java;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.common.returnsreceiver.qual.This;
+import org.geysermc.geyser.api.GeyserApi;
+import org.geysermc.geyser.api.util.GenericBuilder;
+import org.geysermc.geyser.api.util.Identifier;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The repairable component determines which other items can be used
+ * to repair the item.
+ */
+public interface Repairable {
+
+ /**
+ * The Bedrock identifiers of the items
+ * that can be used to repair this item.
+ *
+ * @return the identifiers
+ */
+ List<@NonNull Identifier> items();
+
+ /**
+ * Creates a builder for the repairable component.
+ *
+ * @return a new builder
+ */
+ static Builder builder() {
+ return GeyserApi.api().provider(Repairable.Builder.class);
+ }
+
+ /**
+ * Creates a repairable component.
+ *
+ * @param items the identifiers of the items that
+ * can repair the item
+ * @return the repairable component
+ */
+ static Repairable of(Identifier... items) {
+ Repairable.Builder builder = builder();
+ Arrays.stream(items).forEach(builder::item);
+ return builder.build();
+ }
+
+ /**
+ * Builder for the repairable component.
+ */
+ interface Builder extends GenericBuilder {
+
+ /**
+ * Adds an item that can be used to repair the item.
+ * This will throw when trying to add an item that was already added.
+ *
+ * @param item the Bedrock item identifier that can be used to repair the item
+ * @see Repairable#items()
+ * @return this builder
+ */
+ @This
+ Builder item(@NonNull Identifier item);
+
+ /**
+ * Creates the repairable component.
+ *
+ * @return the new component
+ */
+ @Override
+ Repairable build();
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/ToolProperties.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/ToolProperties.java
new file mode 100644
index 00000000000..caebfd2e137
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/ToolProperties.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.item.custom.v2.component.java;
+
+import org.checkerframework.common.returnsreceiver.qual.This;
+import org.geysermc.geyser.api.GeyserApi;
+import org.geysermc.geyser.api.util.GenericBuilder;
+
+/**
+ * The tool properties component can be used to mark
+ * if the item can destroy blocks when used in creative mode.
+ */
+public interface ToolProperties {
+
+ /**
+ * Whether this item can destroy blocks when trying to break them in
+ * creative mode. Defaults to {@code true}.
+ *
+ * @return whether this item can destroy blocks in creative mode
+ */
+ boolean canDestroyBlocksInCreative();
+
+ /**
+ * Creates a builder for the tool properties component.
+ *
+ * @return a new builder
+ */
+ static Builder builder() {
+ return GeyserApi.api().provider(ToolProperties.Builder.class);
+ }
+
+ /**
+ * Creates a tool properties component.
+ *
+ * @param canDestroyBlocksInCreative determines if the item will break blocks in creative mode
+ * @return a tool properties component
+ */
+ static ToolProperties of(boolean canDestroyBlocksInCreative) {
+ return builder().canDestroyBlocksInCreative(canDestroyBlocksInCreative).build();
+ }
+
+ /**
+ * Builder for the tool properties component.
+ */
+ interface Builder extends GenericBuilder {
+
+ /**
+ * Sets whether this item can destroy blocks when trying to break them in
+ * creative mode.
+ *
+ * @param canDestroyBlocksInCreative determines if the item will break blocks in creative mode
+ * @see ToolProperties#canDestroyBlocksInCreative()
+ * @return this builder
+ */
+ @This
+ Builder canDestroyBlocksInCreative(boolean canDestroyBlocksInCreative);
+
+ /**
+ * Creates the tool properties component.
+ *
+ * @return the new component
+ */
+ @Override
+ ToolProperties build();
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/UseCooldown.java b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/UseCooldown.java
new file mode 100644
index 00000000000..b876f7da8be
--- /dev/null
+++ b/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/java/UseCooldown.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.api.item.custom.v2.component.java;
+
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.common.returnsreceiver.qual.This;
+import org.geysermc.geyser.api.GeyserApi;
+import org.geysermc.geyser.api.util.GenericBuilder;
+import org.geysermc.geyser.api.util.Identifier;
+
+/**
+ * The use cooldown component is used to add an item use cooldown to items.
+ */
+public interface UseCooldown {
+
+ /**
+ * The duration of time in seconds items with a matching category will
+ * spend cooling down before being usable again.
+ *
+ * @return the cooldown duration
+ */
+ @Positive float seconds();
+
+ /**
+ * The cooldown type of the item. Other items in this group
+ * will also have the cooldown applied when any item of this
+ * group is used. {@code null} will result in the item identifier
+ * being used.
+ *
+ * @return the cooldown identifier
+ */
+ @Nullable Identifier cooldownGroup();
+
+ /**
+ * Creates a builder for the use cooldown component.
+ *
+ * @return a new builder
+ */
+ static Builder builder() {
+ return GeyserApi.api().provider(UseCooldown.Builder.class);
+ }
+
+ /**
+ * Builder for the use cooldown component.
+ */
+ interface Builder extends GenericBuilder {
+
+ /**
+ * Sets the duration in seconds in which the item
+ * cannot be used again.
+ *
+ * @param seconds the cooldown time
+ * @see UseCooldown#seconds()
+ * @return this builder
+ */
+ @This
+ Builder seconds(@Positive float seconds);
+
+ /**
+ * Sets the cooldown group that this cooldown belongs to.
+ * When any item in this group is used, all items in the group
+ * are not usable for the amount of time specified in {@link Builder#seconds()}
+ * {@code null} results in the item identifier being specified instead.
+ *
+ * @param cooldownGroup the cooldown group identifier
+ * @see UseCooldown#cooldownGroup()
+ * @return this builder
+ */
+ @This
+ Builder cooldownGroup(@Nullable Identifier cooldownGroup);
+
+ /**
+ * Creates the use cooldown component.
+ *
+ * @return the new component
+ */
+ @Override
+ UseCooldown build();
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/predicate/MinecraftPredicate.java b/api/src/main/java/org/geysermc/geyser/api/predicate/MinecraftPredicate.java
index 1bd93d7e6a0..b9258b0d708 100644
--- a/api/src/main/java/org/geysermc/geyser/api/predicate/MinecraftPredicate.java
+++ b/api/src/main/java/org/geysermc/geyser/api/predicate/MinecraftPredicate.java
@@ -35,7 +35,7 @@
/**
* A predicate for a {@link MinecraftPredicateContext}.
*
- * Right now this is used to determine if a {@link CustomItemDefinition} should be used. While this does
+ *
This is used to determine if a {@link CustomItemDefinition} should be used. While this does
* allow you to define your own predicates with custom checks, it is recommended to use commonly defined predicates and predicate creators when possible,
* since these support conflict detection among other benefits. See {@link CustomItemDefinition#predicates()}.
*
diff --git a/api/src/main/java/org/geysermc/geyser/api/predicate/PredicateStrategy.java b/api/src/main/java/org/geysermc/geyser/api/predicate/PredicateStrategy.java
index 02b1d747b54..0315daa1578 100644
--- a/api/src/main/java/org/geysermc/geyser/api/predicate/PredicateStrategy.java
+++ b/api/src/main/java/org/geysermc/geyser/api/predicate/PredicateStrategy.java
@@ -25,6 +25,9 @@
package org.geysermc.geyser.api.predicate;
+/**
+ * Represents all possible strategies for evaluating multiple predicates.
+ */
public enum PredicateStrategy {
/**
* Require all predicates to pass
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/BlockPlacer.java b/api/src/main/java/org/geysermc/geyser/api/util/GenericBuilder.java
similarity index 82%
rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/BlockPlacer.java
rename to api/src/main/java/org/geysermc/geyser/api/util/GenericBuilder.java
index 3aed4918a39..39066726cac 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/BlockPlacer.java
+++ b/api/src/main/java/org/geysermc/geyser/api/util/GenericBuilder.java
@@ -23,9 +23,18 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.api.item.custom.v2.component;
+package org.geysermc.geyser.api.util;
-import org.geysermc.geyser.api.util.Identifier;
+/**
+ * An interface for builders.
+ *
+ * @param the type of the object built
+ */
+public interface GenericBuilder {
-public record BlockPlacer(Identifier block, boolean useBlockIcon) {
+ /**
+ * Builds the object from the builder
+ * @return the object
+ */
+ T build();
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/util/Identifier.java b/api/src/main/java/org/geysermc/geyser/api/util/Identifier.java
index 679d2f9798f..1e5c1fd6b52 100644
--- a/api/src/main/java/org/geysermc/geyser/api/util/Identifier.java
+++ b/api/src/main/java/org/geysermc/geyser/api/util/Identifier.java
@@ -25,15 +25,54 @@
package org.geysermc.geyser.api.util;
+import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.GeyserApi;
+/**
+ * An identifying object for representing unique objects.
+ * This identifier consists of two parts:
+ *
+ * -
+ * a namespace, which is usually a name identifying your work
+ *
+ * -
+ * a path, which holds a value.
+ *
+ *
+ *
+ * Examples of identifiers:
+ *
+ * - {@code minecraft:fox}
+ * - {@code geysermc:one_fun_example}
+ *
+ *
+ * If this identifier is referencing anything not in the
+ * vanilla Minecraft game, the namespace cannot be "minecraft".
+ * Further, paths cannot contain colons ({@code :}).
+ */
public interface Identifier {
+
+ /**
+ * The namespace for Minecraft.
+ */
String DEFAULT_NAMESPACE = "minecraft";
- static Identifier of(String namespace, String path) {
+ /**
+ * Attempts to create a new identifier from a namespace and path.
+ *
+ * @return the identifier for this namespace and path
+ * @throws IllegalArgumentException if either namespace or path are invalid.
+ */
+ static Identifier of(@NonNull String namespace, @NonNull String path) {
return GeyserApi.api().provider(Identifier.class, namespace, path);
}
+ /**
+ * Attempts to create a new identifier from a string representation.
+ *
+ * @return the identifier for this namespace and path
+ * @throws IllegalArgumentException if either the namespace or path are invalid
+ */
static Identifier of(String identifier) {
String[] split = identifier.split(":");
String namespace;
@@ -50,10 +89,19 @@ static Identifier of(String identifier) {
return of(namespace, path);
}
+ /**
+ * Returns the namespace of this identifier.
+ */
String namespace();
+ /**
+ * Returns the path of this identifier.
+ */
String path();
-
+
+ /**
+ * Checks whether this identifier is using the "minecraft" namespace.
+ */
default boolean vanilla() {
return namespace().equals(DEFAULT_NAMESPACE);
}
diff --git a/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java
index b5bb07ed054..a0350ab0a72 100644
--- a/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java
+++ b/core/src/main/java/org/geysermc/geyser/event/type/GeyserDefineCustomItemsEventImpl.java
@@ -90,7 +90,7 @@ public boolean register(@NonNull String identifier, @NonNull CustomItemData cust
}
@Override
- public boolean register(NonVanillaCustomItemData customItemData) {
+ public boolean register(@NonNull NonVanillaCustomItemData customItemData) {
try {
register(customItemData.toDefinition().build());
deprecatedNonVanillaCustomItems.add(customItemData);
diff --git a/core/src/main/java/org/geysermc/geyser/impl/IdentifierImpl.java b/core/src/main/java/org/geysermc/geyser/impl/IdentifierImpl.java
index 169c8fb523b..6980c9fbf9f 100644
--- a/core/src/main/java/org/geysermc/geyser/impl/IdentifierImpl.java
+++ b/core/src/main/java/org/geysermc/geyser/impl/IdentifierImpl.java
@@ -27,9 +27,22 @@
import net.kyori.adventure.key.Key;
import org.geysermc.geyser.api.util.Identifier;
+import org.geysermc.geyser.util.MinecraftKey;
+
+import java.util.Objects;
public record IdentifierImpl(Key identifier) implements Identifier {
+ public static IdentifierImpl of(String namespace, String value) throws IllegalArgumentException {
+ Objects.requireNonNull(namespace, "namespace cannot be null!");
+ Objects.requireNonNull(value, "value cannot be null!");
+ try {
+ return new IdentifierImpl(MinecraftKey.key(namespace, value));
+ } catch (Throwable e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ }
+
@Override
public String namespace() {
return identifier.namespace();
diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java b/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java
index 582173e1cc1..072b6f6be47 100644
--- a/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java
+++ b/core/src/main/java/org/geysermc/geyser/item/custom/ComponentConverters.java
@@ -28,8 +28,10 @@
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.geysermc.geyser.api.item.custom.v2.component.DataComponent;
import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap;
-import org.geysermc.geyser.api.item.custom.v2.component.Repairable;
+import org.geysermc.geyser.api.item.custom.v2.component.java.ItemDataComponents;
+import org.geysermc.geyser.api.item.custom.v2.component.java.Repairable;
import org.geysermc.geyser.api.util.Identifier;
+import org.geysermc.geyser.registry.populator.CustomItemRegistryPopulator;
import org.geysermc.geyser.util.MinecraftKey;
import org.geysermc.mcprotocollib.protocol.data.game.entity.EquipmentSlot;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Consumable;
@@ -60,18 +62,18 @@
*
* Most components convert over nicely, and it is very much preferred to have every API component have a converter in here. However, this is not always possible. At the moment, there are 2 exceptions:
*
- * - The MCPL counterpart of the {@link DataComponent#REPAIRABLE} component is just an ID holder set, which can't be used in the custom item registry populator.
+ *
- The MCPL counterpart of the {@link ItemDataComponents#REPAIRABLE} component is just an ID holder set, which can't be used in the custom item registry populator.
* Also see {@link org.geysermc.geyser.registry.populator.CustomItemRegistryPopulator#computeRepairableProperties(Repairable, NbtMapBuilder)}.
- * - Non-vanilla data components (from {@link org.geysermc.geyser.api.item.custom.v2.component.GeyserDataComponent}) don't have converters registered, for obvious reasons.
+ *
- Non-vanilla data components (from {@link org.geysermc.geyser.api.item.custom.v2.component.geyser.GeyserDataComponent}) don't have converters registered, for obvious reasons.
* They're used directly in the custom item registry populator. Eventually, some may have converters introduced as Mojang introduces such components in Java.
*
- * For both of these cases proper accommodations have been made in the {@link org.geysermc.geyser.registry.populator.CustomItemRegistryPopulator}.
+ * For both of these cases proper accommodations have been made in the {@link CustomItemRegistryPopulator}.
*/
public class ComponentConverters {
private static final Map, ComponentConverter>> converters = new HashMap<>();
static {
- registerConverter(DataComponent.CONSUMABLE, (itemMap, value) -> {
+ registerConverter(ItemDataComponents.CONSUMABLE, (itemMap, value) -> {
Consumable.ItemUseAnimation convertedAnimation = switch (value.animation()) {
case NONE -> Consumable.ItemUseAnimation.NONE;
case EAT -> Consumable.ItemUseAnimation.EAT;
@@ -87,7 +89,7 @@ public class ComponentConverters {
true, List.of()));
});
- registerConverter(DataComponent.EQUIPPABLE, (itemMap, value) -> {
+ registerConverter(ItemDataComponents.EQUIPPABLE, (itemMap, value) -> {
EquipmentSlot convertedSlot = switch (value.slot()) {
case HEAD -> EquipmentSlot.HELMET;
case CHEST -> EquipmentSlot.CHESTPLATE;
@@ -100,21 +102,21 @@ public class ComponentConverters {
null, null, null, false, false, false, false, false, null));
});
- registerConverter(DataComponent.FOOD, (itemMap, value) -> itemMap.put(DataComponentTypes.FOOD,
+ registerConverter(ItemDataComponents.FOOD, (itemMap, value) -> itemMap.put(DataComponentTypes.FOOD,
new FoodProperties(value.nutrition(), value.saturation(), value.canAlwaysEat())));
- registerConverter(DataComponent.MAX_DAMAGE, (itemMap, value) -> itemMap.put(DataComponentTypes.MAX_DAMAGE, value));
- registerConverter(DataComponent.MAX_STACK_SIZE, (itemMap, value) -> itemMap.put(DataComponentTypes.MAX_STACK_SIZE, value));
+ registerConverter(ItemDataComponents.MAX_DAMAGE, (itemMap, value) -> itemMap.put(DataComponentTypes.MAX_DAMAGE, value));
+ registerConverter(ItemDataComponents.MAX_STACK_SIZE, (itemMap, value) -> itemMap.put(DataComponentTypes.MAX_STACK_SIZE, value));
- registerConverter(DataComponent.USE_COOLDOWN, (itemMap, value) -> itemMap.put(DataComponentTypes.USE_COOLDOWN,
+ registerConverter(ItemDataComponents.USE_COOLDOWN, (itemMap, value) -> itemMap.put(DataComponentTypes.USE_COOLDOWN,
new UseCooldown(value.seconds(), MinecraftKey.identifierToKey(value.cooldownGroup()))));
- registerConverter(DataComponent.ENCHANTABLE, (itemMap, value) -> itemMap.put(DataComponentTypes.ENCHANTABLE, value));
+ registerConverter(ItemDataComponents.ENCHANTABLE, (itemMap, value) -> itemMap.put(DataComponentTypes.ENCHANTABLE, value));
- registerConverter(DataComponent.TOOL, (itemMap, value) -> itemMap.put(DataComponentTypes.TOOL,
+ registerConverter(ItemDataComponents.TOOL, (itemMap, value) -> itemMap.put(DataComponentTypes.TOOL,
new ToolData(List.of(), 1.0F, 1, value.canDestroyBlocksInCreative())));
- registerConverter(DataComponent.ENCHANTMENT_GLINT_OVERRIDE, (itemMap, value) -> itemMap.put(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, value));
+ registerConverter(ItemDataComponents.ENCHANTMENT_GLINT_OVERRIDE, (itemMap, value) -> itemMap.put(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, value));
}
private static void registerConverter(DataComponent component, ComponentConverter converter) {
diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java
index 6fc4d77ec35..9c44028a9e6 100644
--- a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java
+++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserCustomItemDefinition.java
@@ -32,15 +32,17 @@
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions;
import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition;
-import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap;
import org.geysermc.geyser.api.item.custom.v2.component.DataComponent;
+import org.geysermc.geyser.api.item.custom.v2.component.DataComponentMap;
import org.geysermc.geyser.api.predicate.MinecraftPredicate;
import org.geysermc.geyser.api.predicate.PredicateStrategy;
import org.geysermc.geyser.api.predicate.context.item.ItemPredicateContext;
import org.geysermc.geyser.api.util.Identifier;
+import org.geysermc.geyser.item.custom.impl.DataComponentImpl;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
@EqualsAndHashCode
@@ -89,6 +91,7 @@ public GeyserCustomItemDefinition(Builder builder) {
}
@Override
+ @NonNull
public PredicateStrategy predicateStrategy() {
return predicateStrategy;
}
@@ -125,7 +128,9 @@ public static class Builder implements CustomItemDefinition.Builder {
private CustomItemBedrockOptions bedrockOptions = CustomItemBedrockOptions.builder().build();
private PredicateStrategy predicateStrategy = PredicateStrategy.AND;
- public Builder(Identifier bedrockIdentifier, Identifier model) {
+ public Builder(@NonNull Identifier bedrockIdentifier, @NonNull Identifier model) {
+ Objects.requireNonNull(bedrockIdentifier, "bedrockIdentifier cannot be null");
+ Objects.requireNonNull(model, "model cannot be null");
this.bedrockIdentifier = bedrockIdentifier;
this.displayName = bedrockIdentifier.toString();
this.model = model;
@@ -133,6 +138,7 @@ public Builder(Identifier bedrockIdentifier, Identifier model) {
@Override
public CustomItemDefinition.Builder displayName(@NonNull String displayName) {
+ Objects.requireNonNull(displayName, "displayName cannot be null");
this.displayName = displayName;
return this;
}
@@ -145,27 +151,36 @@ public CustomItemDefinition.Builder priority(int priority) {
@Override
public CustomItemDefinition.Builder bedrockOptions(CustomItemBedrockOptions.@NonNull Builder options) {
+ Objects.requireNonNull(options, "options cannot be null");
this.bedrockOptions = options.build();
return this;
}
@Override
public CustomItemDefinition.Builder predicate(@NonNull MinecraftPredicate super ItemPredicateContext> predicate) {
+ Objects.requireNonNull(predicate, "predicate cannot be null");
predicates.add(predicate);
return this;
}
@Override
public CustomItemDefinition.Builder predicateStrategy(@NonNull PredicateStrategy strategy) {
+ Objects.requireNonNull(strategy, "strategy cannot be null");
predicateStrategy = strategy;
return this;
}
@Override
public CustomItemDefinition.Builder component(@NonNull DataComponent component, @NonNull T value) {
+ Objects.requireNonNull(component, "component cannot be null");
+ Objects.requireNonNull(value, "value cannot be null");
+ if (!(component instanceof DataComponentImpl dataComponent)) {
+ throw new IllegalArgumentException("Cannot use custom implementations of the DataComponent interface! Found: " + component.getClass().getSimpleName());
+ }
+
if (!component.vanilla() && !(this instanceof GeyserNonVanillaCustomItemDefinition.Builder)) {
throw new IllegalArgumentException("That component cannot be used for vanilla items");
- } else if (!component.validate(value)) {
+ } else if (!dataComponent.validate(value)) {
throw new IllegalArgumentException("Value " + value + " is invalid for " + component);
}
components.put(component, value);
@@ -173,7 +188,8 @@ public CustomItemDefinition.Builder component(@NonNull DataComponent comp
}
@Override
- public CustomItemDefinition.Builder removeComponent(Identifier component) {
+ public CustomItemDefinition.Builder removeComponent(@NonNull Identifier component) {
+ Objects.requireNonNull(component, "component cannot be null");
removedComponents.add(component);
return this;
}
diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserNonVanillaCustomItemDefinition.java b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserNonVanillaCustomItemDefinition.java
index 8cf82a3ec8b..fc02d4b1643 100644
--- a/core/src/main/java/org/geysermc/geyser/item/custom/GeyserNonVanillaCustomItemDefinition.java
+++ b/core/src/main/java/org/geysermc/geyser/item/custom/GeyserNonVanillaCustomItemDefinition.java
@@ -112,7 +112,7 @@ public NonVanillaCustomItemDefinition.Builder component(@NonNull DataCompone
}
@Override
- public CustomItemDefinition.Builder removeComponent(Identifier component) {
+ public CustomItemDefinition.Builder removeComponent(@NonNull Identifier component) {
throw new UnsupportedOperationException("Removing default item components is not supported for non-vanilla items");
}
diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/impl/BlockPlacerImpl.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/BlockPlacerImpl.java
new file mode 100644
index 00000000000..2e161e6eded
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/BlockPlacerImpl.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.item.custom.impl;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.geyser.api.item.custom.v2.component.geyser.BlockPlacer;
+import org.geysermc.geyser.api.util.Identifier;
+
+import java.util.Objects;
+
+public record BlockPlacerImpl(
+ Identifier block,
+ boolean useBlockIcon
+) implements BlockPlacer {
+
+ public static class Builder implements BlockPlacer.Builder {
+ private Identifier block;
+ private boolean useBlockIcon;
+
+ @Override
+ public Builder block(@NonNull Identifier block) {
+ Objects.requireNonNull(block, "block cannot be null");
+ this.block = block;
+ return this;
+ }
+
+ @Override
+ public Builder useBlockIcon(boolean useBlockIcon) {
+ this.useBlockIcon = useBlockIcon;
+ return this;
+ }
+
+ @Override
+ public BlockPlacer build() {
+ Objects.requireNonNull(block, "block cannot be null");
+ return new BlockPlacerImpl(block, useBlockIcon);
+ }
+ }
+}
diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/impl/ChargeableImpl.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/ChargeableImpl.java
new file mode 100644
index 00000000000..c19276c0754
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/ChargeableImpl.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.item.custom.impl;
+
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.geyser.api.item.custom.v2.component.geyser.Chargeable;
+import org.geysermc.geyser.api.util.Identifier;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public record ChargeableImpl(
+ @NonNegative float maxDrawDuration,
+ boolean chargeOnDraw,
+ @NonNull List<@NonNull Identifier> ammunition
+) implements Chargeable {
+
+ public static class Builder implements Chargeable.Builder {
+ private float maxDrawDuration;
+ private boolean chargeOnDraw;
+ private final List ammunition = new ArrayList<>();
+
+ @Override
+ public Chargeable.Builder maxDrawDuration(@NonNegative float maxDrawDuration) {
+ if (maxDrawDuration < 0) {
+ throw new IllegalArgumentException("maxDrawDuration must be positive");
+ }
+ this.maxDrawDuration = maxDrawDuration;
+ return this;
+ }
+
+ @Override
+ public Chargeable.Builder chargeOnDraw(boolean chargeOnDraw) {
+ this.chargeOnDraw = chargeOnDraw;
+ return this;
+ }
+
+ @Override
+ public Chargeable.Builder ammunition(@NonNull Identifier ammunition) {
+ Objects.requireNonNull(ammunition, "ammunition cannot be null");
+ if (this.ammunition.contains(ammunition)) {
+ throw new IllegalArgumentException("duplicate ammunition " + ammunition);
+ }
+ this.ammunition.add(ammunition);
+ return this;
+ }
+
+ @Override
+ public Chargeable build() {
+ Objects.requireNonNull(ammunition, "ammunition cannot be null");
+ return new ChargeableImpl(maxDrawDuration, chargeOnDraw, ammunition);
+ }
+ }
+}
diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/impl/ConsumableImpl.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/ConsumableImpl.java
new file mode 100644
index 00000000000..ca2de75d058
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/ConsumableImpl.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.item.custom.impl;
+
+import org.checkerframework.checker.index.qual.Positive;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.geyser.api.item.custom.v2.component.java.Consumable;
+
+import java.util.Objects;
+
+public record ConsumableImpl(
+ float consumeSeconds,
+ @NonNull Animation animation
+) implements Consumable {
+
+ public static class Builder implements Consumable.Builder {
+ private float consumeSeconds = 1.6F;
+ private Animation animation = Animation.EAT;
+
+ @Override
+ public Builder consumeSeconds(@Positive float consumeSeconds) {
+ if (consumeSeconds <= 0.0F) {
+ throw new IllegalArgumentException("consume seconds must be above 0");
+ }
+ this.consumeSeconds = consumeSeconds;
+ return this;
+ }
+
+ @Override
+ public Builder animation(@NonNull Animation animation) {
+ Objects.requireNonNull(animation, "animation cannot be null");
+ this.animation = animation;
+ return this;
+ }
+
+ @Override
+ public Consumable build() {
+ return new ConsumableImpl(consumeSeconds, animation);
+ }
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Repairable.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/DataComponentImpl.java
similarity index 76%
rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Repairable.java
rename to core/src/main/java/org/geysermc/geyser/item/custom/impl/DataComponentImpl.java
index 9a7c01d0696..d735b4a68eb 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Repairable.java
+++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/DataComponentImpl.java
@@ -23,9 +23,22 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.api.item.custom.v2.component;
+package org.geysermc.geyser.item.custom.impl;
+import org.geysermc.geyser.api.item.custom.v2.component.DataComponent;
import org.geysermc.geyser.api.util.Identifier;
-public record Repairable(Identifier... items) {
+import java.util.function.Predicate;
+
+public record DataComponentImpl(
+ Identifier identifier,
+ Predicate validator,
+ boolean vanilla
+) implements DataComponent {
+
+ public boolean validate(T value) {
+ return this.validator.test(value);
+ }
+
}
+
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/FoodProperties.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/EquippableImpl.java
similarity index 61%
rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/FoodProperties.java
rename to core/src/main/java/org/geysermc/geyser/item/custom/impl/EquippableImpl.java
index aa1229fa230..f8883a27df3 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/FoodProperties.java
+++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/EquippableImpl.java
@@ -23,15 +23,31 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.api.item.custom.v2.component;
+package org.geysermc.geyser.item.custom.impl;
-import org.checkerframework.checker.index.qual.NonNegative;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.geyser.api.item.custom.v2.component.java.Equippable;
-public record FoodProperties(@NonNegative int nutrition, @NonNegative float saturation, boolean canAlwaysEat) {
+import java.util.Objects;
- public FoodProperties {
- if (nutrition < 0 || saturation < 0.0F) {
- throw new IllegalArgumentException("Nutrition and saturation must be at or above 0");
+public record EquippableImpl(
+ EquipmentSlot slot
+) implements Equippable {
+
+ public static class Builder implements Equippable.Builder {
+ private EquipmentSlot slot;
+
+ @Override
+ public Equippable.Builder slot(@NonNull EquipmentSlot slot) {
+ Objects.requireNonNull(slot, "slot cannot be null");
+ this.slot = slot;
+ return this;
+ }
+
+ @Override
+ public Equippable build() {
+ Objects.requireNonNull(slot, "slot cannot be null");
+ return new EquippableImpl(slot);
}
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/impl/FoodPropertiesImpl.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/FoodPropertiesImpl.java
new file mode 100644
index 00000000000..f18307d926e
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/FoodPropertiesImpl.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.item.custom.impl;
+
+import org.checkerframework.checker.index.qual.NonNegative;
+import org.geysermc.geyser.api.item.custom.v2.component.java.FoodProperties;
+
+public record FoodPropertiesImpl(
+ int nutrition,
+ float saturation,
+ boolean canAlwaysEat
+) implements FoodProperties {
+
+ @SuppressWarnings("ConstantValue") // must enforce api
+ public static class Builder implements FoodProperties.Builder {
+ private int nutrition;
+ private float saturation;
+ private boolean canAlwaysEat;
+
+ @Override
+ public FoodProperties.Builder nutrition(@NonNegative int nutrition) {
+ if (nutrition < 0) throw new IllegalArgumentException("nutrition cannot be negative");
+ this.nutrition = nutrition;
+ return this;
+ }
+
+ @Override
+ public FoodProperties.Builder saturation(@NonNegative float saturation) {
+ if (saturation < 0) throw new IllegalArgumentException("saturation cannot be negative");
+ this.saturation = saturation;
+ return this;
+ }
+
+ @Override
+ public FoodProperties.Builder canAlwaysEat(boolean canAlwaysEat) {
+ this.canAlwaysEat = canAlwaysEat;
+ return this;
+ }
+
+ @Override
+ public FoodProperties build() {
+ return new FoodPropertiesImpl(nutrition, saturation, canAlwaysEat);
+ }
+ }
+
+}
diff --git a/core/src/main/java/org/geysermc/geyser/item/custom/impl/RepairableImpl.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/RepairableImpl.java
new file mode 100644
index 00000000000..a58b2eb1993
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/RepairableImpl.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2025 GeyserMC. http://geysermc.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @author GeyserMC
+ * @link https://github.com/GeyserMC/Geyser
+ */
+
+package org.geysermc.geyser.item.custom.impl;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.geysermc.geyser.api.item.custom.v2.component.java.Repairable;
+import org.geysermc.geyser.api.util.Identifier;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public record RepairableImpl(@NonNull List<@NonNull Identifier> items) implements Repairable {
+
+ public static class Builder implements Repairable.Builder {
+ private final List items = new ArrayList<>();
+
+ @Override
+ public Builder item(@NonNull Identifier item) {
+ Objects.requireNonNull(items, "item cannot be null");
+ if (this.items.contains(item)) {
+ throw new IllegalArgumentException("duplicate repairable item: " + item);
+ }
+ this.items.add(item);
+ return this;
+ }
+
+ @Override
+ public Repairable build() {
+ return new RepairableImpl(items);
+ }
+ }
+}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Chargeable.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/ToolPropertiesImpl.java
similarity index 63%
rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Chargeable.java
rename to core/src/main/java/org/geysermc/geyser/item/custom/impl/ToolPropertiesImpl.java
index dedcd1b0fb5..749ee1f181b 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/Chargeable.java
+++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/ToolPropertiesImpl.java
@@ -23,17 +23,26 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.api.item.custom.v2.component;
+package org.geysermc.geyser.item.custom.impl;
-import org.checkerframework.checker.index.qual.NonNegative;
-import org.geysermc.geyser.api.util.Identifier;
+import org.geysermc.geyser.api.item.custom.v2.component.java.ToolProperties;
-// TODO projectile component
-public record Chargeable(@NonNegative float maxDrawDuration, boolean chargeOnDraw, Identifier... ammunition) {
+public record ToolPropertiesImpl(
+ boolean canDestroyBlocksInCreative
+) implements ToolProperties {
- public Chargeable {
- if (maxDrawDuration < 0.0F) {
- throw new IllegalArgumentException("Max draw duration must be at or above 0");
+ public static class Builder implements ToolProperties.Builder {
+ private boolean destroyBlocksInCreative = true;
+
+ @Override
+ public ToolProperties.Builder canDestroyBlocksInCreative(boolean destroyBlocksInCreative) {
+ this.destroyBlocksInCreative = destroyBlocksInCreative;
+ return this;
+ }
+
+ @Override
+ public ToolProperties build() {
+ return new ToolPropertiesImpl(destroyBlocksInCreative);
}
}
}
diff --git a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/UseCooldown.java b/core/src/main/java/org/geysermc/geyser/item/custom/impl/UseCooldownImpl.java
similarity index 62%
rename from api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/UseCooldown.java
rename to core/src/main/java/org/geysermc/geyser/item/custom/impl/UseCooldownImpl.java
index 4bfd6dfb036..de01dc42206 100644
--- a/api/src/main/java/org/geysermc/geyser/api/item/custom/v2/component/UseCooldown.java
+++ b/core/src/main/java/org/geysermc/geyser/item/custom/impl/UseCooldownImpl.java
@@ -23,17 +23,36 @@
* @link https://github.com/GeyserMC/Geyser
*/
-package org.geysermc.geyser.api.item.custom.v2.component;
+package org.geysermc.geyser.item.custom.impl;
-import org.checkerframework.checker.index.qual.Positive;
import org.checkerframework.checker.nullness.qual.Nullable;
+import org.geysermc.geyser.api.item.custom.v2.component.java.UseCooldown;
import org.geysermc.geyser.api.util.Identifier;
-public record UseCooldown(@Positive float seconds, @Nullable Identifier cooldownGroup) {
-
- public UseCooldown {
- if (seconds <= 0.0F) {
- throw new IllegalArgumentException("Cooldown seconds must be above 0");
+public record UseCooldownImpl(
+ float seconds,
+ @Nullable Identifier cooldownGroup
+) implements UseCooldown {
+
+ public static class Builder implements UseCooldown.Builder {
+ private Identifier cooldownGroup;
+ private float seconds;
+
+ @Override
+ public Builder cooldownGroup(@Nullable Identifier cooldownGroup) {
+ this.cooldownGroup = cooldownGroup;
+ return this;
+ }
+
+ @Override
+ public Builder seconds(float seconds) {
+ this.seconds = seconds;
+ return this;
+ }
+
+ @Override
+ public UseCooldown build() {
+ return new UseCooldownImpl(seconds, cooldownGroup);
}
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java
index eabb46bd62b..4153d8a1888 100644
--- a/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java
+++ b/core/src/main/java/org/geysermc/geyser/registry/loader/ProviderRegistryLoader.java
@@ -25,7 +25,6 @@
package org.geysermc.geyser.registry.loader;
-import net.kyori.adventure.key.Key;
import org.geysermc.geyser.api.bedrock.camera.CameraFade;
import org.geysermc.geyser.api.bedrock.camera.CameraPosition;
import org.geysermc.geyser.api.block.custom.CustomBlockData;
@@ -43,23 +42,41 @@
import org.geysermc.geyser.api.item.custom.v2.CustomItemBedrockOptions;
import org.geysermc.geyser.api.item.custom.v2.CustomItemDefinition;
import org.geysermc.geyser.api.item.custom.v2.NonVanillaCustomItemDefinition;
+import org.geysermc.geyser.api.item.custom.v2.component.DataComponent;
+import org.geysermc.geyser.api.item.custom.v2.component.geyser.BlockPlacer;
+import org.geysermc.geyser.api.item.custom.v2.component.geyser.Chargeable;
+import org.geysermc.geyser.api.item.custom.v2.component.java.Consumable;
+import org.geysermc.geyser.api.item.custom.v2.component.java.Equippable;
+import org.geysermc.geyser.api.item.custom.v2.component.java.FoodProperties;
+import org.geysermc.geyser.api.item.custom.v2.component.java.Repairable;
+import org.geysermc.geyser.api.item.custom.v2.component.java.ToolProperties;
+import org.geysermc.geyser.api.item.custom.v2.component.java.UseCooldown;
import org.geysermc.geyser.api.pack.PathPackCodec;
import org.geysermc.geyser.api.pack.UrlPackCodec;
import org.geysermc.geyser.api.pack.option.PriorityOption;
import org.geysermc.geyser.api.pack.option.SubpackOption;
import org.geysermc.geyser.api.pack.option.UrlFallbackOption;
import org.geysermc.geyser.api.util.Identifier;
+import org.geysermc.geyser.event.GeyserEventRegistrar;
+import org.geysermc.geyser.extension.command.GeyserExtensionCommand;
import org.geysermc.geyser.impl.IdentifierImpl;
import org.geysermc.geyser.impl.camera.GeyserCameraFade;
import org.geysermc.geyser.impl.camera.GeyserCameraPosition;
-import org.geysermc.geyser.event.GeyserEventRegistrar;
-import org.geysermc.geyser.extension.command.GeyserExtensionCommand;
import org.geysermc.geyser.item.GeyserCustomItemData;
import org.geysermc.geyser.item.GeyserCustomItemOptions;
import org.geysermc.geyser.item.GeyserNonVanillaCustomItemData;
import org.geysermc.geyser.item.custom.GeyserCustomItemBedrockOptions;
import org.geysermc.geyser.item.custom.GeyserCustomItemDefinition;
import org.geysermc.geyser.item.custom.GeyserNonVanillaCustomItemDefinition;
+import org.geysermc.geyser.item.custom.impl.BlockPlacerImpl;
+import org.geysermc.geyser.item.custom.impl.ChargeableImpl;
+import org.geysermc.geyser.item.custom.impl.ConsumableImpl;
+import org.geysermc.geyser.item.custom.impl.DataComponentImpl;
+import org.geysermc.geyser.item.custom.impl.EquippableImpl;
+import org.geysermc.geyser.item.custom.impl.FoodPropertiesImpl;
+import org.geysermc.geyser.item.custom.impl.RepairableImpl;
+import org.geysermc.geyser.item.custom.impl.ToolPropertiesImpl;
+import org.geysermc.geyser.item.custom.impl.UseCooldownImpl;
import org.geysermc.geyser.level.block.GeyserCustomBlockComponents;
import org.geysermc.geyser.level.block.GeyserCustomBlockData;
import org.geysermc.geyser.level.block.GeyserGeometryComponent;
@@ -75,6 +92,7 @@
import java.nio.file.Path;
import java.util.Map;
+import java.util.function.Predicate;
/**
* Registers the provider data from the provider.
@@ -84,7 +102,7 @@ public class ProviderRegistryLoader implements RegistryLoader